[Subcontracting] Block Get Order/Receipt Lines and item charges on undone subcontracting receipts#8678
Conversation
…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
| 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 |
There was a problem hiding this comment.
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.
| 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."; |
There was a problem hiding this comment.
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 viaLibraryPurchase.CreatePurchaseLine, consistent with howOrderLineis created earlier in the same test.
| 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) |
There was a problem hiding this comment.
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
ObsoleteStateguard 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") |
There was a problem hiding this comment.
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 inBlockSubcontractingLinesOnCreateInvLinesOnBeforeInsertLineIteration.
| 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
What & why
Fixes AB#632785 and AB#637503 by blocking subcontracting scenarios that aren't supported and corrupt cost.
All subscribers live in the existing
SubcPurchPostExt/SubcItemChargeAssPurchExtcodeunits.Linked work
AB#632785
AB#637503
How I validated this
What I tested and the outcome
Lightweight mock-based tests in
SubcSubcontractingTestcover all three blocks (mock the receipt/order line directly — no full subcontracting setup or posting):GetReceiptLinesBlocksSubcontractingReceiptLine,GetOrderLinesExcludesSubcontractingPurchaseOrderLines, andAssignItemChargeToUndoneSubcontractingReceiptIsBlocked. Local build/run is pending and will be completed by the author before merge.Risk & compatibility
Subc. Feature Flag Handler(IsSubcontractingEnabled()), consistent with the rest of the app.