@fire-spark/core 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +729 -39
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1649,11 +1649,11 @@ const randomUUID = () => {
|
|
|
1649
1649
|
};
|
|
1650
1650
|
//#endregion
|
|
1651
1651
|
//#region src/productCustomization/toOrderLines.ts
|
|
1652
|
-
const displayName = (text, locale) => {
|
|
1652
|
+
const displayName$1 = (text, locale) => {
|
|
1653
1653
|
if (text[locale]) return text[locale];
|
|
1654
1654
|
return Object.values(text)[0] ?? "";
|
|
1655
1655
|
};
|
|
1656
|
-
const applyUnitMoney = (config) => {
|
|
1656
|
+
const applyUnitMoney$1 = (config) => {
|
|
1657
1657
|
const qty = new Big(config.quantity);
|
|
1658
1658
|
const unitDiscount = config.unitSubtotal.times(config.discountRate);
|
|
1659
1659
|
const net = config.unitSubtotal.minus(unitDiscount);
|
|
@@ -1732,7 +1732,7 @@ const buildLine = (args) => {
|
|
|
1732
1732
|
...b2,
|
|
1733
1733
|
...b3
|
|
1734
1734
|
};
|
|
1735
|
-
const money = applyUnitMoney(moneyArgs);
|
|
1735
|
+
const money = applyUnitMoney$1(moneyArgs);
|
|
1736
1736
|
const imageUrl = args.product.image_url ?? void 0;
|
|
1737
1737
|
const imagePatch = imageUrl ? { image_url: imageUrl } : {};
|
|
1738
1738
|
const catalog = lineCatalogFields(args.product);
|
|
@@ -1820,7 +1820,7 @@ const appendModifierLine = (modifier, groupId, parent, locale) => {
|
|
|
1820
1820
|
const unitSubtotal = new Big(modifier.linePrice).div(modifier.quantity);
|
|
1821
1821
|
const l1 = {
|
|
1822
1822
|
product,
|
|
1823
|
-
name: displayName(product.name, locale),
|
|
1823
|
+
name: displayName$1(product.name, locale),
|
|
1824
1824
|
quantity: modifier.quantity,
|
|
1825
1825
|
unitSubtotal
|
|
1826
1826
|
};
|
|
@@ -1844,7 +1844,7 @@ const appendModifierLine = (modifier, groupId, parent, locale) => {
|
|
|
1844
1844
|
const toOrderLines = (root, locale) => {
|
|
1845
1845
|
if (root.quantity <= 0) return [];
|
|
1846
1846
|
if (isVariantBase(root.data)) return [];
|
|
1847
|
-
const name = displayName(root.data.name, locale);
|
|
1847
|
+
const name = displayName$1(root.data.name, locale);
|
|
1848
1848
|
const unitSubtotal = new Big(root.data.pricing.price);
|
|
1849
1849
|
const t1 = {
|
|
1850
1850
|
product: root.data,
|
|
@@ -6603,6 +6603,608 @@ const milestoneSteps = (workflow, currentNodeIds) => {
|
|
|
6603
6603
|
};
|
|
6604
6604
|
};
|
|
6605
6605
|
//#endregion
|
|
6606
|
+
//#region src/order/placeCartLines.ts
|
|
6607
|
+
const selectionKey = (line, kidsOf, cache) => {
|
|
6608
|
+
const cached = cache.get(line.uid);
|
|
6609
|
+
if (cached) return cached;
|
|
6610
|
+
const nested = (kidsOf.get(line.uid) ?? []).map((child) => {
|
|
6611
|
+
return selectionKey(child, kidsOf, cache);
|
|
6612
|
+
});
|
|
6613
|
+
nested.sort((left, right) => left.localeCompare(right));
|
|
6614
|
+
const key = `${line.id}x${line.quantity}(${nested.join(",")})`;
|
|
6615
|
+
cache.set(line.uid, key);
|
|
6616
|
+
return key;
|
|
6617
|
+
};
|
|
6618
|
+
const parentGroups = (lookup, product) => {
|
|
6619
|
+
return product.pricing.modifier_group_ids.flatMap((groupId) => {
|
|
6620
|
+
const group = lookup.modifierGroupsById[groupId];
|
|
6621
|
+
return group ? [group] : [];
|
|
6622
|
+
});
|
|
6623
|
+
};
|
|
6624
|
+
const buildSlots = (lookup, groups) => {
|
|
6625
|
+
const slots = [];
|
|
6626
|
+
for (const group of groups) {
|
|
6627
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6628
|
+
for (const modifierId of group.modifier_ids) {
|
|
6629
|
+
if (seen.has(modifierId)) continue;
|
|
6630
|
+
seen.add(modifierId);
|
|
6631
|
+
const product = lookup.productsById[modifierId];
|
|
6632
|
+
const entry = product ? resolvePricingModifier(product, group.id) : null;
|
|
6633
|
+
const modifierMax = entry?.maximum_quantity ?? group.maximum_quantity;
|
|
6634
|
+
const row = {
|
|
6635
|
+
group,
|
|
6636
|
+
modifierId,
|
|
6637
|
+
modifierMin: entry?.minimum_quantity ?? 0
|
|
6638
|
+
};
|
|
6639
|
+
const counts = {
|
|
6640
|
+
modifierMax,
|
|
6641
|
+
instances: 0,
|
|
6642
|
+
next: 2
|
|
6643
|
+
};
|
|
6644
|
+
slots.push({
|
|
6645
|
+
...row,
|
|
6646
|
+
...counts
|
|
6647
|
+
});
|
|
6648
|
+
}
|
|
6649
|
+
}
|
|
6650
|
+
return slots;
|
|
6651
|
+
};
|
|
6652
|
+
const listingsFor = (lookup, groups, childId) => {
|
|
6653
|
+
const direct = [];
|
|
6654
|
+
for (const group of groups) {
|
|
6655
|
+
if (!group.modifier_ids.includes(childId)) continue;
|
|
6656
|
+
direct.push({
|
|
6657
|
+
group,
|
|
6658
|
+
modifierId: childId
|
|
6659
|
+
});
|
|
6660
|
+
}
|
|
6661
|
+
if (direct.length > 0) return direct;
|
|
6662
|
+
const child = lookup.productsById[childId];
|
|
6663
|
+
const viaBase = [];
|
|
6664
|
+
for (const entry of child?.pricing.variants ?? []) {
|
|
6665
|
+
const base = lookup.productsById[entry.product_id];
|
|
6666
|
+
if (!child || !base) continue;
|
|
6667
|
+
if (!selectionForVariant(lookup, base, child)) continue;
|
|
6668
|
+
for (const group of groups) {
|
|
6669
|
+
if (!group.modifier_ids.includes(base.id)) continue;
|
|
6670
|
+
viaBase.push({
|
|
6671
|
+
group,
|
|
6672
|
+
modifierId: base.id
|
|
6673
|
+
});
|
|
6674
|
+
}
|
|
6675
|
+
}
|
|
6676
|
+
return viaBase;
|
|
6677
|
+
};
|
|
6678
|
+
const slotKey = (groupId, modifierId) => {
|
|
6679
|
+
return `${groupId}/${modifierId}`;
|
|
6680
|
+
};
|
|
6681
|
+
/** Same product id more than once under one parent, at any depth. */
|
|
6682
|
+
const hasRepeatedProductSibling = (lines, rootUid) => {
|
|
6683
|
+
const kidsOf = /* @__PURE__ */ new Map();
|
|
6684
|
+
for (const line of lines) {
|
|
6685
|
+
if (line.parent_uid == null) continue;
|
|
6686
|
+
const list = kidsOf.get(line.parent_uid) ?? [];
|
|
6687
|
+
list.push(line);
|
|
6688
|
+
kidsOf.set(line.parent_uid, list);
|
|
6689
|
+
}
|
|
6690
|
+
const walk = (uid) => {
|
|
6691
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6692
|
+
for (const child of kidsOf.get(uid) ?? []) {
|
|
6693
|
+
if (seen.has(child.id)) return true;
|
|
6694
|
+
seen.add(child.id);
|
|
6695
|
+
if (walk(child.uid)) return true;
|
|
6696
|
+
}
|
|
6697
|
+
return false;
|
|
6698
|
+
};
|
|
6699
|
+
return walk(rootUid);
|
|
6700
|
+
};
|
|
6701
|
+
const noteRules = (state, broken) => {
|
|
6702
|
+
if (broken) state.rulesBroken = true;
|
|
6703
|
+
};
|
|
6704
|
+
/** Search steps per parent. Far above any real menu. A cart that needs more
|
|
6705
|
+
* is rejected: a partial search never accepts anything. */
|
|
6706
|
+
const SEARCH_BUDGET = 4096;
|
|
6707
|
+
const OWN_MONEY = [
|
|
6708
|
+
"unit_subtotal",
|
|
6709
|
+
"subtotal",
|
|
6710
|
+
"unit_discount",
|
|
6711
|
+
"discount_total",
|
|
6712
|
+
"unit_tax_total",
|
|
6713
|
+
"tax_total",
|
|
6714
|
+
"unit_total",
|
|
6715
|
+
"total",
|
|
6716
|
+
"discount_rate"
|
|
6717
|
+
];
|
|
6718
|
+
const emptyUsage = () => {
|
|
6719
|
+
return {
|
|
6720
|
+
bySlot: /* @__PURE__ */ new Map(),
|
|
6721
|
+
byGroup: /* @__PURE__ */ new Map()
|
|
6722
|
+
};
|
|
6723
|
+
};
|
|
6724
|
+
const addUsage = (usage, slot, quantity) => {
|
|
6725
|
+
const own = usage.bySlot.get(slot) ?? 0;
|
|
6726
|
+
usage.bySlot.set(slot, own + quantity);
|
|
6727
|
+
const group = usage.byGroup.get(slot.group.id) ?? 0;
|
|
6728
|
+
usage.byGroup.set(slot.group.id, group + quantity);
|
|
6729
|
+
};
|
|
6730
|
+
const hasRoom = (usage, slot, quantity) => {
|
|
6731
|
+
const own = usage.bySlot.get(slot) ?? 0;
|
|
6732
|
+
if ((usage.byGroup.get(slot.group.id) ?? 0) + quantity > slot.group.maximum_quantity) return false;
|
|
6733
|
+
return own + quantity <= slot.modifierMax;
|
|
6734
|
+
};
|
|
6735
|
+
/** Every instance counts: usage is the sum over all sibling lines. A slot's
|
|
6736
|
+
* own minimum binds once the slot is used, as in the customization session.
|
|
6737
|
+
* An unused slot owes nothing; the group minimum is what requires a pick. */
|
|
6738
|
+
const limitsBroken = (groups, slots, usage) => {
|
|
6739
|
+
for (const group of groups) {
|
|
6740
|
+
const used = usage.byGroup.get(group.id) ?? 0;
|
|
6741
|
+
if (used > group.maximum_quantity) return true;
|
|
6742
|
+
if (used < group.minimum_quantity) return true;
|
|
6743
|
+
}
|
|
6744
|
+
for (const slot of slots) {
|
|
6745
|
+
const used = usage.bySlot.get(slot) ?? 0;
|
|
6746
|
+
if (used > slot.modifierMax) return true;
|
|
6747
|
+
if (used > 0 && used < slot.modifierMin) return true;
|
|
6748
|
+
}
|
|
6749
|
+
return false;
|
|
6750
|
+
};
|
|
6751
|
+
const claimKey = (line) => {
|
|
6752
|
+
const subtotal = new Big(line.unit_subtotal).toString();
|
|
6753
|
+
if (line.tax_rate == null) return `${subtotal}|null`;
|
|
6754
|
+
return `${subtotal}|${new Big(line.tax_rate).toString()}`;
|
|
6755
|
+
};
|
|
6756
|
+
const sameOwnMoney = (line, money) => {
|
|
6757
|
+
for (const field of OWN_MONEY) if (!new Big(line[field]).eq(money[field])) return false;
|
|
6758
|
+
if (line.tax_rate == null) return false;
|
|
6759
|
+
return new Big(line.tax_rate).eq(money.tax_rate);
|
|
6760
|
+
};
|
|
6761
|
+
/** True when the line's own money is exactly what the menu yields for this
|
|
6762
|
+
* slot: the slot's price and tax, with 0 to `quantity` charged units
|
|
6763
|
+
* (charge_above shares free units between siblings). It only narrows which
|
|
6764
|
+
* slots a line may take. Nothing from the cart is ever copied into the
|
|
6765
|
+
* expected tree: the final comparison is still exact. */
|
|
6766
|
+
const slotYieldsLine = (lookup, line, ordered, slot) => {
|
|
6767
|
+
const quantity = line.quantity;
|
|
6768
|
+
if (quantity <= 0) return false;
|
|
6769
|
+
if (quantity > slot.modifierMax) return false;
|
|
6770
|
+
if (quantity > slot.group.maximum_quantity) return false;
|
|
6771
|
+
const listed = lookup.productsById[slot.modifierId] ?? ordered;
|
|
6772
|
+
const pricing = modifierUnitPricing(listed, ordered, slot.group.id);
|
|
6773
|
+
const free = (resolvePricingModifier(listed, slot.group.id)?.charge_above_quantity ?? 0) + (slot.group.charge_above_quantity ?? 0);
|
|
6774
|
+
const unit = new Big(pricing.price);
|
|
6775
|
+
const config = {
|
|
6776
|
+
quantity,
|
|
6777
|
+
taxRate: pricing.taxRate
|
|
6778
|
+
};
|
|
6779
|
+
const inclusive = { taxInclusive: pricing.isTaxInclusive };
|
|
6780
|
+
const fewest = Math.max(0, quantity - free);
|
|
6781
|
+
for (let charged = fewest; charged <= quantity; charged++) {
|
|
6782
|
+
const unitSubtotal = unit.times(charged).div(quantity);
|
|
6783
|
+
const money = applyUnitMoney({
|
|
6784
|
+
...config,
|
|
6785
|
+
...inclusive,
|
|
6786
|
+
unitSubtotal
|
|
6787
|
+
});
|
|
6788
|
+
if (sameOwnMoney(line, money)) return true;
|
|
6789
|
+
}
|
|
6790
|
+
return false;
|
|
6791
|
+
};
|
|
6792
|
+
/** Slots the line may take: those whose menu price it matches, else every
|
|
6793
|
+
* listed slot (the comparison then reports the price it cannot match). */
|
|
6794
|
+
const optionsFor = (lookup, line, ordered, listed) => {
|
|
6795
|
+
if (listed.length < 2) return listed;
|
|
6796
|
+
const yielding = listed.filter((slot) => {
|
|
6797
|
+
return slotYieldsLine(lookup, line, ordered, slot);
|
|
6798
|
+
});
|
|
6799
|
+
if (yielding.length > 0) return yielding;
|
|
6800
|
+
return listed;
|
|
6801
|
+
};
|
|
6802
|
+
/** Depth-first assignment of sibling lines to slots, in a fixed order, within
|
|
6803
|
+
* capacity, until every group and modifier limit holds. Twins (same shape and
|
|
6804
|
+
* same claimed price) are interchangeable, so they are forced into
|
|
6805
|
+
* non-decreasing option order. At most SEARCH_BUDGET steps of O(slots). */
|
|
6806
|
+
const searchAssignment = (plans, groups, slots) => {
|
|
6807
|
+
const total = plans.length;
|
|
6808
|
+
const usage = emptyUsage();
|
|
6809
|
+
const chosen = [];
|
|
6810
|
+
const picked = [];
|
|
6811
|
+
const cursor = [0];
|
|
6812
|
+
let depth = 0;
|
|
6813
|
+
let steps = 0;
|
|
6814
|
+
while (depth >= 0) {
|
|
6815
|
+
steps += 1;
|
|
6816
|
+
if (steps > SEARCH_BUDGET) return null;
|
|
6817
|
+
if (depth === total) {
|
|
6818
|
+
if (!limitsBroken(groups, slots, usage)) return chosen;
|
|
6819
|
+
depth -= 1;
|
|
6820
|
+
addUsage(usage, chosen[depth], -plans[depth].line.quantity);
|
|
6821
|
+
continue;
|
|
6822
|
+
}
|
|
6823
|
+
const plan = plans[depth];
|
|
6824
|
+
const quantity = plan.line.quantity;
|
|
6825
|
+
let found = -1;
|
|
6826
|
+
for (let next = cursor[depth]; next < plan.options.length; next++) {
|
|
6827
|
+
if (!hasRoom(usage, plan.options[next], quantity)) continue;
|
|
6828
|
+
found = next;
|
|
6829
|
+
break;
|
|
6830
|
+
}
|
|
6831
|
+
if (found < 0) {
|
|
6832
|
+
depth -= 1;
|
|
6833
|
+
if (depth < 0) break;
|
|
6834
|
+
addUsage(usage, chosen[depth], -plans[depth].line.quantity);
|
|
6835
|
+
continue;
|
|
6836
|
+
}
|
|
6837
|
+
cursor[depth] = found + 1;
|
|
6838
|
+
picked[depth] = found;
|
|
6839
|
+
chosen[depth] = plan.options[found];
|
|
6840
|
+
addUsage(usage, chosen[depth], quantity);
|
|
6841
|
+
depth += 1;
|
|
6842
|
+
cursor[depth] = plans[depth]?.twin === true ? picked[depth - 1] : 0;
|
|
6843
|
+
}
|
|
6844
|
+
return null;
|
|
6845
|
+
};
|
|
6846
|
+
/** Used when no assignment satisfies every limit: first slot with room, so the
|
|
6847
|
+
* repaired tree and the broken-rules flag still come out deterministic. */
|
|
6848
|
+
const greedyAssignment = (plans) => {
|
|
6849
|
+
const usage = emptyUsage();
|
|
6850
|
+
const chosen = [];
|
|
6851
|
+
let overCapacity = false;
|
|
6852
|
+
for (const plan of plans) {
|
|
6853
|
+
const quantity = plan.line.quantity;
|
|
6854
|
+
const fits = (slot) => hasRoom(usage, slot, quantity);
|
|
6855
|
+
let slot = plan.options.find(fits) ?? plan.listed.find(fits);
|
|
6856
|
+
if (!slot) {
|
|
6857
|
+
slot = plan.listed[0];
|
|
6858
|
+
overCapacity = true;
|
|
6859
|
+
}
|
|
6860
|
+
addUsage(usage, slot, quantity);
|
|
6861
|
+
chosen.push(slot);
|
|
6862
|
+
}
|
|
6863
|
+
return {
|
|
6864
|
+
chosen,
|
|
6865
|
+
overCapacity
|
|
6866
|
+
};
|
|
6867
|
+
};
|
|
6868
|
+
const planKids = (lookup, groups, byKey, kids, prefix, state) => {
|
|
6869
|
+
const plans = [];
|
|
6870
|
+
let previous = "";
|
|
6871
|
+
for (const child of kids) {
|
|
6872
|
+
if (pathVisits(prefix, child.id)) continue;
|
|
6873
|
+
const ordered = lookup.productsById[child.id];
|
|
6874
|
+
const listings = listingsFor(lookup, groups, child.id);
|
|
6875
|
+
if (!ordered || listings.length === 0) continue;
|
|
6876
|
+
const listed = [];
|
|
6877
|
+
for (const item of listings) {
|
|
6878
|
+
const slot = byKey.get(slotKey(item.group.id, item.modifierId));
|
|
6879
|
+
if (slot) listed.push(slot);
|
|
6880
|
+
}
|
|
6881
|
+
if (listed.length === 0) continue;
|
|
6882
|
+
const signature = `${selectionKey(child, state.childrenByParent, state.keyCache)}|${claimKey(child)}`;
|
|
6883
|
+
const options = optionsFor(lookup, child, ordered, listed);
|
|
6884
|
+
const twin = signature === previous;
|
|
6885
|
+
plans.push({
|
|
6886
|
+
line: child,
|
|
6887
|
+
ordered,
|
|
6888
|
+
listed,
|
|
6889
|
+
options,
|
|
6890
|
+
twin
|
|
6891
|
+
});
|
|
6892
|
+
previous = signature;
|
|
6893
|
+
}
|
|
6894
|
+
return plans;
|
|
6895
|
+
};
|
|
6896
|
+
const placeChildren = (lookup, parent, parentLine, prefix, state) => {
|
|
6897
|
+
const groups = parentGroups(lookup, parent);
|
|
6898
|
+
const slots = buildSlots(lookup, groups);
|
|
6899
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
6900
|
+
for (const slot of slots) byKey.set(slotKey(slot.group.id, slot.modifierId), slot);
|
|
6901
|
+
const kids = (state.childrenByParent.get(parentLine.uid) ?? []).slice();
|
|
6902
|
+
kids.sort((left, right) => {
|
|
6903
|
+
const a = selectionKey(left, state.childrenByParent, state.keyCache);
|
|
6904
|
+
const b = selectionKey(right, state.childrenByParent, state.keyCache);
|
|
6905
|
+
const byShape = a.localeCompare(b);
|
|
6906
|
+
if (byShape !== 0) return byShape;
|
|
6907
|
+
return claimKey(left).localeCompare(claimKey(right));
|
|
6908
|
+
});
|
|
6909
|
+
const plans = planKids(lookup, groups, byKey, kids, prefix, state);
|
|
6910
|
+
let assignment = [];
|
|
6911
|
+
if (plans.length > 0) assignment = searchAssignment(plans, groups, slots);
|
|
6912
|
+
if (!assignment) {
|
|
6913
|
+
const fallback = greedyAssignment(plans);
|
|
6914
|
+
assignment = fallback.chosen;
|
|
6915
|
+
if (fallback.overCapacity) state.overCapacity = true;
|
|
6916
|
+
}
|
|
6917
|
+
const usage = emptyUsage();
|
|
6918
|
+
const placed = [];
|
|
6919
|
+
for (let index = 0; index < plans.length; index++) {
|
|
6920
|
+
const plan = plans[index];
|
|
6921
|
+
const chosen = assignment[index];
|
|
6922
|
+
const child = plan.line;
|
|
6923
|
+
const ordered = plan.ordered;
|
|
6924
|
+
const base = `${`${prefix}/group/${chosen.group.id}`}/modifier/${chosen.modifierId}`;
|
|
6925
|
+
let path = base;
|
|
6926
|
+
if (chosen.instances > 0) {
|
|
6927
|
+
path = `${base}/instance/${chosen.next}`;
|
|
6928
|
+
chosen.next += 1;
|
|
6929
|
+
}
|
|
6930
|
+
chosen.instances += 1;
|
|
6931
|
+
addUsage(usage, chosen, child.quantity);
|
|
6932
|
+
state.paths[path] = child.quantity;
|
|
6933
|
+
const listed = lookup.productsById[chosen.modifierId] ?? ordered;
|
|
6934
|
+
const pricing = modifierUnitPricing(listed, ordered, chosen.group.id);
|
|
6935
|
+
const n1 = {
|
|
6936
|
+
product: ordered,
|
|
6937
|
+
quantity: child.quantity,
|
|
6938
|
+
path
|
|
6939
|
+
};
|
|
6940
|
+
const n2 = {
|
|
6941
|
+
children: [],
|
|
6942
|
+
listed,
|
|
6943
|
+
group: chosen.group
|
|
6944
|
+
};
|
|
6945
|
+
const n3 = {
|
|
6946
|
+
unitPrice: new Big(0),
|
|
6947
|
+
taxRate: pricing.taxRate
|
|
6948
|
+
};
|
|
6949
|
+
const n4 = { taxInclusive: pricing.isTaxInclusive };
|
|
6950
|
+
const node = {
|
|
6951
|
+
...n1,
|
|
6952
|
+
...n2,
|
|
6953
|
+
...n3,
|
|
6954
|
+
...n4
|
|
6955
|
+
};
|
|
6956
|
+
node.children = placeChildren(lookup, ordered, child, path, state);
|
|
6957
|
+
placed.push(node);
|
|
6958
|
+
}
|
|
6959
|
+
noteRules(state, limitsBroken(groups, slots, usage));
|
|
6960
|
+
billGroups(placed);
|
|
6961
|
+
return placed;
|
|
6962
|
+
};
|
|
6963
|
+
const billGroups = (nodes) => {
|
|
6964
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
6965
|
+
for (const node of nodes) {
|
|
6966
|
+
if (!node.group) continue;
|
|
6967
|
+
const list = byGroup.get(node.group.id) ?? [];
|
|
6968
|
+
list.push(node);
|
|
6969
|
+
byGroup.set(node.group.id, list);
|
|
6970
|
+
}
|
|
6971
|
+
for (const list of byGroup.values()) {
|
|
6972
|
+
const group = list[0]?.group;
|
|
6973
|
+
if (group) billGroup(group, list);
|
|
6974
|
+
}
|
|
6975
|
+
};
|
|
6976
|
+
/** Menu order, then instance order. One modifier's free units are shared,
|
|
6977
|
+
* so a second sibling line is not free just because it is its own line. */
|
|
6978
|
+
const billGroup = (group, nodes) => {
|
|
6979
|
+
const groupChargeAbove = group.charge_above_quantity ?? 0;
|
|
6980
|
+
let chargedCount = 0;
|
|
6981
|
+
const consumed = /* @__PURE__ */ new Map();
|
|
6982
|
+
const ordered = [];
|
|
6983
|
+
for (const modifierId of group.modifier_ids) for (const node of nodes) if (node.listed?.id === modifierId) ordered.push(node);
|
|
6984
|
+
for (const node of ordered) {
|
|
6985
|
+
if (node.quantity <= 0 || !node.listed || !node.group) continue;
|
|
6986
|
+
const entry = resolvePricingModifier(node.listed, node.group.id);
|
|
6987
|
+
const unit = new Big(modifierUnitPricing(...[
|
|
6988
|
+
node.listed,
|
|
6989
|
+
node.product,
|
|
6990
|
+
node.group.id
|
|
6991
|
+
]).price);
|
|
6992
|
+
const allowance0 = entry?.charge_above_quantity ?? 0;
|
|
6993
|
+
const id = node.listed.id;
|
|
6994
|
+
const taken = consumed.get(id) ?? 0;
|
|
6995
|
+
const allowance = Math.max(0, allowance0 - taken);
|
|
6996
|
+
let remaining = Math.max(0, node.quantity - allowance);
|
|
6997
|
+
consumed.set(id, taken + node.quantity);
|
|
6998
|
+
let linePrice = new Big(0);
|
|
6999
|
+
while (remaining > 0) {
|
|
7000
|
+
if (chargedCount >= groupChargeAbove) linePrice = linePrice.plus(unit);
|
|
7001
|
+
chargedCount += 1;
|
|
7002
|
+
remaining -= 1;
|
|
7003
|
+
}
|
|
7004
|
+
node.unitPrice = node.quantity > 0 ? linePrice.div(node.quantity) : unit;
|
|
7005
|
+
}
|
|
7006
|
+
};
|
|
7007
|
+
const applyUnitMoney = (config) => {
|
|
7008
|
+
const qty = new Big(config.quantity);
|
|
7009
|
+
const net = config.unitSubtotal;
|
|
7010
|
+
let unitTax = net.times(config.taxRate);
|
|
7011
|
+
let unitTotal = net.plus(unitTax);
|
|
7012
|
+
if (config.taxInclusive) {
|
|
7013
|
+
const divisor = new Big(1).plus(config.taxRate);
|
|
7014
|
+
unitTax = net.times(config.taxRate).div(divisor);
|
|
7015
|
+
unitTotal = net;
|
|
7016
|
+
}
|
|
7017
|
+
const r1 = {
|
|
7018
|
+
tax_rate: config.taxRate,
|
|
7019
|
+
discount_rate: 0
|
|
7020
|
+
};
|
|
7021
|
+
const r2 = { unit_subtotal: config.unitSubtotal.toNumber() };
|
|
7022
|
+
const r3 = {
|
|
7023
|
+
unit_discount: 0,
|
|
7024
|
+
unit_tax_total: unitTax.toNumber()
|
|
7025
|
+
};
|
|
7026
|
+
const r4 = { unit_total: unitTotal.toNumber() };
|
|
7027
|
+
const r5 = { subtotal: config.unitSubtotal.times(qty).toNumber() };
|
|
7028
|
+
const r6 = {
|
|
7029
|
+
discount_total: 0,
|
|
7030
|
+
tax_total: unitTax.times(qty).toNumber()
|
|
7031
|
+
};
|
|
7032
|
+
const r7 = { total: unitTotal.times(qty).toNumber() };
|
|
7033
|
+
return {
|
|
7034
|
+
...r1,
|
|
7035
|
+
...r2,
|
|
7036
|
+
...r3,
|
|
7037
|
+
...r4,
|
|
7038
|
+
...r5,
|
|
7039
|
+
...r6,
|
|
7040
|
+
...r7
|
|
7041
|
+
};
|
|
7042
|
+
};
|
|
7043
|
+
const emptyGroup = () => {
|
|
7044
|
+
const g1 = {
|
|
7045
|
+
unit_group_subtotal: 0,
|
|
7046
|
+
unit_group_discount: 0
|
|
7047
|
+
};
|
|
7048
|
+
const g2 = {
|
|
7049
|
+
unit_group_tax_total: 0,
|
|
7050
|
+
unit_group_total: 0
|
|
7051
|
+
};
|
|
7052
|
+
const g3 = {
|
|
7053
|
+
group_subtotal: 0,
|
|
7054
|
+
group_discount: 0
|
|
7055
|
+
};
|
|
7056
|
+
const g4 = {
|
|
7057
|
+
group_tax_total: 0,
|
|
7058
|
+
group_total: 0
|
|
7059
|
+
};
|
|
7060
|
+
return {
|
|
7061
|
+
...g1,
|
|
7062
|
+
...g2,
|
|
7063
|
+
...g3,
|
|
7064
|
+
...g4
|
|
7065
|
+
};
|
|
7066
|
+
};
|
|
7067
|
+
const displayName = (text, locale) => {
|
|
7068
|
+
if (text[locale]) return text[locale];
|
|
7069
|
+
return Object.values(text)[0] ?? "";
|
|
7070
|
+
};
|
|
7071
|
+
const emit = (node, parentUid, parentId, locale, out) => {
|
|
7072
|
+
const mIn1 = {
|
|
7073
|
+
unitSubtotal: node.unitPrice,
|
|
7074
|
+
quantity: node.quantity
|
|
7075
|
+
};
|
|
7076
|
+
const mIn2 = {
|
|
7077
|
+
taxRate: node.taxRate,
|
|
7078
|
+
taxInclusive: node.taxInclusive
|
|
7079
|
+
};
|
|
7080
|
+
const money = applyUnitMoney({
|
|
7081
|
+
...mIn1,
|
|
7082
|
+
...mIn2
|
|
7083
|
+
});
|
|
7084
|
+
const imageUrl = node.product.image_url ?? void 0;
|
|
7085
|
+
const imagePatch = imageUrl ? { image_url: imageUrl } : {};
|
|
7086
|
+
const catalog = {
|
|
7087
|
+
metadata: { ...node.product.metadata ?? {} },
|
|
7088
|
+
bundled_items: [...node.product.bundled_items ?? []]
|
|
7089
|
+
};
|
|
7090
|
+
const r1 = {
|
|
7091
|
+
type: "PRODUCT",
|
|
7092
|
+
uid: randomUUID()
|
|
7093
|
+
};
|
|
7094
|
+
const r2 = {
|
|
7095
|
+
id: node.product.id,
|
|
7096
|
+
parent_uid: parentUid,
|
|
7097
|
+
parent_id: parentId
|
|
7098
|
+
};
|
|
7099
|
+
const r3 = {
|
|
7100
|
+
name: displayName(node.product.name, locale),
|
|
7101
|
+
quantity: node.quantity
|
|
7102
|
+
};
|
|
7103
|
+
const r4 = {
|
|
7104
|
+
redeem: [],
|
|
7105
|
+
...imagePatch,
|
|
7106
|
+
...catalog,
|
|
7107
|
+
...money
|
|
7108
|
+
};
|
|
7109
|
+
const line = {
|
|
7110
|
+
...r1,
|
|
7111
|
+
...r2,
|
|
7112
|
+
...r3,
|
|
7113
|
+
...r4,
|
|
7114
|
+
...emptyGroup()
|
|
7115
|
+
};
|
|
7116
|
+
out.push(line);
|
|
7117
|
+
for (const child of node.children) emit(child, line.uid, line.id, locale, out);
|
|
7118
|
+
let sub = new Big(line.subtotal);
|
|
7119
|
+
let tax = new Big(line.tax_total);
|
|
7120
|
+
let tot = new Big(line.total);
|
|
7121
|
+
let childSub = new Big(0);
|
|
7122
|
+
let childTax = new Big(0);
|
|
7123
|
+
let childTot = new Big(0);
|
|
7124
|
+
for (const child of out) {
|
|
7125
|
+
if (child.parent_uid !== line.uid) continue;
|
|
7126
|
+
childSub = childSub.plus(child.group_subtotal);
|
|
7127
|
+
childTax = childTax.plus(child.group_tax_total);
|
|
7128
|
+
childTot = childTot.plus(child.group_total);
|
|
7129
|
+
}
|
|
7130
|
+
const qty = new Big(line.quantity);
|
|
7131
|
+
sub = sub.plus(childSub.times(qty));
|
|
7132
|
+
tax = tax.plus(childTax.times(qty));
|
|
7133
|
+
tot = tot.plus(childTot.times(qty));
|
|
7134
|
+
line.group_subtotal = sub.toNumber();
|
|
7135
|
+
line.group_tax_total = tax.toNumber();
|
|
7136
|
+
line.group_total = tot.toNumber();
|
|
7137
|
+
line.group_discount = 0;
|
|
7138
|
+
line.unit_group_subtotal = sub.div(qty).toNumber();
|
|
7139
|
+
line.unit_group_tax_total = tax.div(qty).toNumber();
|
|
7140
|
+
line.unit_group_total = tot.div(qty).toNumber();
|
|
7141
|
+
line.unit_group_discount = 0;
|
|
7142
|
+
};
|
|
7143
|
+
const rootNode = (lookup, root, state) => {
|
|
7144
|
+
const product = lookup.productsById[root.id];
|
|
7145
|
+
if (!product) return null;
|
|
7146
|
+
const max = product.pricing.maximum_quantity;
|
|
7147
|
+
const min = product.pricing.minimum_quantity;
|
|
7148
|
+
noteRules(state, root.quantity > max || root.quantity < min);
|
|
7149
|
+
const path = `/${product.id}`;
|
|
7150
|
+
state.paths[path] = root.quantity;
|
|
7151
|
+
const children = placeChildren(lookup, product, root, path, state);
|
|
7152
|
+
const r1 = {
|
|
7153
|
+
product,
|
|
7154
|
+
quantity: root.quantity,
|
|
7155
|
+
path,
|
|
7156
|
+
children
|
|
7157
|
+
};
|
|
7158
|
+
const r2 = {
|
|
7159
|
+
listed: null,
|
|
7160
|
+
group: null
|
|
7161
|
+
};
|
|
7162
|
+
const r3 = { unitPrice: new Big(product.pricing.price) };
|
|
7163
|
+
const r4 = { taxRate: product.pricing.tax_rate };
|
|
7164
|
+
const r5 = { taxInclusive: product.pricing.is_tax_inclusive };
|
|
7165
|
+
return {
|
|
7166
|
+
...r1,
|
|
7167
|
+
...r2,
|
|
7168
|
+
...r3,
|
|
7169
|
+
...r4,
|
|
7170
|
+
...r5
|
|
7171
|
+
};
|
|
7172
|
+
};
|
|
7173
|
+
/** Place cart lines on menu slots and price them from the menu.
|
|
7174
|
+
* The tree is built from product id, parent, and quantity. A line's claimed
|
|
7175
|
+
* own price is read for one purpose: to rank which listed slots it may take
|
|
7176
|
+
* (see slotYieldsLine). The expected money is always the menu's for the
|
|
7177
|
+
* chosen slot, so a claim the menu cannot yield stays a mismatch.
|
|
7178
|
+
* A quantity above 1 stays one line. Extra sibling lines reuse a slot
|
|
7179
|
+
* only while its max still has room, at `/instance/2`, `/instance/3`, ….
|
|
7180
|
+
* Indices are assigned here. A forged index in the cart is not an input. */
|
|
7181
|
+
const placeCartLines = (root, lines, lookup, locale) => {
|
|
7182
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
7183
|
+
for (const line of lines) {
|
|
7184
|
+
if (line.parent_uid == null) continue;
|
|
7185
|
+
const list = childrenByParent.get(line.parent_uid) ?? [];
|
|
7186
|
+
list.push(line);
|
|
7187
|
+
childrenByParent.set(line.parent_uid, list);
|
|
7188
|
+
}
|
|
7189
|
+
const state = {
|
|
7190
|
+
paths: {},
|
|
7191
|
+
overCapacity: false,
|
|
7192
|
+
rulesBroken: false,
|
|
7193
|
+
childrenByParent,
|
|
7194
|
+
keyCache: /* @__PURE__ */ new Map()
|
|
7195
|
+
};
|
|
7196
|
+
const node = rootNode(lookup, root, state);
|
|
7197
|
+
const out = [];
|
|
7198
|
+
if (node) emit(node, null, null, locale, out);
|
|
7199
|
+
if (state.overCapacity) state.rulesBroken = true;
|
|
7200
|
+
return {
|
|
7201
|
+
paths: state.paths,
|
|
7202
|
+
overCapacity: state.overCapacity,
|
|
7203
|
+
rulesBroken: state.rulesBroken,
|
|
7204
|
+
lines: out
|
|
7205
|
+
};
|
|
7206
|
+
};
|
|
7207
|
+
//#endregion
|
|
6606
7208
|
//#region src/order/validate.ts
|
|
6607
7209
|
const PRICE_FIELDS = [
|
|
6608
7210
|
"unit_subtotal",
|
|
@@ -6661,11 +7263,34 @@ const rateStr = (n) => {
|
|
|
6661
7263
|
if (n == null) return "null";
|
|
6662
7264
|
return bigStr(n);
|
|
6663
7265
|
};
|
|
7266
|
+
const structureKey = (lines, line) => {
|
|
7267
|
+
const nested = lines.filter((child) => child.parent_uid === line.uid).map((child) => structureKey(lines, child));
|
|
7268
|
+
nested.sort((left, right) => left.localeCompare(right));
|
|
7269
|
+
return `${line.id}x${line.quantity}(${nested.join(",")})`;
|
|
7270
|
+
};
|
|
7271
|
+
/** Sorted by id as before. Only same-id siblings are told apart further:
|
|
7272
|
+
* by nested shape, then unit price, so each pairs with its own expectation. */
|
|
6664
7273
|
const childrenOf = (lines, parentUid) => {
|
|
6665
7274
|
const children = lines.filter((line) => {
|
|
6666
7275
|
return line.parent_uid === parentUid;
|
|
6667
7276
|
});
|
|
6668
|
-
|
|
7277
|
+
const shapes = /* @__PURE__ */ new Map();
|
|
7278
|
+
const shapeOf = (line) => {
|
|
7279
|
+
const cached = shapes.get(line.uid);
|
|
7280
|
+
if (cached !== void 0) return cached;
|
|
7281
|
+
const shape = structureKey(lines, line);
|
|
7282
|
+
shapes.set(line.uid, shape);
|
|
7283
|
+
return shape;
|
|
7284
|
+
};
|
|
7285
|
+
children.sort((a, b) => {
|
|
7286
|
+
const byId = a.id.localeCompare(b.id);
|
|
7287
|
+
if (byId !== 0) return byId;
|
|
7288
|
+
const byShape = shapeOf(a).localeCompare(shapeOf(b));
|
|
7289
|
+
if (byShape !== 0) return byShape;
|
|
7290
|
+
const bySubtotal = new Big(a.unit_subtotal).cmp(b.unit_subtotal);
|
|
7291
|
+
if (bySubtotal !== 0) return bySubtotal;
|
|
7292
|
+
return new Big(a.unit_tax_total).cmp(b.unit_tax_total);
|
|
7293
|
+
});
|
|
6669
7294
|
return children;
|
|
6670
7295
|
};
|
|
6671
7296
|
const collectUnmatchedModifiers = (lines, lookup, parentProductId, parentUid, out) => {
|
|
@@ -6908,10 +7533,17 @@ const validateProductRoot = (root, lines, lookup, locale, out) => {
|
|
|
6908
7533
|
if (status === "OUT_OF_STOCK") pushLineIssue(out, root.uid, { code: "OUT_OF_STOCK" });
|
|
6909
7534
|
if (!isOnSchedule(product.availability.schedules)) pushLineIssue(out, root.uid, { code: "OUTSIDE_HOURS" });
|
|
6910
7535
|
collectUnmatchedModifiers(lines, lookup, root.id, root.uid, out);
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
7536
|
+
let expected;
|
|
7537
|
+
if (hasRepeatedProductSibling(lines, root.uid)) {
|
|
7538
|
+
const placed = placeCartLines(root, lines, lookup, locale);
|
|
7539
|
+
if (placed.rulesBroken) pushLineIssue(out, root.uid, { code: "MODIFIER_RULES_INVALID" });
|
|
7540
|
+
expected = placed.lines;
|
|
7541
|
+
} else {
|
|
7542
|
+
const snapshot = fromOrderLines(root, lines, lookup);
|
|
7543
|
+
const session = createSession(lookup, root.id, { snapshot });
|
|
7544
|
+
if (session.validate() !== "VALID") pushLineIssue(out, root.uid, { code: "MODIFIER_RULES_INVALID" });
|
|
7545
|
+
expected = toOrderLines(session.root, locale);
|
|
7546
|
+
}
|
|
6915
7547
|
const expectedRoot = expected.find((line) => line.parent_uid == null);
|
|
6916
7548
|
if (!expectedRoot) {
|
|
6917
7549
|
pushLineIssue(out, root.uid, { code: "STRUCTURE_MISMATCH" });
|
|
@@ -7234,6 +7866,17 @@ const collectOrderLineDiffs = (input) => {
|
|
|
7234
7866
|
continue;
|
|
7235
7867
|
}
|
|
7236
7868
|
try {
|
|
7869
|
+
if (hasRepeatedProductSibling(input.lines, root.uid)) {
|
|
7870
|
+
const placed = placeCartLines(root, input.lines, lookup, locale);
|
|
7871
|
+
const next = placed.rulesBroken ? null : placed.lines;
|
|
7872
|
+
diffs.push({
|
|
7873
|
+
uid: root.uid,
|
|
7874
|
+
previous,
|
|
7875
|
+
next,
|
|
7876
|
+
issues
|
|
7877
|
+
});
|
|
7878
|
+
continue;
|
|
7879
|
+
}
|
|
7237
7880
|
const snapshot = fromOrderLines(root, input.lines, lookup);
|
|
7238
7881
|
const next = toOrderLines(createSession(lookup, root.id, { snapshot }).root, locale);
|
|
7239
7882
|
diffs.push({
|
|
@@ -7800,16 +8443,6 @@ const countCartProducts = (lines, includeModifiers) => {
|
|
|
7800
8443
|
return line.type === "PRODUCT" || line.type == null;
|
|
7801
8444
|
}).reduce((sum, line) => sum + line.quantity, 0);
|
|
7802
8445
|
};
|
|
7803
|
-
const getLineTagValues = (lines, tag) => {
|
|
7804
|
-
if (!tag) return [];
|
|
7805
|
-
const values = [];
|
|
7806
|
-
for (const line of lines ?? []) {
|
|
7807
|
-
const value = line.tags?.[tag];
|
|
7808
|
-
if (typeof value === "undefined") continue;
|
|
7809
|
-
values.push(value);
|
|
7810
|
-
}
|
|
7811
|
-
return values;
|
|
7812
|
-
};
|
|
7813
8446
|
//#endregion
|
|
7814
8447
|
//#region src/rules/absoluteDateMatcher.ts
|
|
7815
8448
|
var import_dayjs_min = /* @__PURE__ */ __toESM(require_dayjs_min(), 1);
|
|
@@ -8414,6 +9047,72 @@ const matchRelativeDateOperator = (condition, matcher, referenceDate) => {
|
|
|
8414
9047
|
}
|
|
8415
9048
|
};
|
|
8416
9049
|
//#endregion
|
|
9050
|
+
//#region src/rules/tagMatcher.ts
|
|
9051
|
+
const NEGATED_TO_POSITIVE = {
|
|
9052
|
+
"not equal to": "equal to",
|
|
9053
|
+
"is not": "is"
|
|
9054
|
+
};
|
|
9055
|
+
const SET_OPERATORS = [
|
|
9056
|
+
"is any of",
|
|
9057
|
+
"is all of",
|
|
9058
|
+
"is none of"
|
|
9059
|
+
];
|
|
9060
|
+
const isComparable = (kind, value) => {
|
|
9061
|
+
if (kind === "string") return typeof value === "string";
|
|
9062
|
+
if (kind === "boolean") return typeof value === "boolean";
|
|
9063
|
+
return typeof value === "number";
|
|
9064
|
+
};
|
|
9065
|
+
const normalize = (value, caseInsensitive) => {
|
|
9066
|
+
if (typeof value === "string" && caseInsensitive) return value.toLowerCase();
|
|
9067
|
+
return value;
|
|
9068
|
+
};
|
|
9069
|
+
const matchSet = (condition, kind, elements) => {
|
|
9070
|
+
const { operator, value } = condition;
|
|
9071
|
+
if (!Array.isArray(value)) throw new Error("Invalid tag set value");
|
|
9072
|
+
const caseInsensitive = getCaseInsensitive(condition);
|
|
9073
|
+
const wanted = new Set(value.map((item) => normalize(item, caseInsensitive)));
|
|
9074
|
+
const present = new Set(elements.map((item) => normalize(item, caseInsensitive)));
|
|
9075
|
+
const hasAny = elements.length > 0;
|
|
9076
|
+
switch (operator) {
|
|
9077
|
+
case "is any of": return [...present].some((item) => wanted.has(item));
|
|
9078
|
+
case "is all of": return [...wanted].every((item) => present.has(item));
|
|
9079
|
+
case "is none of": return !hasAny || ![...present].some((item) => wanted.has(item));
|
|
9080
|
+
default: throw new Error(`Operator ${operator} not supported in ${kind} tags`);
|
|
9081
|
+
}
|
|
9082
|
+
};
|
|
9083
|
+
/**
|
|
9084
|
+
* Evaluates one line's tag value against a tag condition.
|
|
9085
|
+
*
|
|
9086
|
+
* - Scalars use the primitive's matcher unchanged. A value of another type
|
|
9087
|
+
* never matches.
|
|
9088
|
+
* - Lists use "any element matches" for positive operators and "no element
|
|
9089
|
+
* matches the positive form" for negated ones. An empty list matches
|
|
9090
|
+
* negated operators and set operator "is none of", never the others.
|
|
9091
|
+
* - Set operators treat a scalar as a one-element set.
|
|
9092
|
+
*/
|
|
9093
|
+
const matchTagLine = (condition, kind, raw, matchScalar) => {
|
|
9094
|
+
const { operator } = condition;
|
|
9095
|
+
const isList = Array.isArray(raw);
|
|
9096
|
+
const items = isList ? raw : [raw];
|
|
9097
|
+
const comparable = items.filter((item) => isComparable(kind, item));
|
|
9098
|
+
if (SET_OPERATORS.includes(operator)) {
|
|
9099
|
+
if (kind === "boolean" || kind === "currency") return false;
|
|
9100
|
+
if (items.length > 0 && comparable.length === 0) return false;
|
|
9101
|
+
return matchSet(condition, kind, comparable);
|
|
9102
|
+
}
|
|
9103
|
+
const match = matchScalar;
|
|
9104
|
+
if (!isList) return comparable.length > 0 ? match(condition, raw) : false;
|
|
9105
|
+
const positive = NEGATED_TO_POSITIVE[operator];
|
|
9106
|
+
if (items.length === 0) return typeof positive === "string";
|
|
9107
|
+
if (comparable.length === 0) return false;
|
|
9108
|
+
if (typeof positive === "undefined") return comparable.some((item) => match(condition, item));
|
|
9109
|
+
const positiveCondition = {
|
|
9110
|
+
...condition,
|
|
9111
|
+
operator: positive
|
|
9112
|
+
};
|
|
9113
|
+
return !comparable.some((item) => match(positiveCondition, item));
|
|
9114
|
+
};
|
|
9115
|
+
//#endregion
|
|
8417
9116
|
//#region src/rules/RulesEngine.ts
|
|
8418
9117
|
var RulesEngine = class {
|
|
8419
9118
|
order;
|
|
@@ -8450,20 +9149,20 @@ var RulesEngine = class {
|
|
|
8450
9149
|
if (!("tag" in condition) || !condition.tag) throw new Error("Invalid tag");
|
|
8451
9150
|
return condition.tag;
|
|
8452
9151
|
};
|
|
8453
|
-
matchOrderProductTag = (condition,
|
|
9152
|
+
matchOrderProductTag = (condition, kind, matchScalar) => {
|
|
8454
9153
|
const order = this.getDependency("order", condition.type);
|
|
8455
9154
|
const tag = this.getProductTag(condition);
|
|
8456
|
-
const
|
|
8457
|
-
|
|
9155
|
+
for (const line of order.lines ?? []) {
|
|
9156
|
+
const raw = line.tags?.[tag];
|
|
9157
|
+
if (typeof raw === "undefined") continue;
|
|
9158
|
+
if (matchTagLine(condition, kind, raw, matchScalar)) return true;
|
|
9159
|
+
}
|
|
8458
9160
|
return false;
|
|
8459
9161
|
};
|
|
8460
9162
|
assertString = (condition) => {
|
|
8461
9163
|
const { primitive, type } = condition;
|
|
8462
9164
|
assert(primitive === "string", primitiveError("string"));
|
|
8463
|
-
if (type === "Order includes product with tag") return this.matchOrderProductTag(condition,
|
|
8464
|
-
if (typeof value !== "string") return false;
|
|
8465
|
-
return orderIncludesProductWithTag(condition, value);
|
|
8466
|
-
});
|
|
9165
|
+
if (type === "Order includes product with tag") return this.matchOrderProductTag(condition, "string", orderIncludesProductWithTag);
|
|
8467
9166
|
if (type === "Request path") {
|
|
8468
9167
|
const monitoring = this.getDependency("monitoring", type);
|
|
8469
9168
|
return requestPath(condition, monitoring.path);
|
|
@@ -8489,10 +9188,7 @@ var RulesEngine = class {
|
|
|
8489
9188
|
const line = getLineByProductId(order.lines, productId);
|
|
8490
9189
|
return orderIncludesProduct(condition, line?.quantity);
|
|
8491
9190
|
}
|
|
8492
|
-
if (type === "Order includes product with tag") return this.matchOrderProductTag(condition,
|
|
8493
|
-
if (typeof value !== "number") return false;
|
|
8494
|
-
return orderIncludesProductWithTag$1(condition, value);
|
|
8495
|
-
});
|
|
9191
|
+
if (type === "Order includes product with tag") return this.matchOrderProductTag(condition, "number", orderIncludesProductWithTag$1);
|
|
8496
9192
|
if (type === "Accumulated events") {
|
|
8497
9193
|
const monitoring = this.getDependency("monitoring", type);
|
|
8498
9194
|
const window = windowMinutes(condition);
|
|
@@ -8525,10 +9221,7 @@ var RulesEngine = class {
|
|
|
8525
9221
|
const isAnonymous = this.getDependency("customer", type).is_anonymous;
|
|
8526
9222
|
return isAnonymousCustomer(condition, isAnonymous);
|
|
8527
9223
|
}
|
|
8528
|
-
if (type === "Order includes product with tag") return this.matchOrderProductTag(condition,
|
|
8529
|
-
if (typeof value !== "boolean") return false;
|
|
8530
|
-
return orderIncludesProductWithTag$3(condition, value);
|
|
8531
|
-
});
|
|
9224
|
+
if (type === "Order includes product with tag") return this.matchOrderProductTag(condition, "boolean", orderIncludesProductWithTag$3);
|
|
8532
9225
|
if (type === "Payment is mixed") {
|
|
8533
9226
|
const payment = this.getDependency("payment", type);
|
|
8534
9227
|
return paymentIsMixed(condition, payment.is_mixed);
|
|
@@ -8546,10 +9239,7 @@ var RulesEngine = class {
|
|
|
8546
9239
|
const order = this.getDependency("order", type);
|
|
8547
9240
|
return orderTotal(condition, order.total);
|
|
8548
9241
|
}
|
|
8549
|
-
if (type === "Order includes product with tag") return this.matchOrderProductTag(condition,
|
|
8550
|
-
if (typeof value !== "number") return false;
|
|
8551
|
-
return orderIncludesProductWithTag$2(condition, value);
|
|
8552
|
-
});
|
|
9242
|
+
if (type === "Order includes product with tag") return this.matchOrderProductTag(condition, "currency", orderIncludesProductWithTag$2);
|
|
8553
9243
|
throw new Error(`Condition ${type} not supported in currency matcher`);
|
|
8554
9244
|
};
|
|
8555
9245
|
assertEnum = (condition) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fire-spark/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Fire Spark headless SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"big.js": "6.2.1",
|
|
21
21
|
"dayjs": "1.11.9",
|
|
22
22
|
"@types/big.js": "6.2.2",
|
|
23
|
-
"@fire-spark/payments": "^0.
|
|
24
|
-
"@fire-spark/types": "^0.
|
|
23
|
+
"@fire-spark/payments": "^0.12.0",
|
|
24
|
+
"@fire-spark/types": "^0.12.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"tsdown": "^0.22.14",
|