So we are trying to shore up some gaps with implementing Microsoft Dynamics Business Central 365. Currently testing out v19 W1 on-prem. One of our use cases involves serialized items. To help shortcut the process, since the Serial No. values are unique across all items, we'd like to be able to manage items at the serial level. Rather than employ the two-step process of first providing the Item No. value and then the Serial No. value for each transaction.
Here is a basic example. In the Transfer Order page we'd like to have the user barcode scan or hand-key the Serial No. into the Item No. field for a new transfer line. Through my AL code I'd then take that Serial No. value, backtrace it to the "parent" Item No., temporarily store the Serial No. value, and then replace the field contents with the proper Item No. value. That's step one.
Step two would then involve adding a Reservation Entry line for the underlying Serial No. So that the item tracking would properly reflect the Serial No. that's associated with the transfer line. When I load up my AL extension code I encounter a quantity error, due to serialization. Although I can manually provide the same exact Item No. in that particular field and the page allows me to then proceed along in providing the quantity and whatnot.
I was advised that I need to programmatically create the transfer line, so I have attempted to define all pertinent property values. I'll paste in my AL code, along with a screen shot of the validation error. Any advice would be appreciated, as always!
tableextension 50103 "DchApi_TransferTableExt" extends "Transfer Line"
{
fields
{
modify("Item No.")
{
trigger OnBeforeValidate()
var
Rec_ILE: Record "Item Ledger Entry";
Rec_ResEnt: Record "Reservation Entry" temporary;
Rec_ResEnt_Lu: Record "Reservation Entry";
Rec_TransLine: Record "Transfer Line";
Rec_Item: Record Item;
Rec_TransHdr: Record "Transfer Header";
CreateReservEntry: Codeunit "Create Reserv. Entry";
ItemTrackingMgt: Codeunit "Item Tracking Management";
ReservStatus: Enum "Reservation Status";
CurrentSourceRowID: Text[250];
SecondSourceRowID: Text[250];
SerialNo: Code[20];
ItemNo: Code[20];
ShortDim1Code: Code[20];
ShortDim2Code: Code[20];
Description: Text[100];
GenProdPostGrp: Code[20];
InvPostGrp: Code[20];
ItemCatCode: Code[20];
InTransCode: Code[10];
TransFromCode: Code[10];
TransToCode: Code[10];
LineNo: Integer;
begin
SerialNo := Rec."Item No.";
Rec_ILE.Reset();
Rec_ILE.SetRange("Entry Type", Rec_ILE."Entry Type"::Purchase);
Rec_ILE.SetRange("Item Tracking", Rec_ILE."Item Tracking"::"Serial No.");
Rec_ILE.SetFilter("Serial No.", '%1', SerialNo);
if Rec_ILE.FindFirst() then begin
ItemNo := Rec_ILE."Item No.";
Rec_Item.Reset();
Rec_Item.SetFilter("No.", ItemNo);
if Rec_Item.FindFirst() then begin
ShortDim1Code := Rec_Item."Global Dimension 1 Code";
ShortDim2Code := Rec_Item."Global Dimension 2 Code";
Description := Rec_Item.Description;
GenProdPostGrp := Rec_Item."Gen. Prod. Posting Group";
InvPostGrp := Rec_Item."Inventory Posting Group";
ItemCatCode := Rec_Item."Item Category Code";
Rec_TransHdr.Reset();
Rec_TransHdr.SetRange("No.", Rec."Document No.");
if Rec_TransHdr.FindFirst() then begin
InTransCode := Rec_TransHdr."In-Transit Code";
TransFromCode := Rec_TransHdr."Transfer-from Code";
TransToCode := Rec_TransHdr."Transfer-to Code";
Rec_TransLine.Reset();
Rec_TransLine.SetRange("Document No.", Rec."Document No.");
if Rec_TransLine.FindLast() then
LineNo := Rec_TransLine."Line No." + 10000
else
LineNo := 10000;
Validate(Rec."Document No.");
Validate(Rec."Line No.", LineNo);
Validate(Rec."Item No.", ItemNo);
Validate(Rec."Variant Code", '');
Validate(Rec."Shortcut Dimension 1 Code", ShortDim1Code);
Validate(Rec."Shortcut Dimension 2 Code", ShortDim2Code);
Validate(Rec.Description, Description);
Validate(Rec."Gen. Prod. Posting Group", GenProdPostGrp);
Validate(Rec."Inventory Posting Group", InvPostGrp);
Validate(Rec."Item Category Code", ItemCatCode);
Validate(Rec.Quantity, 1);
Validate(Rec."Unit of Measure Code", 'PCS');
Validate(Rec."Qty. to Ship", 1);
Validate(Rec."Qty. per Unit of Measure", 1);
Validate(Rec.Status, Rec.Status::Open);
Validate(Rec."In-Transit Code", InTransCode);
Validate(Rec."Transfer-from Code", TransFromCode);
Validate(Rec."Transfer-to Code", TransToCode);
Rec.Insert();
Rec_ResEnt.Init();
Rec_ResEnt_Lu.Reset();
if Rec_ResEnt_Lu.FindLast() then
Rec_ResEnt."Entry No." := Rec_ResEnt_Lu."Entry No." + 1
else
Rec_ResEnt."Entry No." := 1;
Rec_ResEnt."Expiration Date" := Today();
Rec_ResEnt.Quantity := 1;
Rec_ResEnt."Serial No." := SerialNo;
Rec_ResEnt.Insert();
CreateReservEntry.SetDates(0D, Rec_ResEnt."Expiration Date");
CreateReservEntry.CreateReservEntryFor(Database::"Transfer Line", 0, Rec."Document No.", '', Rec."Derived From Line No.", Rec."Line No.",
Rec."Qty. per Unit of Measure", Rec_ResEnt.Quantity, Rec."Qty. per Unit of Measure" * Rec_ResEnt.Quantity, Rec_ResEnt);
CreateReservEntry.CreateEntry(Rec."Item No.", Rec."Variant Code", Rec."Transfer-from Code", Rec.Description, Rec."Receipt Date", 0D, 0, ReservStatus::Surplus);
CurrentSourceRowID := ItemTrackingMgt.ComposeRowID(5741, 0, Rec."Document No.", '', 0, Rec."Line No.");
SecondSourceRowID := ItemTrackingMgt.ComposeRowID(5741, 1, Rec."Document No.", '', 0, Rec."Line No.");
ItemTrackingMgt.SynchronizeItemTracking(CurrentSourceRowID, SecondSourceRowID, '');
end;
end;
end;
end;
}
}
}
I figured it out. I added the Serial No. value as a global variable, and broke out the reservation entry and item tracking into an OnAfterValidate() trigger. Everything works fine now. Full source code below.
tableextension 50103 "DchApi_TransferTableExt" extends "Transfer Line"
{
fields
{
modify("Item No.")
{
trigger OnBeforeValidate()
var
Rec_ILE: Record "Item Ledger Entry";
Rec_TransLine: Record "Transfer Line";
Rec_Item: Record Item;
Rec_TransHdr: Record "Transfer Header";
ItemNo: Code[20];
SerialNo: Code[20];
ShortDim1Code: Code[20];
ShortDim2Code: Code[20];
Description: Text[100];
GenProdPostGrp: Code[20];
InvPostGrp: Code[20];
ItemCatCode: Code[20];
InTransCode: Code[10];
TransFromCode: Code[10];
TransToCode: Code[10];
LineNo: Integer;
InsertResult: Boolean;
begin
SerialNo := Rec."Item No.";
Rec_ILE.Reset();
Rec_ILE.SetRange("Entry Type", Rec_ILE."Entry Type"::Purchase);
Rec_ILE.SetRange("Item Tracking", Rec_ILE."Item Tracking"::"Serial No.");
Rec_ILE.SetFilter("Serial No.", '%1', SerialNo);
if Rec_ILE.FindFirst() then begin
ItemNo := Rec_ILE."Item No.";
Rec_Item.Reset();
Rec_Item.SetFilter("No.", ItemNo);
if Rec_Item.FindFirst() then begin
ShortDim1Code := Rec_Item."Global Dimension 1 Code";
ShortDim2Code := Rec_Item."Global Dimension 2 Code";
Description := Rec_Item.Description;
GenProdPostGrp := Rec_Item."Gen. Prod. Posting Group";
InvPostGrp := Rec_Item."Inventory Posting Group";
ItemCatCode := Rec_Item."Item Category Code";
Rec_TransHdr.Reset();
Rec_TransHdr.SetRange("No.", Rec."Document No.");
if Rec_TransHdr.FindFirst() then begin
InTransCode := Rec_TransHdr."In-Transit Code";
TransFromCode := Rec_TransHdr."Transfer-from Code";
TransToCode := Rec_TransHdr."Transfer-to Code";
Rec_TransLine.Reset();
Rec_TransLine.SetRange("Document No.", Rec."Document No.");
if Rec_TransLine.FindLast() then
LineNo := Rec_TransLine."Line No." + 10000
else
LineNo := 10000;
Validate(Rec."Document No.");
Validate(Rec."Line No.", LineNo);
Validate(Rec."Item No.", ItemNo);
Validate(Rec."Description 2", SerialNo);
gblSerialNo := Rec."Description 2";
Validate(Rec."Variant Code", '');
Validate(Rec."Shortcut Dimension 1 Code", '200');
Validate(Rec."Shortcut Dimension 2 Code", '01');
Validate(Rec.Description, Description);
Validate(Rec."Gen. Prod. Posting Group", GenProdPostGrp);
Validate(Rec."Inventory Posting Group", InvPostGrp);
Validate(Rec."Item Category Code", ItemCatCode);
Validate(Rec.Quantity, 1);
Validate(Rec."Unit of Measure Code", 'PCS');
Validate(Rec."Qty. to Ship", 1);
Validate(Rec."Qty. per Unit of Measure", 1);
Validate(Rec.Status, Rec.Status::Open);
Validate(Rec."In-Transit Code", InTransCode);
Validate(Rec."Transfer-from Code", TransFromCode);
Validate(Rec."Transfer-to Code", TransToCode);
InsertResult := Rec.Insert();
Commit();
end;
end;
end;
end;
trigger OnAfterValidate()
var
Rec_ResEnt: Record "Reservation Entry" temporary;
Rec_ResEnt_Lu: Record "Reservation Entry";
CreateReservEntry: Codeunit "Create Reserv. Entry";
ItemTrackingMgt: Codeunit "Item Tracking Management";
ReservStatus: Enum "Reservation Status";
CurrentSourceRowID: Text[250];
SecondSourceRowID: Text[250];
begin
Rec_ResEnt.Init();
Rec_ResEnt_Lu.Reset();
if Rec_ResEnt_Lu.FindLast() then
Rec_ResEnt."Entry No." := Rec_ResEnt_Lu."Entry No." + 1
else
Rec_ResEnt."Entry No." := 1;
Rec_ResEnt."Expiration Date" := Today();
Rec_ResEnt.Quantity := 1;
Rec_ResEnt."Serial No." := gblSerialNo;
Rec_ResEnt.Insert();
Commit();
CreateReservEntry.SetDates(0D, Rec_ResEnt."Expiration Date");
CreateReservEntry.CreateReservEntryFor(Database::"Transfer Line", 0, Rec."Document No.", '', Rec."Derived From Line No.", Rec."Line No.",
Rec."Qty. per Unit of Measure", Rec_ResEnt.Quantity, Rec."Qty. per Unit of Measure" * Rec_ResEnt.Quantity, Rec_ResEnt);
CreateReservEntry.CreateEntry(Rec."Item No.", Rec."Variant Code", Rec."Transfer-from Code", Rec.Description, Rec."Receipt Date", 0D, 0, ReservStatus::Surplus);
CurrentSourceRowID := ItemTrackingMgt.ComposeRowID(5741, 0, Rec."Document No.", '', 0, Rec."Line No.");
SecondSourceRowID := ItemTrackingMgt.ComposeRowID(5741, 1, Rec."Document No.", '', 0, Rec."Line No.");
ItemTrackingMgt.SynchronizeItemTracking(CurrentSourceRowID, SecondSourceRowID, '');
end;
}
}
var
gblSerialNo: Code[20];
}