@reventlessdev/online-shop-hybrid-ordering 1.0.0-alpha.238 → 1.0.0-alpha.240

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 CHANGED
@@ -3,6 +3,27 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 1.0.0-alpha.240 (2026-09-17)
7
+
8
+ ### Features
9
+
10
+ * **examples:** refuse a delivery window that cannot describe a delivery ([4f36ec3](https://github.com/ReventlessDev/reventless-core/commit/4f36ec39b3b73910edce1ca2ad0108efb06aa9de))
11
+
12
+
13
+ # 1.0.0-alpha.239 (2026-09-16)
14
+
15
+ * feat(spec)!: infer the DCB partition key per slice, and drop @partitionTag where inference agrees ([6c10161](https://github.com/ReventlessDev/reventless-core/commit/6c101611d438beea4bc9175ae8ea0ea1f6f7493b))
16
+ ### Features
17
+
18
+ * **spec:** let a slice's chapter break a partition-key tie ([2b9ae77](https://github.com/ReventlessDev/reventless-core/commit/2b9ae77e98eb42177d2bdd4977bc62bb7c7911aa))
19
+
20
+ ### BREAKING CHANGES
21
+
22
+ * DcbTag.derivePartitionTag is removed; a boundary whose
23
+ partition cannot be inferred no longer deploys.
24
+
25
+
26
+
6
27
  # 1.0.0-alpha.238 (2026-09-11)
7
28
 
8
29
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/online-shop-hybrid-ordering",
3
- "version": "1.0.0-alpha.238",
3
+ "version": "1.0.0-alpha.240",
4
4
  "description": "Online Shop Hybrid example — Ordering plugin (Customer aggregate + Order/CatalogProduct DCB)",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -21,15 +21,15 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "sury": "11.0.0-rc.2",
24
- "@reventlessdev/online-shop-hybrid-catalog-spec": "1.0.0-alpha.131",
25
- "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.20",
26
- "@reventlessdev/reventless-gwt": "1.0.0-alpha.209",
27
- "@reventlessdev/reventless-infra": "3.0.0-alpha.163",
28
- "@reventlessdev/online-shop-hybrid-ordering-spec": "1.0.0-alpha.131",
29
- "@reventlessdev/reventless-local": "3.0.0-alpha.251",
30
- "@reventlessdev/reventless-spec": "3.0.0-alpha.135",
31
- "@reventlessdev/trait-address-geocoding": "1.0.0-alpha.11",
32
- "@reventlessdev/trait-notification": "1.0.0-alpha.11"
24
+ "@reventlessdev/online-shop-hybrid-catalog-spec": "1.0.0-alpha.132",
25
+ "@reventlessdev/online-shop-hybrid-ordering-spec": "1.0.0-alpha.132",
26
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.164",
27
+ "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.21",
28
+ "@reventlessdev/reventless-gwt": "1.0.0-alpha.210",
29
+ "@reventlessdev/reventless-local": "3.0.0-alpha.253",
30
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.136",
31
+ "@reventlessdev/trait-notification": "1.0.0-alpha.12",
32
+ "@reventlessdev/trait-address-geocoding": "1.0.0-alpha.12"
33
33
  },
34
34
  "devDependencies": {
35
35
  "rescript": "12.3.0",
@@ -55,7 +55,7 @@ type lineItem = {
55
55
  @schema
56
56
  type command =
57
57
  | PlaceOrder({
58
- @partitionTag orderId: string,
58
+ orderId: string,
59
59
  // customerId is payload, not a query key — @noDcbTag stops it auto-tagging.
60
60
  // It is also the order's owner: the resolver overwrites this with the
61
61
  // authenticated caller's id before the command is published, so what a
@@ -85,6 +85,11 @@ type error =
85
85
  // currency happened to come first. Naming the codes says which shelf entries
86
86
  // disagree, the way `ProductsNotAvailable` names the products.
87
87
  | MixedCurrencies({currencies: array<string>})
88
+ // A pickup is collected in store, so a delivery slot on one describes a
89
+ // delivery that will never happen.
90
+ | DeliveryWindowOnPickup
91
+ // The window ends before it starts. `reason` is `DateRange.validate`'s wording.
92
+ | InvalidDeliveryWindow({reason: string})
88
93
 
89
94
  // One priced line of a placed order, frozen at placement.
90
95
  //
@@ -103,7 +108,7 @@ type orderLine = {
103
108
  @schema
104
109
  type event =
105
110
  | OrderPlaced({
106
- @partitionTag orderId: string,
111
+ orderId: string,
107
112
  customerId: string,
108
113
  // Redundant against `lines`, and deliberately so. The extension point
109
114
  // decomposes this into one published `ItemOrdered` per product and
@@ -55,7 +55,7 @@ let lineItemSchema = Sury.$schema(s => ({
55
55
 
56
56
  let commandSchema = Sury.$schema(s => ({
57
57
  TAG: "PlaceOrder",
58
- orderId: s.m(DcbTag$Reventless.partition),
58
+ orderId: s.m(DcbTag$Reventless.string),
59
59
  customerId: s.m(Owner$Reventless.string),
60
60
  lineItems: s.m(Sury.array(lineItemSchema)),
61
61
  shippingMethod: s.m(shippingMethodSchema),
@@ -77,6 +77,11 @@ let errorSchema = Sury.union([
77
77
  Sury.$schema(s => ({
78
78
  TAG: "MixedCurrencies",
79
79
  currencies: s.m(Sury.array(Sury.string))
80
+ })),
81
+ Sury.literal("DeliveryWindowOnPickup"),
82
+ Sury.$schema(s => ({
83
+ TAG: "InvalidDeliveryWindow",
84
+ reason: s.m(Sury.string)
80
85
  }))
81
86
  ]);
82
87
 
@@ -90,7 +95,7 @@ let orderLineSchema = Sury.$schema(s => ({
90
95
 
91
96
  let eventSchema = Sury.$schema(s => ({
92
97
  TAG: "OrderPlaced",
93
- orderId: s.m(DcbTag$Reventless.partition),
98
+ orderId: s.m(DcbTag$Reventless.string),
94
99
  customerId: s.m(DcbTag$Reventless.string),
95
100
  productIds: s.m(Sury.array(DcbTag$Reventless.stringForKey("productId"))),
96
101
  lines: s.m(Sury.array(orderLineSchema)),
@@ -114,6 +114,19 @@ let priceLines = (state, lineItems: array<lineItem>): result<array<orderLine>, e
114
114
  }
115
115
  )
116
116
 
117
+ // The window is display data — nothing ships by it — but a window that cannot
118
+ // describe a delivery is refused rather than shown.
119
+ let deliveryWindowError = (~shippingMethod, ~deliveryWindow) =>
120
+ switch (shippingMethod, deliveryWindow) {
121
+ | (_, None) => None
122
+ | (Pickup, Some(_)) => Some(DeliveryWindowOnPickup)
123
+ | (_, Some(window)) =>
124
+ switch Reventless.DateRange.validate(window) {
125
+ | Ok(_) => None
126
+ | Error(reason) => Some(InvalidDeliveryWindow({reason: reason}))
127
+ }
128
+ }
129
+
117
130
  let decide = (state, command) =>
118
131
  switch command {
119
132
  | PlaceOrder({orderId, customerId, lineItems, shippingMethod, ?deliveryWindow}) =>
@@ -122,9 +135,13 @@ let decide = (state, command) =>
122
135
  } else if lineItems->Array.length == 0 {
123
136
  Error(OrderIsEmpty)
124
137
  } else {
125
- switch lineItems->Array.find(({quantity}) => quantity <= 0) {
126
- | Some({productId, quantity}) => Error(InvalidQuantity({productId, quantity}))
127
- | None =>
138
+ switch (
139
+ lineItems->Array.find(({quantity}) => quantity <= 0),
140
+ deliveryWindowError(~shippingMethod, ~deliveryWindow),
141
+ ) {
142
+ | (Some({productId, quantity}), _) => Error(InvalidQuantity({productId, quantity}))
143
+ | (None, Some(error)) => Error(error)
144
+ | (None, None) =>
128
145
  let merged = mergeLines(lineItems)
129
146
  let missing =
130
147
  merged->Array.filterMap(({productId}) =>
@@ -5,6 +5,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
5
  import * as Money$Reventless from "@reventlessdev/reventless-spec/src/semantic/Money.res.mjs";
6
6
  import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
7
7
  import * as Currency$Reventless from "@reventlessdev/reventless-spec/src/semantic/Currency.res.mjs";
8
+ import * as DateRange$Reventless from "@reventlessdev/reventless-spec/src/semantic/DateRange.res.mjs";
8
9
 
9
10
  let initialState_placedOrderIds = [];
10
11
 
@@ -175,6 +176,28 @@ function priceLines(state, lineItems) {
175
176
  });
176
177
  }
177
178
 
179
+ function deliveryWindowError(shippingMethod, deliveryWindow) {
180
+ if (deliveryWindow === undefined) {
181
+ return;
182
+ }
183
+ switch (shippingMethod) {
184
+ case "Standard" :
185
+ case "Express" :
186
+ break;
187
+ case "Pickup" :
188
+ return "DeliveryWindowOnPickup";
189
+ }
190
+ let reason = DateRange$Reventless.validate(deliveryWindow);
191
+ if (reason.TAG === "Ok") {
192
+ return;
193
+ } else {
194
+ return {
195
+ TAG: "InvalidDeliveryWindow",
196
+ reason: reason._0
197
+ };
198
+ }
199
+ }
200
+
178
201
  function decide(state, command) {
179
202
  let orderId = command.orderId;
180
203
  if (state.placedOrderIds.includes(orderId)) {
@@ -190,7 +213,10 @@ function decide(state, command) {
190
213
  _0: "OrderIsEmpty"
191
214
  };
192
215
  }
216
+ let deliveryWindow = command.deliveryWindow;
217
+ let shippingMethod = command.shippingMethod;
193
218
  let match = lineItems.find(param => param.quantity <= 0);
219
+ let match$1 = deliveryWindowError(shippingMethod, deliveryWindow);
194
220
  if (match !== undefined) {
195
221
  return {
196
222
  TAG: "Error",
@@ -201,6 +227,12 @@ function decide(state, command) {
201
227
  }
202
228
  };
203
229
  }
230
+ if (match$1 !== undefined) {
231
+ return {
232
+ TAG: "Error",
233
+ _0: match$1
234
+ };
235
+ }
204
236
  let merged = mergeLines(lineItems);
205
237
  let missing = Stdlib_Array.filterMap(merged, param => {
206
238
  let productId = param.productId;
@@ -224,14 +256,14 @@ function decide(state, command) {
224
256
  return failed;
225
257
  }
226
258
  let lines = failed._0;
227
- let match$1 = Money$Reventless.sum(lines.map(line => line.lineTotal));
228
- if (match$1 === undefined) {
259
+ let match$2 = Money$Reventless.sum(lines.map(line => line.lineTotal));
260
+ if (match$2 === undefined) {
229
261
  return {
230
262
  TAG: "Error",
231
263
  _0: "OrderIsEmpty"
232
264
  };
233
265
  }
234
- if (match$1.TAG !== "Ok") {
266
+ if (match$2.TAG !== "Ok") {
235
267
  return {
236
268
  TAG: "Error",
237
269
  _0: {
@@ -258,9 +290,9 @@ function decide(state, command) {
258
290
  customerId: command.customerId,
259
291
  productIds: lines.map(line => line.productId),
260
292
  lines: lines,
261
- total: match$1._0,
262
- shippingMethod: command.shippingMethod,
263
- deliveryWindow: command.deliveryWindow,
293
+ total: match$2._0,
294
+ shippingMethod: shippingMethod,
295
+ deliveryWindow: deliveryWindow,
264
296
  firstProductName: firstProductName,
265
297
  firstProductImage: firstProductImage
266
298
  }]
@@ -281,6 +313,7 @@ export {
281
313
  mergeLines,
282
314
  priceLine,
283
315
  priceLines,
316
+ deliveryWindowError,
284
317
  decide,
285
318
  moduleUrl,
286
319
  }
@@ -37,7 +37,7 @@ type error =
37
37
  | OrderAlreadyCancelled
38
38
 
39
39
  @schema
40
- type event = OrderShipped({@partitionTag orderId: string, customerId: string})
40
+ type event = OrderShipped({orderId: string, customerId: string})
41
41
 
42
42
  type lifecycleState = Orders.lifecycle
43
43
 
@@ -26,7 +26,7 @@ let errorSchema = Sury.union([
26
26
 
27
27
  let eventSchema = Sury.$schema(s => ({
28
28
  TAG: "OrderShipped",
29
- orderId: s.m(DcbTag$Reventless.partition),
29
+ orderId: s.m(DcbTag$Reventless.string),
30
30
  customerId: s.m(DcbTag$Reventless.string)
31
31
  }));
32
32
 
package/src/Plugin.res CHANGED
@@ -8,42 +8,49 @@ let dcbSliceSchemas: array<Reventless.DcbTag.sliceSchemas> = [
8
8
  commandSchema: CancelOrder.commandSchema->S.castToUnknown,
9
9
  consumedEventSchema: CancelOrder.consumedEventSchema->S.castToUnknown,
10
10
  eventSchema: CancelOrder.eventSchema->S.castToUnknown,
11
+ moduleUrl: CancelOrder.moduleUrl,
11
12
  },
12
13
  {
13
14
  name: EmailVerificationChallenges.name,
14
15
  commandSchema: EmailVerificationChallenges.commandSchema->S.castToUnknown,
15
16
  consumedEventSchema: EmailVerificationChallenges.consumedEventSchema->S.castToUnknown,
16
17
  eventSchema: EmailVerificationChallenges.eventSchema->S.castToUnknown,
18
+ moduleUrl: EmailVerificationChallenges.moduleUrl,
17
19
  },
18
20
  {
19
21
  name: NotificationPreferences.name,
20
22
  commandSchema: NotificationPreferences.commandSchema->S.castToUnknown,
21
23
  consumedEventSchema: NotificationPreferences.consumedEventSchema->S.castToUnknown,
22
24
  eventSchema: NotificationPreferences.eventSchema->S.castToUnknown,
25
+ moduleUrl: NotificationPreferences.moduleUrl,
23
26
  },
24
27
  {
25
28
  name: NotificationSourceClaims.name,
26
29
  commandSchema: NotificationSourceClaims.commandSchema->S.castToUnknown,
27
30
  consumedEventSchema: NotificationSourceClaims.consumedEventSchema->S.castToUnknown,
28
31
  eventSchema: NotificationSourceClaims.eventSchema->S.castToUnknown,
32
+ moduleUrl: NotificationSourceClaims.moduleUrl,
29
33
  },
30
34
  {
31
35
  name: PlaceOrder.name,
32
36
  commandSchema: PlaceOrder.commandSchema->S.castToUnknown,
33
37
  consumedEventSchema: PlaceOrder.consumedEventSchema->S.castToUnknown,
34
38
  eventSchema: PlaceOrder.eventSchema->S.castToUnknown,
39
+ moduleUrl: PlaceOrder.moduleUrl,
35
40
  },
36
41
  {
37
42
  name: ShipOrder.name,
38
43
  commandSchema: ShipOrder.commandSchema->S.castToUnknown,
39
44
  consumedEventSchema: ShipOrder.consumedEventSchema->S.castToUnknown,
40
45
  eventSchema: ShipOrder.eventSchema->S.castToUnknown,
46
+ moduleUrl: ShipOrder.moduleUrl,
41
47
  },
42
48
  {
43
49
  name: SyncCatalogProduct.name,
44
50
  commandSchema: SyncCatalogProduct.commandSchema->S.castToUnknown,
45
51
  consumedEventSchema: SyncCatalogProduct.consumedEventSchema->S.castToUnknown,
46
52
  eventSchema: SyncCatalogProduct.eventSchema->S.castToUnknown,
53
+ moduleUrl: SyncCatalogProduct.moduleUrl,
47
54
  },
48
55
  ]
49
56
 
@@ -52,43 +52,50 @@ let dcbSliceSchemas = [
52
52
  name: CancelOrder$OrderingPlugin.name,
53
53
  commandSchema: CancelOrder$OrderingPlugin.commandSchema,
54
54
  consumedEventSchema: CancelOrder$OrderingPlugin.consumedEventSchema,
55
- eventSchema: CancelOrder$OrderingPlugin.eventSchema
55
+ eventSchema: CancelOrder$OrderingPlugin.eventSchema,
56
+ moduleUrl: CancelOrder$OrderingPlugin.moduleUrl
56
57
  },
57
58
  {
58
59
  name: EmailVerificationChallenges$OrderingPlugin.name,
59
60
  commandSchema: EmailVerificationChallenges$OrderingPlugin.commandSchema,
60
61
  consumedEventSchema: EmailVerificationChallenges$OrderingPlugin.consumedEventSchema,
61
- eventSchema: EmailVerificationChallenges$OrderingPlugin.eventSchema
62
+ eventSchema: EmailVerificationChallenges$OrderingPlugin.eventSchema,
63
+ moduleUrl: EmailVerificationChallenges$OrderingPlugin.moduleUrl
62
64
  },
63
65
  {
64
66
  name: NotificationPreferences$OrderingPlugin.name,
65
67
  commandSchema: NotificationPreferences$OrderingPlugin.commandSchema,
66
68
  consumedEventSchema: NotificationPreferences$OrderingPlugin.consumedEventSchema,
67
- eventSchema: NotificationPreferences$OrderingPlugin.eventSchema
69
+ eventSchema: NotificationPreferences$OrderingPlugin.eventSchema,
70
+ moduleUrl: NotificationPreferences$OrderingPlugin.moduleUrl
68
71
  },
69
72
  {
70
73
  name: NotificationSourceClaims$OrderingPlugin.name,
71
74
  commandSchema: NotificationSourceClaims$OrderingPlugin.commandSchema,
72
75
  consumedEventSchema: NotificationSourceClaims$OrderingPlugin.consumedEventSchema,
73
- eventSchema: NotificationSourceClaims$OrderingPlugin.eventSchema
76
+ eventSchema: NotificationSourceClaims$OrderingPlugin.eventSchema,
77
+ moduleUrl: NotificationSourceClaims$OrderingPlugin.moduleUrl
74
78
  },
75
79
  {
76
80
  name: PlaceOrder$OrderingPlugin.name,
77
81
  commandSchema: PlaceOrder$OrderingPlugin.commandSchema,
78
82
  consumedEventSchema: PlaceOrder$OrderingPlugin.consumedEventSchema,
79
- eventSchema: PlaceOrder$OrderingPlugin.eventSchema
83
+ eventSchema: PlaceOrder$OrderingPlugin.eventSchema,
84
+ moduleUrl: PlaceOrder$OrderingPlugin.moduleUrl
80
85
  },
81
86
  {
82
87
  name: ShipOrder$OrderingPlugin.name,
83
88
  commandSchema: ShipOrder$OrderingPlugin.commandSchema,
84
89
  consumedEventSchema: ShipOrder$OrderingPlugin.consumedEventSchema,
85
- eventSchema: ShipOrder$OrderingPlugin.eventSchema
90
+ eventSchema: ShipOrder$OrderingPlugin.eventSchema,
91
+ moduleUrl: ShipOrder$OrderingPlugin.moduleUrl
86
92
  },
87
93
  {
88
94
  name: SyncCatalogProduct$OrderingPlugin.name,
89
95
  commandSchema: SyncCatalogProduct$OrderingPlugin.commandSchema,
90
96
  consumedEventSchema: SyncCatalogProduct$OrderingPlugin.consumedEventSchema,
91
- eventSchema: SyncCatalogProduct$OrderingPlugin.eventSchema
97
+ eventSchema: SyncCatalogProduct$OrderingPlugin.eventSchema,
98
+ moduleUrl: SyncCatalogProduct$OrderingPlugin.moduleUrl
92
99
  }
93
100
  ];
94
101
 
@@ -262,6 +262,52 @@ describe("PlaceOrder StateChangeSlice", () => {
262
262
  )
263
263
  })
264
264
 
265
+ test("a delivery window on a pickup order is refused", () => {
266
+ let window =
267
+ Reventless.DateRange.make(
268
+ ~start="2026-03-02T09:00:00Z",
269
+ ~end_="2026-03-02T11:00:00Z",
270
+ )->Result.getOrThrow
271
+ givenEvents([synced(~id="p1", ~name="Fathom Dock")])
272
+ ->whenCmd(
273
+ PlaceOrder({
274
+ orderId: "o1",
275
+ customerId: "c1",
276
+ lineItems: [line(~id="p1")],
277
+ shippingMethod: Pickup,
278
+ deliveryWindow: window,
279
+ }),
280
+ )
281
+ ->thenError(DeliveryWindowOnPickup)
282
+ })
283
+
284
+ // Built as a record rather than through `DateRange.make`, which would refuse
285
+ // it: a client sends JSON, and decoding does not apply the ordering rule.
286
+ test("a delivery window that ends before it starts is refused", () => {
287
+ let reversed = {
288
+ Reventless.DateRange.start: "2026-03-02T11:00:00Z",
289
+ end_: "2026-03-02T09:00:00Z",
290
+ }
291
+ givenEvents([synced(~id="p1", ~name="Fathom Dock")])
292
+ ->whenCmd(
293
+ PlaceOrder({
294
+ orderId: "o1",
295
+ customerId: "c1",
296
+ lineItems: [line(~id="p1")],
297
+ shippingMethod: Standard,
298
+ deliveryWindow: reversed,
299
+ }),
300
+ )
301
+ ->thenError(
302
+ InvalidDeliveryWindow({
303
+ reason: switch Reventless.DateRange.validate(reversed) {
304
+ | Error(reason) => reason
305
+ | Ok(_) => "expected the ordering rule to refuse this range"
306
+ },
307
+ }),
308
+ )
309
+ })
310
+
265
311
  test("partial product availability returns ProductsNotAvailable with missing list", () =>
266
312
  givenEvents([synced(~id="p1", ~name="Fathom Dock")])
267
313
  ->whenCmd(
@@ -336,6 +336,45 @@ describe("PlaceOrder StateChangeSlice", () => {
336
336
  firstProductName: "Fathom Dock"
337
337
  });
338
338
  });
339
+ test("a delivery window on a pickup order is refused", () => {
340
+ let window = Stdlib_Result.getOrThrow(DateRange$Reventless.make("2026-03-02T09:00:00Z", "2026-03-02T11:00:00Z"), undefined);
341
+ let qty = 1;
342
+ return thenError(whenCmd(givenEvents([synced("p1", "Fathom Dock", undefined)]), {
343
+ TAG: "PlaceOrder",
344
+ orderId: "o1",
345
+ customerId: "c1",
346
+ lineItems: [{
347
+ productId: "p1",
348
+ quantity: qty
349
+ }],
350
+ shippingMethod: "Pickup",
351
+ deliveryWindow: window
352
+ }), "DeliveryWindowOnPickup");
353
+ });
354
+ test("a delivery window that ends before it starts is refused", () => {
355
+ let reversed = {
356
+ start: "2026-03-02T11:00:00Z",
357
+ end: "2026-03-02T09:00:00Z"
358
+ };
359
+ let qty = 1;
360
+ let reason = DateRange$Reventless.validate(reversed);
361
+ let tmp;
362
+ tmp = reason.TAG === "Ok" ? "expected the ordering rule to refuse this range" : reason._0;
363
+ return thenError(whenCmd(givenEvents([synced("p1", "Fathom Dock", undefined)]), {
364
+ TAG: "PlaceOrder",
365
+ orderId: "o1",
366
+ customerId: "c1",
367
+ lineItems: [{
368
+ productId: "p1",
369
+ quantity: qty
370
+ }],
371
+ shippingMethod: "Standard",
372
+ deliveryWindow: reversed
373
+ }), {
374
+ TAG: "InvalidDeliveryWindow",
375
+ reason: tmp
376
+ });
377
+ });
339
378
  test("partial product availability returns ProductsNotAvailable with missing list", () => {
340
379
  let qty = 1;
341
380
  let qty$1 = 1;