@reventlessdev/online-shop-hybrid-seed 1.0.0-alpha.2
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/CHANGELOG.md +10 -0
- package/LICENSE +202 -0
- package/lib/bs/.compiler.log +2 -0
- package/lib/bs/OnlineShopHybridSeed.cmi +0 -0
- package/lib/bs/OnlineShopHybridSeed.cmj +0 -0
- package/lib/bs/OnlineShopHybridSeed.cmt +0 -0
- package/lib/bs/OnlineShopHybridSeed.mlmap +4 -0
- package/lib/bs/build.ninja +0 -0
- package/lib/bs/compiler-info.json +8 -0
- package/lib/bs/src/DemoCommands-OnlineShopHybridSeed.cmi +0 -0
- package/lib/bs/src/DemoCommands-OnlineShopHybridSeed.cmj +0 -0
- package/lib/bs/src/DemoCommands-OnlineShopHybridSeed.cmt +0 -0
- package/lib/bs/src/DemoCommands.ast +0 -0
- package/lib/bs/src/DemoCommands.res +154 -0
- package/lib/bs/src/DemoCommands.res.mjs +388 -0
- package/lib/bs/src/DemoData-OnlineShopHybridSeed.cmi +0 -0
- package/lib/bs/src/DemoData-OnlineShopHybridSeed.cmj +0 -0
- package/lib/bs/src/DemoData-OnlineShopHybridSeed.cmt +0 -0
- package/lib/bs/src/DemoData.ast +0 -0
- package/lib/bs/src/DemoData.res +307 -0
- package/lib/bs/src/DemoData.res.mjs +481 -0
- package/lib/bs/src/HybridSeedData-OnlineShopHybridSeed.cmi +0 -0
- package/lib/bs/src/HybridSeedData-OnlineShopHybridSeed.cmj +0 -0
- package/lib/bs/src/HybridSeedData-OnlineShopHybridSeed.cmt +0 -0
- package/lib/bs/src/HybridSeedData.ast +0 -0
- package/lib/bs/src/HybridSeedData.res +418 -0
- package/lib/bs/src/HybridSeedData.res.mjs +317 -0
- package/lib/ocaml/.compiler.log +2 -0
- package/lib/ocaml/DemoCommands-OnlineShopHybridSeed.cmi +0 -0
- package/lib/ocaml/DemoCommands-OnlineShopHybridSeed.cmj +0 -0
- package/lib/ocaml/DemoCommands-OnlineShopHybridSeed.cmt +0 -0
- package/lib/ocaml/DemoCommands.ast +0 -0
- package/lib/ocaml/DemoCommands.res +154 -0
- package/lib/ocaml/DemoData-OnlineShopHybridSeed.cmi +0 -0
- package/lib/ocaml/DemoData-OnlineShopHybridSeed.cmj +0 -0
- package/lib/ocaml/DemoData-OnlineShopHybridSeed.cmt +0 -0
- package/lib/ocaml/DemoData.ast +0 -0
- package/lib/ocaml/DemoData.res +307 -0
- package/lib/ocaml/HybridSeedData-OnlineShopHybridSeed.cmi +0 -0
- package/lib/ocaml/HybridSeedData-OnlineShopHybridSeed.cmj +0 -0
- package/lib/ocaml/HybridSeedData-OnlineShopHybridSeed.cmt +0 -0
- package/lib/ocaml/HybridSeedData.ast +0 -0
- package/lib/ocaml/HybridSeedData.res +418 -0
- package/lib/ocaml/OnlineShopHybridSeed.cmi +0 -0
- package/lib/ocaml/OnlineShopHybridSeed.cmj +0 -0
- package/lib/ocaml/OnlineShopHybridSeed.cmt +0 -0
- package/lib/ocaml/OnlineShopHybridSeed.mlmap +4 -0
- package/package.json +34 -0
- package/rescript.json +27 -0
- package/src/DemoCommands.res +154 -0
- package/src/DemoCommands.res.mjs +388 -0
- package/src/DemoData.res +307 -0
- package/src/DemoData.res.mjs +481 -0
- package/src/HybridSeedData.res +418 -0
- package/src/HybridSeedData.res.mjs +317 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
// The online-shop-hybrid demo data set(s), exported as `Seed.dataSet` values.
|
|
2
|
+
//
|
|
3
|
+
// This is the shareable artifact: `dataSets` carries everything domain-specific
|
|
4
|
+
// — the phases, the view verification, and the summary board — behind the
|
|
5
|
+
// generic `Seed.dataSet` shape, so any platform script (this example's
|
|
6
|
+
// `platform-local`/`platform-aws`, or a downstream consumer) seeds it with just
|
|
7
|
+
// `Seed.Runner.seed(~sets=HybridSeedData.dataSets, ~connect=…)`.
|
|
8
|
+
//
|
|
9
|
+
// Because every phase drives the public GraphQL command API, a run doubles as a
|
|
10
|
+
// smoke test: it exercises the DCB append path, both extension points, and the
|
|
11
|
+
// AutoShipOrder automation. `DemoData.res` holds *what* to seed; `DemoCommands.res`
|
|
12
|
+
// maps command values onto mutations; the transport and reporting come from
|
|
13
|
+
// `ReventlessSeed`.
|
|
14
|
+
|
|
15
|
+
open ReventlessSeed
|
|
16
|
+
|
|
17
|
+
// Every queryable view this example exposes. `Unfillable` is not a way to
|
|
18
|
+
// silence a gap — it records a view no volume of seed data can reach, with the
|
|
19
|
+
// reason, so a zero is never read as a seeding miss.
|
|
20
|
+
let views = [
|
|
21
|
+
Seed.Runner.Seeded("Catalog_Categories"),
|
|
22
|
+
Seed.Runner.Seeded("Catalog_Products"),
|
|
23
|
+
Seed.Runner.Seeded("Catalog_ProductDemands"),
|
|
24
|
+
Seed.Runner.Seeded("Catalog_ImportProductAudits"),
|
|
25
|
+
Seed.Runner.Seeded("Ordering_AvailableProducts"),
|
|
26
|
+
Seed.Runner.Seeded("Ordering_Orders"),
|
|
27
|
+
Seed.Runner.Seeded("Ordering_Customers"),
|
|
28
|
+
Seed.Runner.Seeded("Ordering_AutoShipOrderTodos"),
|
|
29
|
+
Seed.Runner.Seeded("Ordering_SendOrderConfirmationTodos"),
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
// ── Phases ──────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
let seedCategories = async (~client: Seed.Client.t) => {
|
|
35
|
+
await client->Seed.Client.sendAll(
|
|
36
|
+
DemoData.categories->Array.map(c =>
|
|
37
|
+
DemoCommands.addCategory(AddCategory({categoryId: c.id, name: c.name}))
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
Seed.Runner.report(`categories: ${(DemoData.categories->Array.length)->Int.toString} added`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Upload each product's deterministic placeholder SVG through the deployment's
|
|
44
|
+
// upload endpoint and swap `imageUrl` for the returned served `/{prefix}/{key}`
|
|
45
|
+
// ref, so the demo image travels the real upload → store → serve loop (local
|
|
46
|
+
// dev store or AWS bucket) exactly like a user upload — no external image URL.
|
|
47
|
+
let uploadProductImages = async (
|
|
48
|
+
products: array<DemoData.product>,
|
|
49
|
+
~client: Seed.Client.t,
|
|
50
|
+
~uploadEndpoint: string,
|
|
51
|
+
): array<DemoData.product> => {
|
|
52
|
+
let out = []
|
|
53
|
+
for i in 0 to products->Array.length - 1 {
|
|
54
|
+
switch products->Array.get(i) {
|
|
55
|
+
| Some(p) =>
|
|
56
|
+
let svg = DemoData.productSvg(~name=p.name, ~index=i)
|
|
57
|
+
switch await Seed.Upload.uploadAsset(
|
|
58
|
+
~uploadEndpoint,
|
|
59
|
+
~bytes=svg,
|
|
60
|
+
~fileName=`${p.id}.svg`,
|
|
61
|
+
~contentType="image/svg+xml",
|
|
62
|
+
~authToken=?Seed.Client.currentToken(client),
|
|
63
|
+
) {
|
|
64
|
+
| Ok(servedRef) => out->Array.push({...p, imageUrl: servedRef})
|
|
65
|
+
| Error(msg) => throw(Seed.Failed(`product image upload for ${p.id} failed: ${msg}`))
|
|
66
|
+
}
|
|
67
|
+
| None => ()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
Seed.Runner.report(
|
|
71
|
+
`product images: ${(out->Array.length)->Int.toString} uploaded to the served bucket`,
|
|
72
|
+
)
|
|
73
|
+
out
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let seedProducts = async (products: array<DemoData.product>, ~client: Seed.Client.t) => {
|
|
77
|
+
await client->Seed.Client.sendAll(
|
|
78
|
+
products->Array.map(p =>
|
|
79
|
+
DemoCommands.addProduct(
|
|
80
|
+
AddProduct({
|
|
81
|
+
productId: p.id,
|
|
82
|
+
name: p.name,
|
|
83
|
+
description: p.description,
|
|
84
|
+
price: p.price,
|
|
85
|
+
imageUrl: p.imageUrl,
|
|
86
|
+
categoryId: p.categoryId,
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
Seed.Runner.report(`products: ${(products->Array.length)->Int.toString} added`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let seedCatalogEdits = async (products: array<DemoData.product>, ~client: Seed.Client.t) => {
|
|
95
|
+
let repriced = DemoData.repricedProducts(products)
|
|
96
|
+
await client->Seed.Client.sendAll(
|
|
97
|
+
repriced->Array.map(p =>
|
|
98
|
+
DemoCommands.changeProductPrice(
|
|
99
|
+
ChangeProductPrice({productId: p.id, price: DemoData.discountedPrice(p)}),
|
|
100
|
+
)
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
let redescribed = DemoData.redescribedProducts(products)
|
|
104
|
+
await client->Seed.Client.sendAll(
|
|
105
|
+
redescribed->Array.map(p =>
|
|
106
|
+
DemoCommands.changeProductDescription(
|
|
107
|
+
ChangeProductDescription({
|
|
108
|
+
productId: p.id,
|
|
109
|
+
description: `${p.description} Updated listing copy.`,
|
|
110
|
+
}),
|
|
111
|
+
)
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
await client->Seed.Client.sendAll([
|
|
115
|
+
DemoCommands.renameCategory(
|
|
116
|
+
RenameCategory({
|
|
117
|
+
categoryId: DemoData.renamedCategoryId,
|
|
118
|
+
name: DemoData.renamedCategoryName,
|
|
119
|
+
}),
|
|
120
|
+
),
|
|
121
|
+
])
|
|
122
|
+
let archived = DemoData.categories->Array.filter(c => c.archive)
|
|
123
|
+
await client->Seed.Client.sendAll(
|
|
124
|
+
archived->Array.map(c => DemoCommands.archiveCategory(ArchiveCategory({categoryId: c.id}))),
|
|
125
|
+
)
|
|
126
|
+
Seed.Runner.report(
|
|
127
|
+
`catalog edits: ${(repriced->Array.length)->Int.toString} repriced, ${(redescribed
|
|
128
|
+
->Array.length)
|
|
129
|
+
->Int.toString} redescribed, 1 renamed, ${(archived->Array.length)
|
|
130
|
+
->Int.toString} archived`,
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let seedSupplierFeed = async (~client: Seed.Client.t) => {
|
|
135
|
+
for i in 0 to DemoData.supplierFeed->Array.length - 1 {
|
|
136
|
+
switch DemoData.supplierFeed->Array.get(i) {
|
|
137
|
+
| Some(row) =>
|
|
138
|
+
// One feed row is deliberately invalid, so a translation rejection is an
|
|
139
|
+
// expected outcome here rather than a seeding failure.
|
|
140
|
+
(
|
|
141
|
+
await client->Seed.Client.send(
|
|
142
|
+
DemoCommands.importProduct(row),
|
|
143
|
+
~tolerate=["TranslationFailed"],
|
|
144
|
+
)
|
|
145
|
+
)->ignore
|
|
146
|
+
| None => ()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Cross-check the per-request outcomes against the slice's audit view.
|
|
150
|
+
let audits = await client->Seed.Client.queryAllNodes(
|
|
151
|
+
~field="Catalog_ImportProductAudits",
|
|
152
|
+
~selection="status",
|
|
153
|
+
)
|
|
154
|
+
let countWith = status =>
|
|
155
|
+
audits->Array.filter(a => a->Seed.Client.nodeString("status") == Some(status))->Array.length
|
|
156
|
+
let successes = countWith("Success")
|
|
157
|
+
let failures = countWith("Failure")
|
|
158
|
+
if (
|
|
159
|
+
successes != DemoData.expectedImportSuccesses || failures != DemoData.expectedImportFailures
|
|
160
|
+
) {
|
|
161
|
+
throw(
|
|
162
|
+
Seed.Failed(
|
|
163
|
+
`supplier feed: expected ${DemoData.expectedImportSuccesses->Int.toString} Success / ${DemoData.expectedImportFailures->Int.toString} Failure audit rows, got ${successes->Int.toString}/${failures->Int.toString}`,
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
Seed.Runner.report(
|
|
168
|
+
`supplier feed: ${successes->Int.toString} imported, ${failures->Int.toString} rejected (both shown in the audit view)`,
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let seedCustomers = async (customers: array<DemoData.customer>, ~client: Seed.Client.t) => {
|
|
173
|
+
await client->Seed.Client.sendAll(
|
|
174
|
+
customers->Array.map(c =>
|
|
175
|
+
DemoCommands.customer(~id=c.id, Register({email: c.email, address: c.address}))
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
// Give every customer a map coordinate so the Customers map view renders a
|
|
179
|
+
// pin per row. The geo-point payload is a `{lat, lng}` input object.
|
|
180
|
+
await client->Seed.Client.sendAll(
|
|
181
|
+
customers->Array.map(c =>
|
|
182
|
+
DemoCommands.customer(~id=c.id, SetLocation({location: {lat: c.lat, lng: c.lng}}))
|
|
183
|
+
),
|
|
184
|
+
)
|
|
185
|
+
let moved = DemoData.movedCustomers(customers)
|
|
186
|
+
await client->Seed.Client.sendAll(
|
|
187
|
+
moved->Array.map(c =>
|
|
188
|
+
DemoCommands.customer(~id=c.id, UpdateAddress({address: DemoData.newAddress()}))
|
|
189
|
+
),
|
|
190
|
+
)
|
|
191
|
+
Seed.Runner.report(
|
|
192
|
+
`customers: ${(customers->Array.length)->Int.toString} registered, ${(moved->Array.length)
|
|
193
|
+
->Int.toString} moved`,
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let seedOrders = async (orders: array<DemoData.order>, ~client: Seed.Client.t) => {
|
|
198
|
+
for i in 0 to orders->Array.length - 1 {
|
|
199
|
+
switch orders->Array.get(i) {
|
|
200
|
+
| Some(order) =>
|
|
201
|
+
(await client->Seed.Client.send(
|
|
202
|
+
DemoCommands.placeOrder(
|
|
203
|
+
PlaceOrder({
|
|
204
|
+
orderId: order.id,
|
|
205
|
+
customerId: order.customerId,
|
|
206
|
+
productIds: order.productIds,
|
|
207
|
+
shippingMethod: order.shippingMethod,
|
|
208
|
+
}),
|
|
209
|
+
),
|
|
210
|
+
))->ignore
|
|
211
|
+
if mod(i + 1, 50) == 0 {
|
|
212
|
+
Seed.Runner.report(
|
|
213
|
+
`orders: ${(i + 1)->Int.toString}/${(orders->Array.length)->Int.toString} placed`,
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
| None => ()
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
let countMethod = (method: OrderingPlugin.PlaceOrder.shippingMethod) =>
|
|
220
|
+
orders->Array.filter(o => o.shippingMethod == method)->Array.length
|
|
221
|
+
Seed.Runner.report(
|
|
222
|
+
`orders: ${(orders->Array.length)->Int.toString} placed (Standard ${countMethod(
|
|
223
|
+
Standard,
|
|
224
|
+
)->Int.toString}, Express ${countMethod(Express)->Int.toString}, Pickup ${countMethod(
|
|
225
|
+
Pickup,
|
|
226
|
+
)->Int.toString}) — Express placements were auto-shipped on arrival`,
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let dispatchStandardBatch = async (orders: array<DemoData.order>, ~client: Seed.Client.t) => {
|
|
231
|
+
let dispatched = DemoData.batchDispatched(orders)
|
|
232
|
+
await client->Seed.Client.sendAll(
|
|
233
|
+
dispatched->Array.map(o => DemoCommands.shipOrder(ShipOrder({orderId: o.id}))),
|
|
234
|
+
)
|
|
235
|
+
let standardTotal = orders->Array.filter(o => o.shippingMethod == Standard)->Array.length
|
|
236
|
+
Seed.Runner.report(
|
|
237
|
+
`batch dispatch: ${(dispatched->Array.length)
|
|
238
|
+
->Int.toString}/${standardTotal->Int.toString} Standard orders shipped, ${(standardTotal -
|
|
239
|
+
dispatched->Array.length)->Int.toString} left pending`,
|
|
240
|
+
)
|
|
241
|
+
dispatched->Array.map(o => o.id)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let seedCancellations = async (
|
|
245
|
+
orders: array<DemoData.order>,
|
|
246
|
+
~client: Seed.Client.t,
|
|
247
|
+
~dispatched: array<string>,
|
|
248
|
+
) => {
|
|
249
|
+
let cancellable = DemoData.cancellable(orders, ~dispatched)
|
|
250
|
+
let targets = DemoData.cancelled(cancellable)
|
|
251
|
+
await client->Seed.Client.sendAll(
|
|
252
|
+
targets->Array.map(o => DemoCommands.cancelOrder(CancelOrder({orderId: o.id}))),
|
|
253
|
+
)
|
|
254
|
+
Seed.Runner.report(
|
|
255
|
+
`cancellations: ${(targets->Array.length)->Int.toString} of ${(cancellable->Array.length)
|
|
256
|
+
->Int.toString} cancellable orders cancelled`,
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let seedDeactivations = async (customers: array<DemoData.customer>, ~client: Seed.Client.t) => {
|
|
261
|
+
let targets = DemoData.deactivatedCustomers(customers)
|
|
262
|
+
await client->Seed.Client.sendAll(
|
|
263
|
+
targets->Array.map(c => DemoCommands.customer(~id=c.id, Deactivate)),
|
|
264
|
+
)
|
|
265
|
+
Seed.Runner.report(`customers: ${(targets->Array.length)->Int.toString} deactivated`)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ── Summary ─────────────────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
let summarise = async (~client: Seed.Client.t, ~counts: dict<int>) => {
|
|
271
|
+
let orders = await client->Seed.Client.queryAllNodes(
|
|
272
|
+
~field="Ordering_Orders",
|
|
273
|
+
~selection="status shippingMethod",
|
|
274
|
+
)
|
|
275
|
+
let statuses = ["Placed", "Shipped", "Cancelled"]
|
|
276
|
+
let methods = ["Standard", "Express", "Pickup"]
|
|
277
|
+
let countCell = (status, method) =>
|
|
278
|
+
orders
|
|
279
|
+
->Array.filter(o =>
|
|
280
|
+
o->Seed.Client.nodeString("status") == Some(status) &&
|
|
281
|
+
o->Seed.Client.nodeString("shippingMethod") == Some(method)
|
|
282
|
+
)
|
|
283
|
+
->Array.length
|
|
284
|
+
let countStatus = status =>
|
|
285
|
+
orders->Array.filter(o => o->Seed.Client.nodeString("status") == Some(status))->Array.length
|
|
286
|
+
|
|
287
|
+
let demand = await client->Seed.Client.queryAllNodes(
|
|
288
|
+
~field="Catalog_ProductDemands",
|
|
289
|
+
~selection="name orderCount",
|
|
290
|
+
)
|
|
291
|
+
let orderCountOf = node =>
|
|
292
|
+
switch node->Seed.Client.field("orderCount") {
|
|
293
|
+
| Some(Number(n)) => n->Int.fromFloat
|
|
294
|
+
| _ => 0
|
|
295
|
+
}
|
|
296
|
+
let ranked = demand->Array.toSorted((a, b) => Int.compare(orderCountOf(b), orderCountOf(a)))
|
|
297
|
+
let withDemand = ranked->Array.filter(n => orderCountOf(n) > 0)
|
|
298
|
+
let head =
|
|
299
|
+
ranked
|
|
300
|
+
->Array.slice(~start=0, ~end=3)
|
|
301
|
+
->Array.map(n =>
|
|
302
|
+
`${n->Seed.Client.nodeString("name")->Option.getOr("?")} (${orderCountOf(n)->Int.toString})`
|
|
303
|
+
)
|
|
304
|
+
->Array.join(", ")
|
|
305
|
+
|
|
306
|
+
Seed.Runner.heading("Seeded:")
|
|
307
|
+
Console.log(
|
|
308
|
+
` order status: ${statuses
|
|
309
|
+
->Array.map(s => `${s} ${countStatus(s)->Int.toString}`)
|
|
310
|
+
->Array.join(", ")}`,
|
|
311
|
+
)
|
|
312
|
+
Console.log(` demand head: ${head}`)
|
|
313
|
+
Console.log(
|
|
314
|
+
` demand spread: ${(withDemand->Array.length)->Int.toString}/${(ranked->Array.length)
|
|
315
|
+
->Int.toString} products ordered, tail min ${withDemand
|
|
316
|
+
->Array.last
|
|
317
|
+
->Option.mapOr(0, orderCountOf)
|
|
318
|
+
->Int.toString}`,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
Console.log("")
|
|
322
|
+
Console.log(" status × shippingMethod:")
|
|
323
|
+
Console.log(
|
|
324
|
+
` ${" "->String.padEnd(10, " ")}${methods
|
|
325
|
+
->Array.map(m => m->String.padStart(10, " "))
|
|
326
|
+
->Array.join("")}`,
|
|
327
|
+
)
|
|
328
|
+
statuses->Array.forEach(status =>
|
|
329
|
+
Console.log(
|
|
330
|
+
` ${status->String.padEnd(10, " ")}${methods
|
|
331
|
+
->Array.map(m => countCell(status, m)->Int.toString->String.padStart(10, " "))
|
|
332
|
+
->Array.join("")}`,
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
let observedStatuses = statuses->Array.filter(s => countStatus(s) > 0)
|
|
337
|
+
let spreadWarning = if observedStatuses->Array.length < 3 {
|
|
338
|
+
[
|
|
339
|
+
`the board shows only ${(observedStatuses->Array.length)
|
|
340
|
+
->Int.toString} of the 3 order statuses (${observedStatuses->Array.join(
|
|
341
|
+
", ",
|
|
342
|
+
)}) — the shipping-method mix or the batch-dispatch share needs adjusting.`,
|
|
343
|
+
]
|
|
344
|
+
} else {
|
|
345
|
+
[]
|
|
346
|
+
}
|
|
347
|
+
Seed.Runner.warn(Array.concat(spreadWarning, Seed.Runner.unfillableWarnings(~views, ~counts)))
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ── Run ─────────────────────────────────────────────────────────────────────
|
|
351
|
+
|
|
352
|
+
// The shared seed run, parameterized only by volume so every data set walks the
|
|
353
|
+
// same phases. Uploads no-op when the connection carries no upload endpoint
|
|
354
|
+
// (a deployment that serves none), keeping `imageUrl` empty rather than failing.
|
|
355
|
+
let run = async (
|
|
356
|
+
connection: Seed.connection,
|
|
357
|
+
~productCount: int,
|
|
358
|
+
~customerCount: int,
|
|
359
|
+
~orderCount: int,
|
|
360
|
+
): unit => {
|
|
361
|
+
let client = connection.client
|
|
362
|
+
|
|
363
|
+
let built = DemoData.buildProducts(~count=productCount, ())
|
|
364
|
+
let products = if connection.uploadEndpoint == "" {
|
|
365
|
+
Seed.Runner.report(
|
|
366
|
+
"product images: skipped (no upload endpoint / SEED_SKIP_UPLOADS) — imageUrl left empty",
|
|
367
|
+
)
|
|
368
|
+
built
|
|
369
|
+
} else {
|
|
370
|
+
await uploadProductImages(built, ~client, ~uploadEndpoint=connection.uploadEndpoint)
|
|
371
|
+
}
|
|
372
|
+
let customers = DemoData.buildCustomers(~count=customerCount, ())
|
|
373
|
+
let orders = DemoData.buildOrders(products, customers, ~count=orderCount, ())
|
|
374
|
+
|
|
375
|
+
await seedCategories(~client)
|
|
376
|
+
await seedProducts(products, ~client)
|
|
377
|
+
await seedCatalogEdits(products, ~client)
|
|
378
|
+
await seedSupplierFeed(~client)
|
|
379
|
+
await seedCustomers(customers, ~client)
|
|
380
|
+
|
|
381
|
+
// Products reach Ordering asynchronously through the Products extension
|
|
382
|
+
// point; PlaceOrder rejects until the shadow copy lands.
|
|
383
|
+
let expected = Array.concat(products->Array.map(p => p.id), DemoData.importedSkus)
|
|
384
|
+
let available = await client->Seed.Client.waitForIds(
|
|
385
|
+
~field="Ordering_AvailableProductsByIds",
|
|
386
|
+
~ids=expected,
|
|
387
|
+
)
|
|
388
|
+
Seed.Runner.report(`ordering: ${available->Int.toString} products available`)
|
|
389
|
+
|
|
390
|
+
await seedOrders(orders, ~client)
|
|
391
|
+
let dispatched = await dispatchStandardBatch(orders, ~client)
|
|
392
|
+
await seedCancellations(orders, ~client, ~dispatched)
|
|
393
|
+
await seedDeactivations(customers, ~client)
|
|
394
|
+
|
|
395
|
+
let counts = await Seed.Runner.verifyViews(client, ~views)
|
|
396
|
+
await summarise(~client, ~counts)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Two sets, so `Seed.Runner.seed` presents a selection: the full demo, and a
|
|
400
|
+
// compact sample for a quick end-to-end check. Both walk identical phases.
|
|
401
|
+
let dataSets: array<Seed.dataSet> = [
|
|
402
|
+
{
|
|
403
|
+
name: "full",
|
|
404
|
+
label: `full — ${DemoData.productCount->Int.toString} products, ${DemoData.customerCount->Int.toString} customers, ${DemoData.orderCount->Int.toString} orders`,
|
|
405
|
+
seed: connection =>
|
|
406
|
+
run(
|
|
407
|
+
connection,
|
|
408
|
+
~productCount=DemoData.productCount,
|
|
409
|
+
~customerCount=DemoData.customerCount,
|
|
410
|
+
~orderCount=DemoData.orderCount,
|
|
411
|
+
),
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
name: "sample",
|
|
415
|
+
label: "sample — 16 products, 8 customers, 40 orders",
|
|
416
|
+
seed: connection => run(connection, ~productCount=16, ~customerCount=8, ~orderCount=40),
|
|
417
|
+
},
|
|
418
|
+
]
|