@fire-spark/core 0.12.4 → 0.12.5

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.ts CHANGED
@@ -941,6 +941,7 @@ declare const capRedeems: <T extends CapOrder>(order: T) => T & {
941
941
  id: string;
942
942
  parent_uid: string | null;
943
943
  parent_id: string | null;
944
+ group_id?: string | undefined;
944
945
  name: string;
945
946
  image_url?: string | undefined;
946
947
  menu_id?: string | undefined;
package/dist/index.js CHANGED
@@ -1415,9 +1415,11 @@ const flattenMenuCategories = (categories, categoriesById, options) => {
1415
1415
  //#endregion
1416
1416
  //#region src/productCustomization/fromOrderLines.ts
1417
1417
  /** Group slot of a child line: listed directly, or as the variant of a base
1418
- * listed in one of the parent's modifier groups. */
1419
- const matchModifierLine = (lookup, parent, childId) => {
1418
+ * listed in one of the parent's modifier groups. With `onlyGroupId` (the
1419
+ * line's declared `group_id`) only that group of this parent is searched. */
1420
+ const matchModifierLine = (lookup, parent, childId, onlyGroupId) => {
1420
1421
  const groups = parent.pricing.modifier_group_ids.flatMap((groupId) => {
1422
+ if (onlyGroupId !== void 0 && groupId !== onlyGroupId) return [];
1421
1423
  const group = lookup.modifierGroupsById[groupId];
1422
1424
  return group ? [group] : [];
1423
1425
  });
@@ -1463,7 +1465,13 @@ const fromOrderLines = (root, lines, lookup) => {
1463
1465
  const parentProduct = lookup.productsById[parentLine.id];
1464
1466
  if (!parentProduct) return;
1465
1467
  for (const child of childrenByParent.get(parentLine.uid) ?? []) {
1466
- const match = matchModifierLine(lookup, parentProduct, child.id);
1468
+ const p = [
1469
+ lookup,
1470
+ parentProduct,
1471
+ child.id,
1472
+ child.group_id
1473
+ ];
1474
+ const match = matchModifierLine(...p);
1467
1475
  if (!match) continue;
1468
1476
  const { groupId, modifierId, selection } = match;
1469
1477
  const path = `${pathPrefix}/group/${groupId}/modifier/${modifierId}`;
@@ -1750,6 +1758,7 @@ const buildLine = (args) => {
1750
1758
  name: args.name
1751
1759
  };
1752
1760
  const r4 = {
1761
+ ...args.group_id ? { group_id: args.group_id } : {},
1753
1762
  ...imagePatch,
1754
1763
  ...catalog
1755
1764
  };
@@ -1835,7 +1844,8 @@ const appendModifierLine = (modifier, groupId, parent, locale) => {
1835
1844
  const line = buildLine({
1836
1845
  ...l1,
1837
1846
  ...l2,
1838
- ...l3
1847
+ ...l3,
1848
+ group_id: groupId
1839
1849
  });
1840
1850
  parent.children.push(line);
1841
1851
  if (modifier.product) appendGroupLines(modifier.product.groups, line, locale);
@@ -3687,7 +3697,8 @@ const computeLineFingerprint = (root, lines) => {
3687
3697
  const keys = lines.filter((line) => {
3688
3698
  return line.parent_uid === parentUid;
3689
3699
  }).map((line) => {
3690
- return `${line.id}x${line.quantity}(${childKeys(line.uid)})`;
3700
+ const slot = line.group_id ? `@${line.group_id}` : "";
3701
+ return `${line.id}${slot}x${line.quantity}(${childKeys(line.uid)})`;
3691
3702
  });
3692
3703
  keys.sort((left, right) => left.localeCompare(right));
3693
3704
  return keys.join(",");
@@ -6643,7 +6654,8 @@ const selectionKey = (line, kidsOf, cache) => {
6643
6654
  return selectionKey(child, kidsOf, cache);
6644
6655
  });
6645
6656
  nested.sort((left, right) => left.localeCompare(right));
6646
- const key = `${line.id}x${line.quantity}(${nested.join(",")})`;
6657
+ const slot = line.group_id ? `@${line.group_id}` : "";
6658
+ const key = `${line.id}${slot}x${line.quantity}(${nested.join(",")})`;
6647
6659
  cache.set(line.uid, key);
6648
6660
  return key;
6649
6661
  };
@@ -6730,6 +6742,36 @@ const hasRepeatedProductSibling = (lines, rootUid) => {
6730
6742
  };
6731
6743
  return walk(rootUid);
6732
6744
  };
6745
+ /** A line whose product the parent's menu lists in more than one slot, at
6746
+ * any depth. Cart lines carry no group id, so the slot is chosen by
6747
+ * `placeCartLines`; with a single listing the session path is unambiguous. */
6748
+ const hasMultiSlotChild = (lines, rootUid, lookup) => {
6749
+ const kidsOf = /* @__PURE__ */ new Map();
6750
+ for (const line of lines) {
6751
+ if (line.parent_uid == null) continue;
6752
+ const list = kidsOf.get(line.parent_uid) ?? [];
6753
+ list.push(line);
6754
+ kidsOf.set(line.parent_uid, list);
6755
+ }
6756
+ const walk = (line) => {
6757
+ const parent = lookup.productsById[line.id];
6758
+ const groups = parent ? parentGroups(lookup, parent) : [];
6759
+ for (const kid of kidsOf.get(line.uid) ?? []) {
6760
+ const ids = listingsFor(lookup, groups, kid.id).map((row) => slotKey(row.group.id, row.modifierId));
6761
+ if (new Set(ids).size > 1) return true;
6762
+ if (walk(kid)) return true;
6763
+ }
6764
+ return false;
6765
+ };
6766
+ const root = lines.find((line) => line.uid === rootUid);
6767
+ return root ? walk(root) : false;
6768
+ };
6769
+ /** Cart shapes the slot search must place: a repeated sibling product, or
6770
+ * a product listed in several slots. Everything else keeps the session. */
6771
+ const needsSlotPlacement = (lines, rootUid, lookup) => {
6772
+ if (hasRepeatedProductSibling(lines, rootUid)) return true;
6773
+ return hasMultiSlotChild(lines, rootUid, lookup);
6774
+ };
6733
6775
  const noteRules = (state, broken) => {
6734
6776
  if (broken) state.rulesBroken = true;
6735
6777
  };
@@ -6897,13 +6939,21 @@ const greedyAssignment = (plans) => {
6897
6939
  overCapacity
6898
6940
  };
6899
6941
  };
6942
+ /** The groups a line may sit in under this parent: only its declared
6943
+ * `group_id` when it names one, else every group of the parent. A declared id
6944
+ * the parent does not own leaves none, so the line is never placed. */
6945
+ const groupsDeclared = (groups, line) => {
6946
+ if (line.group_id === void 0) return groups;
6947
+ return groups.filter((group) => group.id === line.group_id);
6948
+ };
6900
6949
  const planKids = (lookup, groups, byKey, kids, prefix, state) => {
6901
6950
  const plans = [];
6902
6951
  let previous = "";
6903
6952
  for (const child of kids) {
6904
6953
  if (pathVisits(prefix, child.id)) continue;
6905
6954
  const ordered = lookup.productsById[child.id];
6906
- const listings = listingsFor(lookup, groups, child.id);
6955
+ const scope = groupsDeclared(groups, child);
6956
+ const listings = listingsFor(lookup, scope, child.id);
6907
6957
  if (!ordered || listings.length === 0) continue;
6908
6958
  const listed = [];
6909
6959
  for (const item of listings) {
@@ -7123,14 +7173,17 @@ const emit = (node, parentUid, parentId, locale, out) => {
7123
7173
  type: "PRODUCT",
7124
7174
  uid: randomUUID()
7125
7175
  };
7176
+ const id = node.product.id;
7177
+ const groupPatch = node.group ? { group_id: node.group.id } : {};
7126
7178
  const r2 = {
7127
- id: node.product.id,
7179
+ id,
7128
7180
  parent_uid: parentUid,
7129
7181
  parent_id: parentId
7130
7182
  };
7131
7183
  const r3 = {
7132
7184
  name: displayName(node.product.name, locale),
7133
- quantity: node.quantity
7185
+ quantity: node.quantity,
7186
+ ...groupPatch
7134
7187
  };
7135
7188
  const r4 = {
7136
7189
  redeem: [],
@@ -7321,10 +7374,25 @@ const childrenOf = (lines, parentUid) => {
7321
7374
  if (byShape !== 0) return byShape;
7322
7375
  const bySubtotal = new Big(a.unit_subtotal).cmp(b.unit_subtotal);
7323
7376
  if (bySubtotal !== 0) return bySubtotal;
7324
- return new Big(a.unit_tax_total).cmp(b.unit_tax_total);
7377
+ const byTax = new Big(a.unit_tax_total).cmp(b.unit_tax_total);
7378
+ if (byTax !== 0) return byTax;
7379
+ return (a.group_id ?? "").localeCompare(b.group_id ?? "");
7325
7380
  });
7326
7381
  return children;
7327
7382
  };
7383
+ /** A declared `group_id` that the parent's menu does not back (unknown under
7384
+ * this parent, or it does not hold the product) names the field; a line that
7385
+ * is no modifier of its parent at all keeps the plain code. */
7386
+ const notFoundIssue = (lookup, parent, child) => {
7387
+ const code = "MODIFIER_NOT_FOUND";
7388
+ if (child.group_id === void 0) return { code };
7389
+ if (!matchModifierLine(lookup, parent, child.id)) return { code };
7390
+ return {
7391
+ code,
7392
+ field: "group_id",
7393
+ actual: child.group_id
7394
+ };
7395
+ };
7328
7396
  const collectUnmatchedModifiers = (lines, lookup, parentProductId, parentUid, out) => {
7329
7397
  const parent = lookup.productsById[parentProductId];
7330
7398
  if (!parent) return;
@@ -7336,9 +7404,14 @@ const collectUnmatchedModifiers = (lines, lookup, parentProductId, parentUid, ou
7336
7404
  pushLineIssue(out, child.uid, { code: "VARIANT_BASE_NOT_ORDERABLE" });
7337
7405
  continue;
7338
7406
  }
7339
- const match = matchModifierLine(lookup, parent, child.id);
7407
+ const match = matchModifierLine(...[
7408
+ lookup,
7409
+ parent,
7410
+ child.id,
7411
+ child.group_id
7412
+ ]);
7340
7413
  if (!match) {
7341
- pushLineIssue(out, child.uid, { code: "MODIFIER_NOT_FOUND" });
7414
+ pushLineIssue(out, child.uid, notFoundIssue(lookup, parent, child));
7342
7415
  continue;
7343
7416
  }
7344
7417
  const base = lookup.productsById[match.modifierId];
@@ -7481,6 +7554,13 @@ const compareTrees = (actualLines, expectedLines, actualUid, expectedUid, out) =
7481
7554
  });
7482
7555
  return;
7483
7556
  }
7557
+ const declared = actual.group_id;
7558
+ if (declared !== void 0 && declared !== expected.group_id) pushLineIssue(out, actual.uid, {
7559
+ code: "STRUCTURE_MISMATCH",
7560
+ field: "group_id",
7561
+ expected: expected.group_id,
7562
+ actual: declared
7563
+ });
7484
7564
  if (actual.quantity !== expected.quantity) pushLineIssue(out, actual.uid, {
7485
7565
  code: "QUANTITY_MISMATCH",
7486
7566
  field: "quantity",
@@ -7566,7 +7646,7 @@ const validateProductRoot = (root, lines, lookup, locale, out) => {
7566
7646
  if (!isOnSchedule(product.availability.schedules)) pushLineIssue(out, root.uid, { code: "OUTSIDE_HOURS" });
7567
7647
  collectUnmatchedModifiers(lines, lookup, root.id, root.uid, out);
7568
7648
  let expected;
7569
- if (hasRepeatedProductSibling(lines, root.uid)) {
7649
+ if (needsSlotPlacement(lines, root.uid, lookup)) {
7570
7650
  const placed = placeCartLines(root, lines, lookup, locale);
7571
7651
  if (placed.rulesBroken) pushLineIssue(out, root.uid, { code: "MODIFIER_RULES_INVALID" });
7572
7652
  expected = placed.lines;
@@ -7898,7 +7978,7 @@ const collectOrderLineDiffs = (input) => {
7898
7978
  continue;
7899
7979
  }
7900
7980
  try {
7901
- if (hasRepeatedProductSibling(input.lines, root.uid)) {
7981
+ if (needsSlotPlacement(input.lines, root.uid, lookup)) {
7902
7982
  const placed = placeCartLines(root, input.lines, lookup, locale);
7903
7983
  const next = placed.rulesBroken ? null : placed.lines;
7904
7984
  diffs.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fire-spark/core",
3
- "version": "0.12.4",
3
+ "version": "0.12.5",
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.12.4",
24
- "@fire-spark/types": "^0.12.4"
23
+ "@fire-spark/payments": "^0.12.5",
24
+ "@fire-spark/types": "^0.12.5"
25
25
  },
26
26
  "devDependencies": {
27
27
  "tsdown": "^0.22.14",