@final-commerce/common 2.5.0-staging.1 → 2.5.1-preprod.1

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/dist/index.mjs CHANGED
@@ -237,6 +237,7 @@ var InventorySpecificActionType = /* @__PURE__ */ ((InventorySpecificActionType2
237
237
  InventorySpecificActionType2["PO_RECEIVED"] = "PO_RECEIVED";
238
238
  InventorySpecificActionType2["EXTERNAL_SALE"] = "EXTERNAL_SALE";
239
239
  InventorySpecificActionType2["EXTERNAL_RESTOCK"] = "EXTERNAL_RESTOCK";
240
+ InventorySpecificActionType2["CUSTOM"] = "CUSTOM";
240
241
  return InventorySpecificActionType2;
241
242
  })(InventorySpecificActionType || {});
242
243
  var ExtensionCategory = /* @__PURE__ */ ((ExtensionCategory2) => {
@@ -423,7 +424,6 @@ var ProductType = /* @__PURE__ */ ((ProductType2) => {
423
424
  ProductType2["SIMPLE"] = "simple";
424
425
  ProductType2["VARIABLE"] = "variable";
425
426
  ProductType2["BOOKING"] = "booking";
426
- ProductType2["COMPOSITE"] = "composite";
427
427
  return ProductType2;
428
428
  })(ProductType || {});
429
429
  var ProductStatus = /* @__PURE__ */ ((ProductStatus2) => {
@@ -1354,309 +1354,6 @@ function normalizeMongoIdString(value) {
1354
1354
  return null;
1355
1355
  }
1356
1356
 
1357
- // utils/composite-price.util.ts
1358
- var compositeItemCost = (item) => item.extraCharge * item.quantity;
1359
- function cheapestCompositeItem(items) {
1360
- let cheapest;
1361
- items.forEach((item, index) => {
1362
- if (!item.available) return;
1363
- if (cheapest === void 0 || compositeItemCost(item) < compositeItemCost(items[cheapest])) cheapest = index;
1364
- });
1365
- return cheapest;
1366
- }
1367
- function cheapestCompositePrice(parts) {
1368
- let total = 0;
1369
- for (const part of parts) {
1370
- if (part.min === 0) continue;
1371
- const index = cheapestCompositeItem(part.items) ?? cheapestCompositeItem(part.items.map((item) => ({ ...item, available: true })));
1372
- if (index === void 0) return void 0;
1373
- total += part.min * compositeItemCost(part.items[index]);
1374
- }
1375
- return total;
1376
- }
1377
-
1378
- // utils/modifier-resolution.util.ts
1379
- function ancestorChain(categoryId, categoriesById) {
1380
- const chain = [];
1381
- const seen = /* @__PURE__ */ new Set([categoryId]);
1382
- let current = categoriesById.get(categoryId)?.parentId ?? void 0;
1383
- while (current && !seen.has(current)) {
1384
- chain.push(current);
1385
- seen.add(current);
1386
- current = categoriesById.get(current)?.parentId ?? void 0;
1387
- }
1388
- return chain;
1389
- }
1390
- function clampRule(rule) {
1391
- if (rule.min != null && rule.max != null && rule.min > rule.max) {
1392
- return { ...rule, min: rule.max };
1393
- }
1394
- return rule;
1395
- }
1396
- function mergeRules(a, b) {
1397
- const required = a.required || b.required;
1398
- const min = a.min == null ? b.min : b.min == null ? a.min : Math.max(a.min, b.min);
1399
- const max = a.max == null ? b.max : b.max == null ? a.max : Math.min(a.max, b.max);
1400
- return clampRule({ required, min, max, sortOrder: Math.min(a.sortOrder, b.sortOrder) });
1401
- }
1402
- function toRule(row) {
1403
- const required = row.required ?? false;
1404
- return clampRule({
1405
- required,
1406
- // `required` is the gate: a stale min left on a placement that went optional is
1407
- // ignored. A cleared min/max stays the explicit null it arrived as — nothing
1408
- // downstream distinguishes null from absent.
1409
- min: required ? row.min : void 0,
1410
- max: row.max,
1411
- sortOrder: row.sortOrder ?? 0
1412
- });
1413
- }
1414
- function toResolvedChoices(modifier) {
1415
- return (modifier.choices ?? []).map((choice, index) => ({
1416
- _id: String(choice._id),
1417
- name: choice.name,
1418
- price: choice.price ?? 0,
1419
- isDefault: choice.isDefault,
1420
- defaultQuantity: choice.defaultQuantity,
1421
- unavailableOutletIds: choice.unavailableOutletIds?.map(String),
1422
- sortOrder: choice.sortOrder ?? index
1423
- })).sort((a, b) => a.sortOrder - b.sortOrder);
1424
- }
1425
- function resolveModifiersForProduct(inputs) {
1426
- const categoriesById = new Map(
1427
- inputs.categories.filter((category) => !category.isDeleted).map((category) => [String(category._id), category])
1428
- );
1429
- const modifiersById = new Map(
1430
- inputs.modifiers.filter((modifier) => !modifier.isDeleted).map((modifier) => [String(modifier._id), modifier])
1431
- );
1432
- const directCategories = new Set(inputs.categoryIds.map(String));
1433
- const inheritedCategories = /* @__PURE__ */ new Set();
1434
- for (const categoryId of directCategories) {
1435
- for (const ancestor of ancestorChain(categoryId, categoriesById)) {
1436
- if (!directCategories.has(ancestor)) inheritedCategories.add(ancestor);
1437
- }
1438
- }
1439
- const rules = /* @__PURE__ */ new Map();
1440
- for (const placement of inputs.categoryModifiers) {
1441
- if (placement.isDeleted) continue;
1442
- const categoryId = String(placement.categoryId);
1443
- const precedence = directCategories.has(categoryId) ? 0 : inheritedCategories.has(categoryId) ? 1 : -1;
1444
- if (precedence === -1) continue;
1445
- const modifierId = String(placement.modifierId);
1446
- if (!modifiersById.has(modifierId)) continue;
1447
- const incoming = toRule(placement);
1448
- const existing = rules.get(modifierId);
1449
- if (!existing || precedence < existing.precedence) {
1450
- rules.set(modifierId, { precedence, rule: incoming });
1451
- } else if (precedence === existing.precedence) {
1452
- rules.set(modifierId, { precedence, rule: mergeRules(existing.rule, incoming) });
1453
- }
1454
- }
1455
- const resolved = [];
1456
- for (const [modifierId, { rule }] of rules) {
1457
- const modifier = modifiersById.get(modifierId);
1458
- if (!modifier) continue;
1459
- const required = rule.required;
1460
- resolved.push({
1461
- _id: modifierId,
1462
- name: modifier.name,
1463
- selectionType: modifier.selectionType,
1464
- allowQuantity: modifier.allowQuantity,
1465
- required,
1466
- // single-selection is implicitly capped at 1 — min/max are never carried.
1467
- min: modifier.selectionType === "single" ? void 0 : rule.min,
1468
- max: modifier.selectionType === "single" ? void 0 : rule.max,
1469
- sortOrder: rule.sortOrder,
1470
- choices: toResolvedChoices(modifier)
1471
- });
1472
- }
1473
- return resolved.sort((a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name));
1474
- }
1475
- function isChoiceAvailableAtOutlet(choice, outletId) {
1476
- return !(choice.unavailableOutletIds ?? []).includes(outletId);
1477
- }
1478
- function hasSellableChoice(modifier, outletId) {
1479
- return modifier.choices.some((choice) => isChoiceAvailableAtOutlet(choice, outletId));
1480
- }
1481
- var modifiersSellable = (modifiers, outletId) => modifiers.every((modifier) => !modifier.required || hasSellableChoice(modifier, outletId));
1482
-
1483
- // utils/composite-availability.util.ts
1484
- function poolStockAt(rows, outletId) {
1485
- if (!rows.some((row) => row.manageStock === true)) return null;
1486
- return Math.max(0, rows.find((row) => String(row.outletId) === outletId)?.quantity ?? 0);
1487
- }
1488
- function compositeVariantInStock(variantId, quantity, outlet) {
1489
- const shelf = outlet.shelves.get(variantId);
1490
- if (!shelf || shelf.unresolved) return false;
1491
- const onHand = outlet.poolStock.get(shelf.pool);
1492
- if (onHand === void 0) return false;
1493
- if (onHand === null || shelf.backorder) return true;
1494
- return onHand >= (shelf.unit ? toBase(quantity, shelf.unit) : quantity);
1495
- }
1496
- function compositeItemUnavailable(item, rows) {
1497
- const { outlet } = rows;
1498
- const sellableHere = (productId2) => !outlet || !outlet.hiddenProductIds.has(productId2) && !outlet.unsellableProductIds.has(productId2);
1499
- if (item.categoryId) {
1500
- const variants = rows.categoryVariants.get(String(item.categoryId));
1501
- if (!variants) return "deleted";
1502
- const offered = variants.filter((variantId2) => sellableHere(rows.variantProducts.get(variantId2) ?? ""));
1503
- if (!offered.length) return "empty";
1504
- if (!outlet) return null;
1505
- return offered.some((variantId2) => compositeVariantInStock(variantId2, item.quantity, outlet)) ? null : "out_of_stock";
1506
- }
1507
- const variantId = String(item.variantId);
1508
- const productId = rows.variantProducts.get(variantId);
1509
- if (!productId || !rows.productStatuses.has(productId)) return "deleted";
1510
- if (rows.productStatuses.get(productId) !== "active" /* ACTIVE */) return "inactive";
1511
- if (!outlet) return null;
1512
- if (outlet.hiddenProductIds.has(productId)) return "hidden";
1513
- if (outlet.unsellableProductIds.has(productId)) return "empty";
1514
- if (rows.bookableProductIds.has(productId)) {
1515
- if (!outlet.bookableSeats) return "by_date";
1516
- return (outlet.bookableSeats.get(productId) ?? 0) >= item.quantity ? null : "fully_booked";
1517
- }
1518
- return compositeVariantInStock(variantId, item.quantity, outlet) ? null : "out_of_stock";
1519
- }
1520
- function compositeMakeable(parts, rows) {
1521
- const { outlet } = rows;
1522
- if (!outlet) throw new Error("compositeMakeable counts at one outlet: rows.outlet is required");
1523
- const required = parts.map((part, partIndex) => ({ ...part, partIndex })).filter((part) => part.min > 0);
1524
- const isBookable = (variantId) => rows.bookableProductIds.has(rows.variantProducts.get(String(variantId)) ?? "");
1525
- if (required.some((part) => part.items.some((item) => item.variantId && isBookable(item.variantId))))
1526
- return { byDate: true };
1527
- const optionsOf = (item) => (item.categoryId ? rows.categoryVariants.get(String(item.categoryId)) ?? [] : [String(item.variantId)]).flatMap(
1528
- (variantId) => {
1529
- const reason = compositeItemUnavailable({ variantId, quantity: item.quantity }, rows);
1530
- const shelf = outlet.shelves.get(variantId);
1531
- if (reason && reason !== "out_of_stock" || !shelf) return [];
1532
- const stock = outlet.poolStock.get(shelf.pool);
1533
- const onHand = shelf.unresolved || stock === void 0 ? 0 : stock === null || shelf.backorder ? null : stock;
1534
- const perPick = shelf.unit ? toBase(item.quantity, shelf.unit) : item.quantity;
1535
- return [{ itemId: item.id, pool: shelf.pool, perPick, onHand }];
1536
- }
1537
- );
1538
- const counted = required.map((part) => ({ ...part, options: part.items.flatMap(optionsOf) }));
1539
- const order = [...counted].sort((a, b) => Number(a.options.length !== 1) - Number(b.options.length !== 1));
1540
- const picksLeft = (option, reserved) => option.onHand === null ? Infinity : Math.max(0, Math.floor((option.onHand - (reserved.get(option.pool) ?? 0)) / option.perPick));
1541
- const shortAt = (n) => {
1542
- const reserved = /* @__PURE__ */ new Map();
1543
- for (const part of order) {
1544
- let wanted = part.min * n;
1545
- const ranked = [...part.options].sort((a, b) => picksLeft(b, reserved) - picksLeft(a, reserved));
1546
- for (const option of ranked) {
1547
- if (wanted <= 0) break;
1548
- const taken = Math.min(wanted, picksLeft(option, reserved));
1549
- if (option.onHand !== null)
1550
- reserved.set(option.pool, (reserved.get(option.pool) ?? 0) + taken * option.perPick);
1551
- wanted -= taken;
1552
- }
1553
- if (wanted > 0) return part.partIndex;
1554
- }
1555
- return void 0;
1556
- };
1557
- const whyRows = [];
1558
- const rowPools = /* @__PURE__ */ new Map();
1559
- const makes = (has, uses) => has === null ? null : Math.floor(has / uses);
1560
- for (const part of counted) {
1561
- const [option] = part.options;
1562
- if (part.options.length === 1) {
1563
- const uses = option.perPick * part.min;
1564
- const row = whyRows.find((candidate) => rowPools.get(candidate) === option.pool);
1565
- if (!row) {
1566
- const created = { itemIds: [option.itemId], uses, has: option.onHand, makes: makes(option.onHand, uses) };
1567
- rowPools.set(created, option.pool);
1568
- whyRows.push(created);
1569
- continue;
1570
- }
1571
- row.itemIds.push(option.itemId);
1572
- row.uses += uses;
1573
- if (option.onHand === null) row.has = null;
1574
- row.makes = makes(row.has, row.uses);
1575
- continue;
1576
- }
1577
- const perPool = /* @__PURE__ */ new Map();
1578
- for (const candidate of part.options)
1579
- perPool.set(candidate.pool, Math.max(perPool.get(candidate.pool) ?? 0, picksLeft(candidate, /* @__PURE__ */ new Map())));
1580
- const total = [...perPool.values()].reduce((sum, picks) => sum + picks, 0);
1581
- const has = total === Infinity ? null : total;
1582
- whyRows.push({
1583
- itemIds: part.items.map((item) => item.id),
1584
- partIndex: part.partIndex,
1585
- uses: part.min,
1586
- has,
1587
- makes: makes(has, part.min)
1588
- });
1589
- }
1590
- if (counted.every((part) => part.options.some((option) => option.onHand === null)))
1591
- return { count: null, rows: whyRows };
1592
- const ceiling = Math.min(
1593
- ...counted.filter((part) => !part.options.some((option) => option.onHand === null)).map(
1594
- (part) => Math.floor(part.options.reduce((sum, option) => sum + picksLeft(option, /* @__PURE__ */ new Map()), 0) / part.min)
1595
- )
1596
- );
1597
- let [low, high] = [0, ceiling];
1598
- while (low < high) {
1599
- const mid = Math.ceil((low + high) / 2);
1600
- if (shortAt(mid) === void 0) low = mid;
1601
- else high = mid - 1;
1602
- }
1603
- const short = shortAt(low + 1);
1604
- const shortPart = counted.find((part) => part.partIndex === short);
1605
- const limitedBy = whyRows.findIndex(
1606
- (row) => shortPart?.options.length === 1 ? row.partIndex === void 0 && row.itemIds.includes(shortPart.options[0].itemId) : row.partIndex === short
1607
- );
1608
- return { count: low, rows: whyRows, ...limitedBy >= 0 ? { limitedBy } : {} };
1609
- }
1610
- var poolOf = (line) => line.stockVariantId || line.variantId;
1611
- function poolDemand(lines) {
1612
- const demand = /* @__PURE__ */ new Map();
1613
- const add = (line, times) => demand.set(
1614
- poolOf(line),
1615
- (demand.get(poolOf(line)) ?? 0) + (line.unit ? toBase(line.quantity * times, line.unit) : line.quantity * times)
1616
- );
1617
- for (const line of lines) {
1618
- if (line.components) for (const component of line.components) add(component, line.quantity);
1619
- else add(line, 1);
1620
- }
1621
- return demand;
1622
- }
1623
- function poolShortfall(demand, poolStock, backordered = /* @__PURE__ */ new Set()) {
1624
- for (const [poolId, wanted] of demand) {
1625
- const available = poolStock.get(poolId);
1626
- if (available === null || available === void 0 || backordered.has(poolId)) continue;
1627
- if (wanted > available) return { poolId, available, wanted };
1628
- }
1629
- return null;
1630
- }
1631
-
1632
- // utils/composite-reservation.util.ts
1633
- function compositeModifierMismatches(lineItems) {
1634
- return lineItems.flatMap(
1635
- (line) => line.components?.length ? (line.modifiers ?? []).flatMap(
1636
- (modifier) => modifier.componentIndex !== void 0 && line.components[modifier.componentIndex] !== void 0 ? [] : [
1637
- `Modifier ${modifier.modifierName ?? "?"} on composite line ${line.internalId ?? "?"} names no component (componentIndex ${modifier.componentIndex}) \u2014 a composite has no modifiers of its own`
1638
- ]
1639
- ) : []
1640
- );
1641
- }
1642
- function compositeReservationMismatches(order) {
1643
- return (order.reservations ?? []).flatMap((reservation) => {
1644
- if (reservation.lineItemInternalId === void 0) return [];
1645
- const name = `Booking ${String(reservation.bookingId ?? "?")}`;
1646
- const line = order.lineItems?.find((candidate) => candidate.internalId === reservation.lineItemInternalId);
1647
- if (!line) return [`${name} names line ${reservation.lineItemInternalId}, which is not on the order`];
1648
- const component = reservation.componentIndex === void 0 ? void 0 : line.components?.[reservation.componentIndex];
1649
- if (!component) return [`${name} names no component ${reservation.componentIndex} of line ${line.internalId}`];
1650
- if (String(component.productId) !== String(reservation.productId))
1651
- return [
1652
- `${name} is for ${String(reservation.productId)}, but that component sold ${String(component.productId)}`
1653
- ];
1654
- if (reservation.price !== 0 || reservation.total !== 0)
1655
- return [`${name} carries money (${reservation.price}/${reservation.total}); its component already does`];
1656
- return [];
1657
- });
1658
- }
1659
-
1660
1357
  // units/family-code.ts
1661
1358
  var MAX_SLUG_LENGTH = 32;
1662
1359
  var FALLBACK_SLUG = "family";
@@ -3306,9 +3003,7 @@ function buildGrid(rule, timeZone, from, to, now, busy = []) {
3306
3003
  const start = startAt.getTime();
3307
3004
  if (start < askedFrom || start >= askedTo) return false;
3308
3005
  if (start < notBefore || start > notAfter) return false;
3309
- return !blocked.some(
3310
- ({ from: blockFrom, to: blockTo }) => start < blockTo && bufferEndAt.getTime() > blockFrom
3311
- );
3006
+ return !blocked.some(({ from: blockFrom, to: blockTo }) => start < blockTo && bufferEndAt.getTime() > blockFrom);
3312
3007
  }).sort((first, second) => +first.startAt - +second.startAt);
3313
3008
  }
3314
3009
  function slotsForWindow(grid, startAt, endAt) {
@@ -3427,14 +3122,7 @@ function buildAvailability({
3427
3122
  for (const slot of slots2) {
3428
3123
  const { taken, foreignProductId } = occupancyIn(mine, productId, slot.startAt, slot.bufferEndAt, shared);
3429
3124
  const free = foreignProductId ? 0 : Math.max(capacity - taken, 0);
3430
- const entry = byInstant.get(+slot.startAt) ?? {
3431
- ...slot,
3432
- capacity: 0,
3433
- booked: 0,
3434
- free: 0,
3435
- resources: [],
3436
- dayKey: shopDayKey(slot.startAt, resource.timeZone)
3437
- };
3125
+ const entry = byInstant.get(+slot.startAt) ?? { ...slot, capacity: 0, booked: 0, free: 0, resources: [], dayKey: shopDayKey(slot.startAt, resource.timeZone) };
3438
3126
  entry.capacity += capacity;
3439
3127
  entry.booked += foreignProductId ? capacity : taken;
3440
3128
  entry.free += free;
@@ -3592,7 +3280,6 @@ export {
3592
3280
  UserTypes,
3593
3281
  addAmounts,
3594
3282
  amountToString,
3595
- ancestorChain,
3596
3283
  assertUnitSane,
3597
3284
  availableIn,
3598
3285
  baseFor,
@@ -3606,14 +3293,6 @@ export {
3606
3293
  buildResolvedConfig,
3607
3294
  calculatePercentage,
3608
3295
  canTransition,
3609
- cheapestCompositeItem,
3610
- cheapestCompositePrice,
3611
- compositeItemCost,
3612
- compositeItemUnavailable,
3613
- compositeMakeable,
3614
- compositeModifierMismatches,
3615
- compositeReservationMismatches,
3616
- compositeVariantInStock,
3617
3296
  conditionSummary,
3618
3297
  convertFixedFeeToLocalCurrency,
3619
3298
  createAmount,
@@ -3648,11 +3327,9 @@ export {
3648
3327
  getPricingRegionFromCountry,
3649
3328
  getSettlementTotal,
3650
3329
  getTotalCollected,
3651
- hasSellableChoice,
3652
3330
  holdExpiresAt,
3653
3331
  inferStatesFromLegacyStatus,
3654
3332
  isAdyenRolloutCountry,
3655
- isChoiceAvailableAtOutlet,
3656
3333
  isConditionOperator,
3657
3334
  isFulfillmentState,
3658
3335
  isPaymentState,
@@ -3663,7 +3340,6 @@ export {
3663
3340
  mapToLegacyStatus,
3664
3341
  maxPrecisionFor,
3665
3342
  mergeFragment,
3666
- modifiersSellable,
3667
3343
  multiplyAmount,
3668
3344
  normalizeMongoIdString,
3669
3345
  occupancyIn,
@@ -3671,14 +3347,9 @@ export {
3671
3347
  parseCurrencyCode,
3672
3348
  parseStripeCurrencyCode,
3673
3349
  planWindow,
3674
- poolDemand,
3675
- poolOf,
3676
- poolShortfall,
3677
- poolStockAt,
3678
3350
  presentAt,
3679
3351
  removeFragment,
3680
3352
  resolveFamily,
3681
- resolveModifiersForProduct,
3682
3353
  resolveTemplate,
3683
3354
  resolveUnit,
3684
3355
  roundToPrecision,