@final-commerce/common 2.1.0 → 2.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -31,6 +31,8 @@ __export(index_exports, {
31
31
  DEFAULT_CROSS_AXIS_RULES: () => DEFAULT_CROSS_AXIS_RULES,
32
32
  DEFAULT_DISPLAY_STATE_MAP: () => DEFAULT_DISPLAY_STATE_MAP,
33
33
  DEFAULT_RESOLVED_CONFIG: () => DEFAULT_RESOLVED_CONFIG,
34
+ DEFAULT_UNIT_ID: () => DEFAULT_UNIT_ID,
35
+ DualWriteStrategy: () => DualWriteStrategy,
34
36
  EUFixedRates: () => EUFixedRates,
35
37
  EUROPE: () => EUROPE,
36
38
  ExtensionCategory: () => ExtensionCategory,
@@ -40,7 +42,9 @@ __export(index_exports, {
40
42
  InventoryBaseActionEnum: () => InventoryBaseActionEnum,
41
43
  InventorySpecificActionType: () => InventorySpecificActionType,
42
44
  InvoiceStatus: () => InvoiceStatus,
45
+ LegacyWriteStrategy: () => LegacyWriteStrategy,
43
46
  LibraryItemType: () => LibraryItemType,
47
+ MAX_PRECISION: () => MAX_PRECISION,
44
48
  MenuProject: () => MenuProject,
45
49
  NAFixedRates: () => NAFixedRates,
46
50
  NORTH_AMERICA: () => NORTH_AMERICA,
@@ -73,6 +77,10 @@ __export(index_exports, {
73
77
  ResidualStatus: () => ResidualStatus,
74
78
  STATE_SCHEMA: () => STATE_SCHEMA,
75
79
  STRIPE_SUPPORTED_COUNTRIES: () => STRIPE_SUPPORTED_COUNTRIES,
80
+ SYSTEM_FAMILIES: () => SYSTEM_FAMILIES,
81
+ SYSTEM_FAMILY_IDS: () => SYSTEM_FAMILY_IDS,
82
+ SYSTEM_UNITS: () => SYSTEM_UNITS,
83
+ SYSTEM_UNIT_IDS: () => SYSTEM_UNIT_IDS,
76
84
  StationStatus: () => StationStatus,
77
85
  StripeCurrencyCode: () => StripeCurrencyCode,
78
86
  StripeStatus: () => StripeStatus,
@@ -81,7 +89,11 @@ __export(index_exports, {
81
89
  UserTypes: () => UserTypes,
82
90
  addAmounts: () => addAmounts,
83
91
  amountToString: () => amountToString,
92
+ assertUnitSane: () => assertUnitSane,
93
+ availableIn: () => availableIn,
94
+ baseFor: () => baseFor,
84
95
  baseFulfillmentDefinitions: () => baseFulfillmentDefinitions,
96
+ baseOf: () => baseOf,
85
97
  basePaymentDefinitions: () => basePaymentDefinitions,
86
98
  buildOrderContext: () => buildOrderContext,
87
99
  buildPosPresetConfig: () => buildPosPresetConfig,
@@ -93,12 +105,19 @@ __export(index_exports, {
93
105
  createAmount: () => createAmount,
94
106
  crossAxisRuleApplies: () => crossAxisRuleApplies,
95
107
  deriveDisplayState: () => deriveDisplayState,
108
+ deriveFamilyCode: () => deriveFamilyCode,
109
+ deriveUnitId: () => deriveUnitId,
96
110
  displayRuleMatchesPair: () => displayRuleMatchesPair,
97
111
  divideAmount: () => divideAmount,
98
112
  evaluateCondition: () => evaluateCondition,
99
113
  evaluateConditionSet: () => evaluateConditionSet,
114
+ extendPrice: () => extendPrice,
115
+ extendPriceFor: () => extendPriceFor,
100
116
  formatCurrency: () => formatCurrency,
117
+ formatInUnit: () => formatInUnit,
101
118
  formatMinorUnitsToString: () => formatMinorUnitsToString,
119
+ formatQuantity: () => formatQuantity,
120
+ fromBase: () => fromBase,
102
121
  fromMinorUnits: () => fromMinorUnits,
103
122
  generateMongoID: () => generateMongoID,
104
123
  getAvailableTransitions: () => getAvailableTransitions,
@@ -120,7 +139,11 @@ __export(index_exports, {
120
139
  isFulfillmentState: () => isFulfillmentState,
121
140
  isPaymentState: () => isPaymentState,
122
141
  isSupportedCurrency: () => isSupportedCurrency,
142
+ isSystemFamilyCode: () => isSystemFamilyCode,
143
+ isValidQuantity: () => isValidQuantity,
144
+ isWhole: () => isWhole,
123
145
  mapToLegacyStatus: () => mapToLegacyStatus,
146
+ maxPrecisionFor: () => maxPrecisionFor,
124
147
  mergeFragment: () => mergeFragment,
125
148
  multiplyAmount: () => multiplyAmount,
126
149
  normalizeMongoIdString: () => normalizeMongoIdString,
@@ -128,11 +151,16 @@ __export(index_exports, {
128
151
  parseCurrencyCode: () => parseCurrencyCode,
129
152
  parseStripeCurrencyCode: () => parseStripeCurrencyCode,
130
153
  removeFragment: () => removeFragment,
154
+ resolveFamily: () => resolveFamily,
131
155
  resolveTemplate: () => resolveTemplate,
156
+ resolveUnit: () => resolveUnit,
157
+ roundToPrecision: () => roundToPrecision,
158
+ sameFamily: () => sameFamily,
132
159
  setOrderState: () => setOrderState,
133
160
  signReportForDisplay: () => signReportForDisplay,
134
161
  stringifyMinorUnits: () => stringifyMinorUnits,
135
162
  subtractAmounts: () => subtractAmounts,
163
+ toBase: () => toBase,
136
164
  toMinorUnits: () => toMinorUnits,
137
165
  validateConfig: () => validateConfig,
138
166
  validateFragmentCompatibility: () => validateFragmentCompatibility,
@@ -370,6 +398,7 @@ var InventorySpecificActionType = /* @__PURE__ */ ((InventorySpecificActionType2
370
398
  InventorySpecificActionType2["REFUND_RESTOCK_RETURN"] = "REFUND_RESTOCK_RETURN";
371
399
  InventorySpecificActionType2["REFUND_DAMAGE"] = "REFUND_DAMAGE";
372
400
  InventorySpecificActionType2["SALE"] = "SALE";
401
+ InventorySpecificActionType2["COMPOSED_FOR_SALE"] = "COMPOSED_FOR_SALE";
373
402
  InventorySpecificActionType2["TRANSFER"] = "TRANSFER";
374
403
  InventorySpecificActionType2["BULK_RECOUNT"] = "BULK_RECOUNT";
375
404
  InventorySpecificActionType2["APPLIED_FROM_WOO"] = "APPLIED_FROM_WOO";
@@ -859,6 +888,252 @@ function amountToString(amount) {
859
888
  return majorValue.toFixed(amount.minorUnits);
860
889
  }
861
890
 
891
+ // units/system-units.ts
892
+ var SYSTEM_FAMILY_IDS = {
893
+ COUNT: "count",
894
+ WEIGHT: "weight-metric",
895
+ WEIGHT_IMPERIAL: "weight-imperial",
896
+ VOLUME: "volume-metric",
897
+ VOLUME_IMPERIAL: "volume-imperial",
898
+ LENGTH: "length-metric",
899
+ LENGTH_IMPERIAL: "length-imperial",
900
+ AREA: "area-metric",
901
+ AREA_IMPERIAL: "area-imperial"
902
+ };
903
+ var SYSTEM_UNIT_IDS = {
904
+ EACH: "EA",
905
+ GRAM: "GRM",
906
+ KILOGRAM: "KGM",
907
+ /** 100 g is a hectogram; the merchant-facing name stays "100 g". */
908
+ HECTOGRAM: "HGM",
909
+ MILLILITRE: "MLT",
910
+ LITRE: "LTR"
911
+ };
912
+ var SYSTEM_FAMILIES = [
913
+ {
914
+ familyId: SYSTEM_FAMILY_IDS.COUNT,
915
+ companyId: null,
916
+ name: "Count",
917
+ base: { name: "Piece", abbreviation: "ea", ratioToBase: 1, precision: 0 }
918
+ },
919
+ {
920
+ familyId: SYSTEM_FAMILY_IDS.WEIGHT,
921
+ companyId: null,
922
+ name: "Weight",
923
+ base: { name: "Gram", abbreviation: "g", ratioToBase: 1, precision: 0 }
924
+ },
925
+ {
926
+ familyId: SYSTEM_FAMILY_IDS.WEIGHT_IMPERIAL,
927
+ companyId: null,
928
+ name: "Weight (imperial)",
929
+ base: { name: "Hundred-thousandth of an ounce", abbreviation: "10\u207B\u2075 oz", ratioToBase: 1, precision: 0 }
930
+ },
931
+ {
932
+ familyId: SYSTEM_FAMILY_IDS.VOLUME,
933
+ companyId: null,
934
+ name: "Volume",
935
+ base: { name: "Millilitre", abbreviation: "ml", ratioToBase: 1, precision: 0 }
936
+ },
937
+ {
938
+ // Cubic inches, not fluid ounces: a US gallon is 231 in³ and 128 fl oz, and 231 = 3 × 7 × 11.
939
+ // With an ounce base the board foot lands on 6144000/77 — and lumber is the reason this family
940
+ // exists at all.
941
+ familyId: SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL,
942
+ companyId: null,
943
+ name: "Volume (imperial)",
944
+ base: { name: "1/128000 cubic inch", abbreviation: "\xB5in\xB3", ratioToBase: 1, precision: 0 }
945
+ },
946
+ {
947
+ familyId: SYSTEM_FAMILY_IDS.LENGTH,
948
+ companyId: null,
949
+ name: "Length",
950
+ base: { name: "Millimetre", abbreviation: "mm", ratioToBase: 1, precision: 0 }
951
+ },
952
+ {
953
+ familyId: SYSTEM_FAMILY_IDS.LENGTH_IMPERIAL,
954
+ companyId: null,
955
+ name: "Length (imperial)",
956
+ base: { name: "Millionth of an inch", abbreviation: "\xB5in", ratioToBase: 1, precision: 0 }
957
+ },
958
+ {
959
+ familyId: SYSTEM_FAMILY_IDS.AREA,
960
+ companyId: null,
961
+ name: "Area",
962
+ base: { name: "Square millimetre", abbreviation: "mm\xB2", ratioToBase: 1, precision: 0 }
963
+ },
964
+ {
965
+ familyId: SYSTEM_FAMILY_IDS.AREA_IMPERIAL,
966
+ companyId: null,
967
+ name: "Area (imperial)",
968
+ base: { name: "Thousandth of a square inch", abbreviation: "m-sq in", ratioToBase: 1, precision: 0 }
969
+ }
970
+ ];
971
+ var unit = (unitId, familyId, name, abbreviation, ratioToBase, precision) => ({ unitId, companyId: null, familyId, name, abbreviation, ratioToBase, precision });
972
+ var SELLABLE_UNITS = [
973
+ // ── Count ────────────────────────────────────────────────────────────────
974
+ unit("EA", SYSTEM_FAMILY_IDS.COUNT, "Each", "ea", 1, 0),
975
+ unit("PR", SYSTEM_FAMILY_IDS.COUNT, "Pair", "pr", 2, 0),
976
+ unit("DZN", SYSTEM_FAMILY_IDS.COUNT, "Dozen", "dz", 12, 0),
977
+ unit("GRO", SYSTEM_FAMILY_IDS.COUNT, "Gross", "gro", 144, 0),
978
+ unit("CEN", SYSTEM_FAMILY_IDS.COUNT, "Hundred", "C", 100, 0),
979
+ unit("MIL", SYSTEM_FAMILY_IDS.COUNT, "Thousand", "M", 1e3, 0),
980
+ // ── Weight, metric ───────────────────────────────────────────────────────
981
+ unit("GRM", SYSTEM_FAMILY_IDS.WEIGHT, "Gram", "g", 1, 0),
982
+ unit("HGM", SYSTEM_FAMILY_IDS.WEIGHT, "100 g", "100g", 100, 2),
983
+ unit("KGM", SYSTEM_FAMILY_IDS.WEIGHT, "Kilogram", "kg", 1e3, 3),
984
+ unit("TNE", SYSTEM_FAMILY_IDS.WEIGHT, "Tonne", "t", 1e6, 3),
985
+ // ── Weight, imperial ─────────────────────────────────────────────────────
986
+ unit("ONZ", SYSTEM_FAMILY_IDS.WEIGHT_IMPERIAL, "Ounce", "oz", 1e5, 2),
987
+ unit("LBR", SYSTEM_FAMILY_IDS.WEIGHT_IMPERIAL, "Pound", "lb", 16e5, 2),
988
+ unit("STI", SYSTEM_FAMILY_IDS.WEIGHT_IMPERIAL, "Stone", "st", 224e5, 2),
989
+ // `(US)` is not decoration: Rec 20 also carries `CWI`, the UK hundredweight, at 112 lb — 12 % more.
990
+ // The code here is the right one; an unqualified "cwt" on screen is what would be ambiguous.
991
+ unit("CWA", SYSTEM_FAMILY_IDS.WEIGHT_IMPERIAL, "Hundredweight (US)", "cwt (US)", 16e7, 2),
992
+ unit("STN", SYSTEM_FAMILY_IDS.WEIGHT_IMPERIAL, "Ton (short)", "tn", 32e8, 3),
993
+ // ── Volume, metric ───────────────────────────────────────────────────────
994
+ unit("MLT", SYSTEM_FAMILY_IDS.VOLUME, "Millilitre", "ml", 1, 0),
995
+ unit("CLT", SYSTEM_FAMILY_IDS.VOLUME, "Centilitre", "cl", 10, 1),
996
+ unit("LTR", SYSTEM_FAMILY_IDS.VOLUME, "Litre", "l", 1e3, 3),
997
+ unit("HLT", SYSTEM_FAMILY_IDS.VOLUME, "Hectolitre", "hl", 1e5, 3),
998
+ unit("MTQ", SYSTEM_FAMILY_IDS.VOLUME, "Cubic metre", "m\xB3", 1e6, 3),
999
+ // ── Volume, imperial ─────────────────────────────────────────────────────
1000
+ unit("OZA", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Fluid ounce", "fl oz", 231e3, 2),
1001
+ unit("PTL", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Pint", "pt", 3696e3, 2),
1002
+ unit("QTL", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Quart", "qt", 7392e3, 2),
1003
+ unit("GLL", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Gallon", "gal", 29568e3, 3),
1004
+ unit("INQ", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Cubic inch", "in\xB3", 128e3, 2),
1005
+ unit("BFT", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Board foot", "fbm", 18432e3, 2),
1006
+ unit("FTQ", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Cubic foot", "ft\xB3", 221184e3, 3),
1007
+ unit("YDQ", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Cubic yard", "yd\xB3", 5971968e3, 3),
1008
+ // ── Length, metric ───────────────────────────────────────────────────────
1009
+ unit("MMT", SYSTEM_FAMILY_IDS.LENGTH, "Millimetre", "mm", 1, 0),
1010
+ unit("CMT", SYSTEM_FAMILY_IDS.LENGTH, "Centimetre", "cm", 10, 1),
1011
+ unit("MTR", SYSTEM_FAMILY_IDS.LENGTH, "Metre", "m", 1e3, 3),
1012
+ unit("KMT", SYSTEM_FAMILY_IDS.LENGTH, "Kilometre", "km", 1e6, 3),
1013
+ // ── Length, imperial ─────────────────────────────────────────────────────
1014
+ // `INH` carries four decimals, not three: an eighth of an inch is 0.125 but a sixteenth is
1015
+ // 0.0625, and sixteenths are the everyday fraction on a tape measure. A thirty-second needs five,
1016
+ // which is the ceiling; a sixty-fourth has no decimal form at all and is a question about
1017
+ // fraction input, not about precision.
1018
+ unit("INH", SYSTEM_FAMILY_IDS.LENGTH_IMPERIAL, "Inch", "in", 1e6, 4),
1019
+ unit("FOT", SYSTEM_FAMILY_IDS.LENGTH_IMPERIAL, "Foot", "ft", 12e6, 3),
1020
+ unit("LF", SYSTEM_FAMILY_IDS.LENGTH_IMPERIAL, "Linear foot", "lin ft", 12e6, 2),
1021
+ unit("YRD", SYSTEM_FAMILY_IDS.LENGTH_IMPERIAL, "Yard", "yd", 36e6, 3),
1022
+ unit("SMI", SYSTEM_FAMILY_IDS.LENGTH_IMPERIAL, "Mile", "mi", 6336e7, 3),
1023
+ // ── Area, metric ─────────────────────────────────────────────────────────
1024
+ unit("MMK", SYSTEM_FAMILY_IDS.AREA, "Square millimetre", "mm\xB2", 1, 0),
1025
+ unit("CMK", SYSTEM_FAMILY_IDS.AREA, "Square centimetre", "cm\xB2", 100, 2),
1026
+ unit("MTK", SYSTEM_FAMILY_IDS.AREA, "Square metre", "m\xB2", 1e6, 3),
1027
+ unit("HAR", SYSTEM_FAMILY_IDS.AREA, "Hectare", "ha", 1e10, 3),
1028
+ // ── Area, imperial ───────────────────────────────────────────────────────
1029
+ unit("INK", SYSTEM_FAMILY_IDS.AREA_IMPERIAL, "Square inch", "sq in", 1e3, 2),
1030
+ unit("FTK", SYSTEM_FAMILY_IDS.AREA_IMPERIAL, "Square foot", "sq ft", 144e3, 2),
1031
+ unit("YDK", SYSTEM_FAMILY_IDS.AREA_IMPERIAL, "Square yard", "sq yd", 1296e3, 2),
1032
+ // A roofing square is exactly 100 square feet — a trade unit with a precise definition, which is
1033
+ // why it is a real row and not a label.
1034
+ unit("SQR", SYSTEM_FAMILY_IDS.AREA_IMPERIAL, "Square (roofing)", "sq", 144e5, 2),
1035
+ unit("ACR", SYSTEM_FAMILY_IDS.AREA_IMPERIAL, "Acre", "ac", 627264e4, 2)
1036
+ ];
1037
+ var BASE_UNITS = SYSTEM_FAMILIES.filter(
1038
+ (family) => !SELLABLE_UNITS.some((u) => u.familyId === family.familyId && u.ratioToBase === 1)
1039
+ ).map((family) => ({
1040
+ unitId: `${family.familyId}-base`,
1041
+ companyId: null,
1042
+ familyId: family.familyId,
1043
+ name: family.base.name,
1044
+ abbreviation: family.base.abbreviation,
1045
+ ratioToBase: 1,
1046
+ precision: 0,
1047
+ sellable: false
1048
+ }));
1049
+ var SYSTEM_UNITS = [...SELLABLE_UNITS, ...BASE_UNITS];
1050
+ var DEFAULT_UNIT_ID = SYSTEM_UNIT_IDS.EACH;
1051
+
1052
+ // units/convert.ts
1053
+ var EPSILON = 1e-9;
1054
+ function isWhole(value) {
1055
+ return Math.abs(value - Math.round(value)) <= EPSILON * Math.max(1, Math.abs(value));
1056
+ }
1057
+ var MAX_PRECISION = 5;
1058
+ function resolveUnit(unitId, companyUnits = []) {
1059
+ const unit2 = SYSTEM_UNITS.find((u) => u.unitId === unitId) ?? companyUnits.find((u) => u.unitId === unitId);
1060
+ if (!unit2) throw new Error(`Unknown measurement unit: ${unitId}`);
1061
+ return unit2;
1062
+ }
1063
+ function resolveFamily(familyId, companyFamilies = []) {
1064
+ const family = SYSTEM_FAMILIES.find((f) => f.familyId === familyId) ?? companyFamilies.find((f) => f.familyId === familyId);
1065
+ if (!family) throw new Error(`Unknown measurement unit family: ${familyId}`);
1066
+ return family;
1067
+ }
1068
+ function baseOf(familyId, companyFamilies = []) {
1069
+ return resolveFamily(familyId, companyFamilies).base;
1070
+ }
1071
+ function baseFor(unit2, companyFamilies = []) {
1072
+ return baseOf(unit2.familyId, companyFamilies);
1073
+ }
1074
+ function sameFamily(a, b) {
1075
+ return a.familyId === b.familyId;
1076
+ }
1077
+ function assertUnitSane(unit2) {
1078
+ const { ratioToBase, precision, name } = unit2;
1079
+ if (!Number.isInteger(ratioToBase) || ratioToBase < 1)
1080
+ throw new Error(`Unit "${name}": ratioToBase must be a positive integer, got ${ratioToBase}`);
1081
+ if (!Number.isInteger(precision) || precision < 0 || precision > MAX_PRECISION)
1082
+ throw new Error(`Unit "${name}": precision must be an integer 0\u2013${MAX_PRECISION}, got ${precision}`);
1083
+ if (ratioToBase % 10 ** precision !== 0)
1084
+ throw new Error(
1085
+ `Unit "${name}": precision ${precision} is finer than a ratio of ${ratioToBase} allows \u2014 at most ${maxPrecisionFor(ratioToBase)}. For anything finer the family needs a smaller base unit.`
1086
+ );
1087
+ }
1088
+ function maxPrecisionFor(ratioToBase) {
1089
+ if (!Number.isInteger(ratioToBase) || ratioToBase < 1)
1090
+ throw new Error(`ratioToBase must be a positive integer, got ${ratioToBase}`);
1091
+ let precision = 0;
1092
+ while (precision < MAX_PRECISION && ratioToBase % 10 ** (precision + 1) === 0) precision++;
1093
+ return precision;
1094
+ }
1095
+ function isValidQuantity(quantity, unit2) {
1096
+ if (!Number.isFinite(quantity) || quantity < 0) return false;
1097
+ return isWhole(quantity * 10 ** unit2.precision);
1098
+ }
1099
+ function toBase(quantity, unit2) {
1100
+ const exact = quantity * unit2.ratioToBase;
1101
+ const whole = Math.round(exact);
1102
+ if (!isWhole(exact))
1103
+ throw new Error(
1104
+ `${quantity} ${unit2.abbreviation} is ${exact} base units \u2014 not whole. Check the quantity against precision ${unit2.precision}.`
1105
+ );
1106
+ return whole;
1107
+ }
1108
+ function fromBase(baseQuantity, unit2) {
1109
+ return baseQuantity / unit2.ratioToBase;
1110
+ }
1111
+ function availableIn(baseQuantity, unit2) {
1112
+ const step = unit2.ratioToBase / 10 ** unit2.precision;
1113
+ return Math.floor(baseQuantity / step) / 10 ** unit2.precision;
1114
+ }
1115
+ function formatQuantity(baseQuantity, unit2) {
1116
+ return fromBase(baseQuantity, unit2).toFixed(unit2.precision);
1117
+ }
1118
+ function formatInUnit(quantity, unit2) {
1119
+ return quantity.toFixed(unit2.precision);
1120
+ }
1121
+ function roundToPrecision(quantity, precision) {
1122
+ const scale = 10 ** precision;
1123
+ return Math.round(quantity * scale) / scale;
1124
+ }
1125
+ function extendPrice(priceMinor, quantity) {
1126
+ const scale = 10 ** MAX_PRECISION;
1127
+ const scaled = BigInt(Math.round(priceMinor)) * BigInt(Math.round(quantity * scale));
1128
+ const divisor = BigInt(scale);
1129
+ return Number((scaled + divisor / 2n) / divisor);
1130
+ }
1131
+ function extendPriceFor(priceMinor, quantity, unit2) {
1132
+ if (!isValidQuantity(quantity, unit2))
1133
+ throw new Error(`${quantity} ${unit2.abbreviation} has more decimals than precision ${unit2.precision} allows.`);
1134
+ return extendPrice(priceMinor, quantity);
1135
+ }
1136
+
862
1137
  // utils/order-totals.util.ts
863
1138
  function toMinorUnits2(value) {
864
1139
  return Number(value) || 0;
@@ -876,7 +1151,7 @@ function getSettlementTotal(payments, tipTotal) {
876
1151
  return toMinorUnits2(payments?.cardTotal) + toMinorUnits2(payments?.cashTotal) + toMinorUnits2(payments?.customTotal) + toMinorUnits2(tipTotal);
877
1152
  }
878
1153
  function getLineSubtotal(price, quantity) {
879
- return toMinorUnits2(price) * toMinorUnits2(quantity);
1154
+ return extendPrice(toMinorUnits2(price), toMinorUnits2(quantity));
880
1155
  }
881
1156
  function getGoodsPortion(total, totalTax) {
882
1157
  return toMinorUnits2(total) - toMinorUnits2(totalTax);
@@ -1200,6 +1475,31 @@ function normalizeMongoIdString(value) {
1200
1475
  return null;
1201
1476
  }
1202
1477
 
1478
+ // units/family-code.ts
1479
+ var MAX_SLUG_LENGTH = 32;
1480
+ var FALLBACK_SLUG = "family";
1481
+ var SYSTEM_FAMILY_CODES = new Set(SYSTEM_FAMILIES.map((family) => family.familyId));
1482
+ function slugify(name) {
1483
+ const slug = name.normalize("NFKD").replace(/\p{Diacritic}/gu, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, MAX_SLUG_LENGTH).replace(/^-+|-+$/g, "");
1484
+ return slug || FALLBACK_SLUG;
1485
+ }
1486
+ function deriveFamilyCode(name, companyId, takenCodes = []) {
1487
+ if (!companyId) throw new Error(`Cannot derive a family code for "${name}" without a companyId.`);
1488
+ const taken = new Set(takenCodes);
1489
+ const slug = slugify(name);
1490
+ for (let attempt = 1; ; attempt++) {
1491
+ const code = attempt === 1 ? `${slug}-${companyId}` : `${slug}-${attempt}-${companyId}`;
1492
+ if (!taken.has(code) && !isSystemFamilyCode(code)) return code;
1493
+ }
1494
+ }
1495
+ function isSystemFamilyCode(code) {
1496
+ return SYSTEM_FAMILY_CODES.has(code);
1497
+ }
1498
+ function deriveUnitId(abbreviation, documentId) {
1499
+ if (!documentId) throw new Error(`Cannot derive a unit id for "${abbreviation}" without a document id.`);
1500
+ return `${slugify(abbreviation)}-${documentId}`;
1501
+ }
1502
+
1203
1503
  // order-state-machine/types.ts
1204
1504
  var PAYMENT_STATES = [
1205
1505
  "unpaid",
@@ -2595,6 +2895,27 @@ var ORDER_STATE_EVENTS = {
2595
2895
  stateTransitionBlocked: "state-transition-blocked",
2596
2896
  configUpdated: "config-updated"
2597
2897
  };
2898
+
2899
+ // order-state-machine/write-strategy.ts
2900
+ var LegacyWriteStrategy = class {
2901
+ applyState(_order) {
2902
+ }
2903
+ };
2904
+ var DualWriteStrategy = class {
2905
+ constructor(config) {
2906
+ this.config = config;
2907
+ }
2908
+ applyState(order) {
2909
+ if (order.paymentState && order.fulfillmentState) {
2910
+ setOrderState(order, order.paymentState, order.fulfillmentState, this.config);
2911
+ return;
2912
+ }
2913
+ if (order.status) {
2914
+ const inferred = inferStatesFromLegacyStatus(order);
2915
+ setOrderState(order, inferred.payment, inferred.fulfillment, this.config);
2916
+ }
2917
+ }
2918
+ };
2598
2919
  // Annotate the CommonJS export names for ESM import in node:
2599
2920
  0 && (module.exports = {
2600
2921
  AttributeType,
@@ -2609,6 +2930,8 @@ var ORDER_STATE_EVENTS = {
2609
2930
  DEFAULT_CROSS_AXIS_RULES,
2610
2931
  DEFAULT_DISPLAY_STATE_MAP,
2611
2932
  DEFAULT_RESOLVED_CONFIG,
2933
+ DEFAULT_UNIT_ID,
2934
+ DualWriteStrategy,
2612
2935
  EUFixedRates,
2613
2936
  EUROPE,
2614
2937
  ExtensionCategory,
@@ -2618,7 +2941,9 @@ var ORDER_STATE_EVENTS = {
2618
2941
  InventoryBaseActionEnum,
2619
2942
  InventorySpecificActionType,
2620
2943
  InvoiceStatus,
2944
+ LegacyWriteStrategy,
2621
2945
  LibraryItemType,
2946
+ MAX_PRECISION,
2622
2947
  MenuProject,
2623
2948
  NAFixedRates,
2624
2949
  NORTH_AMERICA,
@@ -2651,6 +2976,10 @@ var ORDER_STATE_EVENTS = {
2651
2976
  ResidualStatus,
2652
2977
  STATE_SCHEMA,
2653
2978
  STRIPE_SUPPORTED_COUNTRIES,
2979
+ SYSTEM_FAMILIES,
2980
+ SYSTEM_FAMILY_IDS,
2981
+ SYSTEM_UNITS,
2982
+ SYSTEM_UNIT_IDS,
2654
2983
  StationStatus,
2655
2984
  StripeCurrencyCode,
2656
2985
  StripeStatus,
@@ -2659,7 +2988,11 @@ var ORDER_STATE_EVENTS = {
2659
2988
  UserTypes,
2660
2989
  addAmounts,
2661
2990
  amountToString,
2991
+ assertUnitSane,
2992
+ availableIn,
2993
+ baseFor,
2662
2994
  baseFulfillmentDefinitions,
2995
+ baseOf,
2663
2996
  basePaymentDefinitions,
2664
2997
  buildOrderContext,
2665
2998
  buildPosPresetConfig,
@@ -2671,12 +3004,19 @@ var ORDER_STATE_EVENTS = {
2671
3004
  createAmount,
2672
3005
  crossAxisRuleApplies,
2673
3006
  deriveDisplayState,
3007
+ deriveFamilyCode,
3008
+ deriveUnitId,
2674
3009
  displayRuleMatchesPair,
2675
3010
  divideAmount,
2676
3011
  evaluateCondition,
2677
3012
  evaluateConditionSet,
3013
+ extendPrice,
3014
+ extendPriceFor,
2678
3015
  formatCurrency,
3016
+ formatInUnit,
2679
3017
  formatMinorUnitsToString,
3018
+ formatQuantity,
3019
+ fromBase,
2680
3020
  fromMinorUnits,
2681
3021
  generateMongoID,
2682
3022
  getAvailableTransitions,
@@ -2698,7 +3038,11 @@ var ORDER_STATE_EVENTS = {
2698
3038
  isFulfillmentState,
2699
3039
  isPaymentState,
2700
3040
  isSupportedCurrency,
3041
+ isSystemFamilyCode,
3042
+ isValidQuantity,
3043
+ isWhole,
2701
3044
  mapToLegacyStatus,
3045
+ maxPrecisionFor,
2702
3046
  mergeFragment,
2703
3047
  multiplyAmount,
2704
3048
  normalizeMongoIdString,
@@ -2706,11 +3050,16 @@ var ORDER_STATE_EVENTS = {
2706
3050
  parseCurrencyCode,
2707
3051
  parseStripeCurrencyCode,
2708
3052
  removeFragment,
3053
+ resolveFamily,
2709
3054
  resolveTemplate,
3055
+ resolveUnit,
3056
+ roundToPrecision,
3057
+ sameFamily,
2710
3058
  setOrderState,
2711
3059
  signReportForDisplay,
2712
3060
  stringifyMinorUnits,
2713
3061
  subtractAmounts,
3062
+ toBase,
2714
3063
  toMinorUnits,
2715
3064
  validateConfig,
2716
3065
  validateFragmentCompatibility,