Skip to content

[Bug]: Transfer posting with lot+package tracking scans the whole lot per package in FindLastItemLedgerEntry #11029

Description

@skonsbruck

Describe the issue

Posting a transfer order for an item with lot + package + expiration tracking is quadratic in the number of packages of the same lot.

Item Jnl.-Post Line.CheckExpirationDate → GetExistingExpirationDateFromILE → Item Tracking Management.ExistingExpirationDateAndQty → FindLastItemLedgerEntry.

Two compounding defects:

1. FindLastItemLedgerEntry applies tracking filters as else-if, not AND

https://github.com/microsoft/BCApps/blob/main/src/Layers/W1/BaseApp/Inventory/Tracking/ItemTrackingManagement.Codeunit.al

if ItemTrackingSetup."Lot No." <> '' then
    ItemLedgEntry.SetRange("Lot No.", ItemTrackingSetup."Lot No.")
else
    if ItemTrackingSetup."Serial No." <> '' then
        ItemLedgEntry.SetRange("Serial No.", ItemTrackingSetup."Serial No.")
    else
        if ItemTrackingSetup."Package No." <> '' then
            ItemLedgEntry.SetRange("Package No.", ItemTrackingSetup."Package No.");

The key used on the same procedure already includes Package No.:

"Item No.", Open, "Variant Code", Positive, "Lot No.", "Serial No.", "Package No."

When Lot No. is filled, Package No. and Serial No. are ignored. ExistingExpirationDateAndQty then CalcSums("Remaining Quantity") over every positive ILE of the lot.

2. GetExistingExpirationDateFromILE looks up by New tracking, and the package fallback never runs when the lot exists

https://github.com/microsoft/BCApps/blob/main/src/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al

ItemTrackingSetup.CopyTrackingFromNewTrackingSpec(TempTrackingSpecification);

Transfer reservation / tracking lines often have Lot No. + Package No. filled and New Lot No. / New Package No. blank (same identity at destination). CopyTrackingFromNewTrackingSpec then supplies a blank package.

The fallback only runs when the first lookup found nothing:

if ((ExistingExpirationDate = 0D) and (SumOfEntries = 0)) and
   ((New Lot No. = Lot No.) and (New Package No. <> Package No.))

A lot-only lookup always finds the lot, so SumOfEntries is the remaining qty of the whole lot. The fallback that would CopyTrackingFromTrackingSpec (and include Package No.) never executes.

CheckExpirationDate then does:

if (SumOfEntries > 0) and
   ((SumOfEntries <> SumLot) or (New Lot No. <> Lot No.))
then
    TempTrackingSpecification.TestField("New Expiration Date", ExistingExpirationDate);

SumLot is the current split (e.g. 1 kg). SumOfEntries is the lot (e.g. 2000 kg). They never match, so every package TestFields New Expiration Date. If that field is blank the post fails with New Expiration Date must be equal to '…'. Current value is ''. If it is filled, the post succeeds but each of N packages still does a lot-wide FindLast + CalcSums → O(N × lot size).

CPU profile on a ~2000-package transfer (same lot): ~420 s / 88% of posting in this SQL:

SELECT TOP (1) … Item Ledger Entry WHERE Item No_ + Positive + Variant + Lot No_ (no Package No.).

After an ISV stamped New Lot No. / New Package No. on the reservation entries, the same post dropped to ~71 s and this lookup to ~11 s. The product should not require that stamp.

Expected behavior

  • FindLastItemLedgerEntry / ExistingExpirationDateAndQty apply every tracking dimension that is present (lot AND serial AND package).
  • For a transfer that does not change lot/package, a blank New Lot No. / New Package No. is treated as “same as source”, so the ILE lookup is package-specific.
  • Posting N packages of one lot is roughly linear in N, not quadratic in lot size.
  • The New Expiration Date TestField is not raised solely because remaining qty of the lot differs from qty of this package.

Steps to reproduce

  1. Item tracking code: Lot Specific Tracking = Yes, Package Tracking = Yes, Strict Expiration Posting = Yes (or Use Expiration Dates = Yes).

  2. Post inbound inventory for one item, one lot, one expiration date, ~200–2000 packages (1 base unit each).

  3. Create a transfer order (Direct Transfer = No is enough). Assign item tracking from existing ILE / Item Tracking Summary so each package is a tracking line. Leave New Lot No. / New Package No. / New Expiration Date blank (standard transfer: same identity).

  4. Post shipment, then post receipt (or ship+receive).

  5. Observe posting time growing with package count, and/or error:

    New Expiration Date must be equal to '<lot expiration>'. Current value is ''.

Optional: In-client performance profiler on the Post action. The hot stack is CheckExpirationDate → GetExistingExpirationDateFromILE → ExistingExpirationDateAndQty → FindLastItemLedgerEntry.

Additional context

Proposed product fix (happy to PR after approval):

  1. FindLastItemLedgerEntry: replace the else-if with independent filters (matches the key already set):
if ItemTrackingSetup."Lot No." <> '' then
    ItemLedgEntry.SetRange("Lot No.", ItemTrackingSetup."Lot No.");
if ItemTrackingSetup."Serial No." <> '' then
    ItemLedgEntry.SetRange("Serial No.", ItemTrackingSetup."Serial No.");
if ItemTrackingSetup."Package No." <> '' then
    ItemLedgEntry.SetRange("Package No.", ItemTrackingSetup."Package No.");
  1. GetExistingExpirationDateFromILE: before the lookup, if a New* tracking field is blank, inherit from the corresponding source field (transfer keeps the same lot/package). Then the existing package-change fallback can stay for true re-pack.

  2. (Optional, smaller) OnBeforeExistingExpirationDateAndQty today only receives Lot No. and Serial No. Adding Package No. would let extenders handle this without replacing the whole procedure.

I will add an SCM test: transfer of several packages of one lot with expiration; New tracking fields blank; post must not error, and remaining-qty used for the New Expiration Date check must be the package qty not the lot qty.

This is still present on main (W1 ItemTrackingManagement / ItemJnlPostLine). Customer-impacting on current SaaS; we will also route via partner support for SLA. Opening here because we can contribute the AL change.

I will provide a fix for a bug

  • I will provide a fix for a bug

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Team: SCMGitHub request for SCM area

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions