Add the Calder Pumpworks sample pack, a way to draw Mermaid diagrams, and a check that your SAP sandbox key works, so you can trace processes across modules.
Unit 1B teaches how a whole SAP enterprise fits together: the organizational structure, the master data every module shares, and the main business processes from order to cash. The hands-on parts follow one company's documents from step to step.
That company is Calder Pumpworks, a fictional pump maker with a plant in Rockford, USA, and one in Augsburg, Germany. Learners use the same Calder orders, customers and plants in every Unit 1B topic, so each topic builds on the last.
Very little is new to install. Learners add three things to the laptop they set up in Unit 1:
The Calder sample pack: a small set of made-up data files, written by one script, that describe Calder's org units, master data and one sales order with every document that follows it.
A way to draw diagrams: process flows are written as Mermaid diagrams, plain text that tools turn into pictures.
A check that the SAP sandbox key still works, because some topics compare Calder's documents with SAP's own demo data.
Most SAP problems that reach a leader cross module boundaries. A blocked sales order turns out to be a credit setting. A late invoice turns out to be a goods issue that never posted. People who can trace one order across sales, logistics and finance find those causes in hours instead of weeks.
Tracing needs data that shows the whole chain. A real system has it, but learners must not use real company data for training. The sample pack solves that: it is small enough to read in one sitting, complete enough to follow an order from the customer's purchase order to the payment, and entirely fictional.
The diagram habit matters too. Architects and consultants explain designs with pictures. Writing them as text means they live next to the code, can be reviewed like code, and show up correctly on GitHub, where learners keep their portfolio.
On a real program, a team traces documents in the SAP system itself and reads data through SAP's released APIs. The course mirrors that in two ways:
SAP's sandbox. As of October 2026, the SAP Business Accelerator Hub offers sandbox APIs for SAP S/4HANA with demo data, authenticated with one free API key (SAP Learning). Learners got that key in Unit 1. The sandbox holds SAP's demo company, not Calder.
SAP-shaped sample data. The Calder pack uses the same field names as SAP's released APIs for sales orders, deliveries, billing documents, business partners and products. Code written against the pack therefore reads like code written against a real system. The codes and amounts are invented and are not SAP standard values.
Org units such as company codes and plants are configuration, set up once when a system is implemented. No single API returns them, so the pack keeps them in a file of their own.
"The sample pack is a copy of SAP data." No. Every name, code and amount is invented for the course. Only the field names follow SAP's APIs.
"Calder's codes are SAP standard." No. Codes such as CP10 or NT30 are illustrative. Real systems define their own.
"We need a real SAP system to learn processes." Not to start. Reading a complete, consistent chain of documents teaches the flow. A system adds the screens and the configuration later.
"Diagrams need a drawing tool licence." Mermaid is free text. VS Code's preview, GitHub and mermaid.live all draw it.
Sample pack: the set of made-up Calder data files the Unit 1B scripts read when run with --sample.
Org unit: an organizational element such as a company code (a legal entity's books), a plant (a site that makes or stores goods) or a sales organization (the unit that sells).
Master data: long-lived records that many documents use, such as customers, suppliers and products.
Document flow: the chain of documents one business transaction creates, each pointing to the one before.
Mermaid: a text format for diagrams, written in a code block and drawn by tools that support it.
Sandbox: SAP's public test system on the Business Accelerator Hub, with demo data and read access by API key.
Pick one answer for each question. The explanation appears after you choose.
1What does a learner need to add for Unit 1B?
Answer: B. Unit 1B adds the Calder sample pack, Mermaid diagrams and a check that the Unit 1 sandbox key still works. There are no new libraries, accounts or costs.
2Why does the course use a fictional company instead of real data?
Answer: C. Real company data must stay out of training. Using one fictional company across the unit also means each topic builds on the same orders, customers and plants.
3A consultant says the pack's company code CP10 is an SAP standard value. Is that right?
Answer: D. Only the field names follow SAP's APIs. Codes, names and amounts are invented for the course, and each real system defines its own codes.
4Why write process diagrams as Mermaid text?
Answer: B. Text diagrams sit beside the code, change with it and are reviewed the same way. VS Code, GitHub and mermaid.live draw them for free.
5What is the most useful thing to check with IT before Unit 1B?
Answer: A. Only the sandbox check needs the network, and company proxies often block new sites. The sample pack itself works offline.
6Where do org units such as company codes and plants come from in a real system?
Answer: A. Org units are configuration, set up once during implementation. No single API returns them, which is why the pack keeps them in their own file.
Ask a question
Testing: only staff see this
Stuck on something in this layer? Ask it here. Questions are answered in the order they arrive, and the answer appears under My questions.
Sign in (free) to ask a question. You can ask anonymously.
Unit 1B is about following one business transaction through many modules. The sample pack is built for exactly that: a few small files, each one a kind of SAP data, linked by the codes they share.
flowchart LR
O[org_units.json<br/>company codes, plants,<br/>sales and purchasing orgs] --> D
B[business_partners.json<br/>customers and suppliers] --> D
P[products.json<br/>pumps, parts, components] --> D
D[order_1000001.json<br/>order, delivery, goods issue,<br/>invoice, journal entries, payment]
Every code in the order file must exist in another file. Sales organization CPUS must be an org unit. Customer 10000001 must be a business partner. Product CP-ES200 must have data for plant CP11. That is how a real SAP system behaves too: a document can only use org units and master data that already exist. The check script tests exactly this.
Pack name, version, a fictional flag and the file list
Course format
org_units.json
Client, company codes CP10 and CP20, controlling area, plants CP11 and CP21, storage locations, sales organizations, distribution channels, divisions, sales areas, purchasing organizations, shipping points and the Rockford warehouse
SAP field names for each unit's key; course layout
business_partners.json
Three customers and four suppliers, including the two Calder companies as intercompany partners
API_BUSINESS_PARTNER: A_BusinessPartner with to_Customer and to_Supplier
products.json
Two finished pumps, a seal kit, a machined impeller and a motor, with plant and valuation data
API_PRODUCT_SRV: A_Product with to_Description, to_Plant, to_Valuation
order_1000001.json
One sales order and everything after it: outbound delivery, goods issue, invoice, three journal entries and the document flow
Sales order, outbound delivery, material document and billing document APIs; journal entries in a simplified course shape
Every field name in the business partner, product, sales order, delivery, material document and billing document records was checked against SAP Cloud SDK packages generated from those services' metadata (version 2.1.0, npm). Product types use SAP's standard material type keys, such as FERT for finished products and ROH for raw materials (SAP Help Portal). Your own system may define others.
Order 1000001 is the thread the whole unit pulls on. Brennock Water Utility, a fictional customer, buys four ES-200 pumps and two ES-300 pumps from Calder's US sales organization. Here is what the pack records, in date order:
Date
Document
Number
What it records
2026-09-14
Sales order
1000001
Customer, sales area, two items, prices and the plant that ships them (CP11)
2026-09-25
Outbound delivery
80000001
What leaves the Rockford dock (RK01), each item pointing back to the order item
2026-09-25
Material document
4900000001
The goods issue: stock leaves storage location FG01
2026-09-25
Journal entry
5000000001
The goods issue's cost moving from inventory to cost of goods sold
2026-09-26
Billing document
90000001
The invoice, pointing to the delivery and to its own journal entry
2026-09-26
Journal entry
9400000001
Revenue and the customer's receivable
2026-10-20
Journal entry
1400000001
The customer's payment clearing the receivable
Two things are worth noticing already. First, logistics documents (delivery, goods issue) and finance documents (journal entries) are separate, but linked. Second, in the pack two different events post to finance: the goods issue posts cost, and the invoice posts revenue. Why each one posts, and to which accounts, is the subject of Document flow and how logistics becomes finance.
The pack is written by a script rather than downloaded, for three reasons:
It is reproducible. The script contains no random numbers. Everyone who runs it gets byte-for-byte the same files, so your output can be compared with the topic's.
It is consistent by construction. Amounts are computed, not typed: each item's net value is quantity times price, the order total is the sum of the items, and the cost posted at goods issue comes from the products' standard prices. Change a price and every document that uses it changes with it.
It is readable. Opening the script shows how the data hangs together. A functional consultant can read it like a configuration table without running anything.
If you break a file while experimenting, run the script again. It rewrites all five files and the diagram.
The pack keeps only the fields the Unit 1B topics need. Three simplifications matter:
Dates are plain YYYY-MM-DD text. SAP's OData V2 APIs return dates as /Date(milliseconds)/, which you met in Calling your first SAP API. The pack uses readable dates so you can follow the timeline by eye.
Journal entries are a course shape. Each has a company code, fiscal year, document number and lines with a GL account and an amount. In the pack, debits are positive and credits negative, so every entry's lines add up to zero. The GL account numbers are made up.
The document flow is an explicit list. In SAP you read it from the documents' references or from process-flow entities. The pack lists the links once, so a script or a diagram can follow them directly.
Later Unit 1B topics add files to the pack when they need them, for example a purchase order for procure-to-pay. The pack_version field says which version you have.
A Mermaid diagram is a fenced code block marked mermaid. Three free tools draw it:
VS Code. As of October 2026, the built-in Markdown preview renders mermaid code blocks, with pan and zoom (VS Code docs). On an older VS Code that shows the code as text, update VS Code or install the Markdown Preview Mermaid Support extension (GitHub).
GitHub. Mermaid blocks in Markdown files render as diagrams (GitHub blog), so your portfolio shows the pictures, not the code.
mermaid.live. The Mermaid project's free live editor in the browser (Mermaid). Use it if you can't install anything. Don't paste company data into it.
#Build it yourself: add the sample pack and prove your tools work
You will write the Calder sample pack with one script, look at its files and its diagram, and run a check that proves everything Unit 1B needs is in place.
flowchart LR
S[make_calder_pack.py] --> P[calder_pack/<br/>5 JSON files]
S --> M[calder_order_flow.md<br/>Mermaid diagram]
P --> C[check_unit1b.py]
M --> C
K[.env<br/>SAP_API_KEY] --> C
C -->|one read-only call| X[SAP sandbox]
Before you start: complete Set up your computer for this course. It creates your orchestrate-course folder with its .venv, and stores SAP_API_KEY in .env. This topic adds nothing else to install.
Update VS Code: on Windows choose Help > Check for Updates; on macOS choose Code > Check for Updates; on Linux update it with your package manager. Restart it if it asks.
You will test the preview in Step 4. If it shows the diagram as plain code there, click the Extensions icon on the left, search for Markdown Preview Mermaid Support, and install it.
In VS Code, create a new file, paste the script below, and save it as make_calder_pack.py inside unit1b.
Run it from the course folder:
Windows (PowerShell):
python unit1b\make_calder_pack.py
macOS / Linux:
python unit1b/make_calder_pack.py
"""Write the Calder Pumpworks sample pack for Unit 1B.
Calder Pumpworks is a fictional company. Every code, name, number and amount
in this pack is made up for the course. Field names follow SAP's released
OData APIs where an API exists; values are illustrative, not SAP standard.
Run it from your course folder: python unit1b/make_calder_pack.py
It writes the folder unit1b/calder_pack/ and changes nothing else.
Run it again at any time: it always writes exactly the same files.
"""
import json
from pathlib import Path
PACK_VERSION = "1.0"
HERE = Path(__file__).resolve().parent
OUT = HERE / "calder_pack"
# ---------------------------------------------------------------- org units
# Org units are configuration, not business data, so no single API returns
# them. This file uses SAP's field names for each unit's key.
ORG_UNITS = {
"Client": "100",
"CompanyCodes": [
{"CompanyCode": "CP10", "CompanyCodeName": "Calder Pumpworks Inc.",
"Country": "US", "Currency": "USD", "ControllingArea": "CP00", "System": "S/4HANA"},
{"CompanyCode": "CP20", "CompanyCodeName": "Calder Pumpworks GmbH",
"Country": "DE", "Currency": "EUR", "ControllingArea": "CP00", "System": "ECC"},
],
"ControllingAreas": [
{"ControllingArea": "CP00", "Name": "Calder group controlling", "Currency": "USD"},
],
"Plants": [
{"Plant": "CP11", "PlantName": "Rockford", "CompanyCode": "CP10",
"Role": "Final assembly and test of pumps"},
{"Plant": "CP21", "PlantName": "Augsburg", "CompanyCode": "CP20",
"Role": "Machined components"},
],
"StorageLocations": [
{"Plant": "CP11", "StorageLocation": "FG01", "Name": "Rockford distribution center"},
{"Plant": "CP11", "StorageLocation": "RM01", "Name": "Rockford plant stores"},
{"Plant": "CP21", "StorageLocation": "RM01", "Name": "Augsburg plant stores"},
{"Plant": "CP21", "StorageLocation": "FG01", "Name": "Augsburg shipping stock"},
],
"SalesOrganizations": [
{"SalesOrganization": "CPUS", "Name": "Calder sales USA", "CompanyCode": "CP10", "Currency": "USD"},
{"SalesOrganization": "CPDE", "Name": "Calder sales Germany", "CompanyCode": "CP20", "Currency": "EUR"},
],
"DistributionChannels": [
{"DistributionChannel": "10", "Name": "Direct to equipment makers and utilities"},
{"DistributionChannel": "20", "Name": "Distributors"},
{"DistributionChannel": "30", "Name": "Aftermarket parts and service"},
],
"Divisions": [
{"Division": "PU", "Name": "Pumps"},
{"Division": "SP", "Name": "Spare parts"},
],
"SalesAreas": [
{"SalesOrganization": "CPUS", "DistributionChannel": "10", "Division": "PU"},
{"SalesOrganization": "CPUS", "DistributionChannel": "20", "Division": "PU"},
{"SalesOrganization": "CPUS", "DistributionChannel": "30", "Division": "SP"},
{"SalesOrganization": "CPDE", "DistributionChannel": "10", "Division": "PU"},
],
"SalesOrgPlants": [
{"SalesOrganization": "CPUS", "DistributionChannel": "10", "Plant": "CP11"},
{"SalesOrganization": "CPUS", "DistributionChannel": "20", "Plant": "CP11"},
{"SalesOrganization": "CPUS", "DistributionChannel": "30", "Plant": "CP11"},
{"SalesOrganization": "CPDE", "DistributionChannel": "10", "Plant": "CP21"},
],
"PurchasingOrganizations": [
{"PurchasingOrganization": "PUUS", "Name": "Purchasing USA", "CompanyCode": "CP10", "Plants": ["CP11"]},
{"PurchasingOrganization": "PUDE", "Name": "Purchasing Germany", "CompanyCode": "CP20", "Plants": ["CP21"]},
],
"ShippingPoints": [
{"ShippingPoint": "RK01", "Name": "Rockford dock", "Plant": "CP11"},
{"ShippingPoint": "AU01", "Name": "Augsburg dock", "Plant": "CP21"},
],
"Warehouses": [
{"Warehouse": "RKDC", "Name": "Rockford distribution center",
"Plant": "CP11", "StorageLocations": ["FG01"]},
],
}
# ------------------------------------------------------------ master data
def customer(bp, name, company_code, recon, sales_areas, terms="NT30"):
"""A business partner in the customer role, shaped like API_BUSINESS_PARTNER."""
return {
"BusinessPartner": bp, "BusinessPartnerFullName": name, "Customer": bp, "Supplier": "",
"to_Customer": {
"Customer": bp, "CustomerName": name,
"to_CustomerCompany": [{"Customer": bp, "CompanyCode": company_code,
"PaymentTerms": terms, "ReconciliationAccount": recon}],
"to_CustomerSalesArea": [
{"Customer": bp, "SalesOrganization": so, "DistributionChannel": dc, "Division": dv,
"Currency": cur, "CustomerPaymentTerms": terms}
for so, dc, dv, cur in sales_areas],
},
}
def supplier(bp, name, company_code, purch_org, currency, recon="21100", terms="NT45"):
"""A business partner in the supplier role, shaped like API_BUSINESS_PARTNER."""
return {
"BusinessPartner": bp, "BusinessPartnerFullName": name, "Customer": "", "Supplier": bp,
"to_Supplier": {
"Supplier": bp, "SupplierName": name,
"to_SupplierCompany": [{"Supplier": bp, "CompanyCode": company_code,
"PaymentTerms": terms, "ReconciliationAccount": recon}],
"to_SupplierPurchasingOrg": [{"Supplier": bp, "PurchasingOrganization": purch_org,
"PurchaseOrderCurrency": currency, "PaymentTerms": terms,
"InvoiceIsGoodsReceiptBased": True}],
},
}
BUSINESS_PARTNERS = {"BusinessPartners": [
customer("10000001", "Brennock Water Utility", "CP10", "12100", [("CPUS", "10", "PU", "USD")]),
customer("10000002", "Hallam Fluid Systems", "CP10", "12100",
[("CPUS", "20", "PU", "USD"), ("CPUS", "30", "SP", "USD")]),
customer("10000010", "Calder Pumpworks Inc. (intercompany)", "CP20", "12900",
[("CPDE", "10", "PU", "EUR")]),
supplier("20000001", "Orrin Motor Works", "CP10", "PUUS", "USD"),
supplier("20000002", "Tamsfield Foundry", "CP20", "PUDE", "EUR"),
supplier("20000003", "Ridgeline Bearing Supply", "CP10", "PUUS", "USD"),
supplier("20000010", "Calder Pumpworks GmbH (intercompany)", "CP10", "PUUS", "USD", recon="21900"),
]}
def product(code, ptype, division, text, plants, std_price, currency):
"""A product shaped like API_PRODUCT_SRV, with plant and valuation data."""
return {
"Product": code, "ProductType": ptype, "BaseUnit": "EA", "Division": division,
"to_Description": [{"Product": code, "Language": "EN", "ProductDescription": text}],
"to_Plant": [{"Product": code, "Plant": p, "BaseUnit": "EA"} for p in plants],
"to_Valuation": [{"Product": code, "ValuationArea": p, "StandardPrice": std_price,
"PriceUnitQty": 1, "Currency": currency} for p in plants],
}
PRODUCTS = {"Products": [
product("CP-ES200", "FERT", "PU", "End-suction pump ES-200", ["CP11"], 2850.00, "USD"),
product("CP-ES300", "FERT", "PU", "End-suction pump ES-300", ["CP11"], 4100.00, "USD"),
product("CP-SK200", "HAWA", "SP", "Seal kit for ES-200", ["CP11"], 38.00, "USD"),
product("CP-IMP200", "HALB", "PU", "Machined impeller for ES-200", ["CP21"], 310.00, "EUR"),
product("CP-MTR15", "ROH", "PU", "Electric motor 15 kW", ["CP11"], 920.00, "USD"),
]}
# ------------------------------------------------- one order, end to end
ITEMS = [ # item, product, quantity, price each (USD); costs come from PRODUCTS
("10", "CP-ES200", 4, 4200.00),
("20", "CP-ES300", 2, 6150.00),
]
STD_PRICE = {p["Product"]: p["to_Valuation"][0]["StandardPrice"] for p in PRODUCTS["Products"]}
NET = round(sum(q * price for _, _, q, price in ITEMS), 2)
COST = round(sum(q * STD_PRICE[m] for _, m, q, _ in ITEMS), 2)
ORDER_DOCUMENTS = {
"SalesOrder": {
"SalesOrder": "1000001", "SalesOrganization": "CPUS", "DistributionChannel": "10",
"OrganizationDivision": "PU", "SoldToParty": "10000001",
"PurchaseOrderByCustomer": "BWU-PO-5531", "SalesOrderDate": "2026-09-14",
"RequestedDeliveryDate": "2026-09-28", "TransactionCurrency": "USD",
"TotalNetAmount": NET, "CustomerPaymentTerms": "NT30",
"to_Item": [{"SalesOrder": "1000001", "SalesOrderItem": i, "Material": m,
"RequestedQuantity": q, "RequestedQuantityUnit": "EA",
"NetAmount": round(q * price, 2), "ProductionPlant": "CP11",
"StorageLocation": "FG01", "ShippingPoint": "RK01"}
for i, m, q, price in ITEMS],
},
"OutboundDelivery": {
"DeliveryDocument": "80000001", "ShippingPoint": "RK01", "SalesOrganization": "CPUS",
"SoldToParty": "10000001", "ShipToParty": "10000001", "Warehouse": "RKDC",
"PlannedGoodsIssueDate": "2026-09-25", "ActualGoodsMovementDate": "2026-09-25",
"to_DeliveryDocumentItem": [{"DeliveryDocument": "80000001", "DeliveryDocumentItem": i,
"Material": m, "ActualDeliveryQuantity": q,
"DeliveryQuantityUnit": "EA", "Plant": "CP11",
"StorageLocation": "FG01", "ReferenceSDDocument": "1000001",
"ReferenceSDDocumentItem": i}
for i, m, q, _ in ITEMS],
},
"MaterialDocument": {
"MaterialDocument": "4900000001", "MaterialDocumentYear": "2026", "PostingDate": "2026-09-25",
"ReferenceDocument": "80000001",
"to_MaterialDocumentItem": [{"MaterialDocument": "4900000001", "MaterialDocumentYear": "2026",
"MaterialDocumentItem": str(n), "Material": m, "Plant": "CP11",
"StorageLocation": "FG01", "QuantityInEntryUnit": q,
"EntryUnit": "EA", "Delivery": "80000001", "DeliveryItem": i,
"Customer": "10000001"}
for n, (i, m, q, _) in enumerate(ITEMS, start=1)],
},
"BillingDocument": {
"BillingDocument": "90000001", "BillingDocumentDate": "2026-09-26",
"SalesOrganization": "CPUS", "DistributionChannel": "10", "Division": "PU",
"SoldToParty": "10000001", "PayerParty": "10000001", "TotalNetAmount": NET,
"TaxAmount": 0.00, "TransactionCurrency": "USD", "CustomerPaymentTerms": "NT30",
"CompanyCode": "CP10", "FiscalYear": "2026", "AccountingDocument": "9400000001",
"to_Item": [{"BillingDocument": "90000001", "BillingDocumentItem": i, "Material": m,
"BillingQuantity": q, "BillingQuantityUnit": "EA",
"NetAmount": round(q * price, 2), "Plant": "CP11",
"ReferenceSDDocument": "80000001", "ReferenceSDDocumentItem": i,
"SalesDocument": "1000001", "SalesDocumentItem": i}
for i, m, q, price in ITEMS],
},
# Journal entries: debits are positive and credits negative in this pack,
# so every entry's lines add up to zero. GL accounts are illustrative.
"JournalEntries": [
{"CompanyCode": "CP10", "FiscalYear": "2026", "AccountingDocument": "5000000001",
"PostingDate": "2026-09-25", "Source": "MaterialDocument 4900000001 (goods issue)",
"Lines": [{"GLAccount": "51000", "Text": "Cost of goods sold",
"AmountInCompanyCodeCurrency": COST, "CompanyCodeCurrency": "USD"},
{"GLAccount": "13100", "Text": "Finished goods inventory",
"AmountInCompanyCodeCurrency": -COST, "CompanyCodeCurrency": "USD"}]},
{"CompanyCode": "CP10", "FiscalYear": "2026", "AccountingDocument": "9400000001",
"PostingDate": "2026-09-26", "Source": "BillingDocument 90000001 (invoice)",
"Lines": [{"GLAccount": "12100", "Text": "Receivable from 10000001", "Customer": "10000001",
"AmountInCompanyCodeCurrency": NET, "CompanyCodeCurrency": "USD"},
{"GLAccount": "41000", "Text": "Revenue, pumps",
"AmountInCompanyCodeCurrency": -NET, "CompanyCodeCurrency": "USD"}]},
{"CompanyCode": "CP10", "FiscalYear": "2026", "AccountingDocument": "1400000001",
"PostingDate": "2026-10-20", "Source": "Incoming payment for invoice 90000001",
"Lines": [{"GLAccount": "11000", "Text": "Bank",
"AmountInCompanyCodeCurrency": NET, "CompanyCodeCurrency": "USD"},
{"GLAccount": "12100", "Text": "Receivable from 10000001", "Customer": "10000001",
"AmountInCompanyCodeCurrency": -NET, "CompanyCodeCurrency": "USD"}]},
],
# The chain from first document to last. A course shape, not an SAP API.
"DocumentFlow": [
{"From": "SalesOrder 1000001", "To": "OutboundDelivery 80000001"},
{"From": "OutboundDelivery 80000001", "To": "MaterialDocument 4900000001"},
{"From": "MaterialDocument 4900000001", "To": "JournalEntry 5000000001"},
{"From": "OutboundDelivery 80000001", "To": "BillingDocument 90000001"},
{"From": "BillingDocument 90000001", "To": "JournalEntry 9400000001"},
{"From": "JournalEntry 9400000001", "To": "JournalEntry 1400000001"},
],
}
FILES = {
"org_units.json": ORG_UNITS,
"business_partners.json": BUSINESS_PARTNERS,
"products.json": PRODUCTS,
"order_1000001.json": ORDER_DOCUMENTS,
}
PACK_INFO = {
"pack": "Calder Pumpworks sample pack", "pack_version": PACK_VERSION,
"fictional": True,
"note": "Made-up data for the Orchestrate course. Not SAP standard values, not from any real system.",
"files": sorted(FILES),
}
def flow_diagram() -> str:
"""A Mermaid flowchart of the order's document flow, built from the pack."""
fence = "`" * 3 # three backticks open and close a Markdown code block
ids = {}
lines = [fence + "mermaid", "flowchart LR"]
for link in ORDER_DOCUMENTS["DocumentFlow"]:
for doc in (link["From"], link["To"]):
if doc not in ids:
ids[doc] = f"D{len(ids) + 1}"
kind, number = doc.split(" ")
lines.append(f' {ids[doc]}["{kind}<br/>{number}"]')
lines.append(f" {ids[link['From']]} --> {ids[link['To']]}")
lines.append(fence)
return "# Calder order 1000001: document flow\n\n" + "\n".join(lines) + "\n"
def main() -> None:
OUT.mkdir(exist_ok=True)
for name, data in {**FILES, "pack_info.json": PACK_INFO}.items():
(OUT / name).write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
print(f" wrote calder_pack/{name}")
(HERE / "calder_order_flow.md").write_text(flow_diagram(), encoding="utf-8")
print(" wrote calder_order_flow.md")
print(f"\nCalder sample pack {PACK_VERSION}: order 1000001, net {NET:,.2f} USD, cost {COST:,.2f} USD.")
print("All data is fictional. Next: python check_unit1b.py")
if __name__ == "__main__":
main()
You should see:
wrote calder_pack/org_units.json
wrote calder_pack/business_partners.json
wrote calder_pack/products.json
wrote calder_pack/order_1000001.json
wrote calder_pack/pack_info.json
wrote calder_order_flow.md
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 contains no random numbers, so every run writes exactly the same files. Your output must match the totals above.
In VS Code's file list, open unit1b/calder_pack/order_1000001.json. Find "SoldToParty": "10000001" in the sales order.
Open unit1b/calder_pack/business_partners.json and find business partner 10000001. It is Brennock Water Utility, a fictional water utility, with a sales area for sales organization CPUS.
Back in the order file, find the delivery's "ReferenceSDDocument": "1000001". That is the delivery pointing back to the sales order: document flow in one field.
Open unit1b/calder_order_flow.md and press Ctrl+Shift+V (Windows, Linux) or Cmd+Shift+V (macOS) to open the preview. You should see a flowchart from the sales order through the delivery to the invoice and the payment.
If the preview shows code instead of a picture, finish Step 2.2. If you can't install anything, open mermaid.live in your browser and paste the lines between the two mermaid fences.
Create a new file in the course folder (not in unit1b), paste the script below and save it as check_unit1b.py.
Run it:
python check_unit1b.py
"""Check that your computer is ready for Unit 1B (SAP end to end).
Run it from your course folder: python check_unit1b.py
Add --offline to skip the sandbox call (for example on a plane).
It reads the Calder sample pack and your .env, and makes one read-only
call to SAP's sandbox. It changes nothing and prints no keys.
"""
import json
import os
import sys
import urllib.error
import urllib.request
from pathlib import Path
PACK = Path("unit1b") / "calder_pack"
FILES = ["pack_info.json", "org_units.json", "business_partners.json",
"products.json", "order_1000001.json"]
SANDBOX = ("https://sandbox.api.sap.com/s4hanacloud/sap/opu/odata/sap/"
"API_SALES_ORDER_SRV/A_SalesOrder?$top=1&$select=SalesOrder&$format=json")
OFFLINE = "--offline" in sys.argv
problems = 0
def report(ok: bool, label: str, fix: str = "", optional: bool = False) -> None:
"""Print one line: OK, MISSING (must fix) or LATER (optional for now)."""
global problems
if ok:
print(f" OK {label}")
elif optional:
print(f" LATER {label} -> {fix}")
else:
problems += 1
print(f" MISSING {label} -> {fix}")
def read_env_key(name: str) -> str:
"""Return a key from the environment or from .env, using built-in modules only."""
if os.environ.get(name):
return os.environ[name]
env = Path(".env")
if env.exists():
for line in env.read_text(encoding="utf-8").splitlines():
key, sep, value = line.partition("=")
if sep and key.strip() == name:
return value.strip().strip('"').strip("'")
return ""
def pack_problems(pack: dict) -> list:
"""Cross-check the pack: every code a document uses must exist in the org units or master data."""
org, bps, prods, order = (pack[f] for f in FILES[1:])
company_codes = {c["CompanyCode"] for c in org["CompanyCodes"]}
plants = {p["Plant"]: p["CompanyCode"] for p in org["Plants"]}
sales_orgs = {s["SalesOrganization"] for s in org["SalesOrganizations"]}
customers = {b["Customer"] for b in bps["BusinessPartners"] if b["Customer"]}
products = {p["Product"]: {pl["Plant"] for pl in p["to_Plant"]} for p in prods["Products"]}
found = []
found += [f"plant {p} points to unknown company code {c}" for p, c in plants.items() if c not in company_codes]
so = order["SalesOrder"]
if so["SalesOrganization"] not in sales_orgs:
found.append(f"sales order uses unknown sales organization {so['SalesOrganization']}")
if so["SoldToParty"] not in customers:
found.append(f"sales order uses unknown customer {so['SoldToParty']}")
for item in so["to_Item"]:
if item["ProductionPlant"] not in products.get(item["Material"], set()):
found.append(f"item {item['SalesOrderItem']}: {item['Material']} has no plant data in {item['ProductionPlant']}")
for item in order["OutboundDelivery"]["to_DeliveryDocumentItem"]:
if item["ReferenceSDDocument"] != so["SalesOrder"]:
found.append(f"delivery item {item['DeliveryDocumentItem']} does not point to the sales order")
billing = order["BillingDocument"]
accounting = {j["AccountingDocument"] for j in order["JournalEntries"]}
if billing["AccountingDocument"] not in accounting:
found.append("billing document points to a journal entry that is not in the pack")
for entry in order["JournalEntries"]:
total = round(sum(line["AmountInCompanyCodeCurrency"] for line in entry["Lines"]), 2)
if total != 0:
found.append(f"journal entry {entry['AccountingDocument']} does not balance ({total})")
return found
print("\n1. Python")
v = sys.version_info
report(v >= (3, 10), f"Python {v.major}.{v.minor}.{v.micro}", "install Python 3.10 or newer (course setup topic)")
report(sys.prefix != sys.base_prefix, "virtual environment is active", "activate .venv (course setup topic, Step 4)")
print("\n2. Calder sample pack")
pack = {}
for name in FILES:
try:
pack[name] = json.loads((PACK / name).read_text(encoding="utf-8"))
report(True, f"calder_pack/{name}")
except FileNotFoundError:
report(False, f"calder_pack/{name}", "run python unit1b/make_calder_pack.py (Step 3)")
except json.JSONDecodeError as err:
report(False, f"calder_pack/{name}", f"not valid JSON (line {err.lineno}); run the pack script again")
if len(pack) == len(FILES):
info = pack["pack_info.json"]
report(info.get("fictional") is True, f"pack version {info.get('pack_version')}, marked fictional",
"run python unit1b/make_calder_pack.py again")
issues = pack_problems(pack)
report(not issues, "documents point to org units and master data that exist",
"; ".join(issues[:3]) + " (run the pack script again)")
order = pack["order_1000001.json"]
print(f" order {order['SalesOrder']['SalesOrder']}: {len(order['DocumentFlow'])} links "
f"in its document flow, {len(order['JournalEntries'])} journal entries")
print("\n3. Diagrams")
flow = Path("unit1b") / "calder_order_flow.md"
report(flow.exists() and "`" * 3 + "mermaid" in flow.read_text(encoding="utf-8"),
"unit1b/calder_order_flow.md holds a Mermaid diagram", "run python unit1b/make_calder_pack.py (Step 3)")
print("\n4. SAP sandbox")
key = read_env_key("SAP_API_KEY")
report(bool(key), "SAP_API_KEY", "add it to .env (course setup topic, Step 6)")
if OFFLINE:
report(False, "sandbox call", "skipped (--offline); run again without --offline", optional=True)
elif key:
request = urllib.request.Request(SANDBOX, headers={"APIKey": key, "Accept": "application/json"})
try:
with urllib.request.urlopen(request, timeout=30) as resp:
report(resp.status == 200, "sandbox accepted your key (Sales Order API)", f"HTTP {resp.status}")
except urllib.error.HTTPError as err:
hint = "key rejected: copy it again from api.sap.com" if err.code in (401, 403) else f"HTTP {err.code}"
report(False, "sandbox call", hint)
except (urllib.error.URLError, TimeoutError, OSError) as err:
report(False, "sandbox call", f"network problem ({type(err).__name__}): try another network or ask IT")
print()
if problems:
print(f"{problems} item(s) to fix. Fix them in order, then run this again.")
sys.exit(1)
print("All set. You are ready for Unit 1B.")
When everything is ready you see:
1. Python
OK Python 3.14.4
OK virtual environment is active
2. Calder sample pack
OK calder_pack/pack_info.json
OK calder_pack/org_units.json
OK calder_pack/business_partners.json
OK calder_pack/products.json
OK calder_pack/order_1000001.json
OK pack version 1.0, marked fictional
OK documents point to org units and master data that exist
order 1000001: 6 links in its document flow, 3 journal entries
3. Diagrams
OK unit1b/calder_order_flow.md holds a Mermaid diagram
4. SAP sandbox
OK SAP_API_KEY
OK sandbox accepted your key (Sales Order API)
All set. You are ready for Unit 1B.
Your Python version may differ. If you are offline, run python check_unit1b.py --offline: the sandbox line then shows LATER, which is fine for now, because every Unit 1B script also runs with --sample.
On a real program, the same three habits appear with SAP's tools. Consultants trace a document's flow in the system itself, from the order to the invoice to the accounting document. Architects keep landscape and process diagrams in the project repository next to the specifications. Test teams build consistent test data sets in which every document uses org units and master data that exist, because a missing customer or plant makes a test fail for the wrong reason.
The pack's cross-check is a tiny version of that last habit. When you add a document to the pack in a later topic, add its master data first, just as a real system requires.
In PRODUCTS, look up the product code of the seal kit (CP-SK200) and its division (SP).
In org_units.json, find which sales area sells division SP. (It is sales organization CPUS, distribution channel 30.)
In business_partners.json, find which customer has that sales area. (It is 10000002, Hallam Fluid Systems, a fictional distributor.)
On paper or in a new file unit1b/order_1000002_plan.md, write down a second order: Hallam orders 20 seal kits from the aftermarket channel. List its sales organization, distribution channel, division, customer, plant and shipping point, and say which file each value comes from.
Add a Mermaid flowchart to the same file showing the documents this order would create, using the first order's flow as a model, and check it in the preview.
Commit the file with Git.
Done when:unit1b/order_1000002_plan.md lists every code for the new order with the file it comes from, its diagram renders in the VS Code preview, and python check_unit1b.py --offline still prints All set. Keep it: later Unit 1B topics add more orders to the pack, and your plan is a head start.
Pick one answer for each question. The explanation appears after you choose.
1The check reports that the sales order uses an unknown customer. What happened?
Answer: C. The check compares every code the order uses with the org units and master data files. A real system enforces the same rule: a document can only use a customer that exists. Run the pack script again or fix the code it names.
2Which field in the pack shows that the delivery belongs to sales order 1000001?
Answer: C. Each delivery item's ReferenceSDDocument and ReferenceSDDocumentItem point back to the sales order and its item. SoldToParty names the customer, not the order.
3Why does every journal entry in the pack add up to zero?
Answer: C. In the pack, debits are positive and credits negative, so a balanced entry sums to zero. The check tests this for every entry, which catches an edited amount at once.
4You run code against the sandbox and see dates like /Date(1789344000000)/, but the pack shows 2026-09-14. Which is right?
Answer: D. SAP's OData V2 APIs return dates as /Date(milliseconds)/. The pack uses plain dates so you can read the timeline by eye. Convert when you compare the two.
5Your Markdown preview shows the Mermaid block as code, not a picture. What do you do?
Answer: B. As of October 2026, VS Code's built-in preview draws Mermaid. An older version needs an update or the Markdown Preview Mermaid Support extension. mermaid.live works in the browser if you can't install anything.
6The sandbox check fails with a network problem on a company laptop. Can you continue with Unit 1B?
Answer: C. The pack is written and read locally, so --offline turns the sandbox line into LATER and the unit's scripts run with --sample. Ask IT about sandbox.api.sap.com meanwhile.
7A colleague wants to show sandbox output as an example of Calder's orders. What do you tell them?
Answer: D. The sandbox holds SAP's own demo data and Calder exists only in the pack. Label sandbox output as sandbox output so readers never mix the two.
Ask a question
Testing: only staff see this
Stuck on something in this layer? Ask it here. Questions are answered in the order they arrive, and the answer appears under My questions.
Sign in (free) to ask a question. You can ask anonymously.
Related topics
Picked from the terms, links and tags this topic shares with others.
@sap/cloud-sdk-vdm-business-partner-service (npm, SAP Cloud SDK)— version 2.1.0, generated from service metadata; entity and field names used in the pack were checked against this package and its siblings for sales order, outbound delivery, material document, billing document, product and journal entry item (all 2.1.0)