How an SAP enterprise is wired: organizational structure
Learn the SAP org units from client to warehouse, how their assignments decide which modules share data, and why they decide what users and AI agents see.
Before an SAP system can take a single order, someone has to describe the company to it. Which legal entities exist? Where are the factories and warehouses? Who sells, who buys, and from where do goods ship? The answers become the organizational structure: a set of codes, called org units, and the links between them.
Every business document carries some of these codes. A sales order names a sales organization and a plant. An invoice posts to a company code. A goods movement happens in a storage location. Because the codes are linked, SAP knows that a shipment from one plant belongs in one company's books.
Three things follow from that, and they are why this topic matters to anyone leading an SAP program:
The structure decides what can talk to what. If a plant isn't linked to a sales organization, that sales team can't ship from it.
The structure is hard to change later. Documents and master data are stamped with the codes for years.
The structure scopes access. Roles are restricted by these same codes, so they decide what a person, or an AI agent acting for them, can see.
The org structure is a design decision with a long tail. A few examples from Calder Pumpworks, the fictional pump maker this unit follows:
Reporting. Calder has two legal entities, so it has two company codes: CP10 in the USA and CP20 in Germany. Each produces its own financial statements. Had Calder created a company code per product line, it would close the books, file taxes and reconcile intercompany balances for each one, every month.
Master data volume. Customers are maintained per sales area (a sales organization, a distribution channel and a division). Calder has four sales areas. A design with 36 would mean up to 36 sales records for a customer who buys everything, multiplied across every customer.
Intercompany. Calder's German plant makes components for the US plant. Because each plant belongs to exactly one company code, that flow is a sale between two legal entities, with invoices on both sides.
Access and AI. A blocked-order assistant for the US order desk should see US sales orders and nothing from Germany. That boundary is drawn with org units. Get the structure wrong and either the assistant sees too much, or roles become so complex that nobody can review them.
Changing a company code or merging plants after go-live usually means a project of its own, with data conversion and re-testing. The cheapest time to get it right is the design workshop.
The org units themselves are the same across SAP's ERP family, with differences in how they are set up:
SAP S/4HANA Cloud Public Edition. As of October 2026, the structure is built in the Organizational Structure app of SAP Central Business Configuration, usually in a dedicated fit-to-standard workshop. Units sit in a staging area until confirmed; once confirmed they can't be deleted. Some finance settings, such as the fiscal year variant, can't be changed after saving (SAP Learning).
SAP S/4HANA (private cloud and on-premise) and SAP ECC. The structure is set up in Customizing (the system's configuration tool), including which plants each sales organization may deliver from (SAP Learning).
Warehousing changed most. In SAP S/4HANA, the classic ECC warehouse management (LE-WM) is not the target. SAP positions Extended Warehouse Management (EWM) and Stock Room Management instead, and LE-WM's limited use right under the compatibility package ended on 31 December 2025 (SAP Learning).
The whole system's top level; settings here apply to all company codes
—
100
Company code
A legal entity that keeps its own books
Client
CP10 USA, CP20 Germany
Controlling area
The boundary for internal cost accounting
Holds one or more company codes
CP00
Plant
A site where goods are made, stored or planned
Exactly one company code
CP11 Rockford, CP21 Augsburg
Storage location
A place where stock is kept inside a plant
One plant
FG01, RM01
Sales organization
The unit that sells and owns the terms of sale
Exactly one company code
CPUS, CPDE
Distribution channel
How goods reach the customer
Assigned to sales orgs
10 direct, 20 distributors, 30 aftermarket
Division
A product line
Assigned to sales orgs
PU pumps, SP spare parts
Sales area
One sales org + one channel + one division
—
4 combinations
Purchasing organization
The unit that buys and negotiates with suppliers
Optionally one company code; serves plants
PUUS, PUDE
Shipping point
Where outbound deliveries are processed
Assigned to plants
RK01, AU01
Warehouse number
A warehouse managed bin by bin
Adds bin-level management to a plant's stock
RKDC
The two links that cause the most trouble are sales organization to plant (which plants a sales team may ship from) and purchasing organization to plant (which plants a buying team may buy for).
"More org units give us more detailed reporting." Most reporting needs are better met by reporting dimensions, such as profit centers or customer groups, than by new org units that every document and role must carry.
"A warehouse is a plant." Often it is a storage location in a plant, perhaps managed bin by bin with a warehouse number. A separate plant adds its own planning, transfers and settings.
"We can fix the structure after go-live." Codes are stamped on years of documents. Changes are possible but are projects in their own right.
"The AI assistant will see what it needs." It sees what its identity's roles allow. If those roles aren't scoped by org unit, it may see far more than its users.
"Org units are an IT topic." They encode legal entities, sales channels and sites. Finance, sales and operations leaders own the decisions.
Pick one answer for each question. The explanation appears after you choose.
1Why does the org structure matter beyond configuration?
Answer: D. Every document is stamped with org units, customer data is kept per sales area, and roles are restricted by these codes. That is why the structure is hard to change and why it shapes access.
2Calder wants a company code for each product line to get product-line profit figures. What is the best response?
Answer: C. A company code represents a legal entity with its own books. Product-line views are better met by reporting dimensions, without extra closes and intercompany reconciliation.
3Why does a design with many sales areas worry the master data team?
Answer: D. A customer who buys through several sales areas needs a sales record in each one. More sales areas means more records to create, maintain and keep consistent.
4Calder's German plant supplies the US plant. What does the org structure make this flow?
Answer: C. Each plant belongs to exactly one company code, and the plants sit in different company codes. So goods moving between them cross legal entities and need intercompany billing.
5A vendor says its AI assistant for the US order desk "sees what it needs". What should you ask?
Answer: C. Roles are restricted by org units such as sales organization. An assistant that follows the user's roles inherits that scope; one with a broad technical user can see other countries' data.
6What is a common mistake when modeling a distribution center at the same site as a plant?
Answer: C. A distribution center at an existing site is usually a storage location, possibly with a warehouse number. A separate plant adds planning, transfers and settings, so it needs a real reason.
7Which statement about SAP S/4HANA Cloud Public Edition org structure is accurate as of October 2026?
Answer: B. SAP Learning describes the Organizational Structure app in Central Business Configuration. Once units are confirmed they can't be deleted, which makes the design workshop the moment to get it right.
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.
Think of the org structure as the company's address system. Every document has an address made of org units: a sales order lives at sales organization CPUS, channel 10, division PU, plant CP11. The assignments between org units are the roads between addresses. A process can only travel along roads that exist.
Three consequences make the rest of this topic click:
Integration follows assignments. Sales can ship from a plant only if that plant is assigned to the sales org and channel. Finance can post a delivery's cost only because the plant belongs to a company code. No assignment, no flow.
Master data is stored at the addresses. A material has plant data, a customer has company code and sales area data, a supplier has company code and purchasing org data. Every extra address means more records.
Authorizations are written in addresses. Roles say "may change sales orders in sales org CPUS". So the structure is also the map of who can see what, for people and for the AI agents that act for them.
If you know the address of a document, you know which module owns it, which books it lands in and who may read it.
Client. The top level, with a three-digit key. Settings made at client level apply to all company codes in it (SAP Learning). Calder's US company runs on SAP S/4HANA and its German company on SAP ECC, so in practice Calder has two systems, each with its own client and its own configuration.
Company code. The smallest unit of external accounting, with a four-character key unique in the client. Several company codes can share a client (same source). Every financial posting names one.
Controlling area. The boundary for management accounting. One controlling area holds one or more company codes. Those company codes must use the same operational chart of accounts and fiscal year variant, but they may use different currencies (SAP Learning). Putting company codes in one controlling area is what lets costs be allocated across them.
Plant. A logistics unit seen from production, procurement, plant maintenance and materials planning. Four-character key, unique in the client (SAP Learning). Each plant belongs to exactly one company code (SAP Learning). That single link is how every goods movement finds its books.
Storage location. Separates stock within a plant; inventory is managed at this level. Its four-character key is unique within a plant (SAP Learning), so FG01 in Rockford and FG01 in Augsburg are two different places.
#Sales: sales organization, channel, division, sales area
As of October 2026, SAP Learning states these rules for SAP S/4HANA Sales (SAP Learning):
Each sales organization is assigned to exactly one company code. A company code can have several.
A distribution channel is assigned to one or more sales organizations.
Several divisions can be assigned to a sales organization.
A sales area is one sales organization, one channel and one division. Sales employees are authorized for one or more sales areas.
The pair of sales organization and channel is called a distribution chain.
The link from sales to logistics is the delivering plant: plants are maintained in Customizing for each sales organization and channel. A plant can serve several of them, and the sales org and plant don't have to share a company code (SAP Learning). When they don't, the sale is intercompany: the selling company bills the customer, and the delivering company bills the seller. Intercompany and multi-company scenarios covers the postings.
Shipping point. The top unit of shipping. Shipping points and plants are many-to-many, set in Customizing; one shipping point serving several plants only makes sense when they are close together. The shipping point is found per sales order item, and each outbound delivery has exactly one (SAP Learning).
Warehouse number. For a warehouse managed bin by bin. In SAP EWM, the warehouse number is the top of a hierarchy of storage types, sections and bins; SAP recommends one warehouse number for storage areas or buildings in the same area, and a separate one for a distant complex (same source). Plant and storage location stay the inventory view; the warehouse number adds the bin-level view on top.
1. Too many company codes. A company code is a self-contained set of books. Each one adds a period close, tax reporting, and master data: customers, suppliers and G/L accounts all have company-code-level data. Flows between company codes become intercompany, with invoices on both sides and balances to reconcile. Product lines, regions or business units are usually better served by reporting dimensions.
2. Plant versus storage location confusion. A plant is where SAP plans materials and production; a storage location only separates stock. Making a dock or a cage into a plant forces stock transfers, separate planning and extra master data for every material stored there. Making a distant site into a storage location hides it from planning. Ask: does this place plan on its own, sit in a different company, or need its own settings? If not, it's a storage location.
3. Sales areas that multiply master data. Customer sales data is kept per sales area: the business partner API has a to_CustomerSalesArea record for each one, as the Unit 1B setup pack shows. A division for every product family and a channel for every route to market can turn two sales organizations into dozens of sales areas, each needing customer records, pricing and tests. Divisions and channels should reflect real differences in terms, pricing or responsibility.
#Org structure scopes authorizations, and therefore AI
SAP checks authorizations with authorization objects, and many of them have org-unit fields. In role maintenance, these appear as organizational levels, such as plant, company code or business area, each given a value for the role (SAP Help Portal). In SAP S/4HANA Cloud Public Edition, the same idea appears as restrictions on business roles, covered in Agent permissions and SAP authorizations. Released authorization objects include F_BKPF_BUK, the company code check for accounting documents, and V_VBAK_VKO in sales (Cloudification Repository).
For AI, the chain is direct. An agent that reads SAP as the user, through principal propagation, sees what the user's org-level values allow. An agent that reads through a broad technical user sees everything that user can, regardless of who asked. Agent permissions and SAP authorizations and Grounding on SAP data with authorizations cover the mechanics. The point here is upstream: a clean org structure makes clean roles possible. If the US order desk and the German order desk share a sales organization, no role can separate them, and neither can an agent.
You will write a script that reads Calder's org units from the sample pack and does three things. It checks the assignment rules from this topic. It draws the structure as a Mermaid diagram. And it scopes the structure: given an org unit value, it shows which of order 1000001's documents a role limited to that value would reach. That last part is the authorization idea in miniature.
flowchart LR
A[calder_pack/<br/>org_units.json] --> S[org_structure.py]
B[business_partners.json] --> S
C[order_1000001.json] --> S
S --> R[Rule check<br/>in the terminal]
S --> D[calder_org_structure.md<br/>diagram]
S --> V[Scope report<br/>SEES / BLOCKED]
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
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
If your exercise from the setup topic changed the script, this run uses your changed version. That's fine as long as python check_unit1b.py --offline still reports the pack as OK.
In VS Code, create a new file, paste the script below, and save it as org_structure.py inside the unit1b folder.
"""Check, draw and scope Calder Pumpworks' organizational structure.
Calder Pumpworks is a fictional company. This script reads the Calder sample
pack (unit1b/calder_pack/, written by make_calder_pack.py) and needs no account,
no key and no network.
Run it from your course folder:
python unit1b/org_structure.py check the rules, print a summary, draw the diagram
python unit1b/org_structure.py --scope plant=CP11 what a role limited to plant CP11 would see
python unit1b/org_structure.py --what-if 2 3 6 sales areas for 2 sales orgs x 3 channels x 6 divisions
It writes unit1b/calder_org_structure.md and changes nothing else.
"""
import argparse
import json
import sys
from collections import Counter
from pathlib import Path
PACK = Path("unit1b") / "calder_pack"
DIAGRAM = Path("unit1b") / "calder_org_structure.md"
def load(name: str) -> dict:
"""Read one pack file, or stop with a clear message."""
path = PACK / name
try:
return json.loads(path.read_text(encoding="utf-8"))
except FileNotFoundError:
sys.exit(f"Can't find {path}. Run: python unit1b/make_calder_pack.py")
except json.JSONDecodeError as err:
sys.exit(f"{path} is not valid JSON (line {err.lineno}). Run make_calder_pack.py again.")
def check_rules(org: dict) -> tuple:
"""Test the assignment rules. Returns (errors, notes): errors break processes, notes are design facts."""
errors, notes = [], []
company_codes = {c["CompanyCode"]: c for c in org["CompanyCodes"]}
plant_cc = {p["Plant"]: p["CompanyCode"] for p in org["Plants"]}
so_cc = {s["SalesOrganization"]: s["CompanyCode"] for s in org["SalesOrganizations"]}
channels = {d["DistributionChannel"] for d in org["DistributionChannels"]}
divisions = {d["Division"] for d in org["Divisions"]}
# Rule 1: every plant belongs to exactly one company code that exists.
for plant, cc in plant_cc.items():
if cc not in company_codes:
errors.append(f"plant {plant} points to unknown company code {cc}")
# Rule 2: a storage location exists only inside a plant, and its key is unique within that plant.
seen = Counter((s["Plant"], s["StorageLocation"]) for s in org["StorageLocations"])
for (plant, sloc), n in seen.items():
if plant not in plant_cc:
errors.append(f"storage location {sloc} sits in unknown plant {plant}")
if n > 1:
errors.append(f"storage location {sloc} is defined {n} times in plant {plant}")
reused = Counter(sloc for _, sloc in seen)
for sloc, n in sorted(reused.items()):
if n > 1:
notes.append(f"storage location key {sloc} is used in {n} plants: fine, keys are unique per plant only")
# Rule 3: every sales organization belongs to exactly one company code that exists.
for so, cc in so_cc.items():
if cc not in company_codes:
errors.append(f"sales organization {so} points to unknown company code {cc}")
# Rule 4: a sales area is one sales org + one channel + one division, all defined.
for area in org["SalesAreas"]:
so, dc, dv = area["SalesOrganization"], area["DistributionChannel"], area["Division"]
if so not in so_cc or dc not in channels or dv not in divisions:
errors.append(f"sales area {so}/{dc}/{dv} uses an undefined unit")
# Rule 5: each distribution chain (sales org + channel) that sells needs at least one plant.
chains = {(a["SalesOrganization"], a["DistributionChannel"]) for a in org["SalesAreas"]}
chain_plants = {}
for link in org["SalesOrgPlants"]:
chain_plants.setdefault((link["SalesOrganization"], link["DistributionChannel"]), []).append(link["Plant"])
for so, dc in sorted(chains):
if not chain_plants.get((so, dc)):
errors.append(f"distribution chain {so}/{dc} has no delivering plant")
# Rule 6: a sales org may deliver from another company code's plant: that is intercompany.
for (so, dc), plants in sorted(chain_plants.items()):
for plant in plants:
if plant not in plant_cc:
errors.append(f"distribution chain {so}/{dc} delivers from unknown plant {plant}")
elif plant_cc[plant] != so_cc.get(so):
notes.append(f"{so}/{dc} delivers from plant {plant} of company code {plant_cc[plant]}: "
"intercompany sales, needs intercompany billing set up")
# Rule 7: a purchasing org assigned to a company code may only serve that company code's plants.
for po in org["PurchasingOrganizations"]:
cc = po.get("CompanyCode")
for plant in po["Plants"]:
if plant not in plant_cc:
errors.append(f"purchasing org {po['PurchasingOrganization']} serves unknown plant {plant}")
elif cc and plant_cc[plant] != cc:
errors.append(f"purchasing org {po['PurchasingOrganization']} belongs to {cc} "
f"but serves plant {plant} of {plant_cc[plant]}")
if not cc:
notes.append(f"purchasing org {po['PurchasingOrganization']} has no company code: cross-company-code")
# Rule 8: shipping points and warehouses hang off plants and storage locations that exist.
for sp in org["ShippingPoints"]:
if sp["Plant"] not in plant_cc:
errors.append(f"shipping point {sp['ShippingPoint']} points to unknown plant {sp['Plant']}")
for wh in org["Warehouses"]:
for sloc in wh["StorageLocations"]:
if (wh["Plant"], sloc) not in seen:
errors.append(f"warehouse {wh['Warehouse']} uses unknown storage location {wh['Plant']}/{sloc}")
# Rule 9: a controlling area is configuration inside one system, so its company codes must share one.
by_area = {}
for cc in company_codes.values():
by_area.setdefault(cc["ControllingArea"], set()).add(cc.get("System", "?"))
for area, systems in sorted(by_area.items()):
if len(systems) > 1:
notes.append(f"controlling area {area} spans systems {sorted(systems)}: "
"a target design only; today each system needs its own")
return errors, notes
def summary(org: dict, bps: dict) -> None:
"""Print each company code with the units under it, then the sales-area master data count."""
for cc in org["CompanyCodes"]:
code = cc["CompanyCode"]
plants = [p["Plant"] for p in org["Plants"] if p["CompanyCode"] == code]
slocs = [f"{s['Plant']}/{s['StorageLocation']}" for s in org["StorageLocations"] if s["Plant"] in plants]
sales = [s["SalesOrganization"] for s in org["SalesOrganizations"] if s["CompanyCode"] == code]
purch = [p["PurchasingOrganization"] for p in org["PurchasingOrganizations"] if p.get("CompanyCode") == code]
print(f" {code} {cc['CompanyCodeName']} ({cc['Currency']}, {cc['System']}, "
f"controlling area {cc['ControllingArea']})")
print(f" plants {plants} storage locations {slocs}")
print(f" sales orgs {sales} purchasing orgs {purch}")
rows = sum(len(b["to_Customer"]["to_CustomerSalesArea"])
for b in bps["BusinessPartners"] if b.get("to_Customer"))
customers = sum(1 for b in bps["BusinessPartners"] if b.get("to_Customer"))
print(f"\n {len(org['SalesAreas'])} sales areas defined; {customers} customers hold "
f"{rows} sales-area records between them")
def scope(org: dict, order: dict, field: str, value: str) -> None:
"""Show which org units and order documents a role limited to one org value would reach."""
plant_cc = {p["Plant"]: p["CompanyCode"] for p in org["Plants"]}
if field == "company_code":
plants = {p for p, cc in plant_cc.items() if cc == value}
sales_orgs = {s["SalesOrganization"] for s in org["SalesOrganizations"] if s["CompanyCode"] == value}
elif field == "plant":
plants = {value} if value in plant_cc else set()
sales_orgs = set()
elif field == "sales_org":
plants = set()
sales_orgs = {value} if any(s["SalesOrganization"] == value for s in org["SalesOrganizations"]) else set()
else:
sys.exit("Use --scope company_code=..., plant=... or sales_org=...")
if not plants and not sales_orgs:
sys.exit(f"{value} is not a {field} in the pack.")
print(f"\nA role whose org-level values cover {field} {value} and the units under it reaches:")
print(f" plants {sorted(plants) or '-'} sales orgs {sorted(sales_orgs) or '-'}")
so = order["SalesOrder"]
checks = [
("SalesOrder " + so["SalesOrder"], "sales org", so["SalesOrganization"] in sales_orgs),
("OutboundDelivery " + order["OutboundDelivery"]["DeliveryDocument"], "plant",
any(i["Plant"] in plants for i in order["OutboundDelivery"]["to_DeliveryDocumentItem"])),
("MaterialDocument " + order["MaterialDocument"]["MaterialDocument"], "plant",
any(i["Plant"] in plants for i in order["MaterialDocument"]["to_MaterialDocumentItem"])),
("BillingDocument " + order["BillingDocument"]["BillingDocument"], "sales org",
order["BillingDocument"]["SalesOrganization"] in sales_orgs),
]
company_codes = {cc for cc in [value] if field == "company_code"}
checks += [(f"JournalEntry {j['AccountingDocument']}", "company code", j["CompanyCode"] in company_codes)
for j in order["JournalEntries"]]
for doc, checked_by, visible in checks:
print(f" {'SEES ' if visible else 'BLOCKED'} {doc:<28} (checked by {checked_by})")
def what_if(sales_orgs: int, channels: int, divisions: int, customers: int) -> None:
"""How many sales areas, and customer sales-area records, a design could create."""
areas = sales_orgs * channels * divisions
print(f"\nWhat if: {sales_orgs} sales orgs x {channels} channels x {divisions} divisions "
f"= up to {areas} sales areas.")
print(f" A customer buying in every one of them needs {areas} sales-area records;")
print(f" {customers} such customers would need {areas * customers:,}.")
def diagram(org: dict) -> str:
"""A Mermaid flowchart: company codes, plants, storage locations, sales and purchasing orgs."""
fence = "`" * 3
out = [fence + "mermaid", "flowchart TB", f' CL["Client {org["Client"]}"]']
for area in org["ControllingAreas"]:
out.append(f' CA_{area["ControllingArea"]}["Controlling area {area["ControllingArea"]}"]')
for cc in org["CompanyCodes"]:
c = cc["CompanyCode"]
out.append(f' CC_{c}["Company code {c}<br/>{cc["Currency"]}, {cc["System"]}"]')
out.append(f" CL --> CC_{c}")
out.append(f" CC_{c} -.-> CA_{cc['ControllingArea']}")
for p in org["Plants"]:
out.append(f' PL_{p["Plant"]}["Plant {p["Plant"]}<br/>{p["PlantName"]}"]')
out.append(f" CC_{p['CompanyCode']} --> PL_{p['Plant']}")
for s in org["StorageLocations"]:
node = f"SL_{s['Plant']}_{s['StorageLocation']}"
out.append(f' {node}["Storage location {s["StorageLocation"]}"]')
out.append(f" PL_{s['Plant']} --> {node}")
for w in org["Warehouses"]:
out.append(f' WH_{w["Warehouse"]}["Warehouse {w["Warehouse"]}"]')
for sloc in w["StorageLocations"]:
out.append(f" SL_{w['Plant']}_{sloc} -.-> WH_{w['Warehouse']}")
for sp in org["ShippingPoints"]:
out.append(f' SP_{sp["ShippingPoint"]}["Shipping point {sp["ShippingPoint"]}"]')
out.append(f" PL_{sp['Plant']} --> SP_{sp['ShippingPoint']}")
for s in org["SalesOrganizations"]:
out.append(f' SO_{s["SalesOrganization"]}["Sales org {s["SalesOrganization"]}"]')
out.append(f" CC_{s['CompanyCode']} --> SO_{s['SalesOrganization']}")
for link in org["SalesOrgPlants"]:
out.append(f" SO_{link['SalesOrganization']} -.->|ch {link['DistributionChannel']}| PL_{link['Plant']}")
for po in org["PurchasingOrganizations"]:
out.append(f' PO_{po["PurchasingOrganization"]}["Purchasing org {po["PurchasingOrganization"]}"]')
if po.get("CompanyCode"):
out.append(f" CC_{po['CompanyCode']} --> PO_{po['PurchasingOrganization']}")
for plant in po["Plants"]:
out.append(f" PO_{po['PurchasingOrganization']} -.-> PL_{plant}")
out.append(fence)
return ("# Calder Pumpworks (fictional): organizational structure\n\n"
"Solid lines: belongs to. Dotted lines: is assigned to or serves.\n\n" + "\n".join(out) + "\n")
def main() -> None:
parser = argparse.ArgumentParser(description="Check, draw and scope Calder's org structure.")
parser.add_argument("--scope", help="company_code=CP10, plant=CP11 or sales_org=CPUS")
parser.add_argument("--what-if", nargs=3, type=int, metavar=("ORGS", "CHANNELS", "DIVISIONS"))
parser.add_argument("--customers", type=int, default=500, help="customers for --what-if (default 500)")
args = parser.parse_args()
org, bps, order = load("org_units.json"), load("business_partners.json"), load("order_1000001.json")
print("Calder Pumpworks (fictional) org structure\n")
summary(org, bps)
errors, notes = check_rules(org)
print(f"\nRules: {len(errors)} error(s), {len(notes)} design note(s)")
for e in errors:
print(f" ERROR {e}")
for n in notes:
print(f" NOTE {n}")
DIAGRAM.write_text(diagram(org), encoding="utf-8")
print(f"\nWrote {DIAGRAM.as_posix()}")
if args.scope:
field, _, value = args.scope.partition("=")
scope(org, order, field.strip(), value.strip())
if args.what_if:
what_if(*args.what_if, args.customers)
sys.exit(1 if errors else 0)
if __name__ == "__main__":
main()
Calder Pumpworks (fictional) org structure
CP10 Calder Pumpworks Inc. (USD, S/4HANA, controlling area CP00)
plants ['CP11'] storage locations ['CP11/FG01', 'CP11/RM01']
sales orgs ['CPUS'] purchasing orgs ['PUUS']
CP20 Calder Pumpworks GmbH (EUR, ECC, controlling area CP00)
plants ['CP21'] storage locations ['CP21/RM01', 'CP21/FG01']
sales orgs ['CPDE'] purchasing orgs ['PUDE']
4 sales areas defined; 3 customers hold 4 sales-area records between them
Rules: 0 error(s), 3 design note(s)
NOTE storage location key FG01 is used in 2 plants: fine, keys are unique per plant only
NOTE storage location key RM01 is used in 2 plants: fine, keys are unique per plant only
NOTE controlling area CP00 spans systems ['ECC', 'S/4HANA']: a target design only; today each system needs its own
Wrote unit1b/calder_org_structure.md
"0 error(s)" is the result you want. The notes are design facts, not faults. The third one matters: the pack puts both companies in controlling area CP00, but CP20 still runs on ECC. A controlling area is configuration inside one system, so CP00 spanning both is the target after Germany moves to S/4HANA. Until then, each system has its own, and group cost reporting happens outside both.
In VS Code's file list, open unit1b/calder_org_structure.md and press Ctrl+Shift+V (Windows, Linux) or Cmd+Shift+V (macOS). You should see Calder's structure as a tree, from client to storage locations, with sales and purchasing orgs beside it.
The commands from here on are the same in PowerShell and in macOS or Linux terminals; on Windows you may also write unit1b\org_structure.py.
Ask what a role limited to the Rockford plant would reach:
python unit1b/org_structure.py --scope plant=CP11
After the summary, you should see:
A role whose org-level values cover plant CP11 and the units under it reaches:
plants ['CP11'] sales orgs -
BLOCKED SalesOrder 1000001 (checked by sales org)
SEES OutboundDelivery 80000001 (checked by plant)
SEES MaterialDocument 4900000001 (checked by plant)
BLOCKED BillingDocument 90000001 (checked by sales org)
BLOCKED JournalEntry 5000000001 (checked by company code)
BLOCKED JournalEntry 9400000001 (checked by company code)
BLOCKED JournalEntry 1400000001 (checked by company code)
A warehouse clerk at Rockford sees the delivery and the goods issue, but not the order's prices, the invoice or the books. Now try the German sales org:
Every line of the scope report shows BLOCKED. That is a valid result, not an error: the US order is outside the German sales org. An AI assistant running with that user's roles would find nothing either. Try --scope company_code=CP10 to see a role that reaches everything in the US company.
Ask how many sales areas a richer design would create, for example 2 sales organizations, 3 channels and 6 divisions:
python unit1b/org_structure.py --what-if 2 3 6
At the end you should see:
What if: 2 sales orgs x 3 channels x 6 divisions = up to 36 sales areas.
A customer buying in every one of them needs 36 sales-area records;
500 such customers would need 18,000.
On a real system you don't read org units from a JSON file. You read them from the system, or design them in its configuration tools.
Reading the structure. As of the Cloudification Repository file of 21 October 2026, SAP has released CDS views for the org units in this topic: I_CompanyCode, I_ControllingArea, I_Plant, I_StorageLocation, I_SalesOrganization, I_DistributionChannel, I_Division, I_SalesArea, I_PurchasingOrganization and I_ShippingPoint. The underlying tables, such as T001 (company codes) and TVKO (sales organizations), are marked not to be released, with those views as successors (Cloudification Repository). The same file lists the old function module BAPI_COMPANYCODE_GETLIST as not to be released, with I_CompanyCode as its successor. That is the clean-core rule from The SAP data model for AI engineers applied to configuration data.
For remote reads, SAP publishes OData services for some org units. The SAP Cloud SDK packages generated from them (version 2.1.0) show API_COMPANYCODE_SRV, whose A_CompanyCode entity carries ControllingArea, ChartOfAccounts, FiscalYearVariant and CreditControlArea, and API_SALESORGANIZATION_SRV, whose A_SalesOrganization entity carries CompanyCode and IntercompanyBillingCustomer (npm). Check each service on the SAP Business Accelerator Hub for your edition before relying on it.
Designing the structure.
SAP S/4HANA Cloud Public Edition: the Organizational Structure app in SAP Central Business Configuration. It derives the units needed from the activated scope and countries, checks mandatory links such as a distribution chain having at least one plant, and keeps units in staging until confirmed (SAP Learning).
SAP S/4HANA private cloud and on-premise, and SAP ECC: Customizing, for example the delivering plants per sales org and channel (SAP Learning).
Licensing. Org units aren't licensed separately; they come with the ERP. Warehouse options differ: SAP Learning states that Stock Room Management's license is included in the SAP S/4HANA Enterprise Management component (SAP Learning). Check EWM licensing with SAP for your contract.
Where it sits. Org units are configuration inside each ERP system. At Calder, CP10 lives in the S/4HANA system and CP20 in the ECC system, so the "one" org structure is really two, kept consistent by agreement. Everything else reads them: BTP apps, the data warehouse and any AI assistant.
flowchart LR
S4[S/4HANA<br/>CP10, CP11, CPUS] --> W[Data warehouse<br/>group view]
ECC[ECC<br/>CP20, CP21, CPDE] --> W
S4 -->|released views, APIs| APP[BTP app or agent]
U[User roles<br/>org-level values] -.-> S4
Non-functional requirements that decide the design
Requirement
Typical target
If missed
Access separation
Each country's sales and finance data reachable only by its roles
Agents and users see other entities' data; audit findings
Master data volume
Customer sales-area records sized before go-live
Data migration and maintenance costs grow with every sales area
Changeability
No org unit change needed for normal growth (new customer, new product)
A new product line forces a new division and new records everywhere
Consistency across systems
Same codes and meanings in S/4HANA, ECC and the warehouse
Group reports double-count or miss entities
Close effort
One period close per legal entity
Extra company codes add closes and intercompany reconciliation
Integration and identity. The org units travel on every document: the sales order's SalesOrganization, the delivery item's Plant, the journal entry's CompanyCode. Integration maps them, for example when ECC's CP20 sends components to S/4HANA's CP10. The user's identity reaches SAP through principal propagation, and SAP applies the org-level values in the user's roles. An app that needs the structure itself reads the released views or APIs above, never the tables.
Context: Calder needs cross-company cost allocation between the US and Germany. CP20 stays on ECC for at least a year.
Options: (a) one controlling area CP00 for both, after CP20 moves to S/4HANA; (b) one controlling area per company code, permanently; (c) a group view built only in the data warehouse.
Decision: (a) as the target, with (c) until the move. Both company codes already share the chart of accounts and fiscal year variant, which (a) requires.
Consequences: Cross-company allocation waits for the migration. Until then, group cost views come from the warehouse, and its mapping of org codes must be maintained.
What a design review will ask
Is every company code a legal entity, and if not, why does it exist?
Which sales org and plant combinations cross company codes, and is intercompany billing set up for each?
How many customer sales-area records will migration create?
Which org-level fields will roles use, and does every agent run with the user's roles?
How are codes kept consistent between S/4HANA, ECC and the warehouse during the hybrid period?
Security and authorizations. Org units are the coarse filter in almost every role. Design roles alongside the structure, test them with users from each company code, and treat any technical user without org restrictions as high risk. For agents, see Agent permissions and SAP authorizations.
Evaluation. For an AI assistant, add test cases that ask for another entity's data. The right answer is "I can't see that", and your eval set should check for it.
Cost. Every org unit adds master data, test cases and role variants. Ask for the business reason before adding one.
Operations. Changes to org units are transports with wide impact. Treat them like data model changes: impact analysis, regression tests, and sign-off from finance and the authorization team.
Clean core. Read org units through released views and APIs. Don't copy them into custom tables that drift from the system.
Designing the structure around today's organization chart. Teams reorganize more often than legal entities change. Use org units for legal and logistics facts; use reporting dimensions and roles for teams.
Forgetting the delivering plant assignment. Orders fail at delivery, not at entry, so the gap shows up late in testing.
Same code, different meaning across systems.FG01 may be finished goods in one plant and a quality hold in another. Document meanings, not just codes.
Testing only the main path. Test every allowed sales org and plant combination, especially intercompany ones.
Letting an agent use a technical user. It erases the org-level boundaries this topic is about.
Write Calder's org structure rationale, the deliverable a solution architect brings to a design review.
Run python unit1b/org_structure.py and confirm it prints 0 error(s).
Create unit1b/calder_org_rationale.md in VS Code.
Copy the Mermaid block from unit1b/calder_org_structure.md into it, under a heading Structure.
Under a heading Decisions, add a table with columns Decision, Choice, Why, Alternative rejected. Fill in one row each for: number of company codes, controlling area, Rockford distribution center (plant or storage location), divisions, distribution channels, purchasing organizations.
Run python unit1b/org_structure.py --what-if 2 3 6 and add one sentence on why Calder's design keeps sales areas few. Label the number as synthetic.
Run python unit1b/org_structure.py --scope sales_org=CPUS and add a short Access section: which documents of order 1000001 a US order-desk role would reach, and what that means for an AI assistant on that desk.
Add one line on the ECC difference: what changes for CP20 when it moves to S/4HANA.
Preview the file to check the diagram renders, then commit it with Git.
Done when:unit1b/calder_org_rationale.md shows the rendered diagram, a decisions table with six rows, a labeled synthetic sales-area figure, an access section based on your scope run, and one ECC line, and fits on one printed page. Keep it: the process topics in Unit 1B refer to these units, and the intercompany topic builds on your decisions.
Pick one answer for each question. The explanation appears after you choose.
1A new Calder sales org CPMX takes orders, but deliveries from plant CP11 fail. What is the most likely cause?
Answer: A. Delivering plants are maintained per sales org and distribution channel. Without that assignment, orders can't be delivered from the plant, and the gap often shows only at delivery.
2Why can storage location FG01 exist in both Rockford and Augsburg?
Answer: B. SAP Learning states the storage location key is unique within a plant. CP11/FG01 and CP21/FG01 are two different places, which is why the script reports it as a note, not an error.
3What must company codes share to sit in one controlling area?
Answer: D. SAP Learning lists the shared chart of accounts and fiscal year variant; currencies may differ. That is why Calder's USD and EUR companies can share CP00 once both are in one system.
4The script's scope run for plant CP11 blocks the sales order but shows the delivery. Why?
Answer: C. Each document carries its own org units, and access is checked against those. The model shows how one role can see logistics documents while prices and invoices stay hidden.
5Why does the topic recommend reading org units through I_CompanyCode rather than table T001?
Answer: A. The Cloudification Repository lists T001 as not to be released, with I_CompanyCode as its successor. Released objects are the stable, clean-core way to read SAP data.
6A partner proposes 4 sales orgs, 4 channels and 5 divisions. What should you check first?
Answer: D. Up to 80 sales areas means customer sales data, pricing and tests for each combination customers use. The --what-if option sizes that before anyone builds it.
7What would you do if an AI agent for Calder's US order desk could read German sales orders?
Answer: D. A prompt instruction is not an access control. Running with the user's identity applies the org-level values in their roles, so German orders are blocked by SAP itself.
8Calder's German company stays on ECC for a year. What is true of controlling area CP00 in the pack?
Answer: B. A controlling area is configuration inside one system, so it can't span S/4HANA and ECC. The decision record makes CP00 the target and uses the data warehouse for group cost views meanwhile.
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.
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 companyThe whole stage practices this topic, on Company codes, Plants and Storage locations.
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
Identifying Organizational Units in SAP S/4HANA Sales (SAP Learning)— each sales organization uniquely assigned to one company code; distribution channels assigned to one or more sales orgs; sales area as one sales org, one channel, one division, used for sales authorizations; one shipping point per outbound delivery
Configuring Intercompany Sales (SAP Learning)— delivering plants maintained per sales org and distribution channel; sales org and delivering plant need not share a company code; each plant and sales org belongs to exactly one company code
Positioning Warehouse Management (SAP Learning)— LE-WM is not the target architecture in SAP S/4HANA, its compatibility-pack use right ended 31 December 2025; EWM (embedded or decentralized) and Stock Room Management are the targets
Cloudification Repository (SAP on GitHub)— objectReleaseInfoLatest.json of 21 October 2026; I_CompanyCode, I_ControllingArea, I_Plant, I_StorageLocation, I_SalesOrganization, I_DistributionChannel, I_Division, I_SalesArea, I_PurchasingOrganization, I_ShippingPoint released; tables T001, TVKO, T024E, TVST not to be released, with those views as successors; authorization objects F_BKPF_BUK, V_VBAK_VKO, M_BEST_EKO, M_BEST_WRK, M_MSEG_WWA released
@sap/cloud-sdk-vdm-company-code-service (npm, SAP Cloud SDK)— version 2.1.0; API_COMPANYCODE_SRV entity A_CompanyCode with ControllingArea, ChartOfAccounts, FiscalYearVariant, CreditControlArea; sibling package for API_SALESORGANIZATION_SRV with A_SalesOrganization (CompanyCode, IntercompanyBillingCustomer)