0104

Return It at the Cost You Sold It

inventory-costing
item-application
sales-return
credit-memo
item-ledger-entry

Return It at the Cost You Sold It

Two deliveries of the same item arrive weeks apart, one cheap and one expensive. A customer buys four pieces off the cheap pile, then sends two of them back. What are those two pieces worth?

Left alone, Business Central answers with its costing method: FIFO, average, standard — whatever the item card says. That answer is wrong here. The goods that came back are the very goods that left, and inventory only stays honest if they re-enter at the cost they left at. Get it wrong and you have quietly invented profit on a return, which is the kind of thing auditors find.

Your job is to build the return in AL so the costing method never gets a vote.

Requirements

Implement the codeunit "Exact Cost Return Mgt." with exactly these two public procedures — the grading tests bind to every name below character for character:

procedure FindSaleEntryNo(PostedShipmentNo: Code[20]): Integer
procedure PostExactCostReturn(PostedShipmentNo: Code[20]; ReturnQty: Decimal): Code[20]

Rule for FindSaleEntryNo:

  1. Return the "Entry No." of the item ledger entry that the posted sales shipment created — the outbound Sale entry whose document is that shipment. Every graded shipment has exactly one item line, so exactly one such entry exists.

Rules for PostExactCostReturn:

  1. Before creating or posting anything, refuse a return bigger than the delivery: if ReturnQty is greater than the Quantity on the posted shipment's item line, raise an error with a message that contains the phrase exceeds the shipped quantity — matched exactly, lower case, as one contiguous phrase. Nothing may be posted in that case.
  2. Otherwise create a sales credit memo for the shipment's "Sell-to Customer No." with exactly one line: type Item, the shipped item, quantity ReturnQty.
  3. Post the credit memo — received and invoiced — and return the number of the posted sales credit memo.
  4. The returned goods must re-enter inventory at the cost they left at. The item ledger entry the credit memo creates must carry a "Cost Amount (Actual)" of ReturnQty × the original sale entry's cost per unit, and an item application entry must record the pair: the return's entry as the inbound entry, the shipment's entry as the outbound entry. The item's costing method must not decide this cost — the fixtures are built so that FIFO's next layer and the item's blended unit cost both give a different answer, and both fail.
  5. The shipment's own item ledger entry keeps its cost. A return posts a new inbound entry; it never revalues, rewrites or replaces the sale.

Environment notes — the tests never violate these:

  • The graded item uses the FIFO costing method and has two purchase layers of 10 pieces at different unit costs, both received and invoiced before any sale is posted.
  • Every graded shipment carries exactly one item line, with no location code, no variant, and no item tracking.
  • ReturnQty is always positive; the only error case graded is a quantity larger than the shipment's.
  • The grading company has "Ext. Doc. No. Mandatory" switched off, so neither the shipment nor your credit memo needs an external document number.
  • The tests run the Adjust Cost - Item Entries batch job before reading any cost, so you never have to force a cost adjustment yourself.
  • Pick object IDs in 50100–50199 and reference other objects by name, never by ID.

What the tests check

A sale of 4 pieces is drawn from a 10.00 layer while a 25.00 layer sits behind it; 2 pieces are returned, and the return's item ledger entry must show "Cost Amount (Actual)" of exactly 20.00 — FIFO's next layer and the item's blended unit cost both give something else; the same case runs again with generated layer costs and a 3-piece return, so a hardcoded amount cannot survive. A test posts two shipments that drew from different layers and returns against the second one, so resolving the item's first sale entry instead of the given shipment's fails; another sells 14 pieces across both layers and returns all 14 — the largest quantity the guard may accept — and requires the return to carry the sale's blended cost and the two entries to cancel out, both to the cent. Further tests check the item application entry that pairs the return's entry (inbound) with the sale's entry (outbound) for the returned quantity, check that the returned number is a posted sales credit memo for the shipment's customer holding an item line of the returned quantity, check that FindSaleEntryNo resolves the shipment it was handed rather than the item's first sale, check that the return still posts and still carries the original cost when "Exact Cost Reversing Mandatory" is switched on in Sales & Receivables Setup, and check that the shipment's own entry keeps its cost afterwards. Returning 5 pieces of a 4-piece shipment must fail with a message containing exceeds the shipped quantity and must leave no inbound entry behind.

Learn More

Hint 1
A plain sales credit memo hands the valuation back to the costing method, and FIFO will happily price the returned goods off a layer the customer never bought from. Business Central lets a document line name the one item ledger entry it is reversing, and when it does, the costing engine steps aside. Look at what the sales line offers beyond type, quantity and price.
Hint 2
The field is "Appl.-from Item Entry" on the sales line: set it to the entry no. of the outbound Sale entry you are reversing, and posting creates a cost application, so the returned units re-enter inventory at the original entry's cost whatever FIFO would have said. Validate it after Quantity — its validation checks the line's quantity against the entry it points at. The originating entry is the "Item Ledger Entry" with Entry Type Sale, Document Type Sales Shipment and Document No. equal to the posted shipment no.
Hint 3
Build the header with Init, "Document Type"::"Credit Memo", Insert(true), then Validate "Sell-to Customer No."; build the line with Insert(true) and Validate Type, "No.", Quantity, then "Appl.-from Item Entry". Post with codeunit "Sales-Post" — a Credit Memo header posts as receive plus invoice on its own — and find the result on "Sales Cr.Memo Header" via "Pre-Assigned No.". Compare the requested quantity against the "Sales Shipment Line" before you build anything: Business Central has its own application check and it will error first, with its own wording, if you let it get that far.
ALBusiness Central 28.5
Press Compile to check your code compiles — Submit runs the tests.
The code editor is desktop-only
Open this problem on a computer to write and run code. Reading the description, tests and discussion works fine here.