@fire-spark/core 0.12.3 → 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(",");
@@ -3934,11 +3945,20 @@ const redemptionTenderInputs = (redeem, providers = /* @__PURE__ */ new Map()) =
3934
3945
  };
3935
3946
  });
3936
3947
  };
3948
+ /**
3949
+ * `collect_at` when the caller omits it, same rule as the API place
3950
+ * default (D23): cash and the Fire Spark card terminal are collected in
3951
+ * person (`DOOR`); every other method is charged by the SDK (`DEVICE`).
3952
+ */
3953
+ const defaultTenderCollectAt = (provider, method) => {
3954
+ return isManualPayment(provider, method) ? "DOOR" : "DEVICE";
3955
+ };
3937
3956
  /** The one real tender of the simple flow (`allocation: null`). */
3938
3957
  const simpleTenderInput = (payment) => {
3939
3958
  if (!payment.type || !payment.provider) return null;
3940
3959
  if (!isRealPaymentMethod(payment.type)) return null;
3941
- const collect_at = payment.collect_at ?? "DEVICE";
3960
+ const fallback = defaultTenderCollectAt(payment.provider, payment.type);
3961
+ const collect_at = payment.collect_at ?? fallback;
3942
3962
  const t1 = {
3943
3963
  method: payment.type,
3944
3964
  provider: payment.provider
@@ -6634,7 +6654,8 @@ const selectionKey = (line, kidsOf, cache) => {
6634
6654
  return selectionKey(child, kidsOf, cache);
6635
6655
  });
6636
6656
  nested.sort((left, right) => left.localeCompare(right));
6637
- 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(",")})`;
6638
6659
  cache.set(line.uid, key);
6639
6660
  return key;
6640
6661
  };
@@ -6721,6 +6742,36 @@ const hasRepeatedProductSibling = (lines, rootUid) => {
6721
6742
  };
6722
6743
  return walk(rootUid);
6723
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
+ };
6724
6775
  const noteRules = (state, broken) => {
6725
6776
  if (broken) state.rulesBroken = true;
6726
6777
  };
@@ -6888,13 +6939,21 @@ const greedyAssignment = (plans) => {
6888
6939
  overCapacity
6889
6940
  };
6890
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
+ };
6891
6949
  const planKids = (lookup, groups, byKey, kids, prefix, state) => {
6892
6950
  const plans = [];
6893
6951
  let previous = "";
6894
6952
  for (const child of kids) {
6895
6953
  if (pathVisits(prefix, child.id)) continue;
6896
6954
  const ordered = lookup.productsById[child.id];
6897
- const listings = listingsFor(lookup, groups, child.id);
6955
+ const scope = groupsDeclared(groups, child);
6956
+ const listings = listingsFor(lookup, scope, child.id);
6898
6957
  if (!ordered || listings.length === 0) continue;
6899
6958
  const listed = [];
6900
6959
  for (const item of listings) {
@@ -7114,14 +7173,17 @@ const emit = (node, parentUid, parentId, locale, out) => {
7114
7173
  type: "PRODUCT",
7115
7174
  uid: randomUUID()
7116
7175
  };
7176
+ const id = node.product.id;
7177
+ const groupPatch = node.group ? { group_id: node.group.id } : {};
7117
7178
  const r2 = {
7118
- id: node.product.id,
7179
+ id,
7119
7180
  parent_uid: parentUid,
7120
7181
  parent_id: parentId
7121
7182
  };
7122
7183
  const r3 = {
7123
7184
  name: displayName(node.product.name, locale),
7124
- quantity: node.quantity
7185
+ quantity: node.quantity,
7186
+ ...groupPatch
7125
7187
  };
7126
7188
  const r4 = {
7127
7189
  redeem: [],
@@ -7312,10 +7374,25 @@ const childrenOf = (lines, parentUid) => {
7312
7374
  if (byShape !== 0) return byShape;
7313
7375
  const bySubtotal = new Big(a.unit_subtotal).cmp(b.unit_subtotal);
7314
7376
  if (bySubtotal !== 0) return bySubtotal;
7315
- 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 ?? "");
7316
7380
  });
7317
7381
  return children;
7318
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
+ };
7319
7396
  const collectUnmatchedModifiers = (lines, lookup, parentProductId, parentUid, out) => {
7320
7397
  const parent = lookup.productsById[parentProductId];
7321
7398
  if (!parent) return;
@@ -7327,9 +7404,14 @@ const collectUnmatchedModifiers = (lines, lookup, parentProductId, parentUid, ou
7327
7404
  pushLineIssue(out, child.uid, { code: "VARIANT_BASE_NOT_ORDERABLE" });
7328
7405
  continue;
7329
7406
  }
7330
- const match = matchModifierLine(lookup, parent, child.id);
7407
+ const match = matchModifierLine(...[
7408
+ lookup,
7409
+ parent,
7410
+ child.id,
7411
+ child.group_id
7412
+ ]);
7331
7413
  if (!match) {
7332
- pushLineIssue(out, child.uid, { code: "MODIFIER_NOT_FOUND" });
7414
+ pushLineIssue(out, child.uid, notFoundIssue(lookup, parent, child));
7333
7415
  continue;
7334
7416
  }
7335
7417
  const base = lookup.productsById[match.modifierId];
@@ -7472,6 +7554,13 @@ const compareTrees = (actualLines, expectedLines, actualUid, expectedUid, out) =
7472
7554
  });
7473
7555
  return;
7474
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
+ });
7475
7564
  if (actual.quantity !== expected.quantity) pushLineIssue(out, actual.uid, {
7476
7565
  code: "QUANTITY_MISMATCH",
7477
7566
  field: "quantity",
@@ -7557,7 +7646,7 @@ const validateProductRoot = (root, lines, lookup, locale, out) => {
7557
7646
  if (!isOnSchedule(product.availability.schedules)) pushLineIssue(out, root.uid, { code: "OUTSIDE_HOURS" });
7558
7647
  collectUnmatchedModifiers(lines, lookup, root.id, root.uid, out);
7559
7648
  let expected;
7560
- if (hasRepeatedProductSibling(lines, root.uid)) {
7649
+ if (needsSlotPlacement(lines, root.uid, lookup)) {
7561
7650
  const placed = placeCartLines(root, lines, lookup, locale);
7562
7651
  if (placed.rulesBroken) pushLineIssue(out, root.uid, { code: "MODIFIER_RULES_INVALID" });
7563
7652
  expected = placed.lines;
@@ -7889,7 +7978,7 @@ const collectOrderLineDiffs = (input) => {
7889
7978
  continue;
7890
7979
  }
7891
7980
  try {
7892
- if (hasRepeatedProductSibling(input.lines, root.uid)) {
7981
+ if (needsSlotPlacement(input.lines, root.uid, lookup)) {
7893
7982
  const placed = placeCartLines(root, input.lines, lookup, locale);
7894
7983
  const next = placed.rulesBroken ? null : placed.lines;
7895
7984
  diffs.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fire-spark/core",
3
- "version": "0.12.3",
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.3",
24
- "@fire-spark/types": "^0.12.3"
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",