@final-commerce/common 2.5.1-preprod.4 → 2.6.0-staging.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/dist/index.d.mts +185 -67
- package/dist/index.d.ts +185 -67
- package/dist/index.js +352 -86
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +335 -77
- package/dist/index.mjs.map +1 -1
- package/dist/pos-types/index.d.mts +183 -121
- package/dist/pos-types/index.d.ts +183 -121
- package/dist/pos-types/index.js +0 -1
- package/dist/pos-types/index.js.map +1 -1
- package/dist/pos-types/index.mjs +0 -1
- package/dist/pos-types/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20,11 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
var index_exports = {};
|
|
21
21
|
__export(index_exports, {
|
|
22
22
|
ADYEN_ROLLOUT_COUNTRY_CODES: () => ADYEN_ROLLOUT_COUNTRY_CODES,
|
|
23
|
-
AFFIRM_COUNTRY_CODES: () => AFFIRM_COUNTRY_CODES,
|
|
24
|
-
AFFIRM_PRICE_PLANS: () => AFFIRM_PRICE_PLANS,
|
|
25
23
|
AttributeType: () => AttributeType,
|
|
26
|
-
BNPL_METHOD_STATUS: () => BNPL_METHOD_STATUS,
|
|
27
|
-
BNPL_SKIP_REASON: () => BNPL_SKIP_REASON,
|
|
28
24
|
BookingRatePeriod: () => BookingRatePeriod,
|
|
29
25
|
BookingResourceKind: () => BookingResourceKind,
|
|
30
26
|
BookingResourceStatus: () => BookingResourceStatus,
|
|
@@ -102,6 +98,7 @@ __export(index_exports, {
|
|
|
102
98
|
UserTypes: () => UserTypes,
|
|
103
99
|
addAmounts: () => addAmounts,
|
|
104
100
|
amountToString: () => amountToString,
|
|
101
|
+
ancestorChain: () => ancestorChain,
|
|
105
102
|
assertUnitSane: () => assertUnitSane,
|
|
106
103
|
availableIn: () => availableIn,
|
|
107
104
|
baseFor: () => baseFor,
|
|
@@ -115,6 +112,14 @@ __export(index_exports, {
|
|
|
115
112
|
buildResolvedConfig: () => buildResolvedConfig,
|
|
116
113
|
calculatePercentage: () => calculatePercentage,
|
|
117
114
|
canTransition: () => canTransition,
|
|
115
|
+
cheapestCompositeItem: () => cheapestCompositeItem,
|
|
116
|
+
cheapestCompositePrice: () => cheapestCompositePrice,
|
|
117
|
+
compositeItemCost: () => compositeItemCost,
|
|
118
|
+
compositeItemUnavailable: () => compositeItemUnavailable,
|
|
119
|
+
compositeMakeable: () => compositeMakeable,
|
|
120
|
+
compositeModifierMismatches: () => compositeModifierMismatches,
|
|
121
|
+
compositeReservationMismatches: () => compositeReservationMismatches,
|
|
122
|
+
compositeVariantInStock: () => compositeVariantInStock,
|
|
118
123
|
conditionSummary: () => conditionSummary,
|
|
119
124
|
convertFixedFeeToLocalCurrency: () => convertFixedFeeToLocalCurrency,
|
|
120
125
|
createAmount: () => createAmount,
|
|
@@ -149,15 +154,13 @@ __export(index_exports, {
|
|
|
149
154
|
getPricingRegionFromCountry: () => getPricingRegionFromCountry,
|
|
150
155
|
getSettlementTotal: () => getSettlementTotal,
|
|
151
156
|
getTotalCollected: () => getTotalCollected,
|
|
152
|
-
|
|
157
|
+
hasSellableChoice: () => hasSellableChoice,
|
|
153
158
|
holdExpiresAt: () => holdExpiresAt,
|
|
154
159
|
inferStatesFromLegacyStatus: () => inferStatesFromLegacyStatus,
|
|
155
160
|
isAdyenRolloutCountry: () => isAdyenRolloutCountry,
|
|
156
|
-
|
|
157
|
-
isAffirmPricePlan: () => isAffirmPricePlan,
|
|
161
|
+
isChoiceAvailableAtOutlet: () => isChoiceAvailableAtOutlet,
|
|
158
162
|
isConditionOperator: () => isConditionOperator,
|
|
159
163
|
isFulfillmentState: () => isFulfillmentState,
|
|
160
|
-
isInCart: () => isInCart,
|
|
161
164
|
isPaymentState: () => isPaymentState,
|
|
162
165
|
isSupportedCurrency: () => isSupportedCurrency,
|
|
163
166
|
isSystemFamilyCode: () => isSystemFamilyCode,
|
|
@@ -166,6 +169,7 @@ __export(index_exports, {
|
|
|
166
169
|
mapToLegacyStatus: () => mapToLegacyStatus,
|
|
167
170
|
maxPrecisionFor: () => maxPrecisionFor,
|
|
168
171
|
mergeFragment: () => mergeFragment,
|
|
172
|
+
modifiersSellable: () => modifiersSellable,
|
|
169
173
|
multiplyAmount: () => multiplyAmount,
|
|
170
174
|
normalizeMongoIdString: () => normalizeMongoIdString,
|
|
171
175
|
occupancyIn: () => occupancyIn,
|
|
@@ -173,9 +177,14 @@ __export(index_exports, {
|
|
|
173
177
|
parseCurrencyCode: () => parseCurrencyCode,
|
|
174
178
|
parseStripeCurrencyCode: () => parseStripeCurrencyCode,
|
|
175
179
|
planWindow: () => planWindow,
|
|
180
|
+
poolDemand: () => poolDemand,
|
|
181
|
+
poolOf: () => poolOf,
|
|
182
|
+
poolShortfall: () => poolShortfall,
|
|
183
|
+
poolStockAt: () => poolStockAt,
|
|
176
184
|
presentAt: () => presentAt,
|
|
177
185
|
removeFragment: () => removeFragment,
|
|
178
186
|
resolveFamily: () => resolveFamily,
|
|
187
|
+
resolveModifiersForProduct: () => resolveModifiersForProduct,
|
|
179
188
|
resolveTemplate: () => resolveTemplate,
|
|
180
189
|
resolveUnit: () => resolveUnit,
|
|
181
190
|
roundToPrecision: () => roundToPrecision,
|
|
@@ -191,7 +200,6 @@ __export(index_exports, {
|
|
|
191
200
|
toMinorUnits: () => toMinorUnits,
|
|
192
201
|
validateConfig: () => validateConfig,
|
|
193
202
|
validateFragmentCompatibility: () => validateFragmentCompatibility,
|
|
194
|
-
validateOrderStatuses: () => validateOrderStatuses,
|
|
195
203
|
violatesFinancialInvariant: () => violatesFinancialInvariant,
|
|
196
204
|
windowEndsAt: () => windowEndsAt,
|
|
197
205
|
withinPresence: () => withinPresence
|
|
@@ -437,7 +445,6 @@ var InventorySpecificActionType = /* @__PURE__ */ ((InventorySpecificActionType2
|
|
|
437
445
|
InventorySpecificActionType2["PO_RECEIVED"] = "PO_RECEIVED";
|
|
438
446
|
InventorySpecificActionType2["EXTERNAL_SALE"] = "EXTERNAL_SALE";
|
|
439
447
|
InventorySpecificActionType2["EXTERNAL_RESTOCK"] = "EXTERNAL_RESTOCK";
|
|
440
|
-
InventorySpecificActionType2["CUSTOM"] = "CUSTOM";
|
|
441
448
|
return InventorySpecificActionType2;
|
|
442
449
|
})(InventorySpecificActionType || {});
|
|
443
450
|
var ExtensionCategory = /* @__PURE__ */ ((ExtensionCategory2) => {
|
|
@@ -624,6 +631,7 @@ var ProductType = /* @__PURE__ */ ((ProductType2) => {
|
|
|
624
631
|
ProductType2["SIMPLE"] = "simple";
|
|
625
632
|
ProductType2["VARIABLE"] = "variable";
|
|
626
633
|
ProductType2["BOOKING"] = "booking";
|
|
634
|
+
ProductType2["COMPOSITE"] = "composite";
|
|
627
635
|
return ProductType2;
|
|
628
636
|
})(ProductType || {});
|
|
629
637
|
var ProductStatus = /* @__PURE__ */ ((ProductStatus2) => {
|
|
@@ -1554,6 +1562,312 @@ function normalizeMongoIdString(value) {
|
|
|
1554
1562
|
return null;
|
|
1555
1563
|
}
|
|
1556
1564
|
|
|
1565
|
+
// utils/composite-price.util.ts
|
|
1566
|
+
var compositeItemCost = (item) => item.extraCharge * item.quantity;
|
|
1567
|
+
function cheapestCompositeItem(items) {
|
|
1568
|
+
let cheapest;
|
|
1569
|
+
items.forEach((item, index) => {
|
|
1570
|
+
if (!item.available) return;
|
|
1571
|
+
if (cheapest === void 0 || compositeItemCost(item) < compositeItemCost(items[cheapest])) cheapest = index;
|
|
1572
|
+
});
|
|
1573
|
+
return cheapest;
|
|
1574
|
+
}
|
|
1575
|
+
function cheapestCompositePrice(parts) {
|
|
1576
|
+
let total = 0;
|
|
1577
|
+
for (const part of parts) {
|
|
1578
|
+
if (part.min === 0) continue;
|
|
1579
|
+
const index = cheapestCompositeItem(part.items) ?? cheapestCompositeItem(part.items.map((item) => ({ ...item, available: true })));
|
|
1580
|
+
if (index === void 0) return void 0;
|
|
1581
|
+
total += part.min * compositeItemCost(part.items[index]);
|
|
1582
|
+
}
|
|
1583
|
+
return total;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// utils/modifier-resolution.util.ts
|
|
1587
|
+
function ancestorChain(categoryId, categoriesById) {
|
|
1588
|
+
const chain = [];
|
|
1589
|
+
const seen = /* @__PURE__ */ new Set([categoryId]);
|
|
1590
|
+
let current = categoriesById.get(categoryId)?.parentId ?? void 0;
|
|
1591
|
+
while (current && !seen.has(current)) {
|
|
1592
|
+
chain.push(current);
|
|
1593
|
+
seen.add(current);
|
|
1594
|
+
current = categoriesById.get(current)?.parentId ?? void 0;
|
|
1595
|
+
}
|
|
1596
|
+
return chain;
|
|
1597
|
+
}
|
|
1598
|
+
function clampRule(rule) {
|
|
1599
|
+
if (rule.min != null && rule.max != null && rule.min > rule.max) {
|
|
1600
|
+
return { ...rule, min: rule.max };
|
|
1601
|
+
}
|
|
1602
|
+
return rule;
|
|
1603
|
+
}
|
|
1604
|
+
function mergeRules(a, b) {
|
|
1605
|
+
const required = a.required || b.required;
|
|
1606
|
+
const min = a.min == null ? b.min : b.min == null ? a.min : Math.max(a.min, b.min);
|
|
1607
|
+
const max = a.max == null ? b.max : b.max == null ? a.max : Math.min(a.max, b.max);
|
|
1608
|
+
return clampRule({ required, min, max, sortOrder: Math.min(a.sortOrder, b.sortOrder) });
|
|
1609
|
+
}
|
|
1610
|
+
function toRule(row) {
|
|
1611
|
+
const required = row.required ?? false;
|
|
1612
|
+
return clampRule({
|
|
1613
|
+
required,
|
|
1614
|
+
// `required` is the gate: a stale min left on a placement that went optional is
|
|
1615
|
+
// ignored. A cleared min/max stays the explicit null it arrived as — nothing
|
|
1616
|
+
// downstream distinguishes null from absent.
|
|
1617
|
+
min: required ? row.min : void 0,
|
|
1618
|
+
max: row.max,
|
|
1619
|
+
sortOrder: row.sortOrder ?? 0
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
function toResolvedChoices(modifier) {
|
|
1623
|
+
return (modifier.choices ?? []).map((choice, index) => ({
|
|
1624
|
+
_id: String(choice._id),
|
|
1625
|
+
name: choice.name,
|
|
1626
|
+
price: choice.price ?? 0,
|
|
1627
|
+
isDefault: choice.isDefault,
|
|
1628
|
+
defaultQuantity: choice.defaultQuantity,
|
|
1629
|
+
unavailableOutletIds: choice.unavailableOutletIds?.map(String),
|
|
1630
|
+
sortOrder: choice.sortOrder ?? index
|
|
1631
|
+
})).sort((a, b) => a.sortOrder - b.sortOrder);
|
|
1632
|
+
}
|
|
1633
|
+
function resolveModifiersForProduct(inputs) {
|
|
1634
|
+
const categoriesById = new Map(
|
|
1635
|
+
inputs.categories.filter((category) => !category.isDeleted).map((category) => [String(category._id), category])
|
|
1636
|
+
);
|
|
1637
|
+
const modifiersById = new Map(
|
|
1638
|
+
inputs.modifiers.filter((modifier) => !modifier.isDeleted).map((modifier) => [String(modifier._id), modifier])
|
|
1639
|
+
);
|
|
1640
|
+
const directCategories = new Set(inputs.categoryIds.map(String));
|
|
1641
|
+
const inheritedCategories = /* @__PURE__ */ new Set();
|
|
1642
|
+
for (const categoryId of directCategories) {
|
|
1643
|
+
for (const ancestor of ancestorChain(categoryId, categoriesById)) {
|
|
1644
|
+
if (!directCategories.has(ancestor)) inheritedCategories.add(ancestor);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
const rules = /* @__PURE__ */ new Map();
|
|
1648
|
+
for (const placement of inputs.categoryModifiers) {
|
|
1649
|
+
if (placement.isDeleted) continue;
|
|
1650
|
+
const categoryId = String(placement.categoryId);
|
|
1651
|
+
const precedence = directCategories.has(categoryId) ? 0 : inheritedCategories.has(categoryId) ? 1 : -1;
|
|
1652
|
+
if (precedence === -1) continue;
|
|
1653
|
+
const modifierId = String(placement.modifierId);
|
|
1654
|
+
if (!modifiersById.has(modifierId)) continue;
|
|
1655
|
+
const incoming = toRule(placement);
|
|
1656
|
+
const existing = rules.get(modifierId);
|
|
1657
|
+
if (!existing || precedence < existing.precedence) {
|
|
1658
|
+
rules.set(modifierId, { precedence, rule: incoming });
|
|
1659
|
+
} else if (precedence === existing.precedence) {
|
|
1660
|
+
rules.set(modifierId, { precedence, rule: mergeRules(existing.rule, incoming) });
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
const resolved = [];
|
|
1664
|
+
for (const [modifierId, { rule }] of rules) {
|
|
1665
|
+
const modifier = modifiersById.get(modifierId);
|
|
1666
|
+
if (!modifier) continue;
|
|
1667
|
+
const required = rule.required;
|
|
1668
|
+
resolved.push({
|
|
1669
|
+
_id: modifierId,
|
|
1670
|
+
name: modifier.name,
|
|
1671
|
+
selectionType: modifier.selectionType,
|
|
1672
|
+
allowQuantity: modifier.allowQuantity,
|
|
1673
|
+
required,
|
|
1674
|
+
// single-selection is implicitly capped at 1 — min/max are never carried.
|
|
1675
|
+
min: modifier.selectionType === "single" ? void 0 : rule.min,
|
|
1676
|
+
max: modifier.selectionType === "single" ? void 0 : rule.max,
|
|
1677
|
+
sortOrder: rule.sortOrder,
|
|
1678
|
+
choices: toResolvedChoices(modifier)
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
return resolved.sort((a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name));
|
|
1682
|
+
}
|
|
1683
|
+
function isChoiceAvailableAtOutlet(choice, outletId) {
|
|
1684
|
+
return !(choice.unavailableOutletIds ?? []).includes(outletId);
|
|
1685
|
+
}
|
|
1686
|
+
function hasSellableChoice(modifier, outletId) {
|
|
1687
|
+
return modifier.choices.some((choice) => isChoiceAvailableAtOutlet(choice, outletId));
|
|
1688
|
+
}
|
|
1689
|
+
var modifiersSellable = (modifiers, outletId) => modifiers.every((modifier) => !modifier.required || hasSellableChoice(modifier, outletId));
|
|
1690
|
+
|
|
1691
|
+
// utils/composite-availability.util.ts
|
|
1692
|
+
function poolStockAt(rows, outletId) {
|
|
1693
|
+
if (!rows.some((row) => row.manageStock === true)) return null;
|
|
1694
|
+
return Math.max(0, rows.find((row) => String(row.outletId) === outletId)?.quantity ?? 0);
|
|
1695
|
+
}
|
|
1696
|
+
function compositeVariantInStock(variantId, quantity, outlet) {
|
|
1697
|
+
const shelf = outlet.shelves.get(variantId);
|
|
1698
|
+
if (!shelf || shelf.unresolved) return false;
|
|
1699
|
+
const onHand = outlet.poolStock.get(shelf.pool);
|
|
1700
|
+
if (onHand === void 0) return false;
|
|
1701
|
+
if (onHand === null || shelf.backorder) return true;
|
|
1702
|
+
return onHand >= (shelf.unit ? toBase(quantity, shelf.unit) : quantity);
|
|
1703
|
+
}
|
|
1704
|
+
function compositeItemUnavailable(item, rows) {
|
|
1705
|
+
const { outlet } = rows;
|
|
1706
|
+
const sellableHere = (productId2) => !outlet || !outlet.hiddenProductIds.has(productId2) && !outlet.unsellableProductIds.has(productId2);
|
|
1707
|
+
if (item.categoryId) {
|
|
1708
|
+
const variants = rows.categoryVariants.get(String(item.categoryId));
|
|
1709
|
+
if (!variants) return "deleted";
|
|
1710
|
+
const offered = variants.filter((variantId2) => sellableHere(rows.variantProducts.get(variantId2) ?? ""));
|
|
1711
|
+
if (!offered.length) return "empty";
|
|
1712
|
+
if (!outlet) return null;
|
|
1713
|
+
const unmarked = offered.filter((variantId2) => !outlet.markedVariantIds.has(variantId2));
|
|
1714
|
+
if (!unmarked.length) return "marked_sold_out";
|
|
1715
|
+
return unmarked.some((variantId2) => compositeVariantInStock(variantId2, item.quantity, outlet)) ? null : "out_of_stock";
|
|
1716
|
+
}
|
|
1717
|
+
const variantId = String(item.variantId);
|
|
1718
|
+
const productId = rows.variantProducts.get(variantId);
|
|
1719
|
+
if (!productId || !rows.productStatuses.has(productId)) return "deleted";
|
|
1720
|
+
if (rows.productStatuses.get(productId) !== "active" /* ACTIVE */) return "inactive";
|
|
1721
|
+
if (!outlet) return null;
|
|
1722
|
+
if (outlet.hiddenProductIds.has(productId)) return "hidden";
|
|
1723
|
+
if (outlet.unsellableProductIds.has(productId)) return "empty";
|
|
1724
|
+
if (outlet.markedVariantIds.has(variantId)) return "marked_sold_out";
|
|
1725
|
+
if (rows.bookableProductIds.has(productId)) {
|
|
1726
|
+
if (!outlet.bookableSeats) return "by_date";
|
|
1727
|
+
return (outlet.bookableSeats.get(productId) ?? 0) >= item.quantity ? null : "fully_booked";
|
|
1728
|
+
}
|
|
1729
|
+
return compositeVariantInStock(variantId, item.quantity, outlet) ? null : "out_of_stock";
|
|
1730
|
+
}
|
|
1731
|
+
function compositeMakeable(parts, rows) {
|
|
1732
|
+
const { outlet } = rows;
|
|
1733
|
+
if (!outlet) throw new Error("compositeMakeable counts at one outlet: rows.outlet is required");
|
|
1734
|
+
const required = parts.map((part, partIndex) => ({ ...part, partIndex })).filter((part) => part.min > 0);
|
|
1735
|
+
const isBookable = (variantId) => rows.bookableProductIds.has(rows.variantProducts.get(String(variantId)) ?? "");
|
|
1736
|
+
if (required.some((part) => part.items.some((item) => item.variantId && isBookable(item.variantId))))
|
|
1737
|
+
return { byDate: true };
|
|
1738
|
+
const optionsOf = (item) => (item.categoryId ? rows.categoryVariants.get(String(item.categoryId)) ?? [] : [String(item.variantId)]).flatMap(
|
|
1739
|
+
(variantId) => {
|
|
1740
|
+
const reason = compositeItemUnavailable({ variantId, quantity: item.quantity }, rows);
|
|
1741
|
+
const shelf = outlet.shelves.get(variantId);
|
|
1742
|
+
if (reason && reason !== "out_of_stock" || !shelf) return [];
|
|
1743
|
+
const stock = outlet.poolStock.get(shelf.pool);
|
|
1744
|
+
const onHand = shelf.unresolved || stock === void 0 ? 0 : stock === null || shelf.backorder ? null : stock;
|
|
1745
|
+
const perPick = shelf.unit ? toBase(item.quantity, shelf.unit) : item.quantity;
|
|
1746
|
+
return [{ itemId: item.id, pool: shelf.pool, perPick, onHand }];
|
|
1747
|
+
}
|
|
1748
|
+
);
|
|
1749
|
+
const counted = required.map((part) => ({ ...part, options: part.items.flatMap(optionsOf) }));
|
|
1750
|
+
const order = [...counted].sort((a, b) => Number(a.options.length !== 1) - Number(b.options.length !== 1));
|
|
1751
|
+
const picksLeft = (option, reserved) => option.onHand === null ? Infinity : Math.max(0, Math.floor((option.onHand - (reserved.get(option.pool) ?? 0)) / option.perPick));
|
|
1752
|
+
const shortAt = (n) => {
|
|
1753
|
+
const reserved = /* @__PURE__ */ new Map();
|
|
1754
|
+
for (const part of order) {
|
|
1755
|
+
let wanted = part.min * n;
|
|
1756
|
+
const ranked = [...part.options].sort((a, b) => picksLeft(b, reserved) - picksLeft(a, reserved));
|
|
1757
|
+
for (const option of ranked) {
|
|
1758
|
+
if (wanted <= 0) break;
|
|
1759
|
+
const taken = Math.min(wanted, picksLeft(option, reserved));
|
|
1760
|
+
if (option.onHand !== null)
|
|
1761
|
+
reserved.set(option.pool, (reserved.get(option.pool) ?? 0) + taken * option.perPick);
|
|
1762
|
+
wanted -= taken;
|
|
1763
|
+
}
|
|
1764
|
+
if (wanted > 0) return part.partIndex;
|
|
1765
|
+
}
|
|
1766
|
+
return void 0;
|
|
1767
|
+
};
|
|
1768
|
+
const whyRows = [];
|
|
1769
|
+
const rowPools = /* @__PURE__ */ new Map();
|
|
1770
|
+
const makes = (has, uses) => has === null ? null : Math.floor(has / uses);
|
|
1771
|
+
for (const part of counted) {
|
|
1772
|
+
const [option] = part.options;
|
|
1773
|
+
if (part.options.length === 1) {
|
|
1774
|
+
const uses = option.perPick * part.min;
|
|
1775
|
+
const row = whyRows.find((candidate) => rowPools.get(candidate) === option.pool);
|
|
1776
|
+
if (!row) {
|
|
1777
|
+
const created = { itemIds: [option.itemId], uses, has: option.onHand, makes: makes(option.onHand, uses) };
|
|
1778
|
+
rowPools.set(created, option.pool);
|
|
1779
|
+
whyRows.push(created);
|
|
1780
|
+
continue;
|
|
1781
|
+
}
|
|
1782
|
+
row.itemIds.push(option.itemId);
|
|
1783
|
+
row.uses += uses;
|
|
1784
|
+
if (option.onHand === null) row.has = null;
|
|
1785
|
+
row.makes = makes(row.has, row.uses);
|
|
1786
|
+
continue;
|
|
1787
|
+
}
|
|
1788
|
+
const perPool = /* @__PURE__ */ new Map();
|
|
1789
|
+
for (const candidate of part.options)
|
|
1790
|
+
perPool.set(candidate.pool, Math.max(perPool.get(candidate.pool) ?? 0, picksLeft(candidate, /* @__PURE__ */ new Map())));
|
|
1791
|
+
const total = [...perPool.values()].reduce((sum, picks) => sum + picks, 0);
|
|
1792
|
+
const has = total === Infinity ? null : total;
|
|
1793
|
+
whyRows.push({
|
|
1794
|
+
itemIds: part.items.map((item) => item.id),
|
|
1795
|
+
partIndex: part.partIndex,
|
|
1796
|
+
uses: part.min,
|
|
1797
|
+
has,
|
|
1798
|
+
makes: makes(has, part.min)
|
|
1799
|
+
});
|
|
1800
|
+
}
|
|
1801
|
+
if (counted.every((part) => part.options.some((option) => option.onHand === null)))
|
|
1802
|
+
return { count: null, rows: whyRows };
|
|
1803
|
+
const ceiling = Math.min(
|
|
1804
|
+
...counted.filter((part) => !part.options.some((option) => option.onHand === null)).map(
|
|
1805
|
+
(part) => Math.floor(part.options.reduce((sum, option) => sum + picksLeft(option, /* @__PURE__ */ new Map()), 0) / part.min)
|
|
1806
|
+
)
|
|
1807
|
+
);
|
|
1808
|
+
let [low, high] = [0, ceiling];
|
|
1809
|
+
while (low < high) {
|
|
1810
|
+
const mid = Math.ceil((low + high) / 2);
|
|
1811
|
+
if (shortAt(mid) === void 0) low = mid;
|
|
1812
|
+
else high = mid - 1;
|
|
1813
|
+
}
|
|
1814
|
+
const short = shortAt(low + 1);
|
|
1815
|
+
const shortPart = counted.find((part) => part.partIndex === short);
|
|
1816
|
+
const limitedBy = whyRows.findIndex(
|
|
1817
|
+
(row) => shortPart?.options.length === 1 ? row.partIndex === void 0 && row.itemIds.includes(shortPart.options[0].itemId) : row.partIndex === short
|
|
1818
|
+
);
|
|
1819
|
+
return { count: low, rows: whyRows, ...limitedBy >= 0 ? { limitedBy } : {} };
|
|
1820
|
+
}
|
|
1821
|
+
var poolOf = (line) => line.stockVariantId || line.variantId;
|
|
1822
|
+
function poolDemand(lines) {
|
|
1823
|
+
const demand = /* @__PURE__ */ new Map();
|
|
1824
|
+
const add = (line, times) => demand.set(
|
|
1825
|
+
poolOf(line),
|
|
1826
|
+
(demand.get(poolOf(line)) ?? 0) + (line.unit ? toBase(line.quantity * times, line.unit) : line.quantity * times)
|
|
1827
|
+
);
|
|
1828
|
+
for (const line of lines) {
|
|
1829
|
+
if (line.components) for (const component of line.components) add(component, line.quantity);
|
|
1830
|
+
else add(line, 1);
|
|
1831
|
+
}
|
|
1832
|
+
return demand;
|
|
1833
|
+
}
|
|
1834
|
+
function poolShortfall(demand, poolStock, backordered = /* @__PURE__ */ new Set()) {
|
|
1835
|
+
for (const [poolId, wanted] of demand) {
|
|
1836
|
+
const available = poolStock.get(poolId);
|
|
1837
|
+
if (available === null || available === void 0 || backordered.has(poolId)) continue;
|
|
1838
|
+
if (wanted > available) return { poolId, available, wanted };
|
|
1839
|
+
}
|
|
1840
|
+
return null;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
// utils/composite-reservation.util.ts
|
|
1844
|
+
function compositeModifierMismatches(lineItems) {
|
|
1845
|
+
return lineItems.flatMap(
|
|
1846
|
+
(line) => line.components?.length ? (line.modifiers ?? []).flatMap(
|
|
1847
|
+
(modifier) => modifier.componentIndex !== void 0 && line.components[modifier.componentIndex] !== void 0 ? [] : [
|
|
1848
|
+
`Modifier ${modifier.modifierName ?? "?"} on composite line ${line.internalId ?? "?"} names no component (componentIndex ${modifier.componentIndex}) \u2014 a composite has no modifiers of its own`
|
|
1849
|
+
]
|
|
1850
|
+
) : []
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
function compositeReservationMismatches(order) {
|
|
1854
|
+
return (order.reservations ?? []).flatMap((reservation) => {
|
|
1855
|
+
if (reservation.lineItemInternalId === void 0) return [];
|
|
1856
|
+
const name = `Booking ${String(reservation.bookingId ?? "?")}`;
|
|
1857
|
+
const line = order.lineItems?.find((candidate) => candidate.internalId === reservation.lineItemInternalId);
|
|
1858
|
+
if (!line) return [`${name} names line ${reservation.lineItemInternalId}, which is not on the order`];
|
|
1859
|
+
const component = reservation.componentIndex === void 0 ? void 0 : line.components?.[reservation.componentIndex];
|
|
1860
|
+
if (!component) return [`${name} names no component ${reservation.componentIndex} of line ${line.internalId}`];
|
|
1861
|
+
if (String(component.productId) !== String(reservation.productId))
|
|
1862
|
+
return [
|
|
1863
|
+
`${name} is for ${String(reservation.productId)}, but that component sold ${String(component.productId)}`
|
|
1864
|
+
];
|
|
1865
|
+
if (reservation.price !== 0 || reservation.total !== 0)
|
|
1866
|
+
return [`${name} carries money (${reservation.price}/${reservation.total}); its component already does`];
|
|
1867
|
+
return [];
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1557
1871
|
// units/family-code.ts
|
|
1558
1872
|
var MAX_SLUG_LENGTH = 32;
|
|
1559
1873
|
var FALLBACK_SLUG = "family";
|
|
@@ -2692,51 +3006,6 @@ function inferStatesFromLegacyStatus(order) {
|
|
|
2692
3006
|
return { payment: "unpaid", fulfillment: "pending" };
|
|
2693
3007
|
}
|
|
2694
3008
|
|
|
2695
|
-
// order-state-machine/in-cart.ts
|
|
2696
|
-
function hasInCartFlag(order) {
|
|
2697
|
-
return order.inCart != null;
|
|
2698
|
-
}
|
|
2699
|
-
function isInCart(order) {
|
|
2700
|
-
if (order.inCart != null) return order.inCart.active === true;
|
|
2701
|
-
if (order.fulfillmentState) return order.fulfillmentState === "draft";
|
|
2702
|
-
return (order.status ?? "").trim().toLowerCase() === "in-cart";
|
|
2703
|
-
}
|
|
2704
|
-
|
|
2705
|
-
// order-state-machine/order-status.ts
|
|
2706
|
-
function validateOrderStatuses(statuses) {
|
|
2707
|
-
if (!Array.isArray(statuses)) {
|
|
2708
|
-
return [{ index: -1, field: "orderStatuses", message: "must be an array" }];
|
|
2709
|
-
}
|
|
2710
|
-
const errors = [];
|
|
2711
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2712
|
-
statuses.forEach((raw, index) => {
|
|
2713
|
-
const s = raw ?? {};
|
|
2714
|
-
const add = (field, message) => errors.push({ index, field, message });
|
|
2715
|
-
if (typeof s.id !== "string" || !s.id.trim()) add("id", "must be a non-empty string");
|
|
2716
|
-
else if (seen.has(s.id)) add("id", `duplicate id "${s.id}"`);
|
|
2717
|
-
else seen.add(s.id);
|
|
2718
|
-
if (typeof s.label !== "string" || !s.label.trim()) add("label", "must be a non-empty string");
|
|
2719
|
-
for (const field of ["color", "icon"]) {
|
|
2720
|
-
if (s[field] !== void 0 && typeof s[field] !== "string") add(field, "must be a string");
|
|
2721
|
-
}
|
|
2722
|
-
if (s.fulfillmentState !== void 0 && !(typeof s.fulfillmentState === "string" && isFulfillmentState(s.fulfillmentState))) {
|
|
2723
|
-
add("fulfillmentState", `"${String(s.fulfillmentState)}" is not a fulfillment state`);
|
|
2724
|
-
}
|
|
2725
|
-
if (s.requiresPaymentState !== void 0) {
|
|
2726
|
-
if (!Array.isArray(s.requiresPaymentState) || s.requiresPaymentState.length === 0) {
|
|
2727
|
-
add("requiresPaymentState", "must be a non-empty array of payment states");
|
|
2728
|
-
} else {
|
|
2729
|
-
for (const p of s.requiresPaymentState) {
|
|
2730
|
-
if (!(typeof p === "string" && isPaymentState(p))) {
|
|
2731
|
-
add("requiresPaymentState", `"${String(p)}" is not a payment state`);
|
|
2732
|
-
}
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
}
|
|
2736
|
-
});
|
|
2737
|
-
return errors;
|
|
2738
|
-
}
|
|
2739
|
-
|
|
2740
3009
|
// order-state-machine/write-strategy.ts
|
|
2741
3010
|
var LegacyWriteStrategy = class {
|
|
2742
3011
|
applyState(_order) {
|
|
@@ -3248,7 +3517,9 @@ function buildGrid(rule, timeZone, from, to, now, busy = []) {
|
|
|
3248
3517
|
const start = startAt.getTime();
|
|
3249
3518
|
if (start < askedFrom || start >= askedTo) return false;
|
|
3250
3519
|
if (start < notBefore || start > notAfter) return false;
|
|
3251
|
-
return !blocked.some(
|
|
3520
|
+
return !blocked.some(
|
|
3521
|
+
({ from: blockFrom, to: blockTo }) => start < blockTo && bufferEndAt.getTime() > blockFrom
|
|
3522
|
+
);
|
|
3252
3523
|
}).sort((first, second) => +first.startAt - +second.startAt);
|
|
3253
3524
|
}
|
|
3254
3525
|
function slotsForWindow(grid, startAt, endAt) {
|
|
@@ -3367,7 +3638,14 @@ function buildAvailability({
|
|
|
3367
3638
|
for (const slot of slots2) {
|
|
3368
3639
|
const { taken, foreignProductId } = occupancyIn(mine, productId, slot.startAt, slot.bufferEndAt, shared);
|
|
3369
3640
|
const free = foreignProductId ? 0 : Math.max(capacity - taken, 0);
|
|
3370
|
-
const entry = byInstant.get(+slot.startAt) ?? {
|
|
3641
|
+
const entry = byInstant.get(+slot.startAt) ?? {
|
|
3642
|
+
...slot,
|
|
3643
|
+
capacity: 0,
|
|
3644
|
+
booked: 0,
|
|
3645
|
+
free: 0,
|
|
3646
|
+
resources: [],
|
|
3647
|
+
dayKey: shopDayKey(slot.startAt, resource.timeZone)
|
|
3648
|
+
};
|
|
3371
3649
|
entry.capacity += capacity;
|
|
3372
3650
|
entry.booked += foreignProductId ? capacity : taken;
|
|
3373
3651
|
entry.free += free;
|
|
@@ -3445,34 +3723,10 @@ var ADYEN_ROLLOUT_COUNTRY_CODES = /* @__PURE__ */ new Set(["CA"]);
|
|
|
3445
3723
|
function isAdyenRolloutCountry(alpha2) {
|
|
3446
3724
|
return ADYEN_ROLLOUT_COUNTRY_CODES.has(alpha2);
|
|
3447
3725
|
}
|
|
3448
|
-
var AFFIRM_PRICE_PLANS = ["core", "standard", "signature"];
|
|
3449
|
-
function isAffirmPricePlan(value) {
|
|
3450
|
-
return typeof value === "string" && AFFIRM_PRICE_PLANS.includes(value);
|
|
3451
|
-
}
|
|
3452
|
-
var AFFIRM_COUNTRY_CODES = /* @__PURE__ */ new Set(["CA"]);
|
|
3453
|
-
function isAffirmCountry(alpha2) {
|
|
3454
|
-
return !!alpha2 && AFFIRM_COUNTRY_CODES.has(alpha2.toUpperCase());
|
|
3455
|
-
}
|
|
3456
|
-
var BNPL_METHOD_STATUS = {
|
|
3457
|
-
ACTIVE: "active",
|
|
3458
|
-
PENDING: "pending",
|
|
3459
|
-
UNAVAILABLE: "unavailable",
|
|
3460
|
-
UNKNOWN: "unknown"
|
|
3461
|
-
};
|
|
3462
|
-
var BNPL_SKIP_REASON = {
|
|
3463
|
-
PENDING: "pending",
|
|
3464
|
-
NO_SUPPORT_EMAIL: "no_support_email",
|
|
3465
|
-
NO_CURRENCY: "no_currency",
|
|
3466
|
-
NO_BUSINESS_LINE: "no_business_line"
|
|
3467
|
-
};
|
|
3468
3726
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3469
3727
|
0 && (module.exports = {
|
|
3470
3728
|
ADYEN_ROLLOUT_COUNTRY_CODES,
|
|
3471
|
-
AFFIRM_COUNTRY_CODES,
|
|
3472
|
-
AFFIRM_PRICE_PLANS,
|
|
3473
3729
|
AttributeType,
|
|
3474
|
-
BNPL_METHOD_STATUS,
|
|
3475
|
-
BNPL_SKIP_REASON,
|
|
3476
3730
|
BookingRatePeriod,
|
|
3477
3731
|
BookingResourceKind,
|
|
3478
3732
|
BookingResourceStatus,
|
|
@@ -3550,6 +3804,7 @@ var BNPL_SKIP_REASON = {
|
|
|
3550
3804
|
UserTypes,
|
|
3551
3805
|
addAmounts,
|
|
3552
3806
|
amountToString,
|
|
3807
|
+
ancestorChain,
|
|
3553
3808
|
assertUnitSane,
|
|
3554
3809
|
availableIn,
|
|
3555
3810
|
baseFor,
|
|
@@ -3563,6 +3818,14 @@ var BNPL_SKIP_REASON = {
|
|
|
3563
3818
|
buildResolvedConfig,
|
|
3564
3819
|
calculatePercentage,
|
|
3565
3820
|
canTransition,
|
|
3821
|
+
cheapestCompositeItem,
|
|
3822
|
+
cheapestCompositePrice,
|
|
3823
|
+
compositeItemCost,
|
|
3824
|
+
compositeItemUnavailable,
|
|
3825
|
+
compositeMakeable,
|
|
3826
|
+
compositeModifierMismatches,
|
|
3827
|
+
compositeReservationMismatches,
|
|
3828
|
+
compositeVariantInStock,
|
|
3566
3829
|
conditionSummary,
|
|
3567
3830
|
convertFixedFeeToLocalCurrency,
|
|
3568
3831
|
createAmount,
|
|
@@ -3597,15 +3860,13 @@ var BNPL_SKIP_REASON = {
|
|
|
3597
3860
|
getPricingRegionFromCountry,
|
|
3598
3861
|
getSettlementTotal,
|
|
3599
3862
|
getTotalCollected,
|
|
3600
|
-
|
|
3863
|
+
hasSellableChoice,
|
|
3601
3864
|
holdExpiresAt,
|
|
3602
3865
|
inferStatesFromLegacyStatus,
|
|
3603
3866
|
isAdyenRolloutCountry,
|
|
3604
|
-
|
|
3605
|
-
isAffirmPricePlan,
|
|
3867
|
+
isChoiceAvailableAtOutlet,
|
|
3606
3868
|
isConditionOperator,
|
|
3607
3869
|
isFulfillmentState,
|
|
3608
|
-
isInCart,
|
|
3609
3870
|
isPaymentState,
|
|
3610
3871
|
isSupportedCurrency,
|
|
3611
3872
|
isSystemFamilyCode,
|
|
@@ -3614,6 +3875,7 @@ var BNPL_SKIP_REASON = {
|
|
|
3614
3875
|
mapToLegacyStatus,
|
|
3615
3876
|
maxPrecisionFor,
|
|
3616
3877
|
mergeFragment,
|
|
3878
|
+
modifiersSellable,
|
|
3617
3879
|
multiplyAmount,
|
|
3618
3880
|
normalizeMongoIdString,
|
|
3619
3881
|
occupancyIn,
|
|
@@ -3621,9 +3883,14 @@ var BNPL_SKIP_REASON = {
|
|
|
3621
3883
|
parseCurrencyCode,
|
|
3622
3884
|
parseStripeCurrencyCode,
|
|
3623
3885
|
planWindow,
|
|
3886
|
+
poolDemand,
|
|
3887
|
+
poolOf,
|
|
3888
|
+
poolShortfall,
|
|
3889
|
+
poolStockAt,
|
|
3624
3890
|
presentAt,
|
|
3625
3891
|
removeFragment,
|
|
3626
3892
|
resolveFamily,
|
|
3893
|
+
resolveModifiersForProduct,
|
|
3627
3894
|
resolveTemplate,
|
|
3628
3895
|
resolveUnit,
|
|
3629
3896
|
roundToPrecision,
|
|
@@ -3639,7 +3906,6 @@ var BNPL_SKIP_REASON = {
|
|
|
3639
3906
|
toMinorUnits,
|
|
3640
3907
|
validateConfig,
|
|
3641
3908
|
validateFragmentCompatibility,
|
|
3642
|
-
validateOrderStatuses,
|
|
3643
3909
|
violatesFinancialInvariant,
|
|
3644
3910
|
windowEndsAt,
|
|
3645
3911
|
withinPresence
|