As you may already have notice, standard commercial layouts (invoice, credit memo, order, etc) don’t show item tracking lines.
Everytime our customers need to have this, they ask us to show the item tracking lines below each item line. Here’s how I did it, following the standard way available in the Sales – Shipment report (R208), with a few improvements, to show it line by line.
This solution works, at least, with NAV 2009, NAV 2015 and NAV2016.
First of all, in codeunit 6500 – Item Tracking Management, you need to create a new function, similar to the existing RetrieveDocumentItemTracking, but related with each line:
PROCEDURE RetrieveDocumentItemTrackingLine@115(VAR TrackingSpecBuffer@1003 : TEMPORARY Record 336;SourceID@1000 : Code[20];SourceType@1001 : Integer;SourceSubType@1002 : Option;SourceRefNo@1004 : Integer) : Integer; BEGIN //RPM,sn // retrieves Item Tracking for Sales Invoice Line TrackingSpecBuffer.DELETEALL; CASE SourceType OF DATABASE::"Purchase Header": RDITPurchaseLine(TrackingSpecBuffer,SourceID,SourceSubType,SourceRefNo); DATABASE::"Sales Header": RDITSalesLine(TrackingSpecBuffer,SourceID,SourceSubType,SourceRefNo); DATABASE::"Sales Cr.Memo Header": RDITSalesCrMemoLine(TrackingSpecBuffer,SourceID,SourceRefNo); DATABASE::"Sales Invoice Header": RDITSalesInvoiceLine(TrackingSpecBuffer,SourceID,SourceRefNo); DATABASE::"Transfer Shipment Header": RDITTransferShipmentLine(TrackingSpecBuffer,SourceID,SourceRefNo); DATABASE::"Return Receipt Header": RDITReturnReceiptLine(TrackingSpecBuffer,SourceID,SourceRefNo); ELSE ERROR(Text009,SourceType); END; TrackingSpecBuffer.RESET; EXIT(TrackingSpecBuffer.COUNT); //RPM,en END;
As you can see, I’m already preparing it for almost all documents (services documents are missing).
Then you have to create the methods to retrieve each document’s item tracking line.
LOCAL PROCEDURE RDITSalesInvoiceLine@114(VAR TempTrackingSpecBuffer@1000 : TEMPORARY Record 336;SourceID@1001 : Code[20];SourceRefNo@1005 : Integer);
VAR
SalesInvoiceLine@1002 : Record 113;
Item@1003 : Record 27;
Descr@1004 : Text[50];
BEGIN
//RPM,sn
SalesInvoiceLine.SETRANGE("Document No.",SourceID);
SalesInvoiceLine.SETRANGE("Line No.",SourceRefNo);
IF NOT SalesInvoiceLine.ISEMPTY THEN BEGIN
SalesInvoiceLine.FINDSET;
REPEAT
IF (SalesInvoiceLine.Type = SalesInvoiceLine.Type::Item) AND
(SalesInvoiceLine."No." <> '') AND
(SalesInvoiceLine."Quantity (Base)" <> 0)
THEN BEGIN
IF Item.GET(SalesInvoiceLine."No.") THEN
Descr := Item.Description;
FindValueEntries(TempTrackingSpecBuffer,DATABASE::"Sales Invoice Line",
0,SalesInvoiceLine."Document No.",'',0,SalesInvoiceLine."Line No.",Descr);
END;
UNTIL SalesInvoiceLine.NEXT = 0;
END;
//RPM,en
END;
LOCAL PROCEDURE RDITTransferShipmentLine@116(VAR TempTrackingSpecBuffer@1000 : TEMPORARY Record 336;SourceID@1001 : Code[20];SourceRefNo@1005 : Integer);
VAR
TransferShptLine@1002 : Record 5745;
Item@1003 : Record 27;
Descr@1004 : Text[50];
BEGIN
//RPM,sn
TransferShptLine.SETRANGE("Document No.",SourceID);
TransferShptLine.SETRANGE("Line No.",SourceRefNo);
IF NOT TransferShptLine.ISEMPTY THEN BEGIN
TransferShptLine.FINDSET;
REPEAT
IF (TransferShptLine."Item No." <> '') AND
(TransferShptLine."Quantity (Base)" <> 0)
THEN BEGIN
IF Item.GET(TransferShptLine."Item No.") THEN
Descr := Item.Description;
FindItemEntries(TempTrackingSpecBuffer,DATABASE::"Transfer Shipment Line",
0,TransferShptLine."Document No.",'',0,TransferShptLine."Line No.",Descr);
END;
UNTIL TransferShptLine.NEXT = 0;
END;
//RPM,en
END;
LOCAL RDITPurchaseLine(VAR TempTrackingSpecBuffer : TEMPORARY Record "Tracking Specification";SourceID : Code[20];SourceSubType : Option;SourceRefNo : Integer)
//RPM,sn
PurchaseLine.SETRANGE("Document Type",SourceSubType);
PurchaseLine.SETRANGE("Document No.",SourceID);
PurchaseLine.SETRANGE("Line No.",SourceRefNo);
IF NOT PurchaseLine.ISEMPTY THEN BEGIN
PurchaseLine.FINDSET;
REPEAT
IF (PurchaseLine.Type = PurchaseLine.Type::Item) AND
(PurchaseLine."Quantity (Base)" <> 0) THEN BEGIN
IF Item.GET(PurchaseLine."No.") THEN
Descr := Item.Description;
FindReservEntries(TempTrackingSpecBuffer,DATABASE::"Purchase Line",PurchaseLine."Document Type",
PurchaseLine."Document No.",'',0,PurchaseLine."Line No.",Descr);
FindTrackingEntries(TempTrackingSpecBuffer,DATABASE::"Purchase Line",PurchaseLine."Document Type",
PurchaseLine."Document No.",'',0,PurchaseLine."Line No.",Descr);
END;
UNTIL PurchaseLine.NEXT = 0;
END;
//RPM,en
LOCAL RDITSalesLine(VAR TempTrackingSpecBuffer : TEMPORARY Record "Tracking Specification";SourceID : Code[20];SourceSubType : Option;SourceRefNo : Integer)
//RPM,sn
SalesLine.SETRANGE("Document Type",SourceSubType);
SalesLine.SETRANGE("Document No.",SourceID);
SalesLine.SETRANGE("Line No.",SourceRefNo);
IF NOT SalesLine.ISEMPTY THEN BEGIN
SalesLine.FINDSET;
REPEAT
IF (SalesLine.Type = SalesLine.Type::Item) AND
(SalesLine."No." <> '') AND
(SalesLine."Quantity (Base)" <> 0) THEN BEGIN
IF Item.GET(SalesLine."No.") THEN
Descr := Item.Description;
FindReservEntries(TempTrackingSpecBuffer,DATABASE::"Sales Line",SalesLine."Document Type",
SalesLine."Document No.",'',0,SalesLine."Line No.",Descr);
FindTrackingEntries(TempTrackingSpecBuffer,DATABASE::"Sales Line",SalesLine."Document Type",
SalesLine."Document No.",'',0,SalesLine."Line No.",Descr);
END;
UNTIL SalesLine.NEXT = 0;
END;
//RPM,en
RDITSalesCrMemoLine(VAR TempTrackingSpecBuffer : TEMPORARY Record "Tracking Specification";SourceID : Code[20];SourceRefNo : Integer)
//RPM,sn
SalesCrMemoLine.SETRANGE("Document No.",SourceID);
SalesCrMemoLine.SETRANGE("Line No.",SourceRefNo);
IF NOT SalesCrMemoLine.ISEMPTY THEN BEGIN
SalesCrMemoLine.FINDSET;
REPEAT
IF (SalesCrMemoLine.Type = SalesCrMemoLine.Type::Item) AND
(SalesCrMemoLine."No." <> '') AND
(SalesCrMemoLine."Quantity (Base)" <> 0)
THEN BEGIN
IF Item.GET(SalesCrMemoLine."No.") THEN
Descr := Item.Description;
FindValueEntries(TempTrackingSpecBuffer,DATABASE::"Sales Cr.Memo Line",
0,SalesCrMemoLine."Document No.",'',0,SalesCrMemoLine."Line No.",Descr);
END;
UNTIL SalesCrMemoLine.NEXT = 0;
END;
//RPM,en
RDITReturnReceiptLine(VAR TempTrackingSpecBuffer : TEMPORARY Record "Tracking Specification";SourceID : Code[20];SourceRefNo : Integer)
//RPM,sn
ReturnReceiptLine.SETRANGE("Document No.",SourceID);
ReturnReceiptLine.SETRANGE("Line No.",SourceRefNo);
IF NOT ReturnReceiptLine.ISEMPTY THEN BEGIN
ReturnReceiptLine.FINDSET;
REPEAT
IF (ReturnReceiptLine.Type = ReturnReceiptLine.Type::Item) AND
(ReturnReceiptLine."No." <> '') AND
(ReturnReceiptLine."Quantity (Base)" <> 0)
THEN BEGIN
IF Item.GET(ReturnReceiptLine."No.") THEN
Descr := Item.Description;
FindItemEntries(TempTrackingSpecBuffer,DATABASE::"Return Receipt Line",
0,ReturnReceiptLine."Document No.",'',0,ReturnReceiptLine."Line No.",Descr);
END;
UNTIL ReturnReceiptLine.NEXT = 0;
END;
//RPM,en
Now, let’s change the reports to include this. I’ll only show report 206 Sales – Invoice, as example, but the changes needed in each report are very similar.
First of all, create a new dataitem called ItemTrackingLine

Then create the following needed variables and text constants.
New Global Variables: Name DataType Subtype TrackingSpecBuffer Record Tracking Specification ItemTrackingMgt Codeunit Item Tracking Management TrackingSpecCount Integer New text constants: Name ConstValue SerialNoCaptionLbl SN: LotNoCaptionLbl LN: QuantityCaptionLbl Quantity
Add this code to the following triggers:
ItemTrackingLine - OnPreDataItem()
//RPM,sn
IF TrackingSpecCount = 0 THEN
CurrReport.BREAK;
SETRANGE(Number,1,TrackingSpecCount);
TrackingSpecBuffer.SETCURRENTKEY("Source ID","Source Type","Source Subtype","Source Batch Name",
"Source Prod. Order Line","Source Ref. No.");
TrackingSpecBuffer.SETRANGE("Source ID", "Sales Invoice Line"."Document No.");
TrackingSpecBuffer.SETRANGE("Source Ref. No.", "Sales Invoice Line"."Line No.");
//RPM,en
Note: When customizing other reports, make sure to use the correct table (Sales Invoice Line in this case)
ItemTrackingLine - OnAfterGetRecord() //RPM,sn IF Number = 1 THEN TrackingSpecBuffer.FINDSET ELSE TrackingSpecBuffer.NEXT; //RPM,en Sales Invoice Line - OnAfterGetRecord() IF LineNumber >= MaxLines THEN BEGIN LineGroup += 1; HeaderGroup +=1; LineNumber := 0; END; LineNumber += 1; IF (Type = Type::"G/L Account") AND (NOT ShowInternalInfo) THEN "No." := ''; //RPM,sn TrackingSpecCount := 0; IF (Type = Type::Item) THEN BEGIN ItemTrackingMgt.SetRetrieveAsmItemTracking(TRUE); //NAV2009 only TrackingSpecCount := ItemTrackingMgt.RetrieveDocumentItemTrackingLine(TrackingSpecBuffer,"Sales Invoice Header"."No.", DATABASE::"Sales Invoice Header",0, "Line No."); ItemTrackingMgt.SetRetrieveAsmItemTracking(FALSE); //NAV2009 only END; // RPM,en VATAmountLine.INIT; (…)
Finally the layout!
Add a new group, below the Table_Lines_Details_Group, to show the serial no. / lot no.
I guess images are self explanatory.


Set group filter:


And set the field expression:

This is the final result:

Please fell free to comment this solution.
Só me aparece o último n. série mas existem outras linhas que têm e não aparecem.
Podes ajudar.
Olá Nuno,
Enviei-lhe um email sobre esta situação mas não obtive qualquer resposta. Já conseguiu perceber o que se passava?