web
You’re offline. This is a read only version of the page.
close
Skip to main content

Notifications

Announcements

No record found.

Community site session details

Community site session details

Session Id :
Small and medium business | Business Central, N...
Answered

Add serialized item tracking details to a transfer

(0) ShareShare
ReportReport
Posted on by 583

So I am attempting to implement similar methodology to what's described here --> https://chrisdsilvablog.wordpress.com/2018/11/09/bar-code-integration-with-business-central/. In my case the user provides a Serial No. value into the item transfer order line, which is then backtraced so that the Item No. is then replaced into that field. And I am also needing to take an extra step, of adding the Serial No. item tracking details for the item transfer order line.

I ran across this helpful blog post -->   which provides an example of creating those reservation entries. I will paste my AL code below and was wondering if any insight could be offered. When I am attempting to paste in a Serial No. into the transfer line's Item No. field, the translation doesn't occur. I just run into an error stating the transfer line for Line No. = 0 doesn't exist. I did place a Message() line right after the cross-reference lookup completes and can see the Item No. does pop up. But for some reason it's not pushing back into that field. Perhaps I should be just creating an extension of the form and not the table? I'm using BC365 v19 W1 on-prem.

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";
                CreateReservEntry: Codeunit "Create Reserv. Entry";
                ItemTrackingMgt: Codeunit "Item Tracking Management";
                ReservStatus: Enum "Reservation Status";
                CurrentSourceRowID: Text[250];
                SecondSourceRowID: Text[250];
                SerialNo: Code[20];
            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', Rec."Item No.");
                if Rec_ILE.FindFirst() then begin
                    Message('The Item No. value is '   Rec_ILE."Item No.");
                    
                    // Replace the Item No. field with the true Item No. value for the item transfer line.
                    Validate(Rec."Item No.", Rec_ILE."Item No.");

                    // Add to the Quantity field value for the item transfer line.
                    Validate(Rec.Quantity, 1);

                    // Create a tracking specification entry for the item transfer line for the Serial No. value.
                    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;
        }
    }
}
  

Transfer_2D00_Order_2D00_Error.png Transfer_2D00_Order_2D00_MsgBox.png

I have the same question (0)
  • Greg Kujawa Profile Picture
    583 on at

    TeddyH You had a very insightful post along these lines. Any chance you could please take a quick peek and what I'm trying to accomplish? :)

  • Verified answer
    Teddy Herryanto (That NAV Guy) Profile Picture
    14,284 Super User 2025 Season 2 on at

    The reason why you have that error is because the transfer line does not exist yet. You need to insert first before creating the reservation entry.

    Also why you are trying to do it on OnBeforeValidate ? OnAfterValidate makes more sense.

  • Suggested answer
    YUN ZHU Profile Picture
    95,329 Super User 2025 Season 2 on at

    Hi, just to add some info.

    If you want to create Item Tracking Lines automatically, hope the following will help.

    https://yzhums.com/7943/

    Thanks.

    ZHU

  • Greg Kujawa Profile Picture
    583 on at

    [quote user="Teddy H"]

    The reason why you have that error is because the transfer line does not exist yet. You need to insert first before creating the reservation entry.

    Also why you are trying to do it on OnBeforeValidate ? OnAfterValidate makes more sense.

    [/quote]

    Thanks for your response. I am attempting to trigger based on the user entering in a serial number in the item transfer order line's Item No. field. If that value matches a serial number assigned to an item, then I am storing that serial number value and replacing it with the item number. I thought that the record insert would be handled by the user as they continue to populate the item transfer order line, right? If you check that one URL I listed it shows something similar with a sales order line. Where in their AL code example they are replacing the user-provided serial number value with the corresponding item number it's associated with. Also, I am using the before validate trigger, since the after trigger would cause an error I'd think. Seeing the serial number put into the Item No. field won't be found against any of my item numbers.

    I will try adding the record insert to my procedure and let you know the outcome!

  • Greg Kujawa Profile Picture
    583 on at

    TeddyH So I made the following change to my AL code:

        Validate(Rec."Document No.");
        Validate(Rec."Item No.", Rec_ILE."Item No.");
        Validate(Rec.Quantity, 1);
        Rec.Insert();

    When I tested this out, I pasted an assigned serial number into the item transfer order line's Item No. field.

    Serial_2D00_No.png

    When I tabbed over the Item No. field, the serial number was indeed translated back to its parent Item No. value. And this was pushed into the Item No. field for the item transfer order line. But the quantity field wasn't updated as expected. See below, with the translated Item No. boxed in green and the Quantity field boxed in red. Any suggestions as to why? Once I am able to get the item transfer order line fully populated then I'm thinking the reservation entry should work fine. Thanks again for your expertise!

    Item_2D00_No.png

  • Greg Kujawa Profile Picture
    583 on at

    So now am attempting to programmatically assign all assumedly required fields for the new transfer line. Below is my revised source code, as you can see I'm pulling more fields as to the item specifics as well as the transfer specifics defined in the transfer header.

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

    And below is a screen shot of the validation error. If I manually enter in this Item No. value that associated with serialized "children," then I am allowed to manually enter in the quantity and whatnot. My previous post where the Item No. value was plugged into the page was associated with a Line No. = 0 in the underlying SQL table. Now the record isn't added to the SQL table due to the validation error.

    Validation-Results.png

  • Greg Kujawa Profile Picture
    583 on at
    [quote user="ZHU YUN"]

    Hi, just to add some info.

    If you want to create Item Tracking Lines automatically, hope the following will help.

    https://yzhums.com/7943/

    Thanks.

    ZHU

    [/quote]

    Thanks for this. I did run across your helpful article when I was searching for a solution. This would presumably take care of that piece. But where I'm getting stuck is initially creating a new item transfer line based on the user providing the Serial No. value. That line won't properly validate and save, so I can't get to the item tracking part yet.

  • Greg Kujawa Profile Picture
    583 on at

    I think I'm getting closer. My trigger code is operating from the transfer line, and I can see the new line is inserted okay if I place a breakpoint right after the Insert() line. But the subsequent code that's placing a reservation entry line and then item tracking lines is where things are choking. When I comment out those subsequent code portions then the transfer line is added without issue. I'll need to split out those other actions so they aren't located in the same trigger. I tried adding Commit() lines after each Insert() but that isn't sufficient.

    Insert_2D00_Success.png

  • Greg Kujawa Profile Picture
    583 on at

    Finally on the right track. Actually the Commit() lines do properly stow away things on the back-end. The front end Transfer Order page throws the validation error, but when I go back into the particular Transfer Order, the line is properly defined against the Item No. and then the Serial No. in item tracking. I just need to figure out how to flush the table changes back up to the page so that it looks correct. :)

  • Suggested answer
    Greg Kujawa Profile Picture
    583 on at

    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. Including the 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];
    }
    

Under review

Thank you for your reply! To ensure a great experience for everyone, your content is awaiting approval by our Community Managers. Please check back later.

Helpful resources

Quick Links

Responsible AI policies

As AI tools become more common, we’re introducing a Responsible AI Use…

Neeraj Kumar – Community Spotlight

We are honored to recognize Neeraj Kumar as our Community Spotlight honoree for…

Leaderboard > Small and medium business | Business Central, NAV, RMS

#1
OussamaSabbouh Profile Picture

OussamaSabbouh 3,143

#2
Jainam M. Kothari Profile Picture

Jainam M. Kothari 1,694 Super User 2025 Season 2

#3
YUN ZHU Profile Picture

YUN ZHU 1,067 Super User 2025 Season 2

Last 30 days Overall leaderboard

Featured topics

Product updates

Dynamics 365 release plans