Añadiendo Campos de Seguimiento de Números de Serie y Lote en Business Central: Una Guía Completa

Este post detalla cómo añadir y transferir campos de seguimiento desde cualquier documento hasta la información de series y lotes, y finalmente, a los movimientos de producto. Con estos cambios, se garantiza la coherencia y precisión de los datos de seguimiento, mejorando la gestión del inventario y optimizando los procesos operativos.

En mi experiencia trabajando con Microsoft Dynamics 365 Business Central, una de las tareas más comunes y esenciales es asegurar que los datos de seguimiento, como los números de serie y de lote, se mantengan consistentes y sean fáciles de gestionar a través de todo el sistema. Esto es especialmente importante cuando se trata de documentos que requieren un seguimiento detallado de productos, desde su origen hasta su registro final en los movimientos de producto (ItemLedgerEntry).

He creado una funcionalidad que extiende tablas y páginas, así como eventos, que permiten añadir y transferir campos adicionales de números de serie y de lote. Estos cambios no solo mejoran la capacidad de rastreo y gestión de inventario, sino que también garantizan que la información se mantenga coherente y precisa a lo largo de diferentes procesos y tablas.

Después de la larga explicación ¡Vamos manos a la obra! 😜

Añadiendo Campos a las Extensiones de Tablas

Extensiones de Tabla: “Item Journal Line”, “Tracking Specification”, “Reservation Entry”, “Item Ledger Entry”

Para empezar, vamos a extender las tablas mencionadas para incluir seis campos adicionales para números de serie y de lote. Aquí está el código necesario:

fields
{
    field(60000; "Serial No. 2"; Code[50])
    {
        Caption = 'Serial No. 2', Comment = 'ESP="Nº serial 2"';
        DataClassification = CustomerContent;
        NotBlank = true;
    }
    field(60001; "Serial No. 3"; Code[50])
    {
        Caption = 'Serial No. 3', Comment = 'ESP="Nº serial 3"';
        DataClassification = CustomerContent;
        NotBlank = true;
    }
    field(60002; "Serial No. 4"; Code[50])
    {
        Caption = 'Serial No. 4', Comment = 'ESP="Nº serial 4"';
        DataClassification = CustomerContent;
        NotBlank = true;
    }
    field(60003; "Lot No. 2"; Code[50])
    {
        Caption = 'Lot No. 2', Comment = 'ESP="Nº lote 2"';
        DataClassification = CustomerContent;
        NotBlank = true;
    }
    field(60004; "Lot No. 3"; Code[50])
    {
        Caption = 'Lot No. 3', Comment = 'ESP="Nº lote 3"';
        DataClassification = CustomerContent;
        NotBlank = true;
    }
    field(60005; "Lot No. 4"; Code[50])
    {
        Caption = 'Lot No. 4', Comment = 'ESP="Nº lote 4"';
        DataClassification = CustomerContent;
        NotBlank = true;
    }
}

Estos campos permiten almacenar información adicional de números de serie y de lote, mejorando la capacidad de rastreo y gestión de inventario.

Extensiones de Tabla: “Serial No. Information” y “Lot No. Information”

Similarmente, necesitamos extender las tablas de información de números de serie y de lote:

tableextension 60004 "Serial No. Information" extends "Serial No. Information"
{
    fields
    {
        field(60000; "Serial No. 2"; Code[50])
        {
            Caption = 'Serial No.2 ', Comment = 'ESP="Nº serial 2"';
            DataClassification = CustomerContent;
            NotBlank = true;

        }
        field(60001; "Serial No. 3"; Code[50])
        {
            Caption = 'Serial No. 3', Comment = 'ESP="Nº serial 3"';
            DataClassification = CustomerContent;
            NotBlank = true;
        }
        field(60002; "Serial No. 4"; Code[50])
        {
            Caption = 'Serial No. 4', Comment = 'ESP="Nº serial 4"';
            DataClassification = CustomerContent;
            NotBlank = true;
        }
    }
}

tableextension 60000 "Lot No. Information" extends "Lot No. Information"
{
    fields
    {
        field(60003; "Lot No. 2"; Code[50])
        {
            Caption = 'Lot No. 2', Comment = 'ESP="Nº lote 2"';
            DataClassification = CustomerContent;
            NotBlank = true;

        }
        field(60004; "Lot No. 3"; Code[50])
        {
            Caption = 'Lot No. 3', Comment = 'ESP="Nº lote 3"';
            DataClassification = CustomerContent;
            NotBlank = true;
        }
        field(60005; "Lot No. 4"; Code[50])
        {
            Caption = 'Lot No. 4', Comment = 'ESP="Nº lote 4"';
            DataClassification = CustomerContent;
            NotBlank = true;
        }
    }
}

Añadiendo Campos a las Extensiones de Página

Extensiones de Página: “Serial No. Information Card” y “Lot No. Information Card”

En las páginas de información, es esencial añadir campos que permitan a los usuarios ver y editar estos nuevos datos. Aquí está el código necesario:

pageextension 60002 "Lot No. Information Card" extends "Lot No. Information Card"
{
    layout
    {
        addafter("Lot No.")
        {
            field("Lot No. 2"; Rec."Lot No. 2")
            {
                ToolTip = 'Specifies the value of the field', comment = 'ESP="Especifica el valor del campo"';
                ApplicationArea = All;
            }
            field("Lot No. 3"; Rec."Lot No. 3")
            {
                ToolTip = 'Specifies the value of the field', comment = 'ESP="Especifica el valor del campo"';
                ApplicationArea = All;
            }
            field("Lot No. 4"; Rec."Lot No. 4")
            {
                ToolTip = 'Specifies the value of the field', comment = 'ESP="Especifica el valor del campo"';
                ApplicationArea = All;
            }
        }
    }
}

pageextension 60000 "Serial No. Information Card" extends "Serial No. Information Card"
{
    layout
    {
        addafter("Serial No.")
        {
            field("Serial No. 2"; Rec."Serial No. 2")
            {
                ToolTip = 'Specifies the value of the field', comment = 'ESP="Especifica el valor del campo"';
                ApplicationArea = All;
                ShowMandatory = true;
            }
            field("Serial No. 3"; Rec."Serial No. 3")
            {
                ToolTip = 'Specifies the value of the field', comment = 'ESP="Especifica el valor del campo"';
                ApplicationArea = All;
            }
            field("Serial No. 4"; Rec."Serial No. 4")
            {
                ToolTip = 'Specifies the value of the field', comment = 'ESP="Especifica el valor del campo"';
                ApplicationArea = All;
            }
        }
    }
}

Estos campos permiten a los usuarios especificar los valores directamente desde las páginas de información.

Extensiones de Página: “Item Tracking Lines” y “Item Ledger Entries”

De igual manera, es importante añadir estos campos en las páginas de líneas de seguimiento y entradas del libro mayor de ítems:

field("Serial No. 2"; Rec."Serial No. 2")
{
    ToolTip = 'Specifies the value of the field', comment = 'ESP="Especifica el valor del campo"';
    ApplicationArea = All;
    ShowMandatory = true;
}
field("Serial No. 3"; Rec."Serial No. 3")
{
    ToolTip = 'Specifies the value of the field', comment = 'ESP="Especifica el valor del campo"';
    ApplicationArea = All;
}
field("Serial No. 4"; Rec."Serial No. 4")
{
    ToolTip = 'Specifies the value of the field', comment = 'ESP="Especifica el valor del campo"';
    ApplicationArea = All;
}
field("Lot No. 2"; Rec."Lot No. 2")
{
    ToolTip = 'Specifies the value of the field', comment = 'ESP="Especifica el valor del campo"';
    ApplicationArea = All;
}
field("Lot No. 3"; Rec."Lot No. 3")
{
    ToolTip = 'Specifies the value of the field', comment = 'ESP="Especifica el valor del campo"';
    ApplicationArea = All;
}
field("Lot No. 4"; Rec."Lot No. 4")
{
    ToolTip = 'Specifies the value of the field', comment = 'ESP="Especifica el valor del campo"';
    ApplicationArea = All;
}

Manteniendo la Consistencia de los Datos con Eventos

Para asegurar que los datos de seguimiento sean consistentes a través de diferentes procesos, utilizo una codeunit que responden a eventos específicos. Aquí hay varias funciones que ayudan a transferir y mantener estos datos:

Función: C22_OnCodeOnBeforeCheckItemTracking
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Jnl.-Post Line", OnCodeOnBeforeCheckItemTracking, '', false, false)]
local procedure C22_OnCodeOnBeforeCheckItemTracking(var ItemJnlLine: Record "Item Journal Line"; DisableItemTracking: Boolean; var IsHandled: Boolean; var TempTrackingSpecification: Record "Tracking Specification"; var ItemTrackingSetup: Record "Item Tracking Setup")
begin
    ItemJnlLine."Serial No. 2" := TempTrackingSpecification."Serial No. 2";
    ItemJnlLine."Serial No. 3" := TempTrackingSpecification."Serial No. 3";
    ItemJnlLine."Serial No. 4" := TempTrackingSpecification."Serial No. 4";
    ItemJnlLine."Lot No. 2" := TempTrackingSpecification."Lot No. 2";
    ItemJnlLine."Lot No. 3" := TempTrackingSpecification."Lot No. 3";
    ItemJnlLine."Lot No. 4" := TempTrackingSpecification."Lot No. 4";
end;

Esta función transfiere los campos adicionales de números de serie y de lote desde TempTrackingSpecification a Item Journal Line antes de la verificación del seguimiento de ítems.

Función: C22_OnBeforeInsertItemLedgEntry

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Jnl.-Post Line", OnBeforeInsertItemLedgEntry, '', false, false)]
local procedure C22_OnBeforeInsertItemLedgEntry(var ItemLedgerEntry: Record "Item Ledger Entry"; ItemJournalLine: Record "Item Journal Line"; TransferItem: Boolean; OldItemLedgEntry: Record "Item Ledger Entry"; ItemJournalLineOrigin: Record "Item Journal Line")
begin
    ItemLedgerEntry."Serial No. 2" := ItemJournalLine."Serial No. 2";
    ItemLedgerEntry."Serial No. 3" := ItemJournalLine."Serial No. 3";
    ItemLedgerEntry."Serial No. 4" := ItemJournalLine."Serial No. 4";
    ItemLedgerEntry."Lot No. 2" := ItemJournalLine."Lot No. 2";
    ItemLedgerEntry."Lot No. 3" := ItemJournalLine."Lot No. 3";
    ItemLedgerEntry."Lot No. 4" := ItemJournalLine."Lot No. 4";
end;

Esta función transfiere los campos adicionales de números de serie y de lote desde Item Journal Line a Item Ledger Entry antes de insertar la entrada en el libro mayor de ítems.

Función: C6500_OnAfterCreateSNInformation

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Tracking Management", OnAfterCreateSNInformation, '', false, false)]
local procedure C6500_OnAfterCreateSNInformation(var SerialNoInfo: Record "Serial No. Information"; TrackingSpecification: Record "Tracking Specification")
begin
    SerialNoInfo."Serial No. 2" := TrackingSpecification."Serial No. 2";
    SerialNoInfo."Serial No. 3" := TrackingSpecification."Serial No. 3";
    SerialNoInfo."Serial No. 4" := TrackingSpecification."Serial No. 4";
    SerialNoInfo.Modify();
end;

Esta función transfiere los datos de números de serie desde Tracking Specification a Serial No. Information después de crear la información del número de serie.

Función: C6500_OnAfterCreateLotInformation

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Tracking Management", OnAfterCreateLotInformation, '', false, false)]
local procedure C6500_OnAfterCreateLotInformation(var LotNoInfo: Record "Lot No. Information"; var TrackingSpecification: Record "Tracking Specification")
begin
    LotNoInfo."Lot No. 2" := TrackingSpecification."Lot No. 2";
    LotNoInfo."Lot No. 3" := TrackingSpecification."Lot No. 3";
    LotNoInfo."Lot No. 4" := TrackingSpecification."Lot No. 4";
    LotNoInfo.Modify();
end;

Esta función transfiere los datos de números de lote desde Tracking Specification a Lot No. Information después de crear la información del lote.

Función: C99000830_OnAfterCreateReservEntryFor

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Create Reserv. Entry", OnAfterCreateReservEntryFor, '', false, false)]
local procedure C99000830_OnAfterCreateReservEntryFor(var ReservationEntry: Record "Reservation Entry"; Sign: Integer; ForType: Option; ForSubtype: Integer; ForID: Code[20]; ForBatchName: Code[10]; ForProdOrderLine: Integer; ForRefNo: Integer; ForQtyPerUOM: Decimal; Quantity: Decimal; QuantityBase: Decimal; ForReservEntry: Record "Reservation Entry")
begin
    ReservationEntry."Serial No. 2" := ForReservEntry."Serial No. 2";
    ReservationEntry."Serial No. 3" := ForReservEntry."Serial No. 3";
    ReservationEntry."Serial No. 4" := ForReservEntry."Serial No. 4";
    ReservationEntry."Lot No. 2" := ForReservEntry."Lot No. 2";
    ReservationEntry."Lot No. 3" := ForReservEntry."Lot No. 3";
    ReservationEntry."Lot No. 4" := ForReservEntry."Lot No. 4";
end;

Esta función asigna los datos de los campos de ForReservEntry a Reservation Entry después de crear una entrada de reserva, asegurando la coherencia de los datos de seguimiento.

Función: C99000830_OnCreateEntryOnBeforeSurplusCondition

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Create Reserv. Entry", OnCreateEntryOnBeforeSurplusCondition, '', false, false)]
local procedure C99000830_OnCreateEntryOnBeforeSurplusCondition(var ReservEntry: Record "Reservation Entry"; QtyToHandleAndInvoiceIsSet: Boolean; var InsertReservEntry: Record "Reservation Entry")
begin
    ReservEntry."Serial No. 2" := InsertReservEntry."Serial No. 2";
    ReservEntry."Serial No. 3" := InsertReservEntry."Serial No. 3";
    ReservEntry."Serial No. 4" := InsertReservEntry."Serial No. 4";
    ReservEntry."Lot No. 2" := InsertReservEntry."Lot No. 2";
    ReservEntry."Lot No. 3" := InsertReservEntry."Lot No. 3";
    ReservEntry."Lot No. 4" := InsertReservEntry."Lot No. 4";
end;

Esta función añade los campos de InsertReservEntry a Reservation Entry antes de evaluar la condición de excedente, asegurando que las entradas de reserva incluyan todos los datos necesarios.

Función: P6510_OnAfterEntriesAreIdentical

[EventSubscriber(ObjectType::Page, Page::"Item Tracking Lines", OnAfterEntriesAreIdentical, '', false, false)]
local procedure P6510_OnAfterEntriesAreIdentical(ReservEntry1: Record "Reservation Entry"; ReservEntry2: Record "Reservation Entry"; var IdenticalArray: array[2] of Boolean)
begin
    if IdenticalArray[1] then begin
        IdenticalArray[1] :=
                   (ReservEntry1."Serial No. 2" = ReservEntry2."Serial No. 2") and
                   (ReservEntry1."Serial No. 3" = ReservEntry2."Serial No. 3") and
                   (ReservEntry1."Lot No. 2" = ReservEntry2."Lot No. 2") and
                   (ReservEntry1."Lot No. 3" = ReservEntry2."Lot No. 3");

        ReservEntry2.Description := ReservEntry1.Description;
    end;
end;

Esta función verifica si las entradas de reserva son idénticas en cuanto a los números de serie y de lote, y actualiza la descripción para mantener la coherencia.

Función: P6510_OnAfterMoveFields

[EventSubscriber(ObjectType::Page, Page::"Item Tracking Lines", OnAfterMoveFields, '', false, false)]
local procedure P6510_OnAfterMoveFields(var TrkgSpec: Record "Tracking Specification"; var ReservEntry: Record "Reservation Entry")
begin
    ReservEntry."Serial No. 2" := TrkgSpec."Serial No. 2";
    ReservEntry."Serial No. 3" := TrkgSpec."Serial No. 3";
    ReservEntry."Serial No. 4" := TrkgSpec."Serial No. 4";
    ReservEntry."Lot No. 2" := TrkgSpec."Lot No. 2";
    ReservEntry."Lot No. 3" := TrkgSpec."Lot No. 3";
    ReservEntry."Lot No. 4" := TrkgSpec."Lot No. 4";
end;

Esta función transfiere los campos de Tracking Specification a Reservation Entry cuando se elimina una línea parcialmente, manteniendo la coherencia de los datos de seguimiento.

Función: P6510_OnRegisterChangeOnChangeTypeModifyOnBeforeCheckEntriesAreIdentical

[EventSubscriber(ObjectType::Page, Page::"Item Tracking Lines", OnRegisterChangeOnChangeTypeModifyOnBeforeCheckEntriesAreIdentical, '', false, false)]
local procedure P6510_OnRegisterChangeOnChangeTypeModifyOnBeforeCheckEntriesAreIdentical(var ReservEntry1: Record "Reservation Entry"; var ReservEntry2: Record "Reservation Entry"; var OldTrackingSpecification: Record "Tracking Specification"; var NewTrackingSpecification: Record "Tracking Specification"; var IdenticalArray: array[2] of Boolean)
var
    Identical: Boolean;
begin
    Identical :=
          (OldTrackingSpecification."Serial No. 2" <> NewTrackingSpecification."Serial No. 2") or
          (OldTrackingSpecification."Serial No. 3" <> NewTrackingSpecification."Serial No. 3") or
          (OldTrackingSpecification."Lot No. 2" <> NewTrackingSpecification."Lot No. 2") or
          (OldTrackingSpecification."Lot No. 3" <> NewTrackingSpecification."Lot No. 3");

    if Identical then
        ReservEntry2.Description := Format(CurrentDateTime);
end;

Esta función identifica si los campos de las especificaciones de seguimiento han cambiado y actualiza la descripción de Reservation Entry para reflejar estos cambios.

Función: t336_OnBeforeTestApplyToItemLedgEntry

[EventSubscriber(ObjectType::Table, Database::"Tracking Specification", OnBeforeTestApplyToItemLedgEntry, '', false, false)]
local procedure t336_OnBeforeTestApplyToItemLedgEntry(var TrackingSpecification: Record "Tracking Specification"; ItemLedgerEntry: Record "Item Ledger Entry"; var IsHandled: Boolean)
begin
    TrackingSpecification."Serial No. 2" := ItemLedgerEntry."Serial No. 2";
    TrackingSpecification."Serial No. 3" := ItemLedgerEntry."Serial No. 3";
    TrackingSpecification."Serial No. 4" := ItemLedgerEntry."Serial No. 4";
    TrackingSpecification."Lot No. 2" := ItemLedgerEntry."Lot No. 2";
    TrackingSpecification."Lot No. 3" := ItemLedgerEntry."Lot No. 3";
    TrackingSpecification."Lot No. 4" := ItemLedgerEntry."Lot No. 4";
end;

Esta función transfiere la información de números de serie y de lote desde Item Ledger Entry a Tracking Specification antes de aplicar la entrada en el libro mayor de ítems.

Función: T337_OnAfterCopyTrackingFromTrackingSpec

[EventSubscriber(ObjectType::Table, Database::"Reservation Entry", OnAfterCopyTrackingFromTrackingSpec, '', false, false)]
local procedure T337_OnAfterCopyTrackingFromTrackingSpec(TrackingSpecification: Record "Tracking Specification"; var ReservationEntry: Record "Reservation Entry")
begin
    ReservationEntry."Serial No. 2" := TrackingSpecification."Serial No. 2";
    ReservationEntry."Serial No. 3" := TrackingSpecification."Serial No. 3";
    ReservationEntry."Serial No. 4" := TrackingSpecification."Serial No. 4";
    ReservationEntry."Lot No. 2" := TrackingSpecification."Lot No. 2";
    ReservationEntry."Lot No. 3" := TrackingSpecification."Lot No. 3";
    ReservationEntry."Lot No. 4" := TrackingSpecification."Lot No. 4";
end;

Esta función copia los campos de números de serie y de lote desde Tracking Specification a Reservation Entry después de realizar una copia del seguimiento, asegurando que las entradas de reserva contengan la información de seguimiento necesaria.

Beneficios y Aplicaciones Prácticas

Implementar estas extensiones y funciones permite a las empresas:

Mejorar la Gestión de Inventarios: Al tener más campos para números de serie y de lote, se puede rastrear y gestionar los productos con mayor precisión.

Mantener la Consistencia de Datos: Las funciones aseguran que los datos se transfieran y mantengan coherentes a lo largo de diferentes procesos y tablas.

Reflexiones Finales

La implementar de esta funcionalidad es fundamental para asegurar que los datos de seguimiento adicionales, se transfieran correctamente desde cualquier documento hasta la información de series y lotes, y finalmente, a los movimientos de producto. Estas mejoras no solo garantizan la coherencia y precisión de los datos, sino que también optimizan la gestión del inventario. Con una configuración adecuada, se logra un sistema más eficiente y confiable, esencial para la operativa diaria de cualquier empresa.

Si quieres ver el código completo, está en GitHub.

¡Hasta la próxima!

Share your love

Leave a Reply

Your email address will not be published. Required fields are marked *