Question

I have recently modified the default report 207 Sales Invoice and created a custom report. One request I had, was to display Shipment information on this invoice for each shipment that the current invoice covers. Particularly I needed to show the following values:

  • Shipment No. from the Delivery Note
  • Quantity of the Shipment

The standard report only shows the posted shipment date and the quantity and that only for one shipment (I believe its the last one). It does not show the "Shipment No.".

When making the required changes I had several problems and it was difficult to find any information about the findPostedShipmentDate() function, so I have decided to post my solution here.

The question would be:

  1. How can I show multiple shipments on the sales invoice?
  2. How can I show the "Shipmente No." and the individual quantities or each shipment?
Was it helpful?

Solution

In the function "Sales Invoice Line"::OnAfterGetRecord() the function findPostedShipmentDate() is called. This function performs several checks to find the posting date and if necessary calls the function GenerateBufferFromValueEntry() which fills the table "SalesShipmentBuffer" which temporary stores all the shipment information for the current "Sales Invoice Line" record:

CASE "Sales Invoice Line".Type OF
  "Sales Invoice Line".Type::Item:
    GenerateBufferFromValueEntry("Sales Invoice Line");
  ...

Afterwards it performs several checks on the buffer table and deletes the entries again (more on this later).

In the GenerateBufferFromValueEntry the following happens. The actual shipment information is stored in the ItemLedgerEntry (32) table, in order to find the correct rows, the ValueEntry (5802) table is used, which contains the right key (ValueEntry."Item Ledger Entry No.").

Additionally to the existing range filters I needed to add one line to limit the range to the Item No. and I had to remove the range filter for the Entry No.. I also modified the function AddBufferEntry to additionally take the ItemLedgerEntry."Document No." which contains the Shipment No. from the delivery note. Here is the complete code:

TotalQuantity := SalesInvoiceLine2."Quantity (Base)";
ValueEntry.SETCURRENTKEY("Document No.");
ValueEntry.SETRANGE("Document No.",SalesInvoiceLine2."Document No.");
ValueEntry.SETRANGE("Posting Date","Sales Invoice Header"."Posting Date");
ValueEntry.SETRANGE(ValueEntry."Item No.", "Sales Invoice Line"."No."); //Added
ValueEntry.SETRANGE("Item Charge No.",'');
//ValueEntry.SETFILTER("Entry No.",'%1..',FirstValueEntryNo); //Removed
IF ValueEntry.FIND('-') THEN BEGIN
  REPEAT
      IF ItemLedgerEntry.GET(ValueEntry."Item Ledger Entry No.") THEN BEGIN
        IF SalesInvoiceLine2."Qty. per Unit of Measure" <> 0 THEN
          Quantity := ValueEntry."Invoiced Quantity" /
                      SalesInvoiceLine2."Qty. per Unit of Measure"
        ELSE
          Quantity := ValueEntry."Invoiced Quantity";
        AddBufferEntry(
          SalesInvoiceLine2,
          -Quantity,
          ItemLedgerEntry."Posting Date",
          ItemLedgerEntry."Document No."); //Added
        TotalQuantity := TotalQuantity + ValueEntry."Invoiced Quantity";
      END;
    FirstValueEntryNo := ValueEntry."Entry No." + 1;
  UNTIL (ValueEntry.NEXT = 0) OR (TotalQuantity = 0);
END;

In the FindPostedShipmentDate function I also deleted the first couple of lines to prevent the function from exiting before the SalesShipmentBuffer had been filled:

IF "Sales Invoice Line"."Shipment No." <> '' THEN
  IF SalesShipmentHeader.GET("Sales Invoice Line"."Shipment No.") THEN
    EXIT(SalesShipmentHeader."Posting Date");

IF "Sales Invoice Header"."Order No."='' THEN
  EXIT("Sales Invoice Header"."Posting Date");

In the last part of FindPostedShipmentDate the records of SalesShipmentBuffer are deleted again, which might seem a bit strange at first (it did to me). The reason is that even after the DELETE and DELETEALL functions are called, the record variable retains the values that were stored in the record before calling these functions. So the result is that the SalesShipmentBuffer tables is cleared, such that its empty for the next run of the findPostedShipmentDate() function, but the values can still be used in the report. The other confusing part is that actually does a very simple thing which is made to look very complicated. If the buffer contains only one line it deletes the line, otherwise if it contains more than one line it first calculates the sum of all quantity fields and then deletes all lines.

Anyway, I only had to make one change, which was removing the following line since the Document No. field now contains the Shipment No. and not the Invoice No.

SalesShipmentBuffer.SETRANGE("Document No.","Sales Invoice Line"."Document No.");

Here is the remaining code:

SalesShipmentBuffer.SETRANGE("Line No." ,"Sales Invoice Line"."Line No.");
SalesShipmentBuffer.SETRANGE("No." ,"Sales Invoice Line"."No.");
IF SalesShipmentBuffer.FIND('-') THEN BEGIN
  SalesShipmentBuffer2 := SalesShipmentBuffer;
    IF SalesShipmentBuffer.NEXT = 0 THEN BEGIN
      SalesShipmentBuffer.GET(
        SalesShipmentBuffer2."Document No.",
        SalesShipmentBuffer2."Line No.",
        SalesShipmentBuffer2."Entry No."
      );
      SalesShipmentBuffer.DELETE;
      EXIT(SalesShipmentBuffer2."Posting Date");
    END ;
   SalesShipmentBuffer.CALCSUMS(Quantity);
   IF SalesShipmentBuffer.Quantity <> "Sales Invoice Line".Quantity THEN BEGIN
     SalesShipmentBuffer.DELETEALL;
     EXIT("Sales Invoice Header"."Posting Date");
   END;
END ELSE
  EXIT("Sales Invoice Header"."Posting Date");

That's it. Now I just had to modify the DataItem source entries to show the necessary fields on the report. The changes I had to make are actually not so many, but the code is not documented at all and I could not find much information when I googled for this problem or the functions involved. So I hope my post is helpful, in case someone has a similar problem than I had.


On a side not, I do not understand why the above code segment is written in such a wired why, I belive (though I have not verified this), that it could be simplified into the following code, which is much more readable. Please leave comments if you feel that my analysis is wrong.

SalesShipmentBuffer.SETRANGE("Line No." ,"Sales Invoice Line"."Line No.");
SalesShipmentBuffer.SETRANGE("No." ,"Sales Invoice Line"."No.");
SalesShipmentBuffer.FINDFIRST;
IF SalesShipmentBuffer.COUNT > 0 THEN BEGIN
  IF SalesShipmentBuffer.COUNT = 1 THEN BEGIN
    SalesShipmentBuffer.DELETE;
    EXIT(SalesShipmentBuffer2."Posting Date");
  END ELSE BEGIN
    SalesShipmentBuffer.CALCSUMS(Quantity);
    SalesShipmentBuffer.DELETEALL;
    EXIT("Sales Invoice Header"."Posting Date");
  END;
END ELSE
  EXIT("Sales Invoice Header"."Posting Date");
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top