diff --git a/.github/scripts/Test-SkillIndex.ps1 b/.github/scripts/Test-SkillIndex.ps1 index ed07204..5354370 100644 --- a/.github/scripts/Test-SkillIndex.ps1 +++ b/.github/scripts/Test-SkillIndex.ps1 @@ -93,14 +93,15 @@ try { 'microsoft/skills/review/al-query-review.md', 'microsoft/skills/review/al-reporting-review.md', 'microsoft/skills/review/al-appsource-review.md', - 'microsoft/skills/review/al-telemetry-review.md' + 'microsoft/skills/review/al-telemetry-review.md', + 'microsoft/skills/review/al-scm-review.md' ) $review = @($skills | Where-Object id -eq 'al-code-review') if ($review.Count -ne 1) { throw "Expected exactly one al-code-review record, found $($review.Count)." } if ((@($review[0].subSkills) -join "`n") -cne ($expectedLeaves -join "`n")) { - throw 'al-code-review subSkills did not preserve the declared 17-leaf order.' + throw "al-code-review subSkills did not preserve the declared $($expectedLeaves.Count)-leaf order." } foreach ($leafPath in $expectedLeaves) { $leaf = @($skills | Where-Object path -ceq $leafPath) @@ -238,4 +239,4 @@ finally { Remove-Item -LiteralPath $tmp -Recurse -Force -ErrorAction SilentlyContinue } -Write-Output 'Skill-index check PASSED: deterministic, schema-valid, and all 17 review leaves preserved in order.' +Write-Output "Skill-index check PASSED: deterministic, schema-valid, and all $($expectedLeaves.Count) review leaves preserved in order." diff --git a/README.md b/README.md index daea405..f29d714 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,8 @@ the review can still discover knowledge by reading the folders. ## Scope -Today's curated content focuses on **technical AL code review**. It augments +Today's curated content covers **technical AL code review** and a focused +**Supply Chain Management (SCM)** functional domain. It augments the agent's judgment; it is not an exhaustive BC manual or a substitute for compilation, analyzers, tests, or human review. See [coverage and limits](docs/using-bcquality.md#coverage-and-limits) for the @@ -84,9 +85,11 @@ available domains and the difference between a folder review and a comparison. Mechanical issues already enforced by the AL compiler or standard analyzers are intentionally left to those deterministic tools rather than duplicated here. -Functional areas such as Finance, Supply Chain Management, Manufacturing, Jobs, -Warehousing, and Service, and technologies such as PowerShell, pipelines, and -Power Platform, remain valid future scope, **not current coverage claims**. +The [SCM domain](microsoft/knowledge/scm/) covers selected inventory, costing, +reservation, tracking, and warehouse/posting workflows, not exhaustive supply +chain validation. Broader functional coverage such as Finance, Manufacturing, +Jobs, and Service, and technologies such as PowerShell, pipelines, and Power +Platform, remain valid future scope, **not current coverage claims**. ## What's in this repo diff --git a/docs/using-bcquality.md b/docs/using-bcquality.md index e55751f..2af08be 100644 --- a/docs/using-bcquality.md +++ b/docs/using-bcquality.md @@ -85,6 +85,7 @@ branch names with ones in your project. | Uncommitted changes | Use the installed al-code-review skill to review my staged and unstaged tracked changes against HEAD, without changing files. Identify any untracked AL files not included in that diff. | | Branch changes | Use the installed al-code-review skill to review changes on this branch since its merge base with `origin/main`. Exclude uncommitted changes and do not edit files. | | Focused review | Use the installed al-code-review skill to review performance in the app in this folder, without changing files. Return the complete performance findings report. | +| Supply chain code | Use the installed al-code-review skill to review SCM posting, inventory, reservations, item tracking, and warehouse workflows in this app folder, without changing files. Return the complete Supply Chain Management findings report. | | Agent SDK code | Use the installed al-code-review skill to review Agent SDK implementation and usage in this app folder, without changing files. Return the complete Agents findings report. | For Git comparisons, the named base ref must exist locally. If it is missing, @@ -183,7 +184,7 @@ using your normal compilation, analyzer, test, and human-review workflow. ## Coverage and limits -The Microsoft broad review composes the 17 Microsoft domains listed below. +The Microsoft broad review composes the Microsoft domains listed below. The Community Agents review is a separate skill selected by the request, not a nested part of that coordinator. All current review leaves accept app folders, files, and diffs; request an Agent SDK review explicitly when that @@ -193,8 +194,22 @@ Available knowledge is **not** a promise that every rule will run. Selection depends on the task, target context, enabled layers, and source evidence. A whole-folder review is a current-state snapshot: detecting a published API removal or another comparison-only regression requires an actual baseline. -The corpus is technical AL guidance, not exhaustive functional validation or -AppSource certification. +The corpus combines technical AL guidance with targeted functional-domain +invariants, not exhaustive functional validation or AppSource certification. + +The SCM leaf owns selected inventory/value, application, reservation, tracking, +and warehouse/posting invariants. It prunes unrelated AL using the actual +tables, codeunits, fields, and operations in scope; an item caption or a broad +`ApplicationArea` alone is not an SCM review signal. Missing workflow context +must not be replaced with an assumed posting defect. Manufacturing, assembly, +planning, and other supply-chain areas are covered only where an article +explicitly names the shared interface or invariant. + +SCM owns Item/Value/Capacity/Warehouse and inventory-application posting +records. Pure G/L, customer/vendor/detailed/VAT and financial-only posting +mutations belong to Finance, even when that domain is not enabled. Equivalent +findings for one inventory-originated posting bypass have one SCM primary +owner; distinct independent financial defects remain separate. BCQuality intentionally does not duplicate mechanical diagnostics already enforced by the AL compiler or standard analyzers. Run the consuming app's @@ -222,6 +237,7 @@ Each article describes one concern. Where samples exist, use its linked | Reporting | [Reporting](../microsoft/knowledge/reporting/) | | Security | [Security](../microsoft/knowledge/security/) | | Style | [Style](../microsoft/knowledge/style/) | +| Supply Chain Management | [SCM](../microsoft/knowledge/scm/) | | Telemetry | [Telemetry](../microsoft/knowledge/telemetry/) | | Testing | [Testing](../microsoft/knowledge/testing/) | | User interface | [UI](../microsoft/knowledge/ui/) | diff --git a/evaluation/README.md b/evaluation/README.md index 2125ce3..05b5e52 100644 --- a/evaluation/README.md +++ b/evaluation/README.md @@ -6,6 +6,16 @@ The evaluation is convention-driven. The harness discovers every `/skills Model-facing preparation hashes case IDs, neutralizes `Good`/`Bad` object-name tokens, and removes full-line sample comments so neither the article slug, domain, nor expected outcome reveals the answer. +The SCM `articles` override deliberately selects every rule in the initial +functional domain, producing nine positive cases and nine clean controls. +Business context is executable: document/status `TestField` guards, +calculated-revaluation fields, source-transfer base quantities, warehouse +reconciliation steps, and additional-demand promising parameters survive +neutralization. Do not move those preconditions into comments or generic +"posting" helper names; removing them can turn a real defect into a valid +alternative workflow. The clean pairs exercise the supported APIs selected by +the same routing cues, not merely unrelated code that contains no SCM tokens. + ## Validate the corpus ```powershell diff --git a/evaluation/review-fixtures.json b/evaluation/review-fixtures.json index 90c42c6..b4a1778 100644 --- a/evaluation/review-fixtures.json +++ b/evaluation/review-fixtures.json @@ -51,6 +51,19 @@ "stop-when-runrequestpage-returns-empty-parameters" ] }, + "scm": { + "articles": [ + "post-item-ledger-changes-through-item-journals", + "post-revaluation-through-the-item-journal-batch", + "change-item-applications-through-posting-routines", + "cancel-reservations-through-reservation-management", + "transfer-item-tracking-through-source-reservation-codeunits", + "reconcile-warehouse-adjustments-with-the-item-ledger", + "post-transfers-through-shipment-and-receipt-codeunits", + "use-date-aware-availability-for-promising", + "carry-out-requisition-actions-through-the-standard-workflow" + ] + }, "style": { "article": "label-comment-explains-placeholders" }, diff --git a/microsoft/knowledge/scm/cancel-reservations-through-reservation-management.bad.al b/microsoft/knowledge/scm/cancel-reservations-through-reservation-management.bad.al new file mode 100644 index 0000000..248f3a4 --- /dev/null +++ b/microsoft/knowledge/scm/cancel-reservations-through-reservation-management.bad.al @@ -0,0 +1,13 @@ +codeunit 50106 "SCM Cancel Reservation Bad" +{ + procedure CancelSalesReservation(ReservationEntryNo: Integer) + var + ReservationEntry: Record "Reservation Entry"; + begin + ReservationEntry.Get(ReservationEntryNo, false); + ReservationEntry.TestField("Source Type", Database::"Sales Line"); + ReservationEntry.TestField("Reservation Status", ReservationEntry."Reservation Status"::Reservation); + + ReservationEntry.Delete(true); + end; +} diff --git a/microsoft/knowledge/scm/cancel-reservations-through-reservation-management.good.al b/microsoft/knowledge/scm/cancel-reservations-through-reservation-management.good.al new file mode 100644 index 0000000..3d5849d --- /dev/null +++ b/microsoft/knowledge/scm/cancel-reservations-through-reservation-management.good.al @@ -0,0 +1,14 @@ +codeunit 50107 "SCM Cancel Reservation Good" +{ + procedure CancelSalesReservation(ReservationEntryNo: Integer) + var + ReservationEntry: Record "Reservation Entry"; + ReservationEngineMgt: Codeunit "Reservation Engine Mgt."; + begin + ReservationEntry.Get(ReservationEntryNo, false); + ReservationEntry.TestField("Source Type", Database::"Sales Line"); + ReservationEntry.TestField("Reservation Status", ReservationEntry."Reservation Status"::Reservation); + + ReservationEngineMgt.CancelReservation(ReservationEntry); + end; +} diff --git a/microsoft/knowledge/scm/cancel-reservations-through-reservation-management.md b/microsoft/knowledge/scm/cancel-reservations-through-reservation-management.md new file mode 100644 index 0000000..1df3b12 --- /dev/null +++ b/microsoft/knowledge/scm/cancel-reservations-through-reservation-management.md @@ -0,0 +1,38 @@ +--- +bc-version: [all] +domain: scm +keywords: [reservation-entry, cancelreservation, reservation-engine-mgt, reservation-status, order-tracking, disallow-cancellation] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Cancel reservations through reservation management + +## Description + +Persistent `"Reservation Entry"` rows are not disposable allocation markers. Reservation and Tracking links use an entry-number pair with opposite `Positive` values, while Surplus and Prospect entries can legitimately be unpaired. Cancelling a binding reservation must handle its counterpart and any remaining item tracking or order tracking, not just remove a row. + +## Best Practice + +Explicit cancellation of an existing binding reservation uses `"Reservation Engine Mgt.".CancelReservation`. It checks the reservation status and `"Disallow Cancellation"`, handles the counterpart, and preserves or retracks the remaining source quantities as appropriate. Source-line quantity changes have their own source-specific reservation management path. + +Not every Reservation Entry has a partner or identical lot/serial values on both sides: Surplus/Prospect entries and supported late-binding scenarios have different relationships. Temporary buffers, engine-owned updates, and supported publisher metadata are not independent cancellation. Cancelling a reservation is also different from intentionally removing an item-tracking assignment. + +The samples retrieve the negative side of a persistent sales-line reservation and cancel only the binding. They do not delete the sales line or remove its tracking specifications. + +See sample: [`cancel-reservations-through-reservation-management.good.al`](cancel-reservations-through-reservation-management.good.al). + +## Anti Pattern + +`Delete(true)`, `DeleteAll`, or a status/source rewrite on persistent `"Reservation Entry"` records does not perform binding-reservation cancellation. Even deleting both sides can discard tracking that should survive and omit retracking. + +Normal processing of temporary Prospect/Surplus buffers is outside that cancellation workflow, and an unpaired row is not intrinsically an orphan. + +See sample: [`cancel-reservations-through-reservation-management.bad.al`](cancel-reservations-through-reservation-management.bad.al). + +## References + +- [Reservation, order tracking, and action messaging](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-reservation-order-tracking-and-action-messaging) +- [Item tracking and reservations](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-item-tracking-and-reservations) +- [BaseApp reservation cancellation](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Inventory/Tracking/ReservationEngineMgt.Codeunit.al#L51-L90) diff --git a/microsoft/knowledge/scm/carry-out-requisition-actions-through-the-standard-workflow.bad.al b/microsoft/knowledge/scm/carry-out-requisition-actions-through-the-standard-workflow.bad.al new file mode 100644 index 0000000..7bc4df8 --- /dev/null +++ b/microsoft/knowledge/scm/carry-out-requisition-actions-through-the-standard-workflow.bad.al @@ -0,0 +1,47 @@ +codeunit 50116 "SCM Requisition Action Bad" +{ + procedure CarryOutAcceptedNewPurchase(TemplateName: Code[10]; BatchName: Code[10]; LineNo: Integer; OrderDate: Date; PostingDate: Date; ReceiptDate: Date; CutoffDate: Date) + var + RequisitionLine: Record "Requisition Line"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + begin + if (OrderDate = 0D) or (PostingDate = 0D) or (ReceiptDate = 0D) or (CutoffDate = 0D) then + Error(PlanningDatesErr); + RequisitionLine.Get(TemplateName, BatchName, LineNo); + RequisitionLine.TestField(Type, RequisitionLine.Type::Item); + RequisitionLine.TestField("Replenishment System", RequisitionLine."Replenishment System"::Purchase); + RequisitionLine.TestField("Action Message", RequisitionLine."Action Message"::New); + RequisitionLine.TestField("Accept Action Message", true); + RequisitionLine.TestField("Demand Type", Database::"Sales Line"); + RequisitionLine.TestField("Demand Order No."); + RequisitionLine.TestField("Vendor No."); + RequisitionLine.SetRecFilter(); + + PurchaseHeader.Init(); + PurchaseHeader."Document Type" := PurchaseHeader."Document Type"::Order; + PurchaseHeader.Insert(true); + PurchaseHeader.Validate("Buy-from Vendor No.", RequisitionLine."Vendor No."); + PurchaseHeader.Validate("Order Date", OrderDate); + PurchaseHeader.Validate("Posting Date", PostingDate); + PurchaseHeader.Validate("Expected Receipt Date", ReceiptDate); + PurchaseHeader.Modify(true); + + PurchaseLine.Init(); + PurchaseLine."Document Type" := PurchaseHeader."Document Type"; + PurchaseLine."Document No." := PurchaseHeader."No."; + PurchaseLine."Line No." := 10000; + PurchaseLine.Validate(Type, PurchaseLine.Type::Item); + PurchaseLine.Validate("No.", RequisitionLine."No."); + PurchaseLine.Validate("Location Code", RequisitionLine."Location Code"); + PurchaseLine.Validate("Variant Code", RequisitionLine."Variant Code"); + PurchaseLine.Validate("Unit of Measure Code", RequisitionLine."Unit of Measure Code"); + PurchaseLine.Validate(Quantity, RequisitionLine.Quantity); + PurchaseLine.Insert(true); + + RequisitionLine.Delete(true); + end; + + var + PlanningDatesErr: Label 'Supply explicit order, posting, receipt, and cutoff dates.'; +} diff --git a/microsoft/knowledge/scm/carry-out-requisition-actions-through-the-standard-workflow.good.al b/microsoft/knowledge/scm/carry-out-requisition-actions-through-the-standard-workflow.good.al new file mode 100644 index 0000000..c09ea65 --- /dev/null +++ b/microsoft/knowledge/scm/carry-out-requisition-actions-through-the-standard-workflow.good.al @@ -0,0 +1,31 @@ +codeunit 50117 "SCM Requisition Action Good" +{ + procedure CarryOutAcceptedNewPurchase(TemplateName: Code[10]; BatchName: Code[10]; LineNo: Integer; OrderDate: Date; PostingDate: Date; ReceiptDate: Date; CutoffDate: Date) + var + RequisitionLine: Record "Requisition Line"; + PurchaseHeaderDefaults: Record "Purchase Header"; + ReqWkshMakeOrder: Codeunit "Req. Wksh.-Make Order"; + begin + if (OrderDate = 0D) or (PostingDate = 0D) or (ReceiptDate = 0D) or (CutoffDate = 0D) then + Error(PlanningDatesErr); + RequisitionLine.Get(TemplateName, BatchName, LineNo); + RequisitionLine.TestField(Type, RequisitionLine.Type::Item); + RequisitionLine.TestField("Replenishment System", RequisitionLine."Replenishment System"::Purchase); + RequisitionLine.TestField("Action Message", RequisitionLine."Action Message"::New); + RequisitionLine.TestField("Accept Action Message", true); + RequisitionLine.TestField("Demand Type", Database::"Sales Line"); + RequisitionLine.TestField("Demand Order No."); + RequisitionLine.TestField("Vendor No."); + RequisitionLine.SetRecFilter(); + + PurchaseHeaderDefaults."Order Date" := OrderDate; + PurchaseHeaderDefaults."Posting Date" := PostingDate; + PurchaseHeaderDefaults."Expected Receipt Date" := ReceiptDate; + ReqWkshMakeOrder.Set(PurchaseHeaderDefaults, CutoffDate, false); + ReqWkshMakeOrder.SetSuppressCommit(true); + ReqWkshMakeOrder.CarryOutBatchAction(RequisitionLine); + end; + + var + PlanningDatesErr: Label 'Supply explicit order, posting, receipt, and cutoff dates.'; +} diff --git a/microsoft/knowledge/scm/carry-out-requisition-actions-through-the-standard-workflow.md b/microsoft/knowledge/scm/carry-out-requisition-actions-through-the-standard-workflow.md new file mode 100644 index 0000000..88f68bd --- /dev/null +++ b/microsoft/knowledge/scm/carry-out-requisition-actions-through-the-standard-workflow.md @@ -0,0 +1,43 @@ +--- +bc-version: [all] +domain: scm +keywords: [requisition-line, action-message, accept-action-message, req-wksh-make-order, carryoutbatchaction, demand-order-no, planning-flexibility] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Carry out requisition actions through the standard workflow + +## Description + +A requisition/planning line is a pending change to a supply/demand network, not just a template for a purchase line. Carry-out interprets New, change-quantity, reschedule, and cancel actions, preserves referenced supply and planning flexibility, and moves reservation/tracking ownership before finalizing the proposal. Creating a plausible purchase order and deleting the requisition line can leave new supply unrelated to the demand that caused it. + +## Best Practice + +Requisition batch carry-out initializes `"Req. Wksh.-Make Order"` with `Set` and invokes `CarryOutBatchAction` on the intended accepted lines. Order/posting/receipt defaults are separate from the ending-order-date cutoff, and worksheet/batch/line filters define the selection. A plain `Run` or a single order-line insertion helper is not a replacement for this batch initialization and finalization. + +The standard `"Carry Out Action"` dispatch handles broader planning output and its configured purchase, transfer, assembly, or manufacturing choices. Each action's supply change and source-specific reservation transfer precede proposal finalization; not every action creates a new purchase order. + +Ordinary manual purchase creation that does not consume planning output is outside this rule. Users may reject or delete unwanted proposals without creating supply; temporary planning simulations, pre-carry-out enrichment, and engine-owned cleanup are also legitimate. `Delete(true)` on a requisition line is not intrinsically a defect. + +The samples select an existing accepted New/Purchase item proposal with sales-demand context. Dates are explicit, the source selection remains bounded, and the clean sample leaves order creation and reservation handoff to the standard workflow; it is not a complete planning-run generator. + +See sample: [`carry-out-requisition-actions-through-the-standard-workflow.good.al`](carry-out-requisition-actions-through-the-standard-workflow.good.al). + +## Anti Pattern + +Manually creating or changing supply from a subset of an accepted persistent `"Requisition Line"`, then deleting or marking that proposal handled, skips the standard carry-out/source-reservation handoff. Purchase-field validation and the requisition delete trigger do not first move the proposal's demand links to the new purchase line. + +Deleting an unwanted suggestion or creating an ordinary purchase order without consuming planning output is a separate operation. The standard carry-out engine's own insert/delete sequence participates in the source handoff rather than replacing it. + +See sample: [`carry-out-requisition-actions-through-the-standard-workflow.bad.al`](carry-out-requisition-actions-through-the-standard-workflow.bad.al). + +## References + +- [Perform planning action messages](https://learn.microsoft.com/en-us/dynamics365/business-central/production-how-to-run-mps-and-mrp#to-perform-action-messages) +- [Planning functionality](https://learn.microsoft.com/en-us/dynamics365/business-central/production-about-planning-functionality) +- [Reservation, order tracking, and action messaging](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-reservation-order-tracking-and-action-messaging) +- [BaseApp carry-out caller and date defaults](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Inventory/Requisition/CarryOutActionMsgReq.Report.al#L116-L133) +- [Batch initialization and selection](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Inventory/Requisition/ReqWkshMakeOrder.Codeunit.al#L116-L215) +- [Reservation handoff before supply finalization](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Inventory/Requisition/ReqWkshMakeOrder.Codeunit.al#L663-L743) diff --git a/microsoft/knowledge/scm/change-item-applications-through-posting-routines.bad.al b/microsoft/knowledge/scm/change-item-applications-through-posting-routines.bad.al new file mode 100644 index 0000000..f0239db --- /dev/null +++ b/microsoft/knowledge/scm/change-item-applications-through-posting-routines.bad.al @@ -0,0 +1,33 @@ +codeunit 50104 "SCM Item Application Bad" +{ + procedure ChangeSalesQuantityApplication(ApplicationEntryNo: Integer; NewInboundEntryNo: Integer) + var + ItemApplicationEntry: Record "Item Application Entry"; + OutboundItemLedgerEntry: Record "Item Ledger Entry"; + InboundItemLedgerEntry: Record "Item Ledger Entry"; + begin + ItemApplicationEntry.Get(ApplicationEntryNo); + ItemApplicationEntry.TestField(Quantity); + ItemApplicationEntry.TestField("Inbound Item Entry No."); + ItemApplicationEntry.TestField("Outbound Item Entry No."); + ItemApplicationEntry.TestField("Transferred-from Entry No.", 0); + if ItemApplicationEntry.CostApplication() then + Error(QuantityApplicationErr); + OutboundItemLedgerEntry.Get(ItemApplicationEntry."Outbound Item Entry No."); + OutboundItemLedgerEntry.TestField("Entry Type", OutboundItemLedgerEntry."Entry Type"::Sale); + OutboundItemLedgerEntry.TestField(Positive, false); + OutboundItemLedgerEntry.TestField("Drop Shipment", false); + OutboundItemLedgerEntry.TestField(Correction, false); + InboundItemLedgerEntry.Get(NewInboundEntryNo); + InboundItemLedgerEntry.TestField(Positive, true); + InboundItemLedgerEntry.TestField("Item No.", OutboundItemLedgerEntry."Item No."); + InboundItemLedgerEntry.TestField("Variant Code", OutboundItemLedgerEntry."Variant Code"); + InboundItemLedgerEntry.TestField("Location Code", OutboundItemLedgerEntry."Location Code"); + + ItemApplicationEntry."Inbound Item Entry No." := NewInboundEntryNo; + ItemApplicationEntry.Modify(true); + end; + + var + QuantityApplicationErr: Label 'Select an ordinary quantity application, not a cost application.'; +} diff --git a/microsoft/knowledge/scm/change-item-applications-through-posting-routines.good.al b/microsoft/knowledge/scm/change-item-applications-through-posting-routines.good.al new file mode 100644 index 0000000..e176aad --- /dev/null +++ b/microsoft/knowledge/scm/change-item-applications-through-posting-routines.good.al @@ -0,0 +1,40 @@ +codeunit 50105 "SCM Item Application Good" +{ + procedure ChangeSalesQuantityApplication(ApplicationEntryNo: Integer; NewInboundEntryNo: Integer) + var + ItemApplicationEntry: Record "Item Application Entry"; + OutboundItemLedgerEntry: Record "Item Ledger Entry"; + InboundItemLedgerEntry: Record "Item Ledger Entry"; + ItemJnlPostLine: Codeunit "Item Jnl.-Post Line"; + OutboundEntryNo: Integer; + begin + ItemApplicationEntry.Get(ApplicationEntryNo); + ItemApplicationEntry.TestField(Quantity); + ItemApplicationEntry.TestField("Inbound Item Entry No."); + ItemApplicationEntry.TestField("Outbound Item Entry No."); + ItemApplicationEntry.TestField("Transferred-from Entry No.", 0); + if ItemApplicationEntry.CostApplication() then + Error(QuantityApplicationErr); + OutboundEntryNo := ItemApplicationEntry."Outbound Item Entry No."; + OutboundItemLedgerEntry.Get(OutboundEntryNo); + OutboundItemLedgerEntry.TestField("Entry Type", OutboundItemLedgerEntry."Entry Type"::Sale); + OutboundItemLedgerEntry.TestField(Positive, false); + OutboundItemLedgerEntry.TestField("Drop Shipment", false); + OutboundItemLedgerEntry.TestField(Correction, false); + InboundItemLedgerEntry.Get(NewInboundEntryNo); + InboundItemLedgerEntry.TestField(Positive, true); + InboundItemLedgerEntry.TestField("Item No.", OutboundItemLedgerEntry."Item No."); + InboundItemLedgerEntry.TestField("Variant Code", OutboundItemLedgerEntry."Variant Code"); + InboundItemLedgerEntry.TestField("Location Code", OutboundItemLedgerEntry."Location Code"); + + ItemJnlPostLine.UnApply(ItemApplicationEntry); + OutboundItemLedgerEntry.Get(OutboundEntryNo); + ItemJnlPostLine.ReApply(OutboundItemLedgerEntry, NewInboundEntryNo); + ItemJnlPostLine.RedoApplications(); + ItemJnlPostLine.CostAdjust(); + ItemJnlPostLine.ClearApplicationLog(); + end; + + var + QuantityApplicationErr: Label 'Select an ordinary quantity application, not a cost application.'; +} diff --git a/microsoft/knowledge/scm/change-item-applications-through-posting-routines.md b/microsoft/knowledge/scm/change-item-applications-through-posting-routines.md new file mode 100644 index 0000000..5b5443e --- /dev/null +++ b/microsoft/knowledge/scm/change-item-applications-through-posting-routines.md @@ -0,0 +1,39 @@ +--- +bc-version: [all] +domain: scm +keywords: [item-application-entry, inbound-item-entry-no, unapply, reapply, redoapplications, costadjust, application-worksheet] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Change item applications through posting routines + +## Description + +An `"Item Application Entry"` connects quantity application to cost flow; changing its inbound entry number is not merely fixing a foreign key. Unapplication/reapplication also affects ledger remaining quantities, open states, valuation, and entries needing cost adjustment. Direct edits can leave a plausible application row attached to inconsistent inventory and costs. + +## Best Practice + +The Application Worksheet provides the interactive correction workflow. A narrowly controlled programmatic correction of an ordinary quantity application uses the same `"Item Jnl.-Post Line"` instance for `UnApply`, a reload of the affected outbound item entry, and `ReApply` to the compatible inbound entry. Its finalization lifecycle includes `RedoApplications`, `CostAdjust`, and `ClearApplicationLog`. + +The posting routines enforce inventory-period, correction, transfer, and drop-shipment restrictions. Entries with `"Transferred-from Entry No."`, outbound transfers, and special application types are outside the ordinary-sales sample's scope. Application-check bypasses remove those protections, and the worksheet's multi-step recovery flags belong to its UI lifecycle rather than a standalone transaction. + +`CostAdjust` honors automatic-cost-adjustment setup; calling it does not mean all costs are settled when adjustment is disabled or deferred. Scheduled/manual adjustment remains necessary in those configurations. Temporary application projections, extension metadata, and source-document reservation/order-tracking changes are not edits to the persistent item-application graph. + +See sample: [`change-item-applications-through-posting-routines.good.al`](change-item-applications-through-posting-routines.good.al). + +## Anti Pattern + +Independent `Modify`, `Delete`, or replacement `Insert` operations on persistent `"Item Application Entry"` rows can repoint a receipt/shipment application without updating remaining quantities or cost propagation. Valid item numbers, matching quantities, and running table triggers do not complete reapplication. + +Omitting finalization or committing between unapply and reapply exposes an incomplete replacement. The standard posting/application workflow's internal table writes differ because they participate in that lifecycle. + +See sample: [`change-item-applications-through-posting-routines.bad.al`](change-item-applications-through-posting-routines.bad.al). + +## References + +- [Item application design](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-item-application) +- [Cost adjustment design](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-cost-adjustment) +- [BaseApp application finalization sequence](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Inventory/Ledger/ApplicationWorksheet.Page.al#L495-L503) +- [BaseApp reapplication and cost-adjustment lifecycle](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al#L5464-L5538) diff --git a/microsoft/knowledge/scm/post-item-ledger-changes-through-item-journals.bad.al b/microsoft/knowledge/scm/post-item-ledger-changes-through-item-journals.bad.al new file mode 100644 index 0000000..1187f96 --- /dev/null +++ b/microsoft/knowledge/scm/post-item-ledger-changes-through-item-journals.bad.al @@ -0,0 +1,32 @@ +codeunit 50100 "SCM Stock Adjustment Bad" +{ + procedure PostPreparedPositiveAdjustment(ItemJournalLine: Record "Item Journal Line"; NewEntryNo: Integer) + var + ItemLedgerEntry: Record "Item Ledger Entry"; + begin + ItemJournalLine.TestField("Entry Type", ItemJournalLine."Entry Type"::"Positive Adjmt."); + ItemJournalLine.TestField("Value Entry Type", ItemJournalLine."Value Entry Type"::"Direct Cost"); + ItemJournalLine.TestField("Item No."); + ItemJournalLine.TestField("Posting Date"); + ItemJournalLine.TestField("Quantity (Base)"); + if ItemJournalLine."Quantity (Base)" < 0 then + Error(PositiveQuantityErr); + + ItemLedgerEntry.Init(); + ItemLedgerEntry."Entry No." := NewEntryNo; + ItemLedgerEntry."Item No." := ItemJournalLine."Item No."; + ItemLedgerEntry."Entry Type" := ItemJournalLine."Entry Type"; + ItemLedgerEntry."Posting Date" := ItemJournalLine."Posting Date"; + ItemLedgerEntry."Document No." := ItemJournalLine."Document No."; + ItemLedgerEntry."Location Code" := ItemJournalLine."Location Code"; + ItemLedgerEntry."Variant Code" := ItemJournalLine."Variant Code"; + ItemLedgerEntry.Quantity := ItemJournalLine."Quantity (Base)"; + ItemLedgerEntry."Remaining Quantity" := ItemLedgerEntry.Quantity; + ItemLedgerEntry.Positive := true; + ItemLedgerEntry.Open := true; + ItemLedgerEntry.Insert(true); + end; + + var + PositiveQuantityErr: Label 'The prepared adjustment must increase inventory.'; +} diff --git a/microsoft/knowledge/scm/post-item-ledger-changes-through-item-journals.good.al b/microsoft/knowledge/scm/post-item-ledger-changes-through-item-journals.good.al new file mode 100644 index 0000000..3e7866a --- /dev/null +++ b/microsoft/knowledge/scm/post-item-ledger-changes-through-item-journals.good.al @@ -0,0 +1,20 @@ +codeunit 50101 "SCM Stock Adjustment Good" +{ + procedure PostPreparedPositiveAdjustment(var ItemJournalLine: Record "Item Journal Line") + var + ItemJnlPostLine: Codeunit "Item Jnl.-Post Line"; + begin + ItemJournalLine.TestField("Entry Type", ItemJournalLine."Entry Type"::"Positive Adjmt."); + ItemJournalLine.TestField("Value Entry Type", ItemJournalLine."Value Entry Type"::"Direct Cost"); + ItemJournalLine.TestField("Item No."); + ItemJournalLine.TestField("Posting Date"); + ItemJournalLine.TestField("Quantity (Base)"); + if ItemJournalLine."Quantity (Base)" < 0 then + Error(PositiveQuantityErr); + + ItemJnlPostLine.RunWithCheck(ItemJournalLine); + end; + + var + PositiveQuantityErr: Label 'The prepared adjustment must increase inventory.'; +} diff --git a/microsoft/knowledge/scm/post-item-ledger-changes-through-item-journals.md b/microsoft/knowledge/scm/post-item-ledger-changes-through-item-journals.md new file mode 100644 index 0000000..0df6dac --- /dev/null +++ b/microsoft/knowledge/scm/post-item-ledger-changes-through-item-journals.md @@ -0,0 +1,42 @@ +--- +bc-version: [all] +domain: scm +keywords: [item-ledger-entry, value-entry, item-journal-line, item-jnl-post-line, runwithcheck, inventory-posting] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Post item ledger changes through item journals + +## Description + +An item ledger entry is not an independently insertable stock balance. Posting connects its quantity to item applications, reservations, tracking, and one or more value entries; expected cost, invoicing, revaluation, and later cost adjustment can produce different value entries for the same item entry. Running a table's insert trigger does not run this posting workflow or its configured inventory-to-G/L integration. + +## Best Practice + +The posting entry point for a prepared standalone item-journal movement is `"Item Jnl.-Post Line".RunWithCheck`. Persisted journal batches use `"Item Jnl.-Post Batch"`; sales, purchase, transfer, assembly, and production transactions retain their owning document/posting orchestration. Those workflows create the ledger, value, and application records together with their required checks. + +Supported posting events enrich validated journal/source data within the owning workflow. Read-only ledger queries, temporary ledger previews, extension-owned metadata fields, and publisher parameters consumed by the poster are not independent ledger posting. A temporary item-journal buffer can still produce persistent entries when passed to a posting codeunit. A publisher's `var` parameter or `IsHandled` flag alone does not supply the missing quantity/cost coordination; the normal engine's own inserts operate within that coordination. + +Not every value entry points to an item ledger entry: capacity and production WIP have their own supported posting relationships. Assembly and manufacturing posting retain order/component/routing and capacity context; one bare output/consumption call is not full order completion. + +The clean sample takes an already prepared positive-adjustment journal line. It is not a substitute for journal preparation, batch revaluation, warehouse reconciliation, or source-document posting. + +See sample: [`post-item-ledger-changes-through-item-journals.good.al`](post-item-ledger-changes-through-item-journals.good.al). + +## Anti Pattern + +Independent inserts/deletes of persistent `"Item Ledger Entry"` or `"Value Entry"` transaction rows, or overwrites of posted quantity, remaining quantity, application identity, or cost amounts, bypass the coordinated receipt, shipment, adjustment, or cost-correction workflow. `Insert(true)`, `Modify(true)`, and balanced-looking quantities do not supply that orchestration: stock can change without the corresponding application/value graph, or downstream cost flow can remain stale. + +A table declaration or custom annotation-field update does not change inventory quantities or costs and is outside this transaction-state concern. + +See sample: [`post-item-ledger-changes-through-item-journals.bad.al`](post-item-ledger-changes-through-item-journals.bad.al). + +## References + +- [Inventory posting design](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-inventory-posting) +- [Item application design](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-item-application) +- [BaseApp item-journal posting entry point](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al#L162-L179) +- [Assembly-order posting context](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-assembly-order-posting) +- [Production-order posting context](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-production-order-posting) diff --git a/microsoft/knowledge/scm/post-revaluation-through-the-item-journal-batch.bad.al b/microsoft/knowledge/scm/post-revaluation-through-the-item-journal-batch.bad.al new file mode 100644 index 0000000..df450cc --- /dev/null +++ b/microsoft/knowledge/scm/post-revaluation-through-the-item-journal-batch.bad.al @@ -0,0 +1,17 @@ +codeunit 50102 "SCM Revaluation Batch Bad" +{ + procedure PostCalculatedRevaluationBatch(TemplateName: Code[10]; BatchName: Code[10]) + var + ItemJournalLine: Record "Item Journal Line"; + ItemJnlPostLine: Codeunit "Item Jnl.-Post Line"; + begin + ItemJournalLine.SetRange("Journal Template Name", TemplateName); + ItemJournalLine.SetRange("Journal Batch Name", BatchName); + ItemJournalLine.FindSet(); + repeat + ItemJournalLine.TestField("Value Entry Type", ItemJournalLine."Value Entry Type"::Revaluation); + ItemJournalLine.TestField("Inventory Value Per"); + ItemJnlPostLine.RunWithCheck(ItemJournalLine); + until ItemJournalLine.Next() = 0; + end; +} diff --git a/microsoft/knowledge/scm/post-revaluation-through-the-item-journal-batch.good.al b/microsoft/knowledge/scm/post-revaluation-through-the-item-journal-batch.good.al new file mode 100644 index 0000000..810fad5 --- /dev/null +++ b/microsoft/knowledge/scm/post-revaluation-through-the-item-journal-batch.good.al @@ -0,0 +1,16 @@ +codeunit 50103 "SCM Revaluation Batch Good" +{ + procedure PostCalculatedRevaluationBatch(TemplateName: Code[10]; BatchName: Code[10]) + var + ItemJournalLine: Record "Item Journal Line"; + ItemJnlPostBatch: Codeunit "Item Jnl.-Post Batch"; + begin + ItemJournalLine.SetRange("Journal Template Name", TemplateName); + ItemJournalLine.SetRange("Journal Batch Name", BatchName); + ItemJournalLine.FindFirst(); + ItemJournalLine.TestField("Value Entry Type", ItemJournalLine."Value Entry Type"::Revaluation); + ItemJournalLine.TestField("Inventory Value Per"); + + ItemJnlPostBatch.Run(ItemJournalLine); + end; +} diff --git a/microsoft/knowledge/scm/post-revaluation-through-the-item-journal-batch.md b/microsoft/knowledge/scm/post-revaluation-through-the-item-journal-batch.md new file mode 100644 index 0000000..9730c2d --- /dev/null +++ b/microsoft/knowledge/scm/post-revaluation-through-the-item-journal-batch.md @@ -0,0 +1,38 @@ +--- +bc-version: [all] +domain: scm +keywords: [revaluation, inventory-value-per, partial-revaluation, item-jnl-post-batch, item-journal-line, runwithcheck, standard-cost] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Post calculated revaluation through the item journal batch + +## Description + +A calculated revaluation line with nonblank `"Inventory Value Per"` represents an aggregate, not a finalized posting against one item ledger entry. Codeunit `"Item Jnl.-Post Batch"` distributes that value over eligible entries, handles rounding, and coordinates Item/SKU standard-cost updates. Calling the line poster directly skips that batch work even though the input is a valid `"Item Journal Line"`. + +## Best Practice + +Posting a prepared revaluation batch through `"Item Jnl.-Post Batch"` preserves the calculated line's valuation date, aggregation scope, location/variant filters, and revaluation fields. The batch expands summarized values into per-entry postings and checks that the eligible inventory has not changed; for partial revaluation it also rechecks remaining quantity before posting. + +The public `"Item Jnl.-Post Line".RunWithCheck` API does not replace that orchestration. It remains legitimate for finalized individual-entry revaluation lines within a workflow that already supplies the necessary checks; the batch itself uses the line poster. Ordinary quantity journals and finalized per-entry revaluations are distinct from this summarized/partial-revaluation case. + +The samples explicitly require `"Value Entry Type" = Revaluation` and a nonblank `"Inventory Value Per"` in an existing calculated journal batch. They demonstrate posting, not how to calculate a new valuation or choose a standard cost. + +See sample: [`post-revaluation-through-the-item-journal-batch.good.al`](post-revaluation-through-the-item-journal-batch.good.al). + +## Anti Pattern + +A loop that sends calculated aggregate revaluation lines straight to `"Item Jnl.-Post Line"` skips distribution over the underlying item entries. A partial-revaluation workflow without the remaining-quantity recheck can post a valuation against inventory that no longer matches the calculation. + +Directly editing existing `"Value Entry"` cost amounts or the Item's unit cost does not repair those allocation and adjustment relationships. Their correction belongs to the revaluation/cost-adjustment workflow. + +See sample: [`post-revaluation-through-the-item-journal-batch.bad.al`](post-revaluation-through-the-item-journal-batch.bad.al). + +## References + +- [Revaluation design](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-revaluation) +- [Inventory posting design](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-inventory-posting) +- [BaseApp summarized revaluation and remaining-quantity checks](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostBatch.Codeunit.al#L510-L714) diff --git a/microsoft/knowledge/scm/post-transfers-through-shipment-and-receipt-codeunits.bad.al b/microsoft/knowledge/scm/post-transfers-through-shipment-and-receipt-codeunits.bad.al new file mode 100644 index 0000000..2e14920 --- /dev/null +++ b/microsoft/knowledge/scm/post-transfers-through-shipment-and-receipt-codeunits.bad.al @@ -0,0 +1,22 @@ +codeunit 50112 "SCM Transfer Posting Bad" +{ + procedure ShipTransferOrder(TransferOrderNo: Code[20]; var ItemJournalLine: Record "Item Journal Line") + var + TransferHeader: Record "Transfer Header"; + Location: Record Location; + ItemJnlPostLine: Codeunit "Item Jnl.-Post Line"; + begin + TransferHeader.Get(TransferOrderNo); + TransferHeader.TestField("Direct Transfer", false); + TransferHeader.TestField("In-Transit Code"); + Location.Get(TransferHeader."Transfer-from Code"); + Location.TestField("Require Shipment", false); + ItemJournalLine.TestField("Entry Type", ItemJournalLine."Entry Type"::Transfer); + ItemJournalLine.TestField("Location Code", TransferHeader."Transfer-from Code"); + ItemJournalLine.TestField("New Location Code", TransferHeader."In-Transit Code"); + + ItemJnlPostLine.RunWithCheck(ItemJournalLine); + TransferHeader."Last Shipment No." := ItemJournalLine."Document No."; + TransferHeader.Modify(true); + end; +} diff --git a/microsoft/knowledge/scm/post-transfers-through-shipment-and-receipt-codeunits.good.al b/microsoft/knowledge/scm/post-transfers-through-shipment-and-receipt-codeunits.good.al new file mode 100644 index 0000000..8507c99 --- /dev/null +++ b/microsoft/knowledge/scm/post-transfers-through-shipment-and-receipt-codeunits.good.al @@ -0,0 +1,32 @@ +codeunit 50113 "SCM Transfer Posting Good" +{ + procedure ShipTransferOrder(TransferOrderNo: Code[20]) + var + TransferHeader: Record "Transfer Header"; + Location: Record Location; + TransferOrderPostShipment: Codeunit "TransferOrder-Post Shipment"; + begin + TransferHeader.Get(TransferOrderNo); + TransferHeader.TestField("Direct Transfer", false); + TransferHeader.TestField("In-Transit Code"); + Location.Get(TransferHeader."Transfer-from Code"); + Location.TestField("Require Shipment", false); + + TransferOrderPostShipment.Run(TransferHeader); + end; + + procedure ReceiveTransferOrder(TransferOrderNo: Code[20]) + var + TransferHeader: Record "Transfer Header"; + Location: Record Location; + TransferOrderPostReceipt: Codeunit "TransferOrder-Post Receipt"; + begin + TransferHeader.Get(TransferOrderNo); + TransferHeader.TestField("Direct Transfer", false); + TransferHeader.TestField("In-Transit Code"); + Location.Get(TransferHeader."Transfer-to Code"); + Location.TestField("Require Receive", false); + + TransferOrderPostReceipt.Run(TransferHeader); + end; +} diff --git a/microsoft/knowledge/scm/post-transfers-through-shipment-and-receipt-codeunits.md b/microsoft/knowledge/scm/post-transfers-through-shipment-and-receipt-codeunits.md new file mode 100644 index 0000000..b541dba --- /dev/null +++ b/microsoft/knowledge/scm/post-transfers-through-shipment-and-receipt-codeunits.md @@ -0,0 +1,41 @@ +--- +bc-version: [all] +domain: scm +keywords: [transfer-header, transfer-line, transferorder-post-shipment, transferorder-post-receipt, in-transit-code, last-shipment-no, item-application-entry] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Post transfers through shipment and receipt codeunits + +## Description + +A two-step transfer order preserves a continuous quantity, reservation, and cost/application lineage from the source through in-transit to the destination. Its shipment and receipt workflows also own posted documents and derived lines for partial receipt. Posting an item-journal movement and manually marking a transfer header or line as shipped is not equivalent, even if the total quantities balance. + +## Best Practice + +A prepared non-direct transfer order without required warehouse documents uses `"TransferOrder-Post Shipment".Run` at shipment and `"TransferOrder-Post Receipt".Run` at receipt, with the actual `"Transfer Header"`. Validated source quantities to ship/receive prepare the operation; posted quantity counters are results of posting. + +Required warehouse shipment or receipt enters the warehouse document posting workflow, which invokes the transfer poster with its real source context. Direct transfers have their configured standard workflow; the two-step sample's in-transit guard is not a universal requirement. + +Standalone item reclassification journals and bin movements are legitimate separate operations, not completion of an existing transfer order. Tracking/application splits and average-cost handling mean transfers do not have one fixed item-entry count or a nonzero `"Transferred-from Entry No."` on every application. + +See sample: [`post-transfers-through-shipment-and-receipt-codeunits.good.al`](post-transfers-through-shipment-and-receipt-codeunits.good.al). + +## Anti Pattern + +Ad-hoc item postings, independent positive/negative adjustments, manually created posted-transfer rows, and direct shipment/receipt-counter changes cannot substitute for transfer-order posting. Updating `"Last Shipment No."` after a bare item-journal call does not create the posted shipment, source-line progress, or transfer application lineage. + +Changing an existing item ledger entry's location or inventing application links does not repair that missing workflow. Metadata enrichment within the normal shipment/receipt or direct-transfer workflow is distinct from replacing the posting operation. + +See sample: [`post-transfers-through-shipment-and-receipt-codeunits.bad.al`](post-transfers-through-shipment-and-receipt-codeunits.bad.al). + +## References + +- [Transfer inventory between locations](https://learn.microsoft.com/en-us/dynamics365/business-central/inventory-how-transfer-between-locations) +- [Item application design](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-item-application) +- [Cost adjustment design](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-cost-adjustment) +- [BaseApp shipment journal/source linkage](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Inventory/Transfer/TransferOrderPostShipment.Codeunit.al#L292-L336) +- [Partial-receipt derived-line handling](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Inventory/Transfer/TransferOrderPostReceipt.Codeunit.al#L457-L529) +- [Transfer application and average-cost branches](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al#L1911-L1976) diff --git a/microsoft/knowledge/scm/reconcile-warehouse-adjustments-with-the-item-ledger.bad.al b/microsoft/knowledge/scm/reconcile-warehouse-adjustments-with-the-item-ledger.bad.al new file mode 100644 index 0000000..97c7cb4 --- /dev/null +++ b/microsoft/knowledge/scm/reconcile-warehouse-adjustments-with-the-item-ledger.bad.al @@ -0,0 +1,37 @@ +codeunit 50110 "SCM Warehouse Adjustment Bad" +{ + procedure ReconcileRegisteredWarehouseAdjustment(ItemNo: Code[20]; LocationCode: Code[10]; TemplateName: Code[10]; BatchName: Code[10]; PostingDate: Date; DocumentNo: Code[20]): Boolean + var + Item: Record Item; + ItemJournalBatch: Record "Item Journal Batch"; + ItemJournalLine: Record "Item Journal Line"; + Location: Record Location; + CalculateWhseAdjustment: Report "Calculate Whse. Adjustment"; + begin + Location.Get(LocationCode); + Location.TestField("Directed Put-away and Pick", true); + Location.TestField("Adjustment Bin Code"); + ItemJournalBatch.Get(TemplateName, BatchName); + ItemJournalLine.SetRange("Journal Template Name", TemplateName); + ItemJournalLine.SetRange("Journal Batch Name", BatchName); + if not ItemJournalLine.IsEmpty() then + Error(EmptyBatchErr); + + Item.Get(ItemNo); + Item.SetRecFilter(); + Item.SetRange("Location Filter", LocationCode); + ItemJournalLine."Journal Template Name" := TemplateName; + ItemJournalLine."Journal Batch Name" := BatchName; + CalculateWhseAdjustment.SetItemJnlLine(ItemJournalLine); + CalculateWhseAdjustment.SetTableView(Item); + CalculateWhseAdjustment.InitializeRequest(PostingDate, DocumentNo); + CalculateWhseAdjustment.SetHideValidationDialog(true); + CalculateWhseAdjustment.UseRequestPage(false); + CalculateWhseAdjustment.RunModal(); + + exit(true); + end; + + var + EmptyBatchErr: Label 'Use an empty, dedicated item journal batch for warehouse reconciliation.'; +} diff --git a/microsoft/knowledge/scm/reconcile-warehouse-adjustments-with-the-item-ledger.good.al b/microsoft/knowledge/scm/reconcile-warehouse-adjustments-with-the-item-ledger.good.al new file mode 100644 index 0000000..469ed2d --- /dev/null +++ b/microsoft/knowledge/scm/reconcile-warehouse-adjustments-with-the-item-ledger.good.al @@ -0,0 +1,42 @@ +codeunit 50111 "SCM Warehouse Adjustment Good" +{ + procedure ReconcileRegisteredWarehouseAdjustment(ItemNo: Code[20]; LocationCode: Code[10]; TemplateName: Code[10]; BatchName: Code[10]; PostingDate: Date; DocumentNo: Code[20]): Boolean + var + Item: Record Item; + ItemJournalBatch: Record "Item Journal Batch"; + ItemJournalLine: Record "Item Journal Line"; + Location: Record Location; + CalculateWhseAdjustment: Report "Calculate Whse. Adjustment"; + ItemJnlPostBatch: Codeunit "Item Jnl.-Post Batch"; + begin + Location.Get(LocationCode); + Location.TestField("Directed Put-away and Pick", true); + Location.TestField("Adjustment Bin Code"); + ItemJournalBatch.Get(TemplateName, BatchName); + ItemJournalLine.SetRange("Journal Template Name", TemplateName); + ItemJournalLine.SetRange("Journal Batch Name", BatchName); + if not ItemJournalLine.IsEmpty() then + Error(EmptyBatchErr); + + Item.Get(ItemNo); + Item.SetRecFilter(); + Item.SetRange("Location Filter", LocationCode); + ItemJournalLine."Journal Template Name" := TemplateName; + ItemJournalLine."Journal Batch Name" := BatchName; + CalculateWhseAdjustment.SetItemJnlLine(ItemJournalLine); + CalculateWhseAdjustment.SetTableView(Item); + CalculateWhseAdjustment.InitializeRequest(PostingDate, DocumentNo); + CalculateWhseAdjustment.SetHideValidationDialog(true); + CalculateWhseAdjustment.UseRequestPage(false); + CalculateWhseAdjustment.RunModal(); + + if ItemJournalLine.FindFirst() then begin + ItemJournalLine.TestField("Warehouse Adjustment", true); + ItemJnlPostBatch.Run(ItemJournalLine); + end; + exit(true); + end; + + var + EmptyBatchErr: Label 'Use an empty, dedicated item journal batch for warehouse reconciliation.'; +} diff --git a/microsoft/knowledge/scm/reconcile-warehouse-adjustments-with-the-item-ledger.md b/microsoft/knowledge/scm/reconcile-warehouse-adjustments-with-the-item-ledger.md new file mode 100644 index 0000000..06230e7 --- /dev/null +++ b/microsoft/knowledge/scm/reconcile-warehouse-adjustments-with-the-item-ledger.md @@ -0,0 +1,40 @@ +--- +bc-version: [all] +domain: scm +keywords: [warehouse-adjustment, calculate-whse-adjustment, adjustment-bin-code, directed-put-away-and-pick, item-journal-line, warehouse-entry] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Reconcile warehouse adjustments with the item ledger + +## Description + +At a Directed Put-away and Pick location, registering an ordinary warehouse quantity or physical-inventory adjustment and synchronizing it to inventory are distinct steps. The warehouse registration balances quantity through the adjustment bin. An additional ordinary item-journal increase/decrease is not the same as consuming that pending warehouse adjustment. + +## Best Practice + +After warehouse registration, `"Calculate Whse. Adjustment"` prepares item-journal lines for the intended item/location and batch; `"Item Jnl.-Post Batch"` posts those lines. The calculation derives the reconciliation by location, variant, units of measure, and tracking, marks the lines `"Warehouse Adjustment"`, and accounts for already prepared unposted adjustments. + +Reconciliation is separate from source-document posting: warehouse receipts/shipments use their document workflows. Intentional warehouse-only staging is valid when a separately owned reconciliation step completes the process; registration need not perform both phases in one call. + +Bin movements need not change total inventory, and warehouse tracking/expiration reclassification has a standard batch path that can also post item-journal entries. Standard reclassification and basic-location item adjustments are not this ordinary advanced-warehouse quantity-adjustment case. + +The samples start after warehouse quantity registration and report whether inventory reconciliation completed. The clean sample calculates and posts into an empty dedicated batch; it is not a complete warehouse physical-count workflow. + +See sample: [`reconcile-warehouse-adjustments-with-the-item-ledger.good.al`](reconcile-warehouse-adjustments-with-the-item-ledger.good.al). + +## Anti Pattern + +A manually mirrored ordinary item-journal line does not reconcile a registered advanced-warehouse quantity adjustment. Likewise, a reconciliation function that returns completion after only registration or adjustment calculation leaves any generated adjustment lines unposted. Calculation prepares journal lines; it does not post them. + +Invented positive/negative quantities or flipping `"Warehouse Adjustment"` on an arbitrary line does not establish the required relationship to the adjustment-bin balance and tracked quantities. That relationship comes from the calculation step. + +See sample: [`reconcile-warehouse-adjustments-with-the-item-ledger.bad.al`](reconcile-warehouse-adjustments-with-the-item-ledger.bad.al). + +## References + +- [Synchronize adjusted warehouse entries with item ledger entries](https://learn.microsoft.com/en-us/dynamics365/business-central/inventory-how-count-adjust-reclassify#to-synchronize-the-adjusted-warehouse-entries-with-the-related-item-ledger-entries) +- [BaseApp warehouse-adjustment calculation](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Warehouse/Journal/CalculateWhseAdjustment.Report.al#L298-L384) +- [Warehouse reclassification exception](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Warehouse/Journal/WhseJnlRegisterBatch.Codeunit.al#L196-L210) diff --git a/microsoft/knowledge/scm/transfer-item-tracking-through-source-reservation-codeunits.bad.al b/microsoft/knowledge/scm/transfer-item-tracking-through-source-reservation-codeunits.bad.al new file mode 100644 index 0000000..89368cf --- /dev/null +++ b/microsoft/knowledge/scm/transfer-item-tracking-through-source-reservation-codeunits.bad.al @@ -0,0 +1,30 @@ +codeunit 50108 "SCM Tracking Transfer Bad" +{ + procedure TransferBlanketOrderTracking(var SourceBlanketOrderLine: Record "Sales Line"; var DestinationSalesOrderLine: Record "Sales Line"; QuantityBaseToTransfer: Decimal) + var + ReservationEntry: Record "Reservation Entry"; + begin + SourceBlanketOrderLine.TestField("Document Type", SourceBlanketOrderLine."Document Type"::"Blanket Order"); + SourceBlanketOrderLine.TestField(Type, SourceBlanketOrderLine.Type::Item); + DestinationSalesOrderLine.TestField("Document Type", DestinationSalesOrderLine."Document Type"::Order); + DestinationSalesOrderLine.TestField(Type, DestinationSalesOrderLine.Type::Item); + DestinationSalesOrderLine.TestField("No.", SourceBlanketOrderLine."No."); + if QuantityBaseToTransfer <= 0 then + Error(PositiveQuantityErr); + + ReservationEntry.SetRange("Source Type", Database::"Sales Line"); + ReservationEntry.SetRange("Source Subtype", SourceBlanketOrderLine."Document Type".AsInteger()); + ReservationEntry.SetRange("Source ID", SourceBlanketOrderLine."Document No."); + ReservationEntry.SetRange("Source Ref. No.", SourceBlanketOrderLine."Line No."); + ReservationEntry.SetRange(Positive, false); + ReservationEntry.FindFirst(); + ReservationEntry."Source Subtype" := DestinationSalesOrderLine."Document Type".AsInteger(); + ReservationEntry."Source ID" := DestinationSalesOrderLine."Document No."; + ReservationEntry."Source Ref. No." := DestinationSalesOrderLine."Line No."; + ReservationEntry.Validate("Quantity (Base)", -QuantityBaseToTransfer); + ReservationEntry.Modify(true); + end; + + var + PositiveQuantityErr: Label 'The base quantity to transfer must be positive.'; +} diff --git a/microsoft/knowledge/scm/transfer-item-tracking-through-source-reservation-codeunits.good.al b/microsoft/knowledge/scm/transfer-item-tracking-through-source-reservation-codeunits.good.al new file mode 100644 index 0000000..a655b34 --- /dev/null +++ b/microsoft/knowledge/scm/transfer-item-tracking-through-source-reservation-codeunits.good.al @@ -0,0 +1,20 @@ +codeunit 50109 "SCM Tracking Transfer Good" +{ + procedure TransferBlanketOrderTracking(var SourceBlanketOrderLine: Record "Sales Line"; var DestinationSalesOrderLine: Record "Sales Line"; QuantityBaseToTransfer: Decimal) + var + SalesLineReserve: Codeunit "Sales Line-Reserve"; + begin + SourceBlanketOrderLine.TestField("Document Type", SourceBlanketOrderLine."Document Type"::"Blanket Order"); + SourceBlanketOrderLine.TestField(Type, SourceBlanketOrderLine.Type::Item); + DestinationSalesOrderLine.TestField("Document Type", DestinationSalesOrderLine."Document Type"::Order); + DestinationSalesOrderLine.TestField(Type, DestinationSalesOrderLine.Type::Item); + DestinationSalesOrderLine.TestField("No.", SourceBlanketOrderLine."No."); + if QuantityBaseToTransfer <= 0 then + Error(PositiveQuantityErr); + + SalesLineReserve.TransferSaleLineToSalesLine(SourceBlanketOrderLine, DestinationSalesOrderLine, QuantityBaseToTransfer); + end; + + var + PositiveQuantityErr: Label 'The base quantity to transfer must be positive.'; +} diff --git a/microsoft/knowledge/scm/transfer-item-tracking-through-source-reservation-codeunits.md b/microsoft/knowledge/scm/transfer-item-tracking-through-source-reservation-codeunits.md new file mode 100644 index 0000000..8bc42cc --- /dev/null +++ b/microsoft/knowledge/scm/transfer-item-tracking-through-source-reservation-codeunits.md @@ -0,0 +1,41 @@ +--- +bc-version: [all] +domain: scm +keywords: [reservation-entry, tracking-specification, sales-line-reserve, transfersalelinetosalesline, transferreserventry, copyitemtracking, quantity-base] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Transfer item tracking through source reservation codeunits + +## Description + +Moving lot/serial tracking between document lines is a source-ownership operation, not a copy of visible tracking fields. Partial movement must retain the old source's remainder, destination units of measure, quantities to handle/invoice, status, sign, and any reservation counterpart. Repointing a `"Reservation Entry"` loses this coordination; inserting a `"Tracking Specification"` row alone does not book the destination's source tracking. + +## Best Practice + +Reservation codeunits provide source-specific tracking workflows. For the tracking portion of blanket-sales-order or quote conversion to a sales order, `"Sales Line-Reserve".TransferSaleLineToSalesLine` takes the existing source line, prepared destination line, and quantity to transfer in **base units**. It delegates the source/status and quantity movement to `"Create Reserv. Entry".TransferReservEntry`. + +The caller still owns document conversion and destination-line preparation; this method does not create a sales order. The source and destination must have compatible item, variant, location, and source identity. Purchases, transfers, assembly, and production have their own source-specific wrappers rather than sharing the sales conversion contract. + +`"Item Tracking Management".CopyItemTracking` serves a different purpose: it creates Prospect copies, not a transfer of reservation ownership. That is valid for its intended copy workflow. Temporary Tracking Specification processing and persisted historical tracking specifications are also normal; the working/historic representation differs from the current source booking. + +See sample: [`transfer-item-tracking-through-source-reservation-codeunits.good.al`](transfer-item-tracking-through-source-reservation-codeunits.good.al). + +## Anti Pattern + +Direct rewrites of persistent `"Reservation Entry"` source type/subtype, ID, reference number, or quantities do not perform the source-line conversion or partial tracking-transfer workflow. Changing only `"Quantity (Base)"` and source keys can drop the remainder or leave the other tracking/reservation quantities attached to the wrong source. + +A tracking copy cannot replace movement of an existing binding reservation. Legitimate Prospect copying, temporary tracking buffers, historical tracking reads, and source-specific engine calls serve distinct purposes and are not independent reservation transfers. + +See sample: [`transfer-item-tracking-through-source-reservation-codeunits.bad.al`](transfer-item-tracking-through-source-reservation-codeunits.bad.al). + +## References + +- [Item Tracking Lines window design](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-item-tracking-lines-window) +- [Active versus historic item-tracking entries](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-active-versus-historic-item-tracking-entries) +- [Item tracking and reservations](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-item-tracking-and-reservations) +- [BaseApp sales tracking transfer](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Sales/Document/SalesLineReserve.Codeunit.al#L532-L578) +- [Base-unit conversion caller](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Sales/Document/BlanketSalesOrdertoOrder.Codeunit.al#L195-L198) +- [Prospect-copy API](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Inventory/Tracking/ItemTrackingManagement.Codeunit.al#L575-L657) diff --git a/microsoft/knowledge/scm/use-date-aware-availability-for-promising.bad.al b/microsoft/knowledge/scm/use-date-aware-availability-for-promising.bad.al new file mode 100644 index 0000000..b90f932 --- /dev/null +++ b/microsoft/knowledge/scm/use-date-aware-availability-for-promising.bad.al @@ -0,0 +1,21 @@ +codeunit 50114 "SCM Additional Promise Bad" +{ + procedure CanPromiseAdditionalDemand(ItemNo: Code[20]; LocationCode: Code[10]; VariantCode: Code[10]; ShipmentDate: Date; RequestedAdditionalQuantityBase: Decimal; LookaheadDateFormula: DateFormula): Boolean + var + Item: Record Item; + begin + if (ShipmentDate = 0D) or (RequestedAdditionalQuantityBase <= 0) then + Error(DemandInputErr); + Item.Get(ItemNo); + Item.TestField(Type, Item.Type::Inventory); + Item.SetRange("Location Filter", LocationCode); + Item.SetRange("Variant Filter", VariantCode); + Item.SetRange("Date Filter", 0D, ShipmentDate); + + Item.CalcFields(Inventory); + exit(Item.Inventory >= RequestedAdditionalQuantityBase); + end; + + var + DemandInputErr: Label 'Enter a shipment date and a positive additional base quantity.'; +} diff --git a/microsoft/knowledge/scm/use-date-aware-availability-for-promising.good.al b/microsoft/knowledge/scm/use-date-aware-availability-for-promising.good.al new file mode 100644 index 0000000..b33ee79 --- /dev/null +++ b/microsoft/knowledge/scm/use-date-aware-availability-for-promising.good.al @@ -0,0 +1,27 @@ +codeunit 50115 "SCM Additional Promise Good" +{ + procedure CanPromiseAdditionalDemand(ItemNo: Code[20]; LocationCode: Code[10]; VariantCode: Code[10]; ShipmentDate: Date; RequestedAdditionalQuantityBase: Decimal; LookaheadDateFormula: DateFormula): Boolean + var + Item: Record Item; + AvailableToPromise: Codeunit "Available to Promise"; + GrossRequirement: Decimal; + ScheduledReceipt: Decimal; + PromisableQuantityBase: Decimal; + begin + if (ShipmentDate = 0D) or (RequestedAdditionalQuantityBase <= 0) then + Error(DemandInputErr); + Item.Get(ItemNo); + Item.TestField(Type, Item.Type::Inventory); + Item.SetRange("Location Filter", LocationCode); + Item.SetRange("Variant Filter", VariantCode); + Item.SetRange("Date Filter", 0D, ShipmentDate); + + PromisableQuantityBase := AvailableToPromise.CalcQtyAvailableToPromise( + Item, GrossRequirement, ScheduledReceipt, ShipmentDate, + Enum::"Analysis Period Type"::Day, LookaheadDateFormula); + exit(PromisableQuantityBase >= RequestedAdditionalQuantityBase); + end; + + var + DemandInputErr: Label 'Enter a shipment date and a positive additional base quantity.'; +} diff --git a/microsoft/knowledge/scm/use-date-aware-availability-for-promising.md b/microsoft/knowledge/scm/use-date-aware-availability-for-promising.md new file mode 100644 index 0000000..a182a94 --- /dev/null +++ b/microsoft/knowledge/scm/use-date-aware-availability-for-promising.md @@ -0,0 +1,39 @@ +--- +bc-version: [all] +domain: scm +keywords: [available-to-promise, calcqtyavailabletopromise, inventory, shipment-date, gross-requirement, scheduled-receipt, location-filter, variant-filter] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Use date-aware availability for promising + +## Description + +`Item.Inventory` is an on-hand quantity, not an available-to-promise answer. Promising additional demand must account for the requested date, location and variant, reservations, scheduled receipts, existing requirements, and demand within the configured lookahead. An on-hand comparison can promise inventory already committed elsewhere and miss incoming supply. + +## Best Practice + +For additional demand not already recorded on a source line, `"Available to Promise".CalcQtyAvailableToPromise` uses the Item's location/variant filters, date range ending on the shipment date, and configured period/lookahead horizon. Its result and the additional quantity are compared in base units. A fresh calculation context or the codeunit's recalculation support avoids carrying cached quantities between unrelated items or requests. + +The source-aware order-promising/availability workflow accounts for an existing sales line's own quantity or delta; applying an additional-demand calculation to that line can double-count it. Assembly and production requirements/supply likewise participate in the standard availability context, not just a sales-only stock subtraction. + +An ATP result is not a reservation or a guarantee of warehouse pickability. Lot/serial constraints, bins, warehouse activity, and later concurrent changes still need their own checks. Conversely, an on-hand display, valuation report, or deliberately immediate-stock-only check can use `Item.Inventory`: it answers a different business question from ATP. + +See sample: [`use-date-aware-availability-for-promising.good.al`](use-date-aware-availability-for-promising.good.al). + +## Anti Pattern + +`CalcFields(Inventory)` or an equivalent item-ledger quantity sum used as the complete decision for a dated additional-demand promise ignores existing demand and incoming supply, even with location/variant filters. An Inventory FlowField read for an on-hand display has no such promising contract. + +Losing location, variant, date, or source-line context changes the calculation's business meaning. Another supported workflow that preserves the same availability semantics does not have to call this exact API. + +See sample: [`use-date-aware-availability-for-promising.bad.al`](use-date-aware-availability-for-promising.bad.al). + +## References + +- [Calculate order-promising dates](https://learn.microsoft.com/en-us/dynamics365/business-central/sales-how-to-calculate-order-promising-dates) +- [Availability in the warehouse](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-availability-in-the-warehouse) +- [BaseApp ATP calculation](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Inventory/Availability/AvailabletoPromise.Codeunit.al#L52-L184) +- [Forward-demand lookahead](https://github.com/microsoft/BCApps/blob/8f7a04cb0db8aa96cb97e055c45c61aead49e280/src/Layers/W1/BaseApp/Inventory/Availability/AvailabletoPromise.Codeunit.al#L295-L356) diff --git a/microsoft/skills/review/al-code-review.md b/microsoft/skills/review/al-code-review.md index dc1f1db..7cfe07b 100644 --- a/microsoft/skills/review/al-code-review.md +++ b/microsoft/skills/review/al-code-review.md @@ -28,6 +28,7 @@ sub-skills: - microsoft/skills/review/al-reporting-review.md - microsoft/skills/review/al-appsource-review.md - microsoft/skills/review/al-telemetry-review.md + - microsoft/skills/review/al-scm-review.md --- # AL code review diff --git a/microsoft/skills/review/al-scm-review.md b/microsoft/skills/review/al-scm-review.md new file mode 100644 index 0000000..a434529 --- /dev/null +++ b/microsoft/skills/review/al-scm-review.md @@ -0,0 +1,121 @@ +--- +kind: action-skill +id: al-scm-review +version: 1 +title: AL Supply Chain Management review +description: Reviews SCM inventory costing, item application, reservations, order tracking, item tracking, warehouse, transfer, and planning workflows in AL. +inputs: [pr-diff, file-path, folder-path] +outputs: [findings-report] +bc-version: [all] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# AL Supply Chain Management review + +Reviews AL source against the `scm` knowledge domain. This leaf invokes no +sub-skills and is composed by `al-code-review`. For a folder, inspect every +relevant AL file; a folder supplies no historical baseline. + +## Source + +Apply Relevance's source gate before retrieval. For a relevant scope, use READ's +**Bounded retrieval for review skills** with `-Domain scm` and +`-Technologies @('al')`. Consume every catalog page across enabled layers, +preserving exact paths and applicability. Select from metadata, then read only +worklisted complete articles. Entry owns index preparation; the leaf does not +rebuild. Unavailable/invalid helpers or indexes use READ's bounded native-read +fallback, never a success-shaped empty result. + +## Relevance + +Resolve changed record/codeunit types, source tables, publishers and calls. +Gate on code that mutates or posts inventory, application, reservation, +tracking, warehouse, transfer or planning state, or makes a supply/demand +availability decision. Stock displays and other read-only queries without +that decision do not pass the gate. Names, comments, captions, an `Item` +reference or a broad `ApplicationArea` alone are not signals. +If no SCM surface remains, return `not-applicable` with zero coverage +and no article-body retrieval; in mixed diffs, retain only relevant procedures +and their visible supporting context. + +SCM owns `"Item Ledger Entry"`, `"Value Entry"`, `"Capacity Ledger Entry"`, +`"Warehouse Entry"` and inventory posting/application records. Pure `"G/L Entry"`, +`"Cust. Ledger Entry"`, `"Vendor Ledger Entry"`, `"Detailed Cust. Ledg. Entry"`, +`"Detailed Vendor Ledg. Entry"`, `"VAT Entry"` and financial-only posting +mutations belong to Finance. They remain outside SCM even if Finance is absent +or disabled; do not reclaim them as SCM agent findings. Ownership is not a +claim that every owned surface already has a dedicated article. + +Apply READ's frontmatter filters using the target BC major version from +application dependency/host context (not the extension version), AL, known +localization and actual task/object application areas. Omitted context stays +unknown, not `[all]`; unknown areas alone do not exclude codeunits/subscribers. +Retain conditional articles only when configured, cap their findings at +`medium`, and name every unknown dimension. + +## Worklist + +Extract resolved object/type names, quoted fields, methods, enum members and +publishers. Normalize these and catalog keywords by lowercasing invariantly, +replacing punctuation/whitespace runs with one hyphen and trimming hyphens: +`"Item Ledger Entry"` becomes `item-ledger-entry`; `RunWithCheck` becomes +`runwithcheck`. Match whole tokens/phrases, not identifier substrings. + +Select matching keywords or catalog topics only for the same source surface +**and operation**. The following cues resolve slugs to actual enabled catalog +paths; they select articles, not findings. Facts and exceptions stay in articles. + +| Changed source surface and operation | Article slug | +| --- | --- | +| `"Item Ledger Entry"`/`"Value Entry"` transaction writes, or a standalone item-journal quantity/value posting entry point | `post-item-ledger-changes-through-item-journals` | +| Revaluation `"Item Journal Line"` with `"Inventory Value Per"` or `"Partial Revaluation"`, and its line/batch posting calls | `post-revaluation-through-the-item-journal-batch` | +| `"Item Application Entry"` relationship/quantity mutation, or `UnApply`, `ReApply`, `RedoApplications`, `CostAdjust` in an application-correction flow | `change-item-applications-through-posting-routines` | +| Binding-reservation cancellation: `"Reservation Entry"` status, delete/quantity/source edits, `CancelReservation`, or source reservation-lifecycle calls | `cancel-reservations-through-reservation-management` | +| Tracking source conversion/partial movement: `"Sales Line-Reserve"`, `TransferSaleLineToSalesLine`, `TransferReservEntry`, `CopyItemTracking`, or `"Reservation Entry"`/`"Tracking Specification"` source/quantity writes | `transfer-item-tracking-through-source-reservation-codeunits` | +| Registered warehouse quantity/physical-adjustment synchronization, `"Directed Put-away and Pick"`, `"Adjustment Bin Code"`, `"Warehouse Adjustment"`, or `"Calculate Whse. Adjustment"` and the resulting item-journal posting | `reconcile-warehouse-adjustments-with-the-item-ledger` | +| `"Transfer Header"`/`"Transfer Line"` shipment/receipt completion, transfer posting publishers, in-transit/document-link changes, or item-journal posting presented as transfer-order completion | `post-transfers-through-shipment-and-receipt-codeunits` | +| `Inventory`, `CalcQtyAvailableToPromise`, or stock sums used in a dated supply/demand promise, including changed location/variant/date filters and source-demand context | `use-date-aware-availability-for-promising` | +| `"Requisition Line"` action-message execution, accepted planning suggestions, `"Req. Wksh.-Make Order"`, `CarryOutBatchAction`, or linked supply creation/change plus requisition-line deletion | `carry-out-requisition-actions-through-the-standard-workflow` | + +Route clean supported calls through the same cues, not just suspicious writes. +Resolve actual normative conflicts per READ, preserving additive layers and +recording `layer-precedence`/`configuration` suppressions, not noncandidates. +Retrieve exact paths in ordinal chunks of at most eight, consume every +continuation, and never impose a top-eight cutoff. Samples use exact READ links. + +## Action + +Evaluate every opened article's normative facts, scope and exclusions against +visible persistence, caller contract, document state and operation. Emit only +concrete violations with business consequences and supported remediation; a +declaration, valid alternative or unseen caller is not evidence of a defect. + +- Use `major` for material SCM defects, `minor` for narrower best-practice + conflicts, and `blocker` only for an article-established platform guarantee. + Applicability alone produces no finding. High confidence requires unambiguous + evidence and known applicability; inference/conditional applicability caps it + at `medium`. +- Apply DO's single-owner deduplication. Equivalent findings for the same + inventory-originated posting bypass and correction have one SCM primary + owner, even when financial records are downstream. Prefer the most specific + SCM article and retain other applicable references as supporting evidence. + Distinct independent financial defects remain Finance; do not duplicate them. +- Agent findings stay strictly SCM-scoped under DO's precision bar, with + `references: []`, an `agent:` id and `minor`/`medium` ceilings. Generic AL and + other domains' concerns remain outside this leaf. +- Supply literal `suggested-code` only for a complete, local, unambiguous fix, + not a sample call that omits workflow setup/source identity. Explain omitted + mechanical-looking fixes with `suggested-code-omission-reason`. + +Outcome selection follows DO, including accurate coverage and reasons for +`partial`/`failed`. No surviving applicable corpus is `no-knowledge`; an existing +corpus with no matching operation is `completed` with an empty worklist. + +## Output + +Output conforms to the DO findings-report contract and shared schema. Every +finding MUST set `domain` to `"Supply Chain Management"`. Knowledge-backed ids +equal the primary opened article's exact catalog path. The coordinator, not +this leaf, sets `from-sub-skill`.