Ship Now, Receive Later
When goods move between two warehouses they spend days on a truck — no longer at the source, not yet at the destination. Business Central models that limbo honestly: at shipment the quantity is posted out of the from-location and into a dedicated in-transit location, and at receipt out of in-transit and into the destination. Your job is to drive that lifecycle from code and to answer, at any moment in between, "what is where?" — straight from the item ledger.
Requirements
Create a codeunit named "Transfer Flow" with five public procedures:
procedure CreateOrder(ItemNo: Code[20]; FromLocation: Code[10]; InTransitLocation: Code[10]; ToLocation: Code[10]; Quantity: Decimal): Code[20]
procedure Ship(OrderNo: Code[20]; QtyToShip: Decimal)
procedure Receive(OrderNo: Code[20]; QtyToReceive: Decimal)
procedure DirectTransfer(ItemNo: Code[20]; FromLocation: Code[10]; ToLocation: Code[10]; Quantity: Decimal)
procedure OnHand(ItemNo: Code[20]; LocationCode: Code[10]): DecimalRules:
CreateOrdercreates a genuine, unposted transfer order — aTransfer Headercarrying the three location codes plus exactly oneTransfer Linefor the item and quantity — and returns the order's number. Creating an order writes no item ledger entries.Shipposts a transfer shipment of exactlyQtyToShip, which may be less than the ordered quantity. Shipping a quantityqwrites exactly two item ledger entries, both withEntry Type=Transfer:-qatFromLocationand+qatInTransitLocation— while underway the goods are at neither real warehouse.- After a partial shipment the order's own line — the one with
"Derived From Line No." = 0; posting may add derived bookkeeping lines, which the tests ignore — must read:Quantity= the ordered quantity,"Quantity Shipped"= what has been shipped so far,"Outstanding Quantity"= the rest,"Qty. in Transit"= shipped but not yet received. Receiveposts a transfer receipt of exactlyQtyToReceiveand writes the mirroring two entries:-qatInTransitLocationand+qatToLocation. A full ship-then-receive of quantityqtherefore leaves exactly fourTransferentries on the item.- Asking
Receivefor more than is currently in transit must raise an error with a message that containsYou cannot receive more than, and must not post anything. DirectTransfermoves the quantity between two real locations in a single step and leaves exactly twoTransferentries:-qatFromLocationand+qatToLocation— no in-transit stop, no other entries.OnHandreturns the net sum of the item's item ledgerQuantityat the given location, and0for a location the item has never touched.- Entry types are part of the contract: a pair of item-journal adjustments produces the right on-hand totals with the wrong history. The tests count the item's
Transferentries and its total entries, so impersonating a transfer withPositive Adjmt./Negative Adjmt.postings fails.
Pick object IDs in the range 50100–50199 and reference other objects by name, never by ID.
What the tests check
The tests create fresh items and locations (two real ones plus an in-transit one), seed stock with their own journal postings, and drive your codeunit through the lifecycle with quantities generated at run time: that CreateOrder returns a real, unposted order; the exact two-entry and four-entry Transfer footprints with the exact signed quantity at each location; the order line's split fields after a partial Ship; that a partial Receive posts exactly the received quantity, leaves the remainder at the in-transit location and updates the line's "Qty. in Transit"; on-hand at all three locations after shipping and again after receiving; the You cannot receive more than error — and that the failed receipt posted nothing; the two-entry direct transfer; and OnHand against ledger entries the tests posted themselves.
Learn More
- Transfer inventory between locations — the business process you are automating, including exactly which item ledger entries each transfer flavor produces.
- Design details: Inventory posting — what posting writes to the item ledger and why the ledger is the source of truth for "what is where".
- Set up locations — locations, in-transit locations, and transfer routes.
Transfer-type item ledger entries, which no pair of adjustments can imitate.Transfer Header (an Insert(true) with a blank number pulls one from the transfer order number series), validate "Transfer-from Code", "Transfer-to Code" and "In-Transit Code", then insert a Transfer Line and validate its "Item No." and Quantity. Shipment and receipt each have a dedicated posting codeunit that releases the order by itself — avoid the Yes/No wrappers, whose confirmation dialog fails a graded run.Ship/Receive: Validate the line's "Qty. to Ship" / "Qty. to Receive", Modify(true), then run Codeunit "TransferOrder-Post Shipment" / "TransferOrder-Post Receipt" against the header — validating "Qty. to Receive" above what is in transit raises the exact 'You cannot receive more than...' error the tests expect. For DirectTransfer, post one item journal line with "Entry Type" = Transfer, a "Location Code" and a "New Location Code" through Codeunit "Item Jnl.-Post Line". (A direct-transfer order only matches the two-entry footprint when Inventory Setup's "Direct Transfer Posting" is set to "Direct Transfer" — under the default "Receipt and Shipment" it posts four entries and fails the tests, so the journal path is simpler.) OnHand is a filtered CalcSums over "Item Ledger Entry".Quantity.