punchout-simulator 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -100,7 +100,7 @@ Document-specific:
100
100
  |---|---|
101
101
  | **PunchOutSetupResponse** | `Status` present & `200`; valid `StartPage/URL` |
102
102
  | **PunchOutOrderMessage** (punchback) | `BuyerCookie` matches the session; header `Total`/`Money`+currency; each `ItemIn` has quantity, `SupplierPartID`, `UnitPrice`+currency, `Description`, `UnitOfMeasure`, `Classification`@domain; **single currency** (mixed-currency is an error unless the supplier allows it); **Total consistency** (sum of line totals) |
103
- | **OrderRequest** | `orderID`/`orderDate`/`Total`; `ShipTo`/`BillTo`; each `ItemOut`; **single currency** (as above); **attachment `cid:` resolution** (see below) |
103
+ | **OrderRequest** | `orderID`/`orderDate`/`Total`; `ShipTo`/`BillTo` present **and complete** (an `addressID` or a postal Street/City — an empty block is flagged); `Contact@role`; each `ItemOut`; **single currency** (as above); **attachment `cid:` resolution** (see below) |
104
104
  | **OrderResponse** | `Status` code present / not an error |
105
105
 
106
106
  > Note: full cXML DTD validation would require a native validating XML parser,
@@ -180,7 +180,7 @@ src/
180
180
 
181
181
  The config is normalized into five entities:
182
182
 
183
- - **Buyer** — a reusable party holding its own cXML identity (the `From` credential) and an optional **platform profile** reference (see below).
183
+ - **Buyer** — a reusable party holding its own cXML identity (the `From` credential), an optional **platform profile** reference (see below), and optional default **ShipTo / BillTo addresses** and an end-user **Contact** (see below).
184
184
  - **Supplier** — a reusable party holding its cXML identity (`To`) plus its **endpoints** (PunchOut URL, Order URL) and the **product lists** it serves as its Mode-B catalog. Endpoints are intrinsic to the supplier — defined once, not per relationship. An optional `allowMixedCurrency` flag relaxes the multi-currency check for this supplier (see below).
185
185
  - **Connection** — the edge pairing one Buyer with one Supplier. It holds only what is specific to that pair: which side the tool simulates (`mode`), the `sharedSecret`, an optional per-pair Sender identity override (defaults to the buyer's identity), `deploymentMode`, and `attachmentEncoding`.
186
186
  - **Product List** — a reusable, named set of catalog products. Assigned to one or more Suppliers; each supplier serves the **union** of its lists as its mock catalog. See below.
@@ -213,6 +213,16 @@ meaningless cross-currency sum) and validation raises a `mixed-currency` **error
213
213
  genuinely handles multi-currency orders can set **`allowMixedCurrency`**, which downgrades that to a
214
214
  non-blocking **warning** (the per-line `Money` currencies are always preserved either way).
215
215
 
216
+ ### Addresses & contact
217
+
218
+ A **Buyer** holds default **ShipTo** / **BillTo** addresses and an end-user **Contact** (name, email,
219
+ phone). They pre-fill the OrderRequest (which stays fully editable as cXML before sending) and, when the
220
+ buyer's profile enables it, are also carried in the **PunchOutSetupRequest** (Ariba-style). Each address
221
+ supports a postal block and/or an `addressID` (+ `addressIDDomain`) reference; the **profile's
222
+ `addressMode`** decides which is emitted (`full` / `id-only` / `both`) — matching how platforms differ
223
+ (Jaggaer/Workday send full postal, SAP is plant-code/`addressID`-centric, Ariba sends both and includes
224
+ ShipTo in setup). An emitted `ShipTo`/`BillTo` with neither an `addressID` nor a Street/City is flagged.
225
+
216
226
  ### Simulating different procurement platforms (Buyer profiles)
217
227
 
218
228
  Real procurement systems emit cXML differently. A **Profile** captures a platform's
@@ -227,6 +237,8 @@ Ariba, Coupa, Jaggaer, Oracle, SAP, or Workday:
227
237
  | `attachmentEncoding` | Default `Content-Transfer-Encoding` for OrderRequest attachments (`binary`/`base64`). |
228
238
  | `cartReturnTransport` | How the punchback is returned in Mode B: `cxml-urlencoded`, `cxml-base64`, or `raw`. |
229
239
  | `extrinsics` | `<Extrinsic>` templates injected into the setup/order documents (values may use `${buyerCookie}` / `${orderId}`). |
240
+ | `addressMode` | How `ShipTo`/`BillTo`/`Contact` are emitted: `full` (PostalAddress), `id-only` (just `addressID`), or `both`. |
241
+ | `shipToInSetup` / `contactInSetup` | Carry the buyer's `ShipTo` / `Contact` already in the **PunchOutSetupRequest** (Ariba-style), so the supplier can price/personalize per ship-to. |
230
242
 
231
243
  Built-in presets (Ariba, Coupa, Jaggaer, Oracle, SAP, Workday, Generic) ship out of the box
232
244
  and can be **loaded into the editor** as a starting point, then customized. A Profile holds
@@ -56,7 +56,10 @@ var PROFILE_PRESETS = [
56
56
  setupOperation: "create",
57
57
  attachmentEncoding: "binary",
58
58
  cartReturnTransport: "cxml-urlencoded",
59
- extrinsics: []
59
+ extrinsics: [],
60
+ addressMode: "full",
61
+ shipToInSetup: false,
62
+ contactInSetup: false
60
63
  },
61
64
  {
62
65
  id: "ariba",
@@ -68,7 +71,12 @@ var PROFILE_PRESETS = [
68
71
  setupOperation: "create",
69
72
  attachmentEncoding: "base64",
70
73
  cartReturnTransport: "cxml-urlencoded",
71
- extrinsics: [{ name: "User", value: "${buyerCookie}", scope: "setup" }]
74
+ extrinsics: [{ name: "User", value: "${buyerCookie}", scope: "setup" }],
75
+ // Ariba sends ShipTo (addressID + postal) already in the SetupRequest so the
76
+ // supplier can return ship-to-specific pricing/availability.
77
+ addressMode: "both",
78
+ shipToInSetup: true,
79
+ contactInSetup: false
72
80
  },
73
81
  {
74
82
  id: "coupa",
@@ -81,7 +89,10 @@ var PROFILE_PRESETS = [
81
89
  setupOperation: "create",
82
90
  attachmentEncoding: "base64",
83
91
  cartReturnTransport: "cxml-urlencoded",
84
- extrinsics: []
92
+ extrinsics: [],
93
+ addressMode: "both",
94
+ shipToInSetup: false,
95
+ contactInSetup: false
85
96
  },
86
97
  {
87
98
  id: "jaggaer",
@@ -93,7 +104,10 @@ var PROFILE_PRESETS = [
93
104
  setupOperation: "create",
94
105
  attachmentEncoding: "binary",
95
106
  cartReturnTransport: "cxml-urlencoded",
96
- extrinsics: []
107
+ extrinsics: [],
108
+ addressMode: "full",
109
+ shipToInSetup: false,
110
+ contactInSetup: false
97
111
  },
98
112
  {
99
113
  id: "oracle",
@@ -105,7 +119,10 @@ var PROFILE_PRESETS = [
105
119
  setupOperation: "create",
106
120
  attachmentEncoding: "binary",
107
121
  cartReturnTransport: "cxml-urlencoded",
108
- extrinsics: []
122
+ extrinsics: [],
123
+ addressMode: "full",
124
+ shipToInSetup: false,
125
+ contactInSetup: false
109
126
  },
110
127
  {
111
128
  id: "sap-srm",
@@ -120,7 +137,11 @@ var PROFILE_PRESETS = [
120
137
  setupOperation: "create",
121
138
  attachmentEncoding: "base64",
122
139
  cartReturnTransport: "cxml-base64",
123
- extrinsics: []
140
+ extrinsics: [],
141
+ // SAP is location/plant-code centric — addresses by reference.
142
+ addressMode: "id-only",
143
+ shipToInSetup: false,
144
+ contactInSetup: false
124
145
  },
125
146
  {
126
147
  id: "workday",
@@ -132,7 +153,10 @@ var PROFILE_PRESETS = [
132
153
  setupOperation: "create",
133
154
  attachmentEncoding: "base64",
134
155
  cartReturnTransport: "cxml-urlencoded",
135
- extrinsics: []
156
+ extrinsics: [],
157
+ addressMode: "full",
158
+ shipToInSetup: false,
159
+ contactInSetup: false
136
160
  }
137
161
  ];
138
162
  var GENERIC_PROFILE = {
@@ -239,6 +263,7 @@ async function initConfig() {
239
263
  db.data.profiles ||= [];
240
264
  db.data.productLists ||= [];
241
265
  seedBuiltinProfiles(db.data, now());
266
+ migrateProfiles(db.data);
242
267
  seedBuiltinProductLists(db.data, now());
243
268
  migrateLegacy(db.data);
244
269
  migrateInlineCatalogs(db.data);
@@ -397,7 +422,10 @@ function effectiveProfile(connection, buyer) {
397
422
  // Connection attachmentEncoding is concrete by construction → explicit override.
398
423
  attachmentEncoding: connection.attachmentEncoding ?? p.attachmentEncoding,
399
424
  cartReturnTransport: p.cartReturnTransport,
400
- extrinsics: p.extrinsics
425
+ extrinsics: p.extrinsics,
426
+ addressMode: p.addressMode,
427
+ shipToInSetup: p.shipToInSetup,
428
+ contactInSetup: p.contactInSetup
401
429
  };
402
430
  }
403
431
  function dtdVersionFor(eff, docType) {
@@ -517,6 +545,15 @@ function migrateInlineCatalogs(data) {
517
545
  delete supplier.catalog;
518
546
  }
519
547
  }
548
+ function migrateProfiles(data) {
549
+ for (const p of data.profiles) {
550
+ if (p.addressMode != null && p.shipToInSetup != null && p.contactInSetup != null) continue;
551
+ const preset = PROFILE_PRESETS.find((x) => x.id === p.id);
552
+ p.addressMode ??= preset?.addressMode ?? "full";
553
+ p.shipToInSetup ??= preset?.shipToInSetup ?? false;
554
+ p.contactInSetup ??= preset?.contactInSetup ?? false;
555
+ }
556
+ }
520
557
  function migrateClassifications(data) {
521
558
  for (const list of data.productLists) {
522
559
  for (const item of list.items) {
@@ -601,12 +638,40 @@ var cred = (c) => ({
601
638
  domain: String(c?.domain ?? ""),
602
639
  identity: String(c?.identity ?? "")
603
640
  });
641
+ var str = (v) => v != null && String(v) !== "" ? String(v) : void 0;
642
+ function address(a) {
643
+ if (a == null || typeof a !== "object") return void 0;
644
+ const out = {
645
+ addressId: str(a.addressId),
646
+ addressIdDomain: str(a.addressIdDomain),
647
+ name: str(a.name),
648
+ deliverTo: str(a.deliverTo),
649
+ street: str(a.street),
650
+ city: str(a.city),
651
+ state: str(a.state),
652
+ postalCode: str(a.postalCode),
653
+ countryIsoCode: str(a.countryIsoCode),
654
+ countryName: str(a.countryName),
655
+ email: str(a.email),
656
+ phone: str(a.phone)
657
+ };
658
+ return Object.values(out).some((v) => v != null) ? out : void 0;
659
+ }
660
+ function contact(c) {
661
+ const a = address(c);
662
+ const role = str(c?.role);
663
+ if (!a && !role) return void 0;
664
+ return { ...a ?? {}, role: role ?? "endUser" };
665
+ }
604
666
  var buyersRoute = new Hono2();
605
667
  function normalizeBuyer(body) {
606
668
  return {
607
669
  name: String(body?.name ?? "Untitled buyer"),
608
670
  identity: cred(body?.identity),
609
- profileId: body?.profileId ? String(body.profileId) : void 0
671
+ profileId: body?.profileId ? String(body.profileId) : void 0,
672
+ shipTo: address(body?.shipTo),
673
+ billTo: address(body?.billTo),
674
+ contact: contact(body?.contact)
610
675
  };
611
676
  }
612
677
  buyersRoute.get("/", (c) => c.json(listBuyers()));
@@ -700,6 +765,7 @@ function normalizeProfile(body) {
700
765
  const cartReturnTransport = ["cxml-urlencoded", "cxml-base64", "raw"].includes(
701
766
  body?.cartReturnTransport
702
767
  ) ? body.cartReturnTransport : "cxml-urlencoded";
768
+ const addressMode = ["id-only", "full", "both"].includes(body?.addressMode) ? body.addressMode : "full";
703
769
  return {
704
770
  name: String(body?.name ?? "Untitled profile"),
705
771
  platform: body?.platform ? String(body.platform) : void 0,
@@ -708,7 +774,10 @@ function normalizeProfile(body) {
708
774
  setupOperation,
709
775
  attachmentEncoding,
710
776
  cartReturnTransport,
711
- extrinsics: normalizeExtrinsics(body?.extrinsics)
777
+ extrinsics: normalizeExtrinsics(body?.extrinsics),
778
+ addressMode,
779
+ shipToInSetup: Boolean(body?.shipToInSetup),
780
+ contactInSetup: Boolean(body?.contactInSetup)
712
781
  // `builtin` is never set from the wire — only code-seeded presets carry it.
713
782
  };
714
783
  }
@@ -1163,6 +1232,11 @@ function extrinsicBlock(items, indent) {
1163
1232
  function buildSetupRequest(o) {
1164
1233
  const lang = o.lang ?? "en-US";
1165
1234
  const deployment = o.deploymentMode ? ` deploymentMode="${escapeXml(o.deploymentMode)}"` : "";
1235
+ const mode = o.addressMode ?? "full";
1236
+ const shipTo = o.shipTo ? `
1237
+ ${addressBlock("ShipTo", o.shipTo, mode)}` : "";
1238
+ const contact2 = o.contact ? `
1239
+ ${contactBlock(o.contact, mode, " ")}` : "";
1166
1240
  const inner = `${header({
1167
1241
  from: o.from,
1168
1242
  to: o.to,
@@ -1175,26 +1249,51 @@ function buildSetupRequest(o) {
1175
1249
  <BuyerCookie>${escapeXml(o.buyerCookie)}</BuyerCookie>
1176
1250
  <BrowserFormPost>
1177
1251
  <URL>${escapeXml(o.browserFormPostUrl)}</URL>
1178
- </BrowserFormPost>${extrinsicBlock(o.extrinsics, " ")}
1252
+ </BrowserFormPost>${extrinsicBlock(o.extrinsics, " ")}${shipTo}${contact2}
1179
1253
  </PunchOutSetupRequest>
1180
1254
  </Request>`;
1181
1255
  return envelope(o.payloadId, o.timestamp, lang, inner, o.dtdVersion);
1182
1256
  }
1183
- function addressBlock(tag, a) {
1257
+ var wantsId = (m) => m === "id-only" || m === "both";
1258
+ var wantsPostal = (m) => m === "full" || m === "both";
1259
+ var hasPostal = (a) => !!(a.deliverTo || a.street || a.city || a.state || a.postalCode);
1260
+ function idAttrs(a, mode) {
1261
+ if (!wantsId(mode) || !a.addressId) return "";
1262
+ return ` addressID="${escapeXml(a.addressId)}"${a.addressIdDomain ? ` addressIDDomain="${escapeXml(a.addressIdDomain)}"` : ""}`;
1263
+ }
1264
+ function postalBlock(a, indent) {
1265
+ return `${indent}<PostalAddress>
1266
+ ${a.deliverTo ? `${indent} <DeliverTo>${escapeXml(a.deliverTo)}</DeliverTo>
1267
+ ` : ""}${indent} <Street>${escapeXml(a.street ?? "")}</Street>
1268
+ ${indent} <City>${escapeXml(a.city ?? "")}</City>
1269
+ ${indent} <State>${escapeXml(a.state ?? "")}</State>
1270
+ ${indent} <PostalCode>${escapeXml(a.postalCode ?? "")}</PostalCode>
1271
+ ${indent} <Country isoCountryCode="${escapeXml(a.countryIsoCode ?? "US")}">${escapeXml(a.countryName ?? "United States")}</Country>
1272
+ ${indent}</PostalAddress>`;
1273
+ }
1274
+ function contactInfo(a, indent) {
1275
+ const email = a.email ? `
1276
+ ${indent}<Email>${escapeXml(a.email)}</Email>` : "";
1277
+ const phone = a.phone ? `
1278
+ ${indent}<Phone><TelephoneNumber><Number>${escapeXml(a.phone)}</Number></TelephoneNumber></Phone>` : "";
1279
+ return email + phone;
1280
+ }
1281
+ function addressBlock(tag, a, mode) {
1282
+ const postal = wantsPostal(mode) ? `
1283
+ ${postalBlock(a, " ")}` : "";
1184
1284
  return ` <${tag}>
1185
- <Address${a.addressId ? ` addressID="${escapeXml(a.addressId)}"` : ""}>
1186
- <Name xml:lang="en">${escapeXml(a.name ?? "")}</Name>
1187
- <PostalAddress>
1188
- ${a.deliverTo ? ` <DeliverTo>${escapeXml(a.deliverTo)}</DeliverTo>
1189
- ` : ""} <Street>${escapeXml(a.street ?? "")}</Street>
1190
- <City>${escapeXml(a.city ?? "")}</City>
1191
- <State>${escapeXml(a.state ?? "")}</State>
1192
- <PostalCode>${escapeXml(a.postalCode ?? "")}</PostalCode>
1193
- <Country isoCountryCode="${escapeXml(a.countryIsoCode ?? "US")}">${escapeXml(a.countryName ?? "United States")}</Country>
1194
- </PostalAddress>
1285
+ <Address${idAttrs(a, mode)}>
1286
+ <Name xml:lang="en">${escapeXml(a.name ?? "")}</Name>${postal}${contactInfo(a, " ")}
1195
1287
  </Address>
1196
1288
  </${tag}>`;
1197
1289
  }
1290
+ function contactBlock(c, mode, indent) {
1291
+ const postal = mode !== "id-only" && hasPostal(c) ? `
1292
+ ${postalBlock(c, indent + " ")}` : "";
1293
+ return `${indent}<Contact role="${escapeXml(c.role || "endUser")}"${idAttrs(c, mode)}>
1294
+ ${indent} <Name xml:lang="en">${escapeXml(c.name ?? "")}</Name>${postal}${contactInfo(c, indent + " ")}
1295
+ ${indent}</Contact>`;
1296
+ }
1198
1297
  function commentsWithAttachments(cids, indent) {
1199
1298
  if (cids.length === 0) return "";
1200
1299
  const atts = cids.map(
@@ -1234,6 +1333,9 @@ ${classificationBlock(it, " ")}${it.manufacturerPartId ? `
1234
1333
  </ItemDetail>
1235
1334
  </ItemOut>`;
1236
1335
  }).join("\n");
1336
+ const mode = o.addressMode ?? "full";
1337
+ const contact2 = o.contact ? `
1338
+ ${contactBlock(o.contact, mode, " ")}` : "";
1237
1339
  const inner = `${header({
1238
1340
  from: o.from,
1239
1341
  to: o.to,
@@ -1249,8 +1351,8 @@ ${classificationBlock(it, " ")}${it.manufacturerPartId ? `
1249
1351
  <Total>
1250
1352
  <Money currency="${escapeXml(o.currency)}">${escapeXml(o.total)}</Money>
1251
1353
  </Total>
1252
- ${addressBlock("ShipTo", o.shipTo ?? {})}
1253
- ${addressBlock("BillTo", o.billTo ?? {})}${commentsWithAttachments(orderLevelCids, " ")}${extrinsicBlock(
1354
+ ${addressBlock("ShipTo", o.shipTo ?? {}, mode)}
1355
+ ${addressBlock("BillTo", o.billTo ?? {}, mode)}${contact2}${commentsWithAttachments(orderLevelCids, " ")}${extrinsicBlock(
1254
1356
  o.extrinsics,
1255
1357
  " "
1256
1358
  )}
@@ -1747,6 +1849,19 @@ function checkSingleCurrency(lineCurrencies, totalCurrency, issues, path, allowM
1747
1849
  }
1748
1850
  return true;
1749
1851
  }
1852
+ function checkAddressComplete(addr, label, issues) {
1853
+ if (addr == null) return;
1854
+ const hasId = !!attr(addr, "addressID");
1855
+ const postal = addr.PostalAddress;
1856
+ const hasPostal2 = !!(text(postal?.Street) || text(postal?.City));
1857
+ if (!hasId && !hasPostal2) {
1858
+ issues.warn(
1859
+ `${label.toLowerCase()}-incomplete`,
1860
+ `${label}/Address has neither an addressID nor a Street/City \u2014 the address is empty`,
1861
+ `cXML/.../OrderRequestHeader/${label}/Address`
1862
+ );
1863
+ }
1864
+ }
1750
1865
  function checkOrderRequest(doc, ctx, issues) {
1751
1866
  const orderReq = root(doc)?.Request?.OrderRequest;
1752
1867
  if (!orderReq) {
@@ -1765,9 +1880,18 @@ function checkOrderRequest(doc, ctx, issues) {
1765
1880
  }
1766
1881
  if (!header2?.ShipTo) {
1767
1882
  issues.warn("missing-shipto", "OrderRequestHeader/ShipTo is missing", "cXML/.../OrderRequestHeader/ShipTo");
1883
+ } else {
1884
+ checkAddressComplete(header2.ShipTo.Address, "ShipTo", issues);
1768
1885
  }
1769
1886
  if (!header2?.BillTo) {
1770
1887
  issues.warn("missing-billto", "OrderRequestHeader/BillTo is missing", "cXML/.../OrderRequestHeader/BillTo");
1888
+ } else {
1889
+ checkAddressComplete(header2.BillTo.Address, "BillTo", issues);
1890
+ }
1891
+ for (const contact2 of asArray(header2?.Contact)) {
1892
+ if (!attr(contact2, "role")) {
1893
+ issues.warn("contact-missing-role", "OrderRequestHeader/Contact is missing @role", "cXML/.../OrderRequestHeader/Contact");
1894
+ }
1771
1895
  }
1772
1896
  const items = asArray(orderReq.ItemOut);
1773
1897
  if (items.length === 0) {
@@ -1898,7 +2022,17 @@ function buyerContext(r) {
1898
2022
  attachmentEncoding: eff.attachmentEncoding,
1899
2023
  eff,
1900
2024
  expected: { from, to, sender, sharedSecret: connection.sharedSecret },
1901
- allowMixedCurrency: supplier.allowMixedCurrency ?? false
2025
+ allowMixedCurrency: supplier.allowMixedCurrency ?? false,
2026
+ shipTo: buyer.shipTo,
2027
+ billTo: buyer.billTo,
2028
+ contact: buyer.contact
2029
+ };
2030
+ }
2031
+ function setupAddresses(ctx) {
2032
+ return {
2033
+ shipTo: ctx.eff.shipToInSetup ? ctx.shipTo : void 0,
2034
+ contact: ctx.eff.contactInSetup ? ctx.contact : void 0,
2035
+ addressMode: ctx.eff.addressMode
1902
2036
  };
1903
2037
  }
1904
2038
  function setupExtrinsics(ctx, buyerCookie) {
@@ -1931,7 +2065,8 @@ flowRoute.get("/:id/setup/preview", (c) => {
1931
2065
  operation: ctx.eff.setupOperation,
1932
2066
  dtdVersion: dtdVersionFor(ctx.eff, "SetupRequest"),
1933
2067
  userAgent: ctx.eff.userAgent,
1934
- extrinsics: setupExtrinsics(ctx, buyerCookie)
2068
+ extrinsics: setupExtrinsics(ctx, buyerCookie),
2069
+ ...setupAddresses(ctx)
1935
2070
  });
1936
2071
  return c.json({ buyerCookie, xml, browserFormPostUrl: browserFormPostUrl() });
1937
2072
  });
@@ -1954,7 +2089,8 @@ flowRoute.post("/:id/setup", async (c) => {
1954
2089
  operation: ctx.eff.setupOperation,
1955
2090
  dtdVersion: dtdVersionFor(ctx.eff, "SetupRequest"),
1956
2091
  userAgent: ctx.eff.userAgent,
1957
- extrinsics: setupExtrinsics(ctx, buyerCookie)
2092
+ extrinsics: setupExtrinsics(ctx, buyerCookie),
2093
+ ...setupAddresses(ctx)
1958
2094
  });
1959
2095
  rememberSessionConnection(buyerCookie, ctx.connectionId);
1960
2096
  const reqValidation = validateDocument(xml, { expected: ctx.expected, forceDocType: "SetupRequest" });
@@ -2016,8 +2152,12 @@ function buildOrderXml(ctx, body) {
2016
2152
  currency,
2017
2153
  total,
2018
2154
  items,
2019
- shipTo: body.shipTo,
2020
- billTo: body.billTo,
2155
+ // Order addresses default to the buyer's configured defaults when the body
2156
+ // doesn't override them (the UI pre-fills from the buyer, editable per order).
2157
+ shipTo: body.shipTo ?? ctx.shipTo,
2158
+ billTo: body.billTo ?? ctx.billTo,
2159
+ contact: body.contact ?? ctx.contact,
2160
+ addressMode: ctx.eff.addressMode,
2021
2161
  attachments: attMeta,
2022
2162
  dtdVersion: dtdVersionFor(ctx.eff, "OrderRequest"),
2023
2163
  userAgent: ctx.eff.userAgent,
@@ -2627,7 +2767,33 @@ async function seedDemoIfEmpty() {
2627
2767
  identity: { domain: "DUNS", identity: "123456789" },
2628
2768
  // Exercise a non-default platform profile end-to-end (Coupa: per-doc-type
2629
2769
  // DTD versions, base64 attachments). Built-in profiles are seeded by initConfig.
2630
- profileId: "coupa"
2770
+ profileId: "coupa",
2771
+ // Default addresses + end-user contact, so the OrderRequest is populated and
2772
+ // the address feature is exercised out of the box.
2773
+ shipTo: {
2774
+ addressId: "1001",
2775
+ addressIdDomain: "buyerSystemID",
2776
+ name: "Demo Buyer HQ \u2014 Receiving",
2777
+ deliverTo: "Dock 3",
2778
+ street: "1 Market St",
2779
+ city: "San Francisco",
2780
+ state: "CA",
2781
+ postalCode: "94105",
2782
+ countryIsoCode: "US",
2783
+ countryName: "United States"
2784
+ },
2785
+ billTo: {
2786
+ addressId: "9001",
2787
+ addressIdDomain: "buyerSystemID",
2788
+ name: "Demo Buyer Accounts Payable",
2789
+ street: "1 Market St",
2790
+ city: "San Francisco",
2791
+ state: "CA",
2792
+ postalCode: "94105",
2793
+ countryIsoCode: "US",
2794
+ countryName: "United States"
2795
+ },
2796
+ contact: { role: "endUser", name: "Jane Buyer", email: "jane.buyer@demo.example", phone: "+1 555 0100" }
2631
2797
  });
2632
2798
  const supplier = await createSupplier({
2633
2799
  id: "demo-supplier",
@@ -1,4 +1,4 @@
1
- import{m as et}from"./index-BBqxMuv9.js";/*!-----------------------------------------------------------------------------
1
+ import{m as et}from"./index-DRD_fRQi.js";/*!-----------------------------------------------------------------------------
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
4
4
  * Released under the MIT license
@@ -1,4 +1,4 @@
1
- import{m as f}from"./index-BBqxMuv9.js";/*!-----------------------------------------------------------------------------
1
+ import{m as f}from"./index-DRD_fRQi.js";/*!-----------------------------------------------------------------------------
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
4
4
  * Released under the MIT license
@@ -1,4 +1,4 @@
1
- import{m as l}from"./index-BBqxMuv9.js";/*!-----------------------------------------------------------------------------
1
+ import{m as l}from"./index-DRD_fRQi.js";/*!-----------------------------------------------------------------------------
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
4
4
  * Released under the MIT license
@@ -1,4 +1,4 @@
1
- import{m as s}from"./index-BBqxMuv9.js";/*!-----------------------------------------------------------------------------
1
+ import{m as s}from"./index-DRD_fRQi.js";/*!-----------------------------------------------------------------------------
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
4
4
  * Released under the MIT license
@@ -1,4 +1,4 @@
1
- import{m as lt}from"./index-BBqxMuv9.js";/*!-----------------------------------------------------------------------------
1
+ import{m as lt}from"./index-DRD_fRQi.js";/*!-----------------------------------------------------------------------------
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
4
4
  * Released under the MIT license