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!