Orchestrate

Report an issue

Something wrong, out of date or unclear? Tell us. Corrections are prioritized over new topics. You don't need to sign in.

0 / 2000

Don't include personal or confidential company details. Reports are reviewed with AI assistance.

Warehouse and transportation execution

Follow a delivery through warehouse tasks, handling units, freight orders and freight settlement in SAP, read where shipping breaks, and decide where AI helps.

Updated Oct 24, 2026Foundational 9 minDeep 40 min
Foundational layer · 9 min read

The 60-second version

Selling a pump and making it are only half the job. Somebody still has to find it on a shelf, pack it, load it on the right truck, and pay the carrier the right amount. That is warehouse and transportation execution.

In SAP it runs as a chain. A sales order creates an outbound delivery. The warehouse system turns the delivery into warehouse tasks: move this item from that bin to the packing station, then to the dock. Goods are packed into handling units, such as a pallet with its contents. Transportation management plans the shipment as a freight order with a carrier. After goods issue, a freight settlement turns the agreed freight charge into an accrual in finance, and the carrier's invoice is checked against it.

Three ideas carry this topic:

  • The warehouse works on tasks, not orders. Sales sees "order 1000004". The warehouse sees six tasks for three pallets. A shipment is ready when every task is confirmed.
  • Warehouse and ERP talk through a message line. When the warehouse runs as a separate system, deliveries travel in queues. If a queue stops, the delivery simply never arrives in the warehouse, and nothing looks broken in sales.
  • Freight is a purchase you check after the fact. The carrier is paid for a service. Its invoice should match what the freight agreement says the trip cost.

Why it matters to the business

This is where customers see the company. A pump that is in stock but leaves a day late is a late pump. Freight is also a real cost line: overbilled fuel surcharges and duplicate invoices are small one by one and large over a year.

Here is the 8:30 shift meeting at Calder Pumpworks, the fictional pump maker this unit follows, on Monday 26 October 2026. The Rockford distribution center's worklist shows:

What the worklist shows What it means Who acts
One of three pallets for Brennock never reached the dock door; the truck comes at 14:00 The delivery can't leave complete today Shift supervisor, with sales
A seal kit pick came up 8 short of 60 The bin count is wrong, or kits are elsewhere Warehouse supervisor
A delivery created on Friday never reached the warehouse system A stuck integration queue; no one can pick it Integration support
Wednesday's shipment to Hallam has no truck The usual carrier's weekly capacity is used up Transport planner
200 seal kits wait at receiving for a quality decision They can't be put away or sold Quality inspector
Two carrier invoices don't match the agreement, and one is a duplicate Overbilling, or a charge someone has to judge Freight settlement clerk

The business decision is not "make the warehouse faster". It is which problems reach which person before the truck arrives, and who checks the carrier's bill.

How SAP does it

As of October 2026, SAP offers these as standard options. Each is documented in SAP's training:

  • Warehouse. SAP Extended Warehouse Management (EWM) runs either embedded in SAP S/4HANA or decentralized as a separate system. An embedded warehouse can be set up as basic or advanced; a decentralized one always has the full scope. EWM is offered with S/4HANA on premise and Cloud Private Edition. SAP S/4HANA Cloud Public Edition's warehouse management is built on EWM with basic features only and has no decentralized option (SAP Learning).
  • Simple stock rooms. Stock Room Management, available since release 1909, covers small, manually run warehouses. SAP Learning says its license is included in the SAP S/4HANA Enterprise Management component (SAP Learning).
  • Transportation. Transportation management (TM) in S/4HANA plans freight units into freight orders, selects and tenders carriers, calculates charges and settles them. SAP's training says the basic scope needs no extra license, while advanced functions such as optimizer planning and load planning need an additional one (SAP Learning). Which features count as advanced is set out in SAP Note 3065464; check it, and your contract, before you design.
  • Carriers. SAP Business Network Freight Collaboration connects carriers for tendering, freight order confirmation, tracking and carrier invoicing, with SAP TM, ECC or S/4HANA back ends (SAP).

For AI, SAP's Logistics Assistant page lists agents including a Logistics Issue Resolution Agent, an Outbound Task Orchestration Agent and a Transport Dispatching Agent, with no availability stated on that page (SAP). SAP's 8 October 2026 announcement plans general availability of new Logistics Assistant capabilities for Q4 2026 (SAP News). Check the status and licensing of each before you plan on it.

The process on one page

flowchart LR
  SO[Sales order] --> DL[Outbound<br/>delivery]
  DL --> WR[Warehouse<br/>request]
  WR --> WT[Warehouse tasks<br/>pick, pack, stage]
  WT --> HU[Handling units<br/>on pallets]
  DL --> FU[Freight unit]
  FU --> FO[Freight order<br/>carrier]
  HU --> GI[Load and<br/>goods issue]
  FO --> GI
  GI --> FS[Freight settlement<br/>accrual]
  FS --> INV[Carrier invoice<br/>checked]
Step Team Document created Posts to finance?
Delivery is created Sales, shipping Outbound delivery No
Warehouse receives it Warehouse system Warehouse request (outbound delivery order) No
Goods are picked and packed Warehouse staff Warehouse tasks, warehouse orders, handling units No
Transport is planned Transport planner Freight unit, freight order with a carrier No
Truck is loaded and leaves Warehouse, carrier Goods issue (material document) Yes: inventory to cost of goods sold
Freight is settled Freight settlement clerk Freight settlement document, service purchase order, service entry sheet Yes: freight accrual
Carrier invoices Accounts payable Supplier invoice Yes: accrual cleared, payable to the carrier

Inbound works in mirror image: an advance shipping notification creates an inbound delivery, the warehouse unloads, posts the goods receipt and puts the goods away with tasks. How each step becomes an accounting entry is the subject of Document flow and how logistics becomes finance.

Questions to ask

  1. Is the warehouse embedded in S/4HANA or a separate system, and who monitors the integration queues every shift?
  2. How many deliveries miss their planned goods issue each week, and for what reasons?
  3. When a pick comes up short, who decides between shipping partial and waiting, and how fast does sales hear?
  4. Which carriers are on freight agreements, and how are rates, fuel surcharges and detention defined?
  5. What share of carrier invoices is checked line by line, and what share is paid as billed?
  6. If an AI tool proposes splitting a delivery or rejecting a carrier invoice, who approves it, and is that logged?

Common misconceptions

  • "The delivery shows picked, so it has shipped." Picked isn't loaded, and loaded isn't goods issued. Each is a separate step, and only goods issue moves the stock and the cost.
  • "If the warehouse system didn't complain, the delivery is there." A stuck queue fails quietly. The delivery exists in S/4HANA but not in the warehouse, so no one can pick it.
  • "Freight cost is what the carrier bills." It is what the freight agreement says. The invoice is a claim to check against the freight order's charges.
  • "Embedded EWM is the small version." Embedded and decentralized are deployment choices. Basic and advanced are scope choices, and they are separate decisions.

Key terms

All course terms, A to Z, in the glossary

  • Outbound delivery: the S/4HANA document that says what to ship to whom and when.
  • Warehouse request: the warehouse's copy of a delivery, called an outbound delivery order in EWM.
  • Warehouse task: one move of goods from a source bin to a destination, confirmed when done.
  • Warehouse order: a package of warehouse tasks for one worker or one trip.
  • Handling unit (HU): a package with its contents, such as a pallet with two pumps, with its own number.
  • Freight unit: the smallest unit that transportation management plans.
  • Freight order: the planned shipment, with the carrier, stops, dates and charges.
  • Freight agreement: the contract that sets a carrier's rates.
  • Freight settlement document: the document that sends the freight charge to purchasing and finance.

Check yourself

Pick one answer for each question. The explanation appears after you choose.

1A sales manager sees "picked" on a delivery and promises the customer it has shipped. What has actually happened?

Answer: C. Picking, loading and goods issue are separate steps. Only goods issue moves the stock out of the warehouse and posts cost, so a picked delivery can still miss its truck.

2A delivery created on Friday never appears in the separate warehouse system, and nothing in sales shows an error. What is the likely cause?

Answer: B. With a decentralized warehouse, deliveries travel to it through queues. When an entry fails and nobody restarts it, the delivery exists in S/4HANA but not in the warehouse, and the failure is quiet.

3Calder's freight cost rose 6% this year with the same volume. What should the CFO ask first?

Answer: A. Freight cost is what the freight agreement says, and invoices are claims to check. Overbilled surcharges and duplicates add up quietly when invoices are paid as billed.

4A vendor says "embedded EWM is too small for a real distribution center". How should an architect answer?

Answer: D. Embedded or decentralized is where EWM runs. Basic or advanced is how much of it a warehouse uses, and an embedded warehouse can be set up as advanced.

5One of three pallets for a customer can't be found an hour before the truck. What decision has to be made, and by whom?

Answer: B. The delivery can't leave complete. Someone on the floor has to find the pallet in time, or the business splits the delivery and tells the customer, which is a sales decision too.

6A vendor pitches an agent that rejects carrier invoices and splits deliveries on its own. What should you ask for?

Answer: A. Rejecting an invoice or splitting a delivery affects carriers, customers and money. An agent can find and explain the problem; a clerk or supervisor decides, and the decision is logged.
Deep layer · 40 min read

Mental model

Think of this process as one delivery seen by three systems, each with its own unit of work.

  • Sales and ERP see the delivery: customer, items, quantities, a planned goods issue date. Goods issue is its finish line.
  • The warehouse sees tasks and handling units: move this quantity from bin A to bin B, put it on that pallet, take the pallet to door D2. A delivery is ready when its tasks are confirmed.
  • Transportation sees freight units and freight orders: this much weight from Rockford to Joliet by Wednesday, on that carrier, at that agreed rate. A freight order is finished when it is settled and the carrier's invoice is checked.

Every problem in this topic is a mismatch between two of those views. A delivery that never reached the warehouse. A task that never got confirmed. A freight unit with no freight order. A carrier invoice that doesn't match the freight order's charges. Every AI idea at the end reads one of those gaps.

Business objects and documents

Before you start: this topic builds on How an SAP enterprise is wired (warehouse numbers, shipping points), Master data that every module depends on, Document flow and how logistics becomes finance, and the three process topics before it: Order to cash supplies the outbound deliveries, Procure to pay the inbound ones, and Plan to produce the production staging. It doesn't repeat them.

Deployment: where the warehouse runs

SAP's training describes four ways to manage a warehouse with S/4HANA (SAP Learning, Private Edition):

Option What it is Fits
Stock Room Management A streamlined successor to classic WM, from release 1909 Small, manual warehouses that manage stock by bin; no task and resource management, waves, cross-docking or yard management
Embedded EWM, basic EWM inside the S/4HANA system, basic scope Warehouses that need bins, tasks and HUs without the advanced functions
Embedded EWM, advanced EWM inside S/4HANA, full scope, adding functions such as cross-docking and kitting Larger warehouses that want one system
Decentralized EWM EWM as a separate system connected to S/4HANA (or ERP), always full scope High-volume or automated sites, or one warehouse system for several ERPs

Embedded EWM arrived with S/4HANA 1610; an S/4HANA-based decentralized EWM became possible with 1809 FP02 (SAP Learning). The difference that matters most to an architect is integration. Embedded EWM shares the basic master data with the ERP part of S/4HANA, so nothing is replicated. A decentralized EWM gets master data through the Core Interface (CIF) on queued remote function calls (qRFC) when it is NetWeaver-based, or through ALE with IDocs or the Data Replication Framework when it is S/4HANA-based. Transactional data such as deliveries goes through qRFC in both cases (same source).

At Calder (fictional), the Rockford distribution center RKDC runs decentralized EWM, on S/4HANA, connected to the S/4HANA system of CP10. The decision and its consequences are in the architecture view below. Augsburg's plant stores on ECC have no EWM.

The warehouse structure

The warehouse number RKDC sits at the top of a hierarchy of storage types, sections and bins, as the org units topic explains. Two more structures drive the daily work:

  • Activity areas group bins for picking or putaway, and warehouse orders are built per activity area.
  • Doors and staging areas are where outbound goods wait for the truck, and where inbound goods arrive.

Outbound: delivery to warehouse request

The process starts in S/4HANA with the outbound delivery. If its warehouse is EWM-managed, the delivery goes to EWM. In a classic ERP-to-EWM setup, EWM first creates an outbound delivery request, which creates the outbound delivery order through the Post Processing Framework (PPF). Embedded EWM has no request, and decentralized EWM 9.5 and later can skip it (SAP Learning).

The outbound delivery order is the warehouse request: the warehouse's work list for that delivery. When it is saved, EWM determines a warehouse process type per item and, depending on Customizing, assigns a wave, a staging area and door, and a route (same source). The process type decides how the pick runs; the stock removal strategy decides which storage types and bins to pick from (SAP Learning).

Level Holds Calder example (fictional)
Delivery header (S/4HANA) Shipping point, warehouse, ship-to party, planned goods issue date, overall picking, packing and goods movement status Delivery 80000011: RK01, RKDC, Brennock, 26 October 14:00
Delivery item Material, quantity, plant, storage location, reference to the sales order item 3 × CP-ES300 from order 1000004
Warehouse request (EWM) Process type, wave, door, staging area per item Outbound delivery order for 80000011, door D2
Warehouse tasks One move each: product, quantity, source and destination bin or HU, status Pick 300000041; HU moves 300000042 to 300000044
Warehouse orders Tasks bundled for one worker 2000000021 (pick), 2000000022 (move to door)
Handling units Packaging material, contents, weights, location 1000000011 to 1000000013, one pump per pallet

In SAP's model of the outbound delivery API, the header carries fields such as Warehouse, WarehouseStagingArea, WarehouseGate, PlannedGoodsIssueDate, OverallPickingStatus, OverallPackingStatus, OverallWarehouseActivityStatus, OverallGoodsMovementStatus, TransportationPlanningStatus and DistrStatusByDecentralizedWrhs (SAP Cloud SDK model, 2022). The status values are SAP domain values; read them from the system rather than assuming them.

Warehouse tasks and warehouse orders

A warehouse task tells a worker what to move and where. Tasks are created by hand or automatically through PPF, one per warehouse request item. A direct move from a pick bin to the goods issue area is a product warehouse task; packed goods moved in several steps use HU warehouse tasks together with product tasks. Each task must be confirmed when it is done, which checks quantity, product and destination bin (SAP Learning).

Tasks are bundled into warehouse orders, the work packages a worker actually executes. Warehouse order creation rules group tasks per activity area, using filters, limits, sort rules and consolidation groups (same source).

When the quantity found differs from the task, the worker confirms the actual quantity with an exception code. SAP's training describes this for putaway, where a difference quantity needs a predefined exception code (SAP Learning). Exception codes and what they trigger are configuration; DIFF in this topic's script is a course label.

In SAP's model of the warehouse order and task API, a task carries WarehouseTask, WarehouseOrder, WarehouseProcessType, SourceStorageBin, DestinationStorageBin, SourceHandlingUnit, DestinationHandlingUnit, TargetQuantityInBaseUnit, ActualQuantityInBaseUnit, DifferenceQuantityInBaseUnit, WarehouseTaskStatus, WarehouseTaskExceptionCode, IsHandlingUnitWarehouseTask, Delivery and ProductionOrder. A warehouse order carries WarehouseOrderStatus, ExecutingResource and WhseOrderLatestStartDateTime (SAP Cloud SDK model, 2022).

Handling units

A handling unit (HU) is a physical package with its contents: a pallet with two pumps, a carton of 50 seal kits. It has its own number, a packaging material, weights and dimensions, and it can be nested. On the way in, an HU can come from the supplier's advance shipping notification, or EWM can create it from a packaging specification, found with the condition technique. One detail matters for architects: ERP handling unit management can't be active for EWM-managed storage locations, so HU changes in EWM aren't always visible in ERP (SAP Learning).

The handling unit strategy is a design decision: pack at the supplier or at receiving, one product per pallet or mixed, HU-managed bins or not. It decides how many tasks the warehouse creates, what the carrier's weights are based on, and what a scanner reads at the door. In SAP's model of the handling unit API, an HU carries HandlingUnitExternalID, PackagingMaterial, GrossWeight, NetWeight, Warehouse, StorageBin, HandlingUnitReferenceDocument and items with Material and HandlingUnitQuantity (SAP Cloud SDK model, 2022).

At Calder (fictional), ES-200 pumps ship two per pallet, ES-300 pumps one per pallet, seal kits 50 per carton. The script uses that rule to build HUs and their weights.

Goods issue

When the picked goods are in the goods issue zone, goods issue is posted, or it is triggered by processing a transportation unit or vehicle. EWM creates its own outbound delivery document, which sends the goods issue to ERP; ERP then creates the inventory and accounting documents. If there isn't enough stock for the full quantity, the delivery is split (SAP Learning). The order-to-cash topic showed what goods issue posts.

Inbound: from advance shipping notification to putaway

A supplier's advance shipping notification (ASN), usually by EDI, creates the inbound delivery in ERP; without EDI, it is created when the goods arrive. EWM gets an inbound delivery notification (not in embedded EWM, and optional in decentralized EWM 9.5 and later) and creates the inbound delivery, the warehouse request for receiving. Goods receipt is posted from EWM to ERP, as a first step, in the warehouse management monitor, or automatically when the putaway task is confirmed. In embedded EWM, a synchronous goods receipt can also be posted in inventory management, for example in Post Goods Movement, and EWM then creates the putaway task. Putaway tasks move the goods to their final bins and are bundled into warehouse orders like picks (SAP Learning).

If the goods need inspection, they wait in quality inspection stock until the usage decision. At Calder, the 200 seal kits from Ridgeline for purchase order 4500000003, the one with a quality-blocked invoice in the procure-to-pay topic, sit in HU 1000000031 at receiving.

Production staging

For production, EWM moves stock from the warehouse to a production supply area (PSA). SAP describes staging methods for pick parts, release order parts and crate parts, plus EWM staging, and two integration models: delivery-based production integration, with separate documents for staging and consumption, and advanced production integration, with one production material request for both (SAP Learning). Calder's components sit in plant stores, not in RKDC, so this topic's scenario has no staging; the plan-to-produce topic covers the order side.

Transportation: freight units and freight orders

Transportation management starts from the transportation demand of sales orders, purchase orders, stock transport orders, returns, scheduling agreements and deliveries (SAP Learning). A freight unit is "the smallest unit that can be transported". It is built from a predecessor document by freight unit building rules: in an embedded deployment the sales order or a forwarding order; in a side-by-side deployment order-based or delivery-based transportation requirements. Strategies range from one freight unit per item to "consolidate as much as possible", and a split quantity caps how large a freight unit can be (SAP Learning).

Planners assign freight units to freight orders (land transport) or freight bookings (reserved space, for example sea or air). Everything in a freight order travels together from its source to its destination (same source).

Carrier selection assigns a carrier by lowest cost or highest priority, within transportation allocations (capacity limits per carrier, region and means of transport) and business shares. Tendering asks carriers to quote: peer-to-peer sends a freight request for quotation to one carrier at a time, broadcast to all preselected carriers at once. Carriers answer by email, by EDI, or through SAP Business Network Freight Collaboration (SAP Learning). At Calder, Larkspur's allocation on the Hallam lane is used up for week 44, so freight unit 4100000014 has no freight order yet.

Charges come from the freight agreement with the carrier, calculated with calculation sheets, rate tables and scales (SAP Learning). Calder's agreements are simplified in the script to a base charge per shipment, a rate per kilogram, a fuel surcharge and detention after two free hours at the dock. All of these are course values.

Execution events, such as arrival and departure at the dock, can be tracked for freight units, freight orders and freight bookings (SAP Learning). They matter later: detention is billed from them.

Freight settlement

After the trip, a freight settlement document (FSD) is created from the freight order or booking. Its charges are usually copied from the freight order, or recalculated from the freight agreement, and can be changed by hand. FSDs are created one by one or collectively, online or in batch. Transferring the FSD to purchasing creates a purchase order and a service entry sheet, which posts an accrual with the costs assigned to a controlling object. The carrier's invoice is then checked against the FSD in logistics invoice verification (MIRO), or generated from it by evaluated receipt settlement (self-billing). Settling the invoice posts the payable and cancels the accrual (SAP Learning).

SAP's training adds that the purchase order and service entry sheet are "only used for technical purposes" to post accruals, and that a carrier invoice that differs from the freight order creates a dispute case, accepted (updating the freight order's charges) or rejected (SAP Learning). Freight costs can then be distributed to margin analysis for outbound shipments, or to material valuation for inbound ones (SAP Learning).

Document flow and postings

flowchart LR
  SO[Sales order<br/>1000001] --> DL[Delivery<br/>80000001]
  DL --> ODO[Warehouse request<br/>EWM]
  ODO --> WT[Tasks and<br/>4 HUs]
  DL --> FU[Freight unit<br/>4100000001]
  FU --> FO[Freight order<br/>6100000001]
  WT --> GI[Goods issue<br/>4900000001]
  GI --> J1[Journal<br/>COGS]
  FO --> FSD[Settlement doc<br/>7100000001]
  FSD --> J2[Journal<br/>freight accrual]
  FSD --> INV[Carrier invoice<br/>LFL-76001]
  INV --> J3[Journal<br/>payable]
Step Posts to finance Why
Delivery, warehouse request, tasks, HUs Nothing Plans and physical moves inside the warehouse
Putaway or internal moves Nothing, unless stock changes plant or valuation A bin change isn't a value change
Goods issue Inventory to cost of goods sold The goods have left
Freight unit and freight order Nothing Planned transport and planned charges
Freight settlement transferred Freight expense to accrued freight, assigned to a controlling object The service was received; the bill isn't in yet
Carrier invoice posted Accrued freight to the carrier's payable The accrual becomes a real debt
Cost distribution Freight cost to margin analysis (outbound) or material valuation (inbound) Product and customer margins include freight

At Calder (fictional), delivery 80000001 from the sample pack left on 25 September 2026 in four HUs, 1,340 kg gross. Larkspur's agreement gives 869.00 USD linehaul plus 156.42 USD fuel, 1,025.42 USD in all, accrued when the settlement document was transferred and cleared by Larkspur's matching invoice. You print the whole trace in Step 5.

Neighbors

Neighbor Gives warehouse and transport Takes from them When the link fails
Sales (order to cash) Outbound deliveries, requested dates Goods issue, which opens billing Orders confirmed but not shipped; invoices late
Purchasing (procure to pay) Purchase orders, inbound deliveries from ASNs Goods receipts Goods on the dock nobody can receive; GR/IR differences
Production (plan to produce) Staging requests, production receipts Components at the production supply area, putaway of finished goods Lines wait for parts; finished goods block the dock
Quality management Usage decisions Inspection stock from receipts Stock stuck at receiving; shortages that aren't real
Finance and controlling Accrual accounts, cost objects Freight accruals, carrier payables, freight cost distribution Freight in the wrong period; margins without freight
Carriers (EDI or Freight Collaboration) Quotes, confirmations, tracking events, invoices Freight requests, freight orders No truck; invoices without a matching freight order
Integration (queues, IDocs) Delivery and goods movement messages Same, in the other direction The two systems disagree silently

Where it breaks

Symptom Usual cause Who resolves it What it costs Calder (scenario)
Delivery 80000013 exists in S/4HANA but not in EWM A failed entry in the integration queue holds the ones behind it Integration support Brennock's order 1000009 can't be picked for Tuesday
Delivery 80000011 can't leave complete One HU task to door D2 is still open Shift supervisor, with sales The 14:00 truck leaves with two of three pumps, or waits
Seal kit pick confirmed 52 of 60 Bin stock is wrong or kits are misplaced Warehouse supervisor, inventory Hallam's order 1000008 ships short; a count is needed
Freight unit 4100000014 has no freight order The carrier's allocation on the lane is used up Transport planner A pricier carrier, or a late shipment to Hallam
200 seal kits wait at receiving No usage decision on inspection lot 010000000041 Quality inspector Stock that can't be sold; a blocked supplier invoice
Invoice LFL-77120 is 32.17 USD over Fuel billed at 22% instead of the agreed 18% Freight settlement clerk Overpayment if paid as billed
Invoice LFL-77188 is 75.00 USD over Detention billed for two hours; the dock log supports one Freight settlement clerk A dispute to settle with evidence
Invoice LFL-77203 repeats LFL-77188 Duplicate Accounts payable 857.41 USD paid twice

When a queue stops, the monitor is the place to look. In ERP and S/4HANA, the qRFC monitor SMQ2 shows them; SAP's knowledge base, for example, describes delivery and goods movement queues stuck there with an error (SAP KBA 3787601). Who may restart or delete a queue entry is an authorization decision, because deleting one loses the message.

How it is exposed

As of 24 October 2026, these released interfaces carry each step. The CDS views come from SAP's Cloudification Repository (file read 24 October 2026), which also marks the classic delivery tables LIKP and LIPS and the handling unit tables VEKP and VEPO as not to be released, with successors such as I_DeliveryDocument and I_HandlingUnitHeader (Cloudification Repository). The OData API names and fields come from SAP's generated API models, published in 2022 (SAP Cloud SDK model, 2022).

Step OData API (A2X) Released CDS views What to read
Outbound delivery API_OUTBOUND_DELIVERY_SRV;v=0002 I_OutboundDelivery, I_DeliveryDocument, I_DeliveryDocumentItem, I_OutboundDeliveryTP Warehouse, statuses, planned goods issue, HUs on the delivery
Inbound delivery API_INBOUND_DELIVERY_SRV;v=0002 I_InboundDelivery, I_InboundDeliveryTP Supplier, ASN data, goods receipt status
Warehouse request (EWM) I_EWM_OutbDelivOrdHdr, I_EWM_OutbDelivOrdItem, I_EWM_InbDeliveryHeader, I_EWM_InbDeliveryItem Process type, wave, door per item
Warehouse tasks and orders API_WAREHOUSE_ORDER_TASK I_EWM_WarehouseTask_2, I_EWM_WarehouseOrder_2, I_EWM_ExceptionCode_2 Open tasks per delivery, exception codes, latest start times
Waves I_EWM_WaveHeader_3, I_EWM_WaveItem_3 Which deliveries are released together
Handling units API_HANDLING_UNIT I_HandlingUnitHeader, I_HandlingUnitItem, I_HandlingUnitTP, I_EWM_HandlingUnitHdr_2 Contents, weights, location
Warehouse stock api_whse_availablestock (OData V4) I_EWM_AvailableStock, I_EWM_StorageBin_2 Available stock by bin, stock type, HU
Production staging I_EWM_ProdnMatlReqHeader, I_EWM_ProductionSupplyArea Production material requests per PSA
Freight units I_FreightUnitTP, I_FreightUnitItemTP Freight units and their predecessor documents
Freight orders Check the Hub for the current freight order API I_FreightOrderTP, I_FreightOrderChargeTP, I_FreightOrderEventTP, I_FreightOrderStopTP Carrier, charges, execution events at each stop
Freight agreements I_FreightAgreementTP, I_FreightAgreementRateTableTP Rates the audit compares against
Freight settlement I_PurchaseOrdItmTranspSettlmt Purchase order items created by freight settlement

The repository also lists released behavior definitions such as I_FreightOrderTP, I_FreightUnitTP and I_OutboundDeliveryTP, and event payload types for transportation order status changes (lifecycle, execution, goods movement). Check their names and event types in the event catalog on the SAP Business Accelerator Hub, and see Event-driven agents with SAP events for how to react to them.

Build it yourself

Before you start: finish Set up your computer for this course and Set up for Unit 1B. This walkthrough uses the Calder sample pack from the Unit 1B setup.

You will write one script that plays Monday morning at Calder's Rockford distribution center. It collects the problems from four places, the warehouse tasks, the integration queue, transport planning and freight settlement, and ranks them into one shift worklist. It traces the sample pack's delivery 80000001 from warehouse tasks and handling units to its freight settlement and the journal entries. And it audits last week's carrier invoices against the freight agreements. An optional step reads outbound deliveries from SAP's sandbox.

flowchart LR
  P[calder_pack/<br/>delivery 80000001,<br/>partners, products] --> S[warehouse_and_transport.py]
  SC[Scenario in the script<br/>tasks, HUs, queue,<br/>freight orders, invoices] --> S
  S --> W[Shift worklist]
  S --> T[Delivery trace<br/>to settlement]
  S --> A[Carrier invoice<br/>audit]
  S --> M[calder_shipping_worklist.md]
  K[SAP sandbox<br/>optional] --> S

What you need

  • The course folder orchestrate-course with its .venv, and the Unit 1B sample pack.
  • For Steps 1 to 6: no account, no key and no network. The script uses only Python's built-in modules, so requirements.txt doesn't change.
  • For Step 7 (optional): your free SAP Business Accelerator Hub key as SAP_API_KEY in .env, from the course setup.
  • Cost: free. Time: about 35 minutes.

Step 1: Open your course folder and turn on the virtual environment

  1. Open VS Code, then File > Open Folder and choose orchestrate-course.
  2. Open a terminal with Terminal > New Terminal.
  3. Turn on the virtual environment:
    • Windows (PowerShell):

      .venv\Scripts\Activate.ps1
    • macOS / Linux:

      source .venv/bin/activate

You should see (.venv) at the start of the prompt.

Step 2: Make sure the sample pack is current

  1. Rewrite the pack, so you start from the same data as this topic:

    • Windows (PowerShell):

      python unit1b\make_calder_pack.py
    • macOS / Linux:

      python unit1b/make_calder_pack.py
  2. The last lines should read:

Calder sample pack 1.0: order 1000001, net 29,100.00 USD, cost 19,600.00 USD.
All data is fictional. Next: python check_unit1b.py

The script reads delivery 80000001, its goods issue and journal entry, and the customer and supplier names from the pack. It stops if the delivery isn't for warehouse RKDC.

Step 3: Create the script

  1. In VS Code, create a new file, paste the script below, and save it as warehouse_and_transport.py inside the unit1b folder.
  2. Read the blocks CARRIERS, DELIVERIES, WAREHOUSE_TASKS, FREIGHT_UNITS, INBOUND, QUEUE, FREIGHT_ORDERS and CARRIER_INVOICES first. They are Calder's scenario written as Python data. Every value is invented. Orders 1000004 and the Ridgeline seal kits come from the order-to-cash and procure-to-pay topics. Weights, packing rules, rates, the tolerance, the queue name, door names and the exception code DIFF are the course's own, not SAP values.
"""Warehouse and transportation at Calder: read the shipping worklist, trace a delivery to freight settlement, audit carrier invoices.

Calder Pumpworks is a fictional company. The pack (unit1b/calder_pack/) and the
scenario below (deliveries, warehouse tasks, handling units, freight orders,
freight rates, carrier invoices) are made up for the course. Field names follow
SAP's released APIs where the script mirrors one (A_OutbDeliveryHeader,
WarehouseTask, WarehouseOrder, HandlingUnit), but statuses, rates, tolerances
and the ranking are course labels and teaching rules, not SAP values or logic.

Run it from your course folder:
  python unit1b/warehouse_and_transport.py                    the Rockford shipping worklist for Monday 26 October 2026
  python unit1b/warehouse_and_transport.py --trace 80000001   one delivery from warehouse task to freight settlement
  python unit1b/warehouse_and_transport.py --audit            check carrier invoices against the freight agreement
  python unit1b/warehouse_and_transport.py --write            write unit1b/calder_shipping_worklist.md
  python unit1b/warehouse_and_transport.py --sandbox          read outbound deliveries from SAP's sandbox (needs SAP_API_KEY)
Steps without --sandbox use only Python's built-in modules and need no account and no network.
"""
import argparse
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime
from pathlib import Path

HERE = Path(__file__).resolve().parent
PACK = HERE / "calder_pack"
NOW = datetime(2026, 10, 26, 8, 30)  # Monday morning shift meeting at the Rockford DC
WAREHOUSE = "RKDC"

# ------------------------------------------------------------- weights (fictional)
KG_EACH = {"CP-ES200": 180.0, "CP-ES300": 260.0, "CP-SK200": 2.0}
TARE_KG = {"PALLET": 25.0, "CARTON": 1.0}  # packaging materials are course labels

# ------------------------------------------------------- freight agreements (fictional)
# One rate per carrier: base per shipment, plus per kg of gross weight, plus a fuel
# surcharge on both. Detention: free time at the dock, then a rate per started hour.
CARRIERS = {
    "20000005": {"Name": "Larkspur Freight Lines", "Agreement": "FA-LFL-2026",
                 "Base": 400.00, "PerKg": 0.35, "FuelPct": 18.0,
                 "FreeDockHours": 2.0, "DetentionPerHour": 75.00},
    "20000006": {"Name": "Tallis Road Freight", "Agreement": "FA-TRF-2026",
                 "Base": 450.00, "PerKg": 0.42, "FuelPct": 18.0,
                 "FreeDockHours": 2.0, "DetentionPerHour": 60.00},
}
TOLERANCE_USD = 25.00  # course rule: an invoice within 25.00 USD of the expected charge posts

# ------------------------------------------------------------- the scenario
# Outbound deliveries from shipping point RK01, after A_OutbDeliveryHeader. Delivery
# 80000001 comes from the pack; the others are this topic's scenario. "InEWM" says
# whether the decentralized warehouse system has received the delivery.
DELIVERIES = {
    "80000011": {"SalesOrder": "1000004", "ShipToParty": "10000001", "Items": [("10", "CP-ES300", 3)],
                 "PlannedGoodsIssue": "2026-10-26 14:00", "InEWM": True, "FreightOrder": "6100000012"},
    "80000012": {"SalesOrder": "1000008", "ShipToParty": "10000002", "Items": [("10", "CP-SK200", 60)],
                 "PlannedGoodsIssue": "2026-10-27 10:00", "InEWM": True, "FreightOrder": "6100000013"},
    "80000013": {"SalesOrder": "1000009", "ShipToParty": "10000001", "Items": [("10", "CP-ES200", 2)],
                 "PlannedGoodsIssue": "2026-10-27 14:00", "InEWM": False, "FreightOrder": ""},
    "80000014": {"SalesOrder": "1000010", "ShipToParty": "10000002", "Items": [("10", "CP-ES200", 4)],
                 "PlannedGoodsIssue": "2026-10-28 10:00", "InEWM": True, "FreightOrder": ""},
}
# Warehouse tasks, after the WarehouseTask entity. Status: "confirmed" or "open".
WAREHOUSE_TASKS = [
    {"WarehouseTask": "300000041", "WarehouseOrder": "2000000021", "Delivery": "80000011",
     "Product": "CP-ES300", "Kind": "pick", "TargetQuantityInBaseUnit": 3, "ActualQuantityInBaseUnit": 3,
     "Status": "confirmed", "DestinationHandlingUnit": "1000000011, 1000000012, 1000000013"},
    {"WarehouseTask": "300000042", "WarehouseOrder": "2000000022", "Delivery": "80000011",
     "Product": "", "Kind": "move HU to door D2", "SourceHandlingUnit": "1000000011", "Status": "confirmed"},
    {"WarehouseTask": "300000043", "WarehouseOrder": "2000000022", "Delivery": "80000011",
     "Product": "", "Kind": "move HU to door D2", "SourceHandlingUnit": "1000000012", "Status": "confirmed"},
    {"WarehouseTask": "300000044", "WarehouseOrder": "2000000022", "Delivery": "80000011",
     "Product": "", "Kind": "move HU to door D2", "SourceHandlingUnit": "1000000013", "Status": "open"},
    {"WarehouseTask": "300000051", "WarehouseOrder": "2000000031", "Delivery": "80000012",
     "Product": "CP-SK200", "Kind": "pick", "TargetQuantityInBaseUnit": 60, "ActualQuantityInBaseUnit": 52,
     "Status": "confirmed", "WarehouseTaskExceptionCode": "DIFF", "SourceStorageBin": "SP-04-02"},
]
# Freight units not yet on a freight order (transportation planning).
FREIGHT_UNITS = [
    {"FreightUnit": "4100000014", "Delivery": "80000014", "Lane": "RK01 -> Hallam, Joliet",
     "PickupBy": "2026-10-28", "Reason": "Larkspur's allocation on this lane is used up for week 44"},
]
# Inbound deliveries waiting in the goods receipt zone.
INBOUND = [
    {"InboundDelivery": "180000003", "PurchaseOrder": "4500000003", "Supplier": "20000003",
     "Product": "CP-SK200", "Quantity": 200, "HandlingUnit": "1000000031",
     "Received": "2026-10-16", "StockType": "quality inspection", "InspectionLot": "010000000041"},
]
# The integration queue from S/4HANA to the warehouse system (course shape of a qRFC monitor row).
# Course rule: entries in one queue run in order, so a failed entry holds the ones behind it.
QUEUE = [
    {"Queue": "DLV_RKDC_OUT", "Status": "error", "Since": "2026-10-23 16:05",
     "Text": "a change to delivery 80000009 failed (the document was locked) and was never restarted",
     "Blocks": ["80000013"]},
]
# Freight orders after goods issue, with charges calculated at planning (closed shipments).
FREIGHT_ORDERS = {
    "6100000001": {"Carrier": "20000005", "Delivery": "80000001", "GrossKg": None,
                   "Arrived": "2026-09-25 09:00", "Departed": "2026-09-25 10:15", "Settlement": "7100000001"},
    "6100000008": {"Carrier": "20000006", "Delivery": "80000008", "GrossKg": 1155.0,
                   "Arrived": "2026-10-14 13:00", "Departed": "2026-10-14 14:05", "Settlement": "7100000008"},
    "6100000009": {"Carrier": "20000005", "Delivery": "80000009", "GrossKg": 1155.0,
                   "Arrived": "2026-10-19 09:00", "Departed": "2026-10-19 10:30", "Settlement": "7100000009"},
    "6100000010": {"Carrier": "20000005", "Delivery": "80000010", "GrossKg": 570.0,
                   "Arrived": "2026-10-20 08:00", "Departed": "2026-10-20 10:40", "Settlement": "7100000010"},
}
# Carrier invoices received last week: (invoice, carrier, freight order, billed lines).
CARRIER_INVOICES = [
    ("TRF-5521", "20000006", "6100000008", {"linehaul": 935.10, "fuel": 168.72}),
    ("LFL-77120", "20000005", "6100000009", {"linehaul": 804.25, "fuel": 176.94}),
    ("LFL-77188", "20000005", "6100000010", {"linehaul": 599.50, "fuel": 107.91, "detention": 150.00}),
    ("LFL-77203", "20000005", "6100000010", {"linehaul": 599.50, "fuel": 107.91, "detention": 150.00}),
]


# --------------------------------------------------------------- the pack
def load_pack() -> dict:
    try:
        products = json.loads((PACK / "products.json").read_text(encoding="utf-8"))
        partners = json.loads((PACK / "business_partners.json").read_text(encoding="utf-8"))
        order = json.loads((PACK / "order_1000001.json").read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        sys.exit(f"Cannot read {PACK}. Run python unit1b/make_calder_pack.py from your course folder first.")
    names = {bp["BusinessPartner"]: bp["BusinessPartnerFullName"] for bp in partners["BusinessPartners"]}
    std = {p["Product"]: p["to_Valuation"][0]["StandardPrice"] for p in products["Products"]}
    if order["OutboundDelivery"].get("Warehouse") != WAREHOUSE:
        sys.exit("Delivery 80000001 in the pack is not for warehouse RKDC. Run the pack script again.")
    return {"names": names, "std": std, "order": order}


def name_of(pack: dict, bp: str) -> str:
    return pack["names"].get(bp) or CARRIERS.get(bp, {}).get("Name", bp)


# --------------------------------------------------------------- handling units and charges
def pack_handling_units(items: list) -> list:
    """Course packing rule: ES-200 two per pallet, ES-300 one per pallet, seal kits 50 per carton."""
    per_hu = {"CP-ES200": (2, "PALLET"), "CP-ES300": (1, "PALLET"), "CP-SK200": (50, "CARTON")}
    hus = []
    for item, product, qty in items:
        size, packaging = per_hu[product]
        left = qty
        while left > 0:
            n = min(size, left)
            hus.append({"HandlingUnit": f"10000000{len(hus) + 1:02d}", "DeliveryItem": item, "Material": product,
                        "Quantity": n, "PackagingMaterial": packaging,
                        "GrossWeight": round(n * KG_EACH[product] + TARE_KG[packaging], 1)})
            left -= n
    return hus


def expected_charges(carrier: str, gross_kg: float, dock_hours: float) -> dict:
    """Charge calculation from the course freight agreement."""
    rate = CARRIERS[carrier]
    linehaul = round(rate["Base"] + rate["PerKg"] * gross_kg, 2)
    charges = {"linehaul": linehaul, "fuel": round(linehaul * rate["FuelPct"] / 100 + 1e-9, 2)}
    extra = dock_hours - rate["FreeDockHours"]
    if extra > 0:
        started_hours = int(extra) + (1 if extra > int(extra) else 0)
        charges["detention"] = round(started_hours * rate["DetentionPerHour"], 2)
    return charges


def dock_hours(fo: dict) -> float:
    fmt = "%Y-%m-%d %H:%M"
    delta = datetime.strptime(fo["Departed"], fmt) - datetime.strptime(fo["Arrived"], fmt)
    return delta.total_seconds() / 3600


def hours_until(stamp: str) -> float:
    return (datetime.strptime(stamp, "%Y-%m-%d %H:%M") - NOW).total_seconds() / 3600


# --------------------------------------------------------------- the worklist
def worklist(pack: dict) -> list:
    """Collect exceptions from warehouse, integration, transport and settlement, then rank them."""
    rows = []
    for t in WAREHOUSE_TASKS:
        d = DELIVERIES[t["Delivery"]]
        customer = name_of(pack, d["ShipToParty"])
        if t["Status"] == "open":
            rows.append({"due": hours_until(d["PlannedGoodsIssue"]), "customer": True,
                         "what": f"delivery {t['Delivery']}: warehouse task {t['WarehouseTask']} open",
                         "detail": f"HU {t['SourceHandlingUnit']} not at door D2, so the goods issue "
                                   f"for the full delivery waits. Truck for freight order {d['FreightOrder']} "
                                   f"at {d['PlannedGoodsIssue'][11:]} today ({customer}, order {d['SalesOrder']})",
                         "owner": "Shift supervisor (find and move the HU, or split the delivery with sales)"})
        if t.get("WarehouseTaskExceptionCode"):
            short = t["TargetQuantityInBaseUnit"] - t["ActualQuantityInBaseUnit"]
            rows.append({"due": hours_until(d["PlannedGoodsIssue"]), "customer": True,
                         "what": f"delivery {t['Delivery']}: picked {short} short",
                         "detail": f"task {t['WarehouseTask']} confirmed {t['ActualQuantityInBaseUnit']} of "
                                   f"{t['TargetQuantityInBaseUnit']} {t['Product']} with exception code "
                                   f"{t['WarehouseTaskExceptionCode']}; bin {t['SourceStorageBin']} needs a count "
                                   f"({customer}, order {d['SalesOrder']})",
                         "owner": "Warehouse supervisor (count the bin; ship 52 or wait, with sales)"})
    for q in QUEUE:
        for dlv in q["Blocks"]:
            d = DELIVERIES[dlv]
            rows.append({"due": hours_until(d["PlannedGoodsIssue"]), "customer": True,
                         "what": f"delivery {dlv}: not in the warehouse system",
                         "detail": f"queue {q['Queue']} in {q['Status']} since {q['Since']}: {q['Text']}. "
                                   f"Created in S/4HANA, but no warehouse task can exist yet "
                                   f"({name_of(pack, d['ShipToParty'])}, order {d['SalesOrder']})",
                         "owner": "Integration support (fix and restart the entry, then check the queue is empty)"})
    for fu in FREIGHT_UNITS:
        d = DELIVERIES[fu["Delivery"]]
        rows.append({"due": hours_until(d["PlannedGoodsIssue"]), "customer": True,
                     "what": f"freight unit {fu['FreightUnit']}: no freight order",
                     "detail": f"delivery {fu['Delivery']} ({fu['Lane']}) must leave by {fu['PickupBy']}; "
                               f"{fu['Reason']}",
                     "owner": "Transport planner (book the second carrier, or move the date with sales)"})
    for ib in INBOUND:
        rows.append({"due": 999, "customer": False,
                     "what": f"inbound delivery {ib['InboundDelivery']}: putaway waiting",
                     "detail": f"{ib['Quantity']} {ib['Product']} in HU {ib['HandlingUnit']} from "
                               f"{name_of(pack, ib['Supplier'])} in {ib['StockType']} stock since "
                               f"{ib['Received']}; inspection lot {ib['InspectionLot']} has no usage decision",
                     "owner": "Quality inspector (usage decision), then the putaway task runs"})
    for inv, verdict, diff, _ in audit_results():
        if verdict != "post":
            rows.append({"due": 1000, "customer": False, "what": f"carrier invoice {inv}: {verdict}",
                         "detail": f"{diff}", "owner": "Freight settlement clerk (dispute case or reject)"})
    # Course ranking: customer shipments first, soonest goods issue first; then stock; then money.
    rows.sort(key=lambda r: (not r["customer"], r["due"]))
    return rows


def print_worklist(pack: dict) -> list:
    rows = worklist(pack)
    print(f"Rockford DC ({WAREHOUSE}) shipping worklist, {NOW:%Y-%m-%d %H:%M} (fictional Calder data)\n")
    for n, r in enumerate(rows, start=1):
        print(f"{n:>2}  {r['what']}")
        print(f"    {r['detail']}")
        print(f"    -> {r['owner']}")
    touching = sum(1 for r in rows if r["customer"])
    print(f"\n  {len(rows)} items, {touching} of them hold a customer shipment.")
    print("  Synthetic data: the list shows the method, not results from an SAP system.")
    return rows


# --------------------------------------------------------------- one delivery, end to end
def trace(pack: dict, delivery: str) -> None:
    order = pack["order"]
    dlv = order["OutboundDelivery"]
    if delivery != dlv["DeliveryDocument"]:
        sys.exit(f"Delivery {delivery} has no full trace in the scenario. Use 80000001 (from the pack).")
    items = [(i["DeliveryDocumentItem"], i["Material"], i["ActualDeliveryQuantity"])
             for i in dlv["to_DeliveryDocumentItem"]]
    hus = pack_handling_units(items)
    gross = round(sum(h["GrossWeight"] for h in hus), 1)
    fo = FREIGHT_ORDERS["6100000001"]
    carrier = fo["Carrier"]
    charges = expected_charges(carrier, gross, dock_hours(fo))
    total = round(sum(charges.values()), 2)
    customer = name_of(pack, dlv["ShipToParty"])
    print(f"Delivery {delivery} for sales order {order['SalesOrder']['SalesOrder']} ({customer}), "
          f"warehouse {dlv['Warehouse']}, shipping point {dlv['ShippingPoint']}\n")
    print("Warehouse (decentralized EWM)")
    print(f"  outbound delivery order for {delivery} received from S/4HANA; wave 2026-09-25 morning")
    print("  warehouse order 2000000001: pick tasks 300000001 (4 CP-ES200) and 300000002 (2 CP-ES300), confirmed")
    print(f"  packed into {len(hus)} handling units:")
    for h in hus:
        print(f"    HU {h['HandlingUnit']}  item {h['DeliveryItem']}  {h['Quantity']} x {h['Material']:<9} "
              f"{h['PackagingMaterial']:<7} {h['GrossWeight']:>6.1f} kg")
    print(f"  HU tasks to door D1 confirmed; loaded; goods issue posted {dlv['ActualGoodsMovementDate']}")
    md = order["MaterialDocument"]["MaterialDocument"]
    cogs = next(j for j in order["JournalEntries"] if "goods issue" in j["Source"])
    cost = cogs["Lines"][0]["AmountInCompanyCodeCurrency"]
    print(f"  S/4HANA: material document {md}, journal entry {cogs['AccountingDocument']} "
          f"(cost of goods sold {cost:,.2f} USD)\n")
    print("Transportation (TM in S/4HANA)")
    print(f"  freight unit 4100000001 built from delivery {delivery}: {gross:,.1f} kg gross")
    print(f"  freight order 6100000001, carrier {carrier} {CARRIERS[carrier]['Name']}, agreement "
          f"{CARRIERS[carrier]['Agreement']}")
    print(f"  at the dock {fo['Arrived'][11:]} to {fo['Departed'][11:]} ({dock_hours(fo):.2f} h, no detention)")
    for k, v in charges.items():
        print(f"    {k:<10} {v:>9,.2f} USD")
    print(f"    {'total':<10} {total:>9,.2f} USD\n")
    print("Settlement")
    print(f"  freight settlement document {fo['Settlement']} transferred: service purchase order 4500000101, "
          f"service entry sheet 1000000101")
    print(f"  journal (accrual):  Dr 52100 Freight out {total:>9,.2f}   Cr 21500 Accrued freight {total:>9,.2f}")
    print(f"  carrier invoice LFL-76001 {total:,.2f} USD matches; posted 2026-10-02:")
    print(f"  journal (invoice):  Dr 21500 Accrued freight {total:>9,.2f}   Cr 21100 Payable {carrier} {total:>9,.2f}")
    print("  freight cost distributed to the delivery items by gross weight, for margin analysis:")
    for item in sorted({h["DeliveryItem"] for h in hus}):
        kg = sum(h["GrossWeight"] for h in hus if h["DeliveryItem"] == item)
        share = round(total * kg / gross, 2)
        material = next(h["Material"] for h in hus if h["DeliveryItem"] == item)
        print(f"    item {item} {material:<9} {kg:>7.1f} kg  {share:>9,.2f} USD")
    print("\n  Synthetic numbers for a fictional delivery; account numbers are illustrative.")


# --------------------------------------------------------------- freight invoice audit
def audit_results() -> list:
    seen, results = set(), []
    for inv, carrier, fo_id, billed in CARRIER_INVOICES:
        fo = FREIGHT_ORDERS[fo_id]
        expected = expected_charges(carrier, fo["GrossKg"], dock_hours(fo))
        b_total, e_total = round(sum(billed.values()), 2), round(sum(expected.values()), 2)
        lines = []
        for k in sorted(set(billed) | set(expected)):
            if abs(billed.get(k, 0) - expected.get(k, 0)) > 0.005:
                lines.append(f"{k} billed {billed.get(k, 0):,.2f}, expected {expected.get(k, 0):,.2f}")
        if (carrier, fo_id) in seen:
            verdict, why = "duplicate", f"freight order {fo_id} was already invoiced; billed {b_total:,.2f} USD"
        elif abs(b_total - e_total) <= TOLERANCE_USD:
            verdict, why = "post", f"billed {b_total:,.2f}, expected {e_total:,.2f} USD, within tolerance"
        else:
            verdict, why = "dispute", (f"billed {b_total:,.2f}, expected {e_total:,.2f} USD "
                                       f"(+{b_total - e_total:,.2f}): " + "; ".join(lines))
        seen.add((carrier, fo_id))
        results.append((inv, verdict, why, fo_id))
    return results


def print_audit(pack: dict) -> None:
    print(f"Carrier invoices against freight agreements (tolerance {TOLERANCE_USD:,.2f} USD, course rule)\n")
    for inv, verdict, why, fo_id in audit_results():
        fo = FREIGHT_ORDERS[fo_id]
        print(f"  {inv:<10} {name_of(pack, fo['Carrier']):<23} FO {fo_id}  {verdict.upper():<9} {why}")
    print("\n  The script proposes; the freight settlement clerk decides and records the reason.")
    print("  Synthetic data: the audit shows the method, not results from an SAP system.")


# --------------------------------------------------------------- Markdown worklist
def write_markdown(rows: list) -> Path:
    out = HERE / "calder_shipping_worklist.md"
    lines = [f"# Rockford DC shipping worklist, {NOW:%Y-%m-%d}", "",
             "Fictional Calder Pumpworks data from the Orchestrate course. Not from an SAP system.", "",
             "| # | Item | Detail | Owner |", "| --- | --- | --- | --- |"]
    for n, r in enumerate(rows, start=1):
        lines.append(f"| {n} | {r['what']} | {r['detail']} | {r['owner']} |")
    out.write_text("\n".join(lines) + "\n", encoding="utf-8")
    return out


# --------------------------------------------------------------- SAP sandbox (optional)
def read_env_key(name: str) -> str:
    value = os.environ.get(name, "")
    env = Path.cwd() / ".env"
    if not value and env.exists():
        for line in env.read_text(encoding="utf-8").splitlines():
            if line.strip().startswith(f"{name}="):
                value = line.split("=", 1)[1].strip().strip('"').strip("'")
    return value


def sandbox() -> None:
    key = read_env_key("SAP_API_KEY")
    if not key:
        sys.exit("SAP_API_KEY is not set. Add it to .env (course setup) or skip this step.")
    base = ("https://sandbox.api.sap.com/s4hanacloud/sap/opu/odata/sap/"
            "API_OUTBOUND_DELIVERY_SRV;v=0002/A_OutbDeliveryHeader")
    query = urllib.parse.urlencode({"$top": "50", "$format": "json",
                                    "$select": "DeliveryDocument,ShippingPoint,Warehouse,PlannedGoodsIssueDate,"
                                               "OverallPickingStatus,OverallGoodsMovementStatus"})
    req = urllib.request.Request(f"{base}?{query}", headers={"APIKey": key, "Accept": "application/json"})
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            data = json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as err:
        sys.exit(f"Sandbox answered HTTP {err.code}. Check the key, or try again later.")
    except (urllib.error.URLError, TimeoutError) as err:
        sys.exit(f"Cannot reach the sandbox ({err}). Check the network or proxy.")
    items = data.get("d", {}).get("results", [])
    combos = {}
    for it in items:
        k = (it.get("OverallPickingStatus") or "-", it.get("OverallGoodsMovementStatus") or "-")
        combos[k] = combos.get(k, 0) + 1
    with_whse = sum(1 for it in items if it.get("Warehouse"))
    print(f"SAP sandbox: {len(items)} outbound deliveries read (SAP demo data, not Calder's)\n")
    for (pick, gm), n in sorted(combos.items()):
        print(f"  OverallPickingStatus '{pick}', OverallGoodsMovementStatus '{gm}': {n}")
    print(f"  with a Warehouse value: {with_whse} of {len(items)}")
    for it in items[:3]:
        print(f"  e.g. {it.get('DeliveryDocument')}  shipping point {it.get('ShippingPoint')}")


# --------------------------------------------------------------- main
def main() -> None:
    parser = argparse.ArgumentParser(description="Warehouse and transportation at Calder (fictional data).")
    parser.add_argument("--trace", help="trace one delivery to freight settlement, e.g. 80000001")
    parser.add_argument("--audit", action="store_true", help="check carrier invoices against the agreements")
    parser.add_argument("--write", action="store_true", help="write unit1b/calder_shipping_worklist.md")
    parser.add_argument("--sandbox", action="store_true", help="read outbound deliveries from SAP's sandbox")
    args = parser.parse_args()
    if args.sandbox:
        sandbox()
        return
    pack = load_pack()
    if args.trace:
        trace(pack, args.trace)
    elif args.audit:
        print_audit(pack)
    else:
        rows = print_worklist(pack)
        if args.write:
            print(f"\nwrote unit1b/{write_markdown(rows).name}")


if __name__ == "__main__":
    main()

Step 4: Read the shift worklist

  1. Run the script:

    • Windows (PowerShell):

      python unit1b\warehouse_and_transport.py
    • macOS / Linux:

      python unit1b/warehouse_and_transport.py
  2. You should see exactly this. The script has no random parts, so your output must match:

Rockford DC (RKDC) shipping worklist, 2026-10-26 08:30 (fictional Calder data)

 1  delivery 80000011: warehouse task 300000044 open
    HU 1000000013 not at door D2, so the goods issue for the full delivery waits. Truck for freight order 6100000012 at 14:00 today (Brennock Water Utility, order 1000004)
    -> Shift supervisor (find and move the HU, or split the delivery with sales)
 2  delivery 80000012: picked 8 short
    task 300000051 confirmed 52 of 60 CP-SK200 with exception code DIFF; bin SP-04-02 needs a count (Hallam Fluid Systems, order 1000008)
    -> Warehouse supervisor (count the bin; ship 52 or wait, with sales)
 3  delivery 80000013: not in the warehouse system
    queue DLV_RKDC_OUT in error since 2026-10-23 16:05: a change to delivery 80000009 failed (the document was locked) and was never restarted. Created in S/4HANA, but no warehouse task can exist yet (Brennock Water Utility, order 1000009)
    -> Integration support (fix and restart the entry, then check the queue is empty)
 4  freight unit 4100000014: no freight order
    delivery 80000014 (RK01 -> Hallam, Joliet) must leave by 2026-10-28; Larkspur's allocation on this lane is used up for week 44
    -> Transport planner (book the second carrier, or move the date with sales)
 5  inbound delivery 180000003: putaway waiting
    200 CP-SK200 in HU 1000000031 from Ridgeline Bearing Supply in quality inspection stock since 2026-10-16; inspection lot 010000000041 has no usage decision
    -> Quality inspector (usage decision), then the putaway task runs
 6  carrier invoice LFL-77120: dispute
    billed 981.19, expected 949.02 USD (+32.17): fuel billed 176.94, expected 144.77
    -> Freight settlement clerk (dispute case or reject)
 7  carrier invoice LFL-77188: dispute
    billed 857.41, expected 782.41 USD (+75.00): detention billed 150.00, expected 75.00
    -> Freight settlement clerk (dispute case or reject)
 8  carrier invoice LFL-77203: duplicate
    freight order 6100000010 was already invoiced; billed 857.41 USD
    -> Freight settlement clerk (dispute case or reject)

  8 items, 4 of them hold a customer shipment.
  Synthetic data: the list shows the method, not results from an SAP system.

How to read it:

  • The order is the script's own rule: shipments a customer is waiting for first, soonest goods issue first; then stock at receiving; then money. A real warehouse picks its own order, and that choice is a business decision.
  • Item 1 is the truck problem. Three pumps for Brennock's order 1000004 are picked onto three pallets, but the task that takes the third pallet to door D2 is still open. EWM posts goods issue when the goods are in the goods issue zone, and splits the delivery if not everything can ship. So the supervisor has until 14:00 to find the pallet, or sales has to agree to two pumps today.
  • Item 3 is the quiet one. Delivery 80000013 was created in S/4HANA on Friday, but it waits in the queue behind a failed change to another delivery. Nothing in sales shows an error. In a decentralized warehouse, someone has to watch the queue every shift.
  • Item 4 is planning, not the warehouse. The goods will be ready; the truck won't. The planner can book Tallis Road Freight at a higher rate, or move the date with sales.
  • Items 6 to 8 come from the audit in Step 6. They don't hold a shipment, so they rank last, but they are money.

Step 5: Trace one delivery from the warehouse to finance

  1. Trace the sample pack's delivery:

    • Windows (PowerShell):

      python unit1b\warehouse_and_transport.py --trace 80000001
    • macOS / Linux:

      python unit1b/warehouse_and_transport.py --trace 80000001
  2. You should see:

Delivery 80000001 for sales order 1000001 (Brennock Water Utility), warehouse RKDC, shipping point RK01

Warehouse (decentralized EWM)
  outbound delivery order for 80000001 received from S/4HANA; wave 2026-09-25 morning
  warehouse order 2000000001: pick tasks 300000001 (4 CP-ES200) and 300000002 (2 CP-ES300), confirmed
  packed into 4 handling units:
    HU 1000000001  item 10  2 x CP-ES200  PALLET   385.0 kg
    HU 1000000002  item 10  2 x CP-ES200  PALLET   385.0 kg
    HU 1000000003  item 20  1 x CP-ES300  PALLET   285.0 kg
    HU 1000000004  item 20  1 x CP-ES300  PALLET   285.0 kg
  HU tasks to door D1 confirmed; loaded; goods issue posted 2026-09-25
  S/4HANA: material document 4900000001, journal entry 5000000001 (cost of goods sold 19,600.00 USD)

Transportation (TM in S/4HANA)
  freight unit 4100000001 built from delivery 80000001: 1,340.0 kg gross
  freight order 6100000001, carrier 20000005 Larkspur Freight Lines, agreement FA-LFL-2026
  at the dock 09:00 to 10:15 (1.25 h, no detention)
    linehaul      869.00 USD
    fuel          156.42 USD
    total       1,025.42 USD

Settlement
  freight settlement document 7100000001 transferred: service purchase order 4500000101, service entry sheet 1000000101
  journal (accrual):  Dr 52100 Freight out  1,025.42   Cr 21500 Accrued freight  1,025.42
  carrier invoice LFL-76001 1,025.42 USD matches; posted 2026-10-02:
  journal (invoice):  Dr 21500 Accrued freight  1,025.42   Cr 21100 Payable 20000005  1,025.42
  freight cost distributed to the delivery items by gross weight, for margin analysis:
    item 10 CP-ES200    770.0 kg     589.23 USD
    item 20 CP-ES300    570.0 kg     436.19 USD

  Synthetic numbers for a fictional delivery; account numbers are illustrative.

Read it from top to bottom, and notice which system owns each line. The warehouse owns tasks and HUs, and nothing it does posts to finance until the goods issue. Transportation owns the weight and the charge: 1,340.0 kg is the four HUs' gross weight from the packing rule, so the HU strategy decides the freight bill. Finance sees the freight twice: first as an accrual when the settlement document is transferred, then as a payable when the invoice matches. The cost distribution at the end splits the freight between the two delivery items by weight, so the ES-200 and ES-300 margins each carry their share.

Only 80000001 has a full trace, because it is the one delivery in the pack. Any other number prints a message saying so.

Step 6: Audit the carrier invoices

  1. Run the audit:

    • Windows (PowerShell):

      python unit1b\warehouse_and_transport.py --audit
    • macOS / Linux:

      python unit1b/warehouse_and_transport.py --audit
  2. You should see:

Carrier invoices against freight agreements (tolerance 25.00 USD, course rule)

  TRF-5521   Tallis Road Freight     FO 6100000008  POST      billed 1,103.82, expected 1,103.42 USD, within tolerance
  LFL-77120  Larkspur Freight Lines  FO 6100000009  DISPUTE   billed 981.19, expected 949.02 USD (+32.17): fuel billed 176.94, expected 144.77
  LFL-77188  Larkspur Freight Lines  FO 6100000010  DISPUTE   billed 857.41, expected 782.41 USD (+75.00): detention billed 150.00, expected 75.00
  LFL-77203  Larkspur Freight Lines  FO 6100000010  DUPLICATE freight order 6100000010 was already invoiced; billed 857.41 USD

  The script proposes; the freight settlement clerk decides and records the reason.
  Synthetic data: the audit shows the method, not results from an SAP system.

For each invoice, the script recalculates the charges from the freight agreement: base plus rate per kilogram, the fuel surcharge on that, and detention from the dock times in the freight order. It compares line by line, then applies the tolerance:

  • TRF-5521 is 0.40 USD over, within the 25.00 USD tolerance, so it posts.
  • LFL-77120 bills fuel at 22% of linehaul instead of the agreed 18%. That is a dispute, not a rounding difference.
  • LFL-77188 bills two hours of detention. The truck was at the dock from 08:00 to 10:40: 2 hours 40 minutes, of which two are free. The course rule bills per started hour, so one hour is owed. The carrier may have a different log; that is why the clerk decides.
  • LFL-77203 is the same freight order billed again.

In SAP TM, the comparison happens when the invoice is verified against the freight settlement document, and a difference opens a dispute case (see Freight settlement above). The script does it in advance, so a clerk sees the evidence before the invoice arrives at their desk.

Step 7 (optional): Read outbound deliveries from SAP's sandbox

This step reads SAP's demo data, not Calder's. It needs your sandbox key and the network.

  1. Make sure .env in the course folder has a line SAP_API_KEY="...".

  2. Run:

    • Windows (PowerShell):

      python unit1b\warehouse_and_transport.py --sandbox
    • macOS / Linux:

      python unit1b/warehouse_and_transport.py --sandbox
  3. The output has this shape. Values in angle brackets come from SAP's demo data and change over time:

SAP sandbox: <n> outbound deliveries read (SAP demo data, not Calder's)

  OverallPickingStatus '<code>', OverallGoodsMovementStatus '<code>': <count>
  with a Warehouse value: <count> of <n>
  e.g. <delivery>  shipping point <shipping point>

The script reads up to 50 delivery headers from API_OUTBOUND_DELIVERY_SRV;v=0002 with $select and counts the combinations of picking and goods movement status. The codes are SAP domain values; look up their meaning in the API's documentation on the Hub instead of guessing. A valid "empty" result is 0 outbound deliveries read: the sandbox has none at that moment. The sandbox is read-only, so nothing you do there changes a delivery.

Step 8: Save your work in Git

  1. Write the worklist file:

    • Windows (PowerShell):

      python unit1b\warehouse_and_transport.py --write
    • macOS / Linux:

      python unit1b/warehouse_and_transport.py --write
  2. The last line reads wrote unit1b/calder_shipping_worklist.md. Commit both files:

git add unit1b/warehouse_and_transport.py unit1b/calder_shipping_worklist.md
git commit -m "Unit 1B: warehouse and transport worklist, delivery trace and freight audit for Calder"

What each part of the code does

Part What it does
CARRIERS, TOLERANCE_USD Two course freight agreements (base, rate per kg, fuel, free dock time, detention) and the audit tolerance
DELIVERIES, WAREHOUSE_TASKS Four open deliveries from RK01 and their tasks, after the outbound delivery and warehouse task API shapes
FREIGHT_UNITS, INBOUND, QUEUE A freight unit without a freight order, a receipt waiting on quality, and a failed queue entry with the delivery it holds
FREIGHT_ORDERS, CARRIER_INVOICES Closed freight orders with weights and dock times, and last week's carrier invoices
load_pack() Reads delivery 80000001, its goods issue, journal entry and names from the pack; stops if the pack is missing or wrong
pack_handling_units() The course packing rule: pumps on pallets, seal kits in cartons, with gross weights
expected_charges(), dock_hours() Charge calculation from the agreement, including detention per started hour after the free time
worklist() Collects open tasks, short picks, queue errors, unplanned freight units, waiting receipts and audit findings, and ranks them
trace(), --trace One delivery from tasks and HUs through the freight order and settlement to the journal entries and cost distribution
audit_results(), --audit Compares each invoice line with the expected charge, flags duplicates, and applies the tolerance
write_markdown(), --write The worklist as a Markdown table
sandbox(), --sandbox Reads up to 50 sandbox delivery headers with $select and counts status combinations

If something goes wrong

What you see What it means What to do
python is not recognized / command not found Python isn't on the path, or the terminal opened before installing Follow the Python step in Set up your computer, then open a new terminal
Cannot read ...calder_pack The pack wasn't written, or you ran from the wrong folder Run Step 2 from the orchestrate-course folder
Delivery 80000001 in the pack is not for warehouse RKDC The pack was edited Run Step 2 again
Delivery ... has no full trace in the scenario You asked to trace another delivery Use --trace 80000001
Your output differs from the one shown A value in the scenario blocks was changed Copy the script again, or compare your edits
KeyError with a carrier or freight order number An invoice or task points to a number that isn't in the scenario Add the carrier or freight order, or fix the number
SAP_API_KEY is not set No key in the environment or .env Add it as in the course setup, or skip Step 7
Sandbox answered HTTP 401 The key is wrong or expired Copy the key again from the Business Accelerator Hub
Cannot reach the sandbox No network, or a company proxy blocks it Try another network, or stay with Steps 1 to 6
ModuleNotFoundError You changed the script to use an outside library The script needs only built-in modules; copy it again

Architecture view

Where it sits. At Calder (fictional), sales, purchasing, transportation management and finance for CP10 run in S/4HANA. The Rockford distribution center runs a decentralized EWM on its own S/4HANA-based system, connected by queues for deliveries and goods movements. Carriers connect by EDI today; SAP Business Network Freight Collaboration is the option under review. CP20 in Augsburg ships on ECC. Anything that ranks, traces or audits reads released interfaces and writes only to a worklist; tasks, deliveries and invoices are changed in SAP's own apps.

flowchart LR
  S4[S/4HANA CP10<br/>sales, TM,<br/>finance] <-->|qRFC queues<br/>deliveries, GI, GR| EWM[Decentralized EWM<br/>RKDC]
  S4 <-->|EDI or Freight<br/>Collaboration| CAR[Carriers]
  EWM -->|scanners| RF[Warehouse floor]
  ECC[ECC CP20] -.->|intercompany<br/>shipments| S4
  S4 -->|released APIs, CDS| TRI[Shipping triage<br/>read-only]
  EWM -->|released APIs, CDS| TRI
  TRI --> WL[Shift worklist,<br/>audit findings]

Non-functional requirements that decide the design

Requirement Typical target If missed
Warehouse availability The warehouse keeps working through ERP maintenance windows Trucks wait while S/4HANA is down
Integration latency A new delivery reaches EWM in seconds to minutes; goods issue reaches ERP as fast Picks start late; billing waits on goods issue
Queue monitoring Every failed entry is seen and owned within the shift Deliveries vanish quietly, as at Calder
Scanner response time Sub-second confirmation on the floor Workers wait, then confirm in bulk later
Volume Peak tasks per hour, with headroom for the season Waves release late; cut-off times are missed
Freight audit coverage Every carrier invoice compared with the agreement Overbilling and duplicates are paid
Security Floor users confirm tasks only in their warehouse; only settlement clerks change charges Wrong stock moves; manipulated freight bills

Integration and identity. Between S/4HANA and a decentralized EWM, master data moves by ALE and IDocs or the Data Replication Framework, and transactional data by qRFC (SAP Learning). The freight settlement document reaches purchasing through a background RFC inside one system, or through middleware when TM runs side by side (SAP Learning). Carriers exchange freight requests and quotations by email, EDI, or SAP Business Network Freight Collaboration's portal or APIs (SAP Learning). At Calder, truck positions and dock arrival times come from the carriers, not from Calder's own devices, so their quality depends on each carrier contract. A triage service reads with the user's identity where possible, so warehouse and company code authorizations apply (grounding with authorizations). Integration patterns are covered in how SAP systems talk.

The decision to record (in the ADR form from Writing a solution design doc):

  • Context: Rockford ships finished pumps and spare parts six days a week with two shifts. Peak season doubles the task volume. S/4HANA maintenance windows fall on Sunday nights, when the DC also ships. Augsburg is on ECC and may get a warehouse later.
  • Options: (a) embedded EWM, advanced, in the CP10 S/4HANA system; (b) decentralized EWM on its own S/4HANA-based system; (c) Stock Room Management.
  • Decision: (b). The DC keeps working when the ERP is down, the warehouse can be scaled and upgraded on its own calendar, and a later Augsburg warehouse could join the same system. Stock Room Management lacks waves and task and resource management.
  • Consequences: Two systems to run. Master data must be replicated and checked. qRFC queues need monitoring and an owner on every shift, as the stuck delivery shows. HU changes in EWM aren't always visible in ERP, so reports read HUs from EWM. Licensing for the EWM scope must be confirmed with SAP before the design is final.

A second decision belongs in the same document: the handling unit strategy. Calder packs pumps on pallets at the packing station and ships seal kits in cartons, so carrier weights and dock scans both read the HU. Packing at the supplier would remove a step at receiving but needs ASNs with HU data from every supplier.

What a design review will ask

  1. Why decentralized, and what happens to shipping when the S/4HANA system is down, or when the EWM system is?
  2. Who monitors the integration queues, how fast must a failed entry be fixed, and who may delete one?
  3. Which HU and packaging rules apply, and where are weights for freight taken from?
  4. How do carriers send status events and invoices, and what is checked before a freight invoice posts?
  5. Can any component confirm a task, split a delivery or accept a carrier invoice without a person's decision?

Where AI fits, and where it doesn't

Step Pattern Fit Note
Delivery creation Plain automation is enough Weak for AI Rules from the sales order; nothing to predict
Integration queue errors Retrieve and explain Strong Read the failed entry, find what it blocks, explain the error in plain words, name the owner. SAP lists a Logistics Issue Resolution Agent that "detects the root cause, recommends the resolution" (SAP News)
Wave and task release Predict, then suggest Moderate Predict pick times from history to size waves; release stays rule-based. SAP lists a Warehouse Workload Optimization Agent (SAP)
Slotting Predict and suggest Moderate Suggest which products belong near the dock from order history; a person approves bin changes
Picking and packing anomalies Detect and explain Strong Short picks, open tasks near cut-off, repeated exception codes on one bin. SAP lists an Outbound Task Orchestration Agent (SAP)
Delay prediction Predict Strong Which deliveries will miss their truck, from task status and history, as in predicting late deliveries
Carrier selection Plain automation, then explain Moderate Carrier selection and the optimizer are SAP functions; AI can explain the ranking or the cost of an alternative
Tracking events Extract Moderate Turn carrier emails or documents into structured events for a person to check; see document AI
Freight invoice audit Extract and compare Strong Read the invoice, compare with the agreement, show the evidence. Unusual patterns per carrier are an anomaly detection job
Freight settlement and payment Plain automation is enough Weak for AI Evaluated receipt settlement or invoice verification already do it

The line is the same at every step: AI finds, predicts, explains and drafts; a supervisor confirms or moves a task, sales agrees to a split, a planner books a carrier, and a settlement clerk accepts or rejects a charge. Human approval in AI workflows and agent permissions show how to enforce that in code. The invoice exception copilot builds the same pattern for supplier invoices.

Pitfalls

  • Watching sales documents to know the warehouse. The delivery's status lags the floor. Read the tasks.
  • Nobody owns the queue. With a decentralized warehouse, a failed entry blocks others quietly. Give queue monitoring a name on every shift.
  • Deleting a stuck queue entry to clear it. The message is lost, and the two systems stay out of step. Fix and restart instead.
  • Packing rules nobody wrote down. The HU strategy decides tasks, weights and freight. Make it a design decision, not a habit.
  • Paying carriers as billed. Without a comparison to the agreement, surcharge errors and duplicates go through.
  • Detention without evidence. If dock arrival and departure aren't recorded as events, you can't dispute a detention charge.
  • Assuming ECC and S/4HANA ship the same way. LE-WM transfer orders and LE-TRA shipment costs differ from EWM tasks and TM freight orders, which matters for Calder's CP20.

Exercise

Turn the worklist into a shift lead's action note and test two changes yourself.

  1. Run python unit1b/warehouse_and_transport.py --write (Windows: python unit1b\warehouse_and_transport.py --write). Copy unit1b/calder_shipping_worklist.md to unit1b/calder_shipping_actions.md.
  2. In the copy, add a column Action to the table. For each line write one sentence: what the owner should do before the truck or the payment run.
  3. Add a column AI could with one of: explain, predict, detect, extract or nothing. Use the table under Where AI fits to choose.
  4. In warehouse_and_transport.py, find task 300000044. Change its "Status": "open" to "Status": "confirmed". This is the supervisor finding the pallet and moving it to door D2.
  5. Run python unit1b/warehouse_and_transport.py. Note how many items remain and how many hold a customer shipment. Change the status back to "open".
  6. In CARRIERS, change Larkspur's "FuelPct": 18.0 to 22.0, as if the carrier were right. Run python unit1b/warehouse_and_transport.py --audit and note what happens to LFL-77120 and LFL-77188. Change it back to 18.0.
  7. In the action note, add a heading Changes tested with three lines: what confirming the task changed, what the fuel test shows about which rate Larkspur itself billed, and who has to agree before a rate in a freight agreement changes.
  8. Add a heading ECC note with one line on how a shipment from CP20 to Rockford is costed today, and what changes when Augsburg moves to S/4HANA. Preview the file and commit it with Git.

Done when: unit1b/calder_shipping_actions.md has the eight-line table with Action and AI could filled in for every line, a Changes tested section that says the worklist drops to 7 items with 3 holding a customer shipment, and that at 22% LFL-77120 posts while LFL-77188 is still disputed with expected 806.39 USD (its fuel was billed at 18%, so Larkspur's own invoices disagree), and an ECC note. Keep it: the capstone at the end of Unit 1B traces one exception across the whole enterprise, and this note is its warehouse and transport part.

Check yourself

Pick one answer for each question. The explanation appears after you choose.

1An architect has to explain the difference between embedded and decentralized EWM for master data. What is it?

Answer: D. Embedded EWM runs in the S/4HANA system and shares its basic master data. A decentralized EWM gets it by CIF when NetWeaver-based, or by ALE and IDocs or DRF when S/4HANA-based.

2What does a warehouse order bundle, and what decides how?

Answer: B. Warehouse tasks are bundled into warehouse orders, the packages a worker executes. Warehouse order creation rules group them per activity area with filters, limits and sort rules.

3Delivery 80000013 exists in S/4HANA but not in Calder's decentralized EWM, and its goods issue is due tomorrow. What do you do first?

Answer: D. The delivery waits behind a failed entry in the same queue. Deleting that entry loses its message and leaves the systems out of step; fixing and restarting it lets both deliveries through.

4In the script, why does delivery 80000001 weigh 1,340.0 kg for freight, not the 1,240 kg of the pumps?

Answer: C. Carriers bill on gross weight, goods plus packaging. The packing rule puts the pumps on four pallets of 25 kg each, so the HU strategy decides part of the freight bill.

5When a freight settlement document is transferred to purchasing, what does finance see?

Answer: A. The transfer creates a purchase order and a service entry sheet, which post the accrual and assign the cost. The invoice later posts the payable and clears the accrual.

6Invoice LFL-77188 bills two hours of detention; the freight order's events show 2 hours 40 minutes at the dock with two hours free. What should the audit do?

Answer: A. Under the course rule, 40 minutes beyond the free time is one started hour, 75.00 USD. The audit shows the difference and the evidence; the settlement clerk decides on the dispute case.

7Which read gives an AI triage service open warehouse tasks per delivery on a clean core?

Answer: C. Released APIs and CDS views are the stable, supported way to read. Classic tables such as LIKP are marked not to be released, with successors, and scraping screens breaks with every change.

8An agent proposes splitting delivery 80000011 and rejecting invoice LFL-77120. How should the design handle it?

Answer: C. A split changes what the customer gets and a rejection changes what a carrier is paid. The agent explains; the supervisor with sales, and the settlement clerk, decide, and the decision is logged.

Practice this in the lab

Orchestrate Lab is a free practice ERP in your browser, built around the fictional Calder Pumpworks. These are the places in it where you do what this topic teaches.

  • Stage 1 of 11Found the companyPractice it on Shipping points.
  • Stage 9 of 11Deliver and shipPractice it on Deliveries and shipping.
  • ChapterQualityInspect what Calder buys before anyone uses it: an inspection setup and characteristics for seal kits, goods receipts that go to quality inspection and open inspection lots, results measured on a sample, and the usage decision that releases a good delivery to unrestricted stock or blocks a bad one, while planning counts only what can be used. Then the final inspection of a pump Calder builds: ES-300 tested at production receipt before anyone can sell it, and an order that confirms only the pumps that passed until a recheck after the decision. Then the complaint: a supplier's invoice blocked for quality, a quality notification, a return delivery and the credit memo that settles it. Then the supplier's replacement inspected and its invoice released by the decision, and the supplier's quality record read for what it can and can't say. Last, ES-300's final inspection fed by the test bench: one measurement per pump, recorded once against its serial number.
  • ChapterTransportationPlan Calder's freight from Rockford: carriers and their agreements, freight units from deliveries, freight orders, the carrier chosen by cost, loads with stops, truck capacity and a departure worked back from the dates, the final charges with stops and detention after free time, tendering each load to a carrier that has to accept it, following the truck by its execution events, and settling the freight: each carrier invoice checked against the agreed charges, the freight cost shared among the deliveries so each customer's margin shows what its freight really cost, and inbound freight on purchased motors planned on the purchase order and carried into the motors' cost.
  • ChapterWarehouseLay out Calder's Rockford distribution center: storage types, sections and bins, then put stock away, pick, pack and count it, put people on the work and watch it.
  • DrillThe carrier Calder can't payFinance, about 6 minutes
  • DrillThe truck that should have left yesterdayLogistics, about 6 minutes
  • DrillThe pallet that weighs nothingLogistics, about 5 minutes
  • DrillNowhere to stage the pickLogistics, about 5 minutes
  • DrillThe seal kits nobody can pickLogistics, about 6 minutes
  • DrillThe pumps that can't be receivedLogistics, about 5 minutes

Stages build on each other: a stage link opens the core path at that stage. To start there without doing the earlier stages yourself, use Do this stage for me on the stage before it. A chapter starts inside your own company once the core path is done (or the lab finishes the core for you first); drills and exercises start from their own prepared copy. Everything is saved in this browser only.

Sources

  • Warehouse Management Solutions (SAP Learning) — EWM with S/4HANA on premise or Cloud Private Edition; embedded or decentralized; decentralized always full scope; embedded warehouses set up as basic or advanced; Public Edition embedded basic features only, no decentralized option
  • Positioning Warehouse Management (SAP Learning, Private Edition) — LE-WM in the compatibility package, use right expired 31 December 2025; Stock Room Management from 1909, license in SAP S/4HANA Enterprise Management; advanced embedded adds cross-docking and kitting; SAP Note 2269324
  • Outlining the Basics of SAP EWM (SAP Learning) — embedded from S/4HANA 1610; S/4HANA-based decentralized from 1809 FP02; CIF with qRFC for NetWeaver-based EWM; ALE/IDoc or DRF for S/4HANA-based; transactional data by qRFC; embedded shares basic master data
  • Processing Goods for Shipping (SAP Learning) — outbound delivery request and order, embedded has no request; warehouse tasks (product and HU), confirmation; warehouse orders by warehouse order creation rules; goods issue in the goods issue zone or via transportation unit; EWM sends goods issue to ERP; delivery split
  • Processing Goods Receipt (SAP Learning) — ASN creates the inbound delivery; inbound delivery notification (not in embedded); goods receipt from EWM to ERP; putaway tasks, exception codes, warehouse orders; HUs from ASN or packaging specification; synchronous goods receipt in embedded
  • Creating Freight Units (SAP Learning) — freight unit is the smallest unit that can be transported; predecessor documents (sales order, delivery, OTR, DTR); freight unit building rules and strategies; split quantities
  • Selecting Carriers and Tendering (SAP Learning) — carrier selection by cost, priority, transportation allocations, business shares; peer-to-peer and broadcast tendering; freight RFQ and quotation; email, EDI or SAP Business Network Freight Collaboration
  • Creating Freight Settlement Documents (SAP Learning) — FSD from freight order or booking; charges copied or recalculated from the freight agreement; transfer to MM creates a purchase order and service entry sheet; accruals; invoice verification (MIRO) or evaluated receipt settlement
  • Differentiating Between Transportation Management and Advanced Transportation Management (SAP Learning) — basic TM replaces LE-TRA and needs no extra license; advanced TM (optimizer, package building, load planning) needs an additional license; LE-TRA not the target architecture; SAP Note 3065464
  • Cloudification Repository (SAP on GitHub) — objectReleaseInfoLatest.json read 24 October 2026; I_EWM_WAREHOUSETASK_2, I_EWM_WAREHOUSEORDER_2, I_EWM_OUTBDELIVORDHDR, I_HANDLINGUNITHEADER, I_FREIGHTUNITTP, I_FREIGHTORDERTP, I_FREIGHTORDERCHARGETP released; LIKP, LIPS, VEKP, VEPO not to be released, with successors

Sign in to track your progress

We'll email you a one-time sign-in link. No password needed.

or

Tell us a little about you

Optional, every field. It helps us pitch answers to your questions at the right level and decide which topics to write next. It is never shown publicly, and you can change or clear it anytime from the account menu.

SAP areas you work in