@final-commerce/common 2.1.1 → 2.2.0-beta.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.js CHANGED
@@ -31,6 +31,7 @@ __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,
34
35
  DualWriteStrategy: () => DualWriteStrategy,
35
36
  EUFixedRates: () => EUFixedRates,
36
37
  EUROPE: () => EUROPE,
@@ -43,6 +44,7 @@ __export(index_exports, {
43
44
  InvoiceStatus: () => InvoiceStatus,
44
45
  LegacyWriteStrategy: () => LegacyWriteStrategy,
45
46
  LibraryItemType: () => LibraryItemType,
47
+ MAX_PRECISION: () => MAX_PRECISION,
46
48
  MenuProject: () => MenuProject,
47
49
  NAFixedRates: () => NAFixedRates,
48
50
  NORTH_AMERICA: () => NORTH_AMERICA,
@@ -75,6 +77,10 @@ __export(index_exports, {
75
77
  ResidualStatus: () => ResidualStatus,
76
78
  STATE_SCHEMA: () => STATE_SCHEMA,
77
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,
78
84
  StationStatus: () => StationStatus,
79
85
  StripeCurrencyCode: () => StripeCurrencyCode,
80
86
  StripeStatus: () => StripeStatus,
@@ -83,7 +89,11 @@ __export(index_exports, {
83
89
  UserTypes: () => UserTypes,
84
90
  addAmounts: () => addAmounts,
85
91
  amountToString: () => amountToString,
92
+ assertUnitSane: () => assertUnitSane,
93
+ availableIn: () => availableIn,
94
+ baseFor: () => baseFor,
86
95
  baseFulfillmentDefinitions: () => baseFulfillmentDefinitions,
96
+ baseOf: () => baseOf,
87
97
  basePaymentDefinitions: () => basePaymentDefinitions,
88
98
  buildOrderContext: () => buildOrderContext,
89
99
  buildPosPresetConfig: () => buildPosPresetConfig,
@@ -95,12 +105,19 @@ __export(index_exports, {
95
105
  createAmount: () => createAmount,
96
106
  crossAxisRuleApplies: () => crossAxisRuleApplies,
97
107
  deriveDisplayState: () => deriveDisplayState,
108
+ deriveFamilyCode: () => deriveFamilyCode,
109
+ deriveUnitId: () => deriveUnitId,
98
110
  displayRuleMatchesPair: () => displayRuleMatchesPair,
99
111
  divideAmount: () => divideAmount,
100
112
  evaluateCondition: () => evaluateCondition,
101
113
  evaluateConditionSet: () => evaluateConditionSet,
114
+ extendPrice: () => extendPrice,
115
+ extendPriceFor: () => extendPriceFor,
102
116
  formatCurrency: () => formatCurrency,
117
+ formatInUnit: () => formatInUnit,
103
118
  formatMinorUnitsToString: () => formatMinorUnitsToString,
119
+ formatQuantity: () => formatQuantity,
120
+ fromBase: () => fromBase,
104
121
  fromMinorUnits: () => fromMinorUnits,
105
122
  generateMongoID: () => generateMongoID,
106
123
  getAvailableTransitions: () => getAvailableTransitions,
@@ -122,7 +139,11 @@ __export(index_exports, {
122
139
  isFulfillmentState: () => isFulfillmentState,
123
140
  isPaymentState: () => isPaymentState,
124
141
  isSupportedCurrency: () => isSupportedCurrency,
142
+ isSystemFamilyCode: () => isSystemFamilyCode,
143
+ isValidQuantity: () => isValidQuantity,
144
+ isWhole: () => isWhole,
125
145
  mapToLegacyStatus: () => mapToLegacyStatus,
146
+ maxPrecisionFor: () => maxPrecisionFor,
126
147
  mergeFragment: () => mergeFragment,
127
148
  multiplyAmount: () => multiplyAmount,
128
149
  normalizeMongoIdString: () => normalizeMongoIdString,
@@ -130,11 +151,16 @@ __export(index_exports, {
130
151
  parseCurrencyCode: () => parseCurrencyCode,
131
152
  parseStripeCurrencyCode: () => parseStripeCurrencyCode,
132
153
  removeFragment: () => removeFragment,
154
+ resolveFamily: () => resolveFamily,
133
155
  resolveTemplate: () => resolveTemplate,
156
+ resolveUnit: () => resolveUnit,
157
+ roundToPrecision: () => roundToPrecision,
158
+ sameFamily: () => sameFamily,
134
159
  setOrderState: () => setOrderState,
135
160
  signReportForDisplay: () => signReportForDisplay,
136
161
  stringifyMinorUnits: () => stringifyMinorUnits,
137
162
  subtractAmounts: () => subtractAmounts,
163
+ toBase: () => toBase,
138
164
  toMinorUnits: () => toMinorUnits,
139
165
  validateConfig: () => validateConfig,
140
166
  validateFragmentCompatibility: () => validateFragmentCompatibility,
@@ -372,6 +398,7 @@ var InventorySpecificActionType = /* @__PURE__ */ ((InventorySpecificActionType2
372
398
  InventorySpecificActionType2["REFUND_RESTOCK_RETURN"] = "REFUND_RESTOCK_RETURN";
373
399
  InventorySpecificActionType2["REFUND_DAMAGE"] = "REFUND_DAMAGE";
374
400
  InventorySpecificActionType2["SALE"] = "SALE";
401
+ InventorySpecificActionType2["COMPOSED_FOR_SALE"] = "COMPOSED_FOR_SALE";
375
402
  InventorySpecificActionType2["TRANSFER"] = "TRANSFER";
376
403
  InventorySpecificActionType2["BULK_RECOUNT"] = "BULK_RECOUNT";
377
404
  InventorySpecificActionType2["APPLIED_FROM_WOO"] = "APPLIED_FROM_WOO";
@@ -861,6 +888,252 @@ function amountToString(amount) {
861
888
  return majorValue.toFixed(amount.minorUnits);
862
889
  }
863
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
+
864
1137
  // utils/order-totals.util.ts
865
1138
  function toMinorUnits2(value) {
866
1139
  return Number(value) || 0;
@@ -878,7 +1151,7 @@ function getSettlementTotal(payments, tipTotal) {
878
1151
  return toMinorUnits2(payments?.cardTotal) + toMinorUnits2(payments?.cashTotal) + toMinorUnits2(payments?.customTotal) + toMinorUnits2(tipTotal);
879
1152
  }
880
1153
  function getLineSubtotal(price, quantity) {
881
- return toMinorUnits2(price) * toMinorUnits2(quantity);
1154
+ return extendPrice(toMinorUnits2(price), toMinorUnits2(quantity));
882
1155
  }
883
1156
  function getGoodsPortion(total, totalTax) {
884
1157
  return toMinorUnits2(total) - toMinorUnits2(totalTax);
@@ -1202,6 +1475,31 @@ function normalizeMongoIdString(value) {
1202
1475
  return null;
1203
1476
  }
1204
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
+
1205
1503
  // order-state-machine/types.ts
1206
1504
  var PAYMENT_STATES = [
1207
1505
  "unpaid",
@@ -2632,6 +2930,7 @@ var DualWriteStrategy = class {
2632
2930
  DEFAULT_CROSS_AXIS_RULES,
2633
2931
  DEFAULT_DISPLAY_STATE_MAP,
2634
2932
  DEFAULT_RESOLVED_CONFIG,
2933
+ DEFAULT_UNIT_ID,
2635
2934
  DualWriteStrategy,
2636
2935
  EUFixedRates,
2637
2936
  EUROPE,
@@ -2644,6 +2943,7 @@ var DualWriteStrategy = class {
2644
2943
  InvoiceStatus,
2645
2944
  LegacyWriteStrategy,
2646
2945
  LibraryItemType,
2946
+ MAX_PRECISION,
2647
2947
  MenuProject,
2648
2948
  NAFixedRates,
2649
2949
  NORTH_AMERICA,
@@ -2676,6 +2976,10 @@ var DualWriteStrategy = class {
2676
2976
  ResidualStatus,
2677
2977
  STATE_SCHEMA,
2678
2978
  STRIPE_SUPPORTED_COUNTRIES,
2979
+ SYSTEM_FAMILIES,
2980
+ SYSTEM_FAMILY_IDS,
2981
+ SYSTEM_UNITS,
2982
+ SYSTEM_UNIT_IDS,
2679
2983
  StationStatus,
2680
2984
  StripeCurrencyCode,
2681
2985
  StripeStatus,
@@ -2684,7 +2988,11 @@ var DualWriteStrategy = class {
2684
2988
  UserTypes,
2685
2989
  addAmounts,
2686
2990
  amountToString,
2991
+ assertUnitSane,
2992
+ availableIn,
2993
+ baseFor,
2687
2994
  baseFulfillmentDefinitions,
2995
+ baseOf,
2688
2996
  basePaymentDefinitions,
2689
2997
  buildOrderContext,
2690
2998
  buildPosPresetConfig,
@@ -2696,12 +3004,19 @@ var DualWriteStrategy = class {
2696
3004
  createAmount,
2697
3005
  crossAxisRuleApplies,
2698
3006
  deriveDisplayState,
3007
+ deriveFamilyCode,
3008
+ deriveUnitId,
2699
3009
  displayRuleMatchesPair,
2700
3010
  divideAmount,
2701
3011
  evaluateCondition,
2702
3012
  evaluateConditionSet,
3013
+ extendPrice,
3014
+ extendPriceFor,
2703
3015
  formatCurrency,
3016
+ formatInUnit,
2704
3017
  formatMinorUnitsToString,
3018
+ formatQuantity,
3019
+ fromBase,
2705
3020
  fromMinorUnits,
2706
3021
  generateMongoID,
2707
3022
  getAvailableTransitions,
@@ -2723,7 +3038,11 @@ var DualWriteStrategy = class {
2723
3038
  isFulfillmentState,
2724
3039
  isPaymentState,
2725
3040
  isSupportedCurrency,
3041
+ isSystemFamilyCode,
3042
+ isValidQuantity,
3043
+ isWhole,
2726
3044
  mapToLegacyStatus,
3045
+ maxPrecisionFor,
2727
3046
  mergeFragment,
2728
3047
  multiplyAmount,
2729
3048
  normalizeMongoIdString,
@@ -2731,11 +3050,16 @@ var DualWriteStrategy = class {
2731
3050
  parseCurrencyCode,
2732
3051
  parseStripeCurrencyCode,
2733
3052
  removeFragment,
3053
+ resolveFamily,
2734
3054
  resolveTemplate,
3055
+ resolveUnit,
3056
+ roundToPrecision,
3057
+ sameFamily,
2735
3058
  setOrderState,
2736
3059
  signReportForDisplay,
2737
3060
  stringifyMinorUnits,
2738
3061
  subtractAmounts,
3062
+ toBase,
2739
3063
  toMinorUnits,
2740
3064
  validateConfig,
2741
3065
  validateFragmentCompatibility,