Skip to content

[Subcontracting] Block Get Order/Receipt Lines and item charges on undone subcontracting receipts#8678

Open
alexei-dobriansky wants to merge 2 commits into
mainfrom
bugs/632785-637503-Subcon_BlockGetOrderLinesAndGetUndoneRcpt
Open

[Subcontracting] Block Get Order/Receipt Lines and item charges on undone subcontracting receipts#8678
alexei-dobriansky wants to merge 2 commits into
mainfrom
bugs/632785-637503-Subcon_BlockGetOrderLinesAndGetUndoneRcpt

Conversation

@alexei-dobriansky

@alexei-dobriansky alexei-dobriansky commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

What & why

Fixes AB#632785 and AB#637503 by blocking subcontracting scenarios that aren't supported and corrupt cost.

  • 632785 — Subcontracting lines can't be invoiced through a separate document (Direct Unit Cost / posting groups aren't carried over). Now excluded from both Get Receipt Lines and Get Order Lines, with a hard block if a subcontracting line is still copied.
  • 637503 — An item charge on an undone subcontracting receipt left orphaned capacity cost. Now blocked both when assigning the charge to an undone receipt line and when posting a charge assigned to one.

All subscribers live in the existing SubcPurchPostExt / SubcItemChargeAssPurchExt codeunits.

Note: the Get Order Lines filter relies on a new base-app integration event (OnGetPurchaseOrderLinesOnAfterSetPurchaseLineOrderFilters in codeunit 5826 "Matched Order Line Mgmt."), shipped separately.

Linked work

AB#632785
AB#637503

How I validated this

  • I read the full diff and it contains only changes I intended.
  • I built the affected app(s) locally with no new analyzer warnings.
  • I ran the change in Business Central and confirmed it behaves as expected.
  • I added or updated tests for the new behavior, or explained below why none are needed.

What I tested and the outcome

Lightweight mock-based tests in SubcSubcontractingTest cover all three blocks (mock the receipt/order line directly — no full subcontracting setup or posting): GetReceiptLinesBlocksSubcontractingReceiptLine, GetOrderLinesExcludesSubcontractingPurchaseOrderLines, and AssignItemChargeToUndoneSubcontractingReceiptIsBlocked. Local build/run is pending and will be completed by the author before merge.

Risk & compatibility

  • New subscribers are gated behind the existing Subc. Feature Flag Handler (IsSubcontractingEnabled()), consistent with the rest of the app.
  • Depends on the new base-app integration event for the Get Order Lines filter; the Get Receipt and item-charge blocks use existing base events.

alexei-dobriansky and others added 2 commits June 18, 2026 17:16
…done subcontracting receipts

Fixes ADO bugs 632785 and 637503.

632785 - subcontracting lines must not be invoiced through a separate document:
- Exclude subcontracting receipt lines (Prod. Order No. set) from Get Receipt Lines
  and block copying them, in SubcPurchPostExt (Purch.-Get Receipt subscribers).
- Exclude subcontracting purchase order lines from Get Order Lines, via the new
  Matched Order Line Mgmt. filter event.

637503 - item charges against undone subcontracting receipts create orphaned capacity cost:
- Block assigning an item charge to an undone (Correction) subcontracting receipt line
  in SubcItemChargeAssPurchExt.
- Block posting an item charge assigned to an undone subcontracting receipt line in
  SubcPurchPostExt.

Tests: lightweight mock-based tests in SubcSubcontractingTest covering all three blocks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…632785-637503-Subcon_BlockGetOrderLinesAndGetUndoneRcpt

# Conflicts:
#	src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Purchase/SubcPurchPostExt.Codeunit.al
@alexei-dobriansky alexei-dobriansky requested a review from a team June 19, 2026 08:30
@github-actions github-actions Bot added the AL: Apps (W1) Add-on apps for W1 label Jun 19, 2026
@alexei-dobriansky alexei-dobriansky self-assigned this Jun 19, 2026
@alexei-dobriansky alexei-dobriansky added the Subcontracting Subcontracting related activities label Jun 19, 2026
@github-actions github-actions Bot added this to the Version 29.0 milestone Jun 19, 2026
@alexei-dobriansky alexei-dobriansky enabled auto-merge (squash) June 19, 2026 08:31
ItemChargeAssignmentPurch2.SetRange("Document Line No.", ItemChargeAssignmentPurch."Document Line No.");
ItemChargeAssignmentPurch2.SetRange("Applies-to Doc. Type", "Purchase Applies-to Document Type"::Receipt);
repeat
if (FromPurchRcptLine."Prod. Order No." <> '') and FromPurchRcptLine.Correction then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Security} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

Inconsistent field used for undone-receipt guard

The event subscriber guard (pre-existing code) exits early when "Subc. Prod. Order No." = '', but the new Correction check inside CreateRcptChargeAssgnt tests "Prod. Order No." <> ''. If a subcontracting receipt line has "Subc. Prod. Order No." ≠ '' (so the guard passes) but "Prod. Order No." = '' (a different field), the undone-receipt block is silently skipped and an item charge can still be assigned to a corrected entry.

Recommendation:

  • Use the same field that the existing guard already relies on — "Subc. Prod. Order No." — in the new Correction check so the two conditions stay in sync.
Suggested change
if (FromPurchRcptLine."Prod. Order No." <> '') and FromPurchRcptLine.Correction then
if (FromPurchRcptLine."Subc. Prod. Order No." <> '') and FromPurchRcptLine.Correction then
Error(AssignToUndoneRcptErr, FromPurchRcptLine."Document No.", FromPurchRcptLine."Line No.");

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

// [GIVEN] A separate purchase invoice for the same vendor
LibraryPurchase.CreatePurchHeader(InvoiceHeader, InvoiceHeader."Document Type"::Invoice, Vendor."No.");
InvoiceLine."Document Type" := InvoiceHeader."Document Type";
InvoiceLine."Document No." := InvoiceHeader."No.";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Style} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

Uninserted InvoiceLine passed to GetPurchaseOrderLines

InvoiceLine is a local Record "Purchase Line" that is never inserted into the database; only Document Type and Document No. are set (Line No. remains 0). Passing an un-persisted record to MatchedOrderLineMgmt.GetPurchaseOrderLines is fragile: if that codeunit ever validates the primary-key or fetches the record from the database, the test will fail with a misleading error rather than a clear assertion.

Recommendation:

  • Insert the invoice line into the database before calling GetPurchaseOrderLines, or use an already-persisted line created via LibraryPurchase.CreatePurchaseLine, consistent with how OrderLine is created earlier in the same test.
Suggested change
InvoiceLine."Document No." := InvoiceHeader."No.";
LibraryPurchase.CreatePurchaseLine(InvoiceLine, InvoiceHeader, InvoiceLine.Type::Item, Item."No.", 0);
// [WHEN] Running Get Order Lines [THEN] the page handler verifies the subcontracting order line is not offered
MatchedOrderLineMgmt.GetPurchaseOrderLines(InvoiceLine);

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch.-Get Receipt", OnCreateInvLinesOnBeforeInsertLineIteration, '', false, false)]
local procedure BlockSubcontractingLinesOnCreateInvLinesOnBeforeInsertLineIteration(var PurchRcptLine2: Record "Purch. Rcpt. Line"; var PurchRcptHeader: Record "Purch. Rcpt. Header"; var PurchaseHeader: Record "Purchase Header"; var PurchaseLine: Record "Purchase Line"; var TransferLine: Boolean; var IsHandled: Boolean)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Upgrade} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

Hard block breaks existing Get-Receipt-Lines integrations

BlockSubcontractingLinesOnCreateInvLinesOnBeforeInsertLineIteration immediately raises a hard error whenever CreateInvLines is called with a subcontracting receipt line, with no deprecation period. Any AL extension, automation, or upgrade codeunit that currently uses Purch.-Get Receipt to invoice subcontracting orders will fail at runtime with no prior warning or migration path.

Recommendation:

  • Add an upgrade codeunit that detects open or pending documents created via the blocked flow and warns administrators. Consider surfacing the error as a validation warning in earlier releases before making it a hard error, or document the breaking change explicitly in release notes and add a ObsoleteState guard if applicable.

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Matched Order Line Mgmt.", OnGetPurchaseOrderLinesOnAfterSetPurchaseLineOrderFilters, '', false, false)]
local procedure ExcludeSubcontractingLinesOnGetPurchaseOrderLines(var PurchaseLineOrder: Record "Purchase Line"; PurchaseHeaderInvoice: Record "Purchase Header")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Upgrade} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

Silent filter asymmetry with Get-Order-Lines block

ExcludeSubcontractingLinesOnGetPurchaseOrderLines only silently filters subcontracting purchase order lines from the page dataset, with no corresponding hard-error subscriber like the one added for receipt lines. Programmatic callers that iterate the record set without going through the page (e.g., custom automation) will silently skip subcontracting lines rather than receiving an actionable error, making the regression harder to diagnose.

Recommendation:

  • Add a companion error subscriber on OnGetPurchaseOrderLinesOnAfterSetPurchaseLineOrderFilters (or the equivalent insertion-time event) so programmatic callers receive the same explicit error as for receipt lines, mirroring the defence-in-depth pattern used in BlockSubcontractingLinesOnCreateInvLinesOnBeforeInsertLineIteration.
Suggested change
local procedure ExcludeSubcontractingLinesOnGetPurchaseOrderLines(var PurchaseLineOrder: Record "Purchase Line"; PurchaseHeaderInvoice: Record "Purchase Header")
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Matched Order Line Mgmt.", OnGetPurchaseOrderLinesOnBeforeInsert, '', false, false)]
local procedure BlockSubcontractingLinesOnGetPurchaseOrderLines(var PurchaseLineOrder: Record "Purchase Line"; var IsHandled: Boolean)
begin
if PurchaseLineOrder."Prod. Order No." <> '' then
Error(GetSubcontractingOrderLineNotSupportedErr);
end;

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

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

Labels

AL: Apps (W1) Add-on apps for W1 Subcontracting Subcontracting related activities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant