Search code examples
dynamics-business-centraldynamics-al

Add serialized item tracking details to a transfer


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;
        }
    }
}

Validation Error


Solution

  • 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];
    }