@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.mjs CHANGED
@@ -228,6 +228,7 @@ var InventorySpecificActionType = /* @__PURE__ */ ((InventorySpecificActionType2
228
228
  InventorySpecificActionType2["REFUND_RESTOCK_RETURN"] = "REFUND_RESTOCK_RETURN";
229
229
  InventorySpecificActionType2["REFUND_DAMAGE"] = "REFUND_DAMAGE";
230
230
  InventorySpecificActionType2["SALE"] = "SALE";
231
+ InventorySpecificActionType2["COMPOSED_FOR_SALE"] = "COMPOSED_FOR_SALE";
231
232
  InventorySpecificActionType2["TRANSFER"] = "TRANSFER";
232
233
  InventorySpecificActionType2["BULK_RECOUNT"] = "BULK_RECOUNT";
233
234
  InventorySpecificActionType2["APPLIED_FROM_WOO"] = "APPLIED_FROM_WOO";
@@ -717,6 +718,252 @@ function amountToString(amount) {
717
718
  return majorValue.toFixed(amount.minorUnits);
718
719
  }
719
720
 
721
+ // units/system-units.ts
722
+ var SYSTEM_FAMILY_IDS = {
723
+ COUNT: "count",
724
+ WEIGHT: "weight-metric",
725
+ WEIGHT_IMPERIAL: "weight-imperial",
726
+ VOLUME: "volume-metric",
727
+ VOLUME_IMPERIAL: "volume-imperial",
728
+ LENGTH: "length-metric",
729
+ LENGTH_IMPERIAL: "length-imperial",
730
+ AREA: "area-metric",
731
+ AREA_IMPERIAL: "area-imperial"
732
+ };
733
+ var SYSTEM_UNIT_IDS = {
734
+ EACH: "EA",
735
+ GRAM: "GRM",
736
+ KILOGRAM: "KGM",
737
+ /** 100 g is a hectogram; the merchant-facing name stays "100 g". */
738
+ HECTOGRAM: "HGM",
739
+ MILLILITRE: "MLT",
740
+ LITRE: "LTR"
741
+ };
742
+ var SYSTEM_FAMILIES = [
743
+ {
744
+ familyId: SYSTEM_FAMILY_IDS.COUNT,
745
+ companyId: null,
746
+ name: "Count",
747
+ base: { name: "Piece", abbreviation: "ea", ratioToBase: 1, precision: 0 }
748
+ },
749
+ {
750
+ familyId: SYSTEM_FAMILY_IDS.WEIGHT,
751
+ companyId: null,
752
+ name: "Weight",
753
+ base: { name: "Gram", abbreviation: "g", ratioToBase: 1, precision: 0 }
754
+ },
755
+ {
756
+ familyId: SYSTEM_FAMILY_IDS.WEIGHT_IMPERIAL,
757
+ companyId: null,
758
+ name: "Weight (imperial)",
759
+ base: { name: "Hundred-thousandth of an ounce", abbreviation: "10\u207B\u2075 oz", ratioToBase: 1, precision: 0 }
760
+ },
761
+ {
762
+ familyId: SYSTEM_FAMILY_IDS.VOLUME,
763
+ companyId: null,
764
+ name: "Volume",
765
+ base: { name: "Millilitre", abbreviation: "ml", ratioToBase: 1, precision: 0 }
766
+ },
767
+ {
768
+ // Cubic inches, not fluid ounces: a US gallon is 231 in³ and 128 fl oz, and 231 = 3 × 7 × 11.
769
+ // With an ounce base the board foot lands on 6144000/77 — and lumber is the reason this family
770
+ // exists at all.
771
+ familyId: SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL,
772
+ companyId: null,
773
+ name: "Volume (imperial)",
774
+ base: { name: "1/128000 cubic inch", abbreviation: "\xB5in\xB3", ratioToBase: 1, precision: 0 }
775
+ },
776
+ {
777
+ familyId: SYSTEM_FAMILY_IDS.LENGTH,
778
+ companyId: null,
779
+ name: "Length",
780
+ base: { name: "Millimetre", abbreviation: "mm", ratioToBase: 1, precision: 0 }
781
+ },
782
+ {
783
+ familyId: SYSTEM_FAMILY_IDS.LENGTH_IMPERIAL,
784
+ companyId: null,
785
+ name: "Length (imperial)",
786
+ base: { name: "Millionth of an inch", abbreviation: "\xB5in", ratioToBase: 1, precision: 0 }
787
+ },
788
+ {
789
+ familyId: SYSTEM_FAMILY_IDS.AREA,
790
+ companyId: null,
791
+ name: "Area",
792
+ base: { name: "Square millimetre", abbreviation: "mm\xB2", ratioToBase: 1, precision: 0 }
793
+ },
794
+ {
795
+ familyId: SYSTEM_FAMILY_IDS.AREA_IMPERIAL,
796
+ companyId: null,
797
+ name: "Area (imperial)",
798
+ base: { name: "Thousandth of a square inch", abbreviation: "m-sq in", ratioToBase: 1, precision: 0 }
799
+ }
800
+ ];
801
+ var unit = (unitId, familyId, name, abbreviation, ratioToBase, precision) => ({ unitId, companyId: null, familyId, name, abbreviation, ratioToBase, precision });
802
+ var SELLABLE_UNITS = [
803
+ // ── Count ────────────────────────────────────────────────────────────────
804
+ unit("EA", SYSTEM_FAMILY_IDS.COUNT, "Each", "ea", 1, 0),
805
+ unit("PR", SYSTEM_FAMILY_IDS.COUNT, "Pair", "pr", 2, 0),
806
+ unit("DZN", SYSTEM_FAMILY_IDS.COUNT, "Dozen", "dz", 12, 0),
807
+ unit("GRO", SYSTEM_FAMILY_IDS.COUNT, "Gross", "gro", 144, 0),
808
+ unit("CEN", SYSTEM_FAMILY_IDS.COUNT, "Hundred", "C", 100, 0),
809
+ unit("MIL", SYSTEM_FAMILY_IDS.COUNT, "Thousand", "M", 1e3, 0),
810
+ // ── Weight, metric ───────────────────────────────────────────────────────
811
+ unit("GRM", SYSTEM_FAMILY_IDS.WEIGHT, "Gram", "g", 1, 0),
812
+ unit("HGM", SYSTEM_FAMILY_IDS.WEIGHT, "100 g", "100g", 100, 2),
813
+ unit("KGM", SYSTEM_FAMILY_IDS.WEIGHT, "Kilogram", "kg", 1e3, 3),
814
+ unit("TNE", SYSTEM_FAMILY_IDS.WEIGHT, "Tonne", "t", 1e6, 3),
815
+ // ── Weight, imperial ─────────────────────────────────────────────────────
816
+ unit("ONZ", SYSTEM_FAMILY_IDS.WEIGHT_IMPERIAL, "Ounce", "oz", 1e5, 2),
817
+ unit("LBR", SYSTEM_FAMILY_IDS.WEIGHT_IMPERIAL, "Pound", "lb", 16e5, 2),
818
+ unit("STI", SYSTEM_FAMILY_IDS.WEIGHT_IMPERIAL, "Stone", "st", 224e5, 2),
819
+ // `(US)` is not decoration: Rec 20 also carries `CWI`, the UK hundredweight, at 112 lb — 12 % more.
820
+ // The code here is the right one; an unqualified "cwt" on screen is what would be ambiguous.
821
+ unit("CWA", SYSTEM_FAMILY_IDS.WEIGHT_IMPERIAL, "Hundredweight (US)", "cwt (US)", 16e7, 2),
822
+ unit("STN", SYSTEM_FAMILY_IDS.WEIGHT_IMPERIAL, "Ton (short)", "tn", 32e8, 3),
823
+ // ── Volume, metric ───────────────────────────────────────────────────────
824
+ unit("MLT", SYSTEM_FAMILY_IDS.VOLUME, "Millilitre", "ml", 1, 0),
825
+ unit("CLT", SYSTEM_FAMILY_IDS.VOLUME, "Centilitre", "cl", 10, 1),
826
+ unit("LTR", SYSTEM_FAMILY_IDS.VOLUME, "Litre", "l", 1e3, 3),
827
+ unit("HLT", SYSTEM_FAMILY_IDS.VOLUME, "Hectolitre", "hl", 1e5, 3),
828
+ unit("MTQ", SYSTEM_FAMILY_IDS.VOLUME, "Cubic metre", "m\xB3", 1e6, 3),
829
+ // ── Volume, imperial ─────────────────────────────────────────────────────
830
+ unit("OZA", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Fluid ounce", "fl oz", 231e3, 2),
831
+ unit("PTL", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Pint", "pt", 3696e3, 2),
832
+ unit("QTL", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Quart", "qt", 7392e3, 2),
833
+ unit("GLL", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Gallon", "gal", 29568e3, 3),
834
+ unit("INQ", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Cubic inch", "in\xB3", 128e3, 2),
835
+ unit("BFT", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Board foot", "fbm", 18432e3, 2),
836
+ unit("FTQ", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Cubic foot", "ft\xB3", 221184e3, 3),
837
+ unit("YDQ", SYSTEM_FAMILY_IDS.VOLUME_IMPERIAL, "Cubic yard", "yd\xB3", 5971968e3, 3),
838
+ // ── Length, metric ───────────────────────────────────────────────────────
839
+ unit("MMT", SYSTEM_FAMILY_IDS.LENGTH, "Millimetre", "mm", 1, 0),
840
+ unit("CMT", SYSTEM_FAMILY_IDS.LENGTH, "Centimetre", "cm", 10, 1),
841
+ unit("MTR", SYSTEM_FAMILY_IDS.LENGTH, "Metre", "m", 1e3, 3),
842
+ unit("KMT", SYSTEM_FAMILY_IDS.LENGTH, "Kilometre", "km", 1e6, 3),
843
+ // ── Length, imperial ─────────────────────────────────────────────────────
844
+ // `INH` carries four decimals, not three: an eighth of an inch is 0.125 but a sixteenth is
845
+ // 0.0625, and sixteenths are the everyday fraction on a tape measure. A thirty-second needs five,
846
+ // which is the ceiling; a sixty-fourth has no decimal form at all and is a question about
847
+ // fraction input, not about precision.
848
+ unit("INH", SYSTEM_FAMILY_IDS.LENGTH_IMPERIAL, "Inch", "in", 1e6, 4),
849
+ unit("FOT", SYSTEM_FAMILY_IDS.LENGTH_IMPERIAL, "Foot", "ft", 12e6, 3),
850
+ unit("LF", SYSTEM_FAMILY_IDS.LENGTH_IMPERIAL, "Linear foot", "lin ft", 12e6, 2),
851
+ unit("YRD", SYSTEM_FAMILY_IDS.LENGTH_IMPERIAL, "Yard", "yd", 36e6, 3),
852
+ unit("SMI", SYSTEM_FAMILY_IDS.LENGTH_IMPERIAL, "Mile", "mi", 6336e7, 3),
853
+ // ── Area, metric ─────────────────────────────────────────────────────────
854
+ unit("MMK", SYSTEM_FAMILY_IDS.AREA, "Square millimetre", "mm\xB2", 1, 0),
855
+ unit("CMK", SYSTEM_FAMILY_IDS.AREA, "Square centimetre", "cm\xB2", 100, 2),
856
+ unit("MTK", SYSTEM_FAMILY_IDS.AREA, "Square metre", "m\xB2", 1e6, 3),
857
+ unit("HAR", SYSTEM_FAMILY_IDS.AREA, "Hectare", "ha", 1e10, 3),
858
+ // ── Area, imperial ───────────────────────────────────────────────────────
859
+ unit("INK", SYSTEM_FAMILY_IDS.AREA_IMPERIAL, "Square inch", "sq in", 1e3, 2),
860
+ unit("FTK", SYSTEM_FAMILY_IDS.AREA_IMPERIAL, "Square foot", "sq ft", 144e3, 2),
861
+ unit("YDK", SYSTEM_FAMILY_IDS.AREA_IMPERIAL, "Square yard", "sq yd", 1296e3, 2),
862
+ // A roofing square is exactly 100 square feet — a trade unit with a precise definition, which is
863
+ // why it is a real row and not a label.
864
+ unit("SQR", SYSTEM_FAMILY_IDS.AREA_IMPERIAL, "Square (roofing)", "sq", 144e5, 2),
865
+ unit("ACR", SYSTEM_FAMILY_IDS.AREA_IMPERIAL, "Acre", "ac", 627264e4, 2)
866
+ ];
867
+ var BASE_UNITS = SYSTEM_FAMILIES.filter(
868
+ (family) => !SELLABLE_UNITS.some((u) => u.familyId === family.familyId && u.ratioToBase === 1)
869
+ ).map((family) => ({
870
+ unitId: `${family.familyId}-base`,
871
+ companyId: null,
872
+ familyId: family.familyId,
873
+ name: family.base.name,
874
+ abbreviation: family.base.abbreviation,
875
+ ratioToBase: 1,
876
+ precision: 0,
877
+ sellable: false
878
+ }));
879
+ var SYSTEM_UNITS = [...SELLABLE_UNITS, ...BASE_UNITS];
880
+ var DEFAULT_UNIT_ID = SYSTEM_UNIT_IDS.EACH;
881
+
882
+ // units/convert.ts
883
+ var EPSILON = 1e-9;
884
+ function isWhole(value) {
885
+ return Math.abs(value - Math.round(value)) <= EPSILON * Math.max(1, Math.abs(value));
886
+ }
887
+ var MAX_PRECISION = 5;
888
+ function resolveUnit(unitId, companyUnits = []) {
889
+ const unit2 = SYSTEM_UNITS.find((u) => u.unitId === unitId) ?? companyUnits.find((u) => u.unitId === unitId);
890
+ if (!unit2) throw new Error(`Unknown measurement unit: ${unitId}`);
891
+ return unit2;
892
+ }
893
+ function resolveFamily(familyId, companyFamilies = []) {
894
+ const family = SYSTEM_FAMILIES.find((f) => f.familyId === familyId) ?? companyFamilies.find((f) => f.familyId === familyId);
895
+ if (!family) throw new Error(`Unknown measurement unit family: ${familyId}`);
896
+ return family;
897
+ }
898
+ function baseOf(familyId, companyFamilies = []) {
899
+ return resolveFamily(familyId, companyFamilies).base;
900
+ }
901
+ function baseFor(unit2, companyFamilies = []) {
902
+ return baseOf(unit2.familyId, companyFamilies);
903
+ }
904
+ function sameFamily(a, b) {
905
+ return a.familyId === b.familyId;
906
+ }
907
+ function assertUnitSane(unit2) {
908
+ const { ratioToBase, precision, name } = unit2;
909
+ if (!Number.isInteger(ratioToBase) || ratioToBase < 1)
910
+ throw new Error(`Unit "${name}": ratioToBase must be a positive integer, got ${ratioToBase}`);
911
+ if (!Number.isInteger(precision) || precision < 0 || precision > MAX_PRECISION)
912
+ throw new Error(`Unit "${name}": precision must be an integer 0\u2013${MAX_PRECISION}, got ${precision}`);
913
+ if (ratioToBase % 10 ** precision !== 0)
914
+ throw new Error(
915
+ `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.`
916
+ );
917
+ }
918
+ function maxPrecisionFor(ratioToBase) {
919
+ if (!Number.isInteger(ratioToBase) || ratioToBase < 1)
920
+ throw new Error(`ratioToBase must be a positive integer, got ${ratioToBase}`);
921
+ let precision = 0;
922
+ while (precision < MAX_PRECISION && ratioToBase % 10 ** (precision + 1) === 0) precision++;
923
+ return precision;
924
+ }
925
+ function isValidQuantity(quantity, unit2) {
926
+ if (!Number.isFinite(quantity) || quantity < 0) return false;
927
+ return isWhole(quantity * 10 ** unit2.precision);
928
+ }
929
+ function toBase(quantity, unit2) {
930
+ const exact = quantity * unit2.ratioToBase;
931
+ const whole = Math.round(exact);
932
+ if (!isWhole(exact))
933
+ throw new Error(
934
+ `${quantity} ${unit2.abbreviation} is ${exact} base units \u2014 not whole. Check the quantity against precision ${unit2.precision}.`
935
+ );
936
+ return whole;
937
+ }
938
+ function fromBase(baseQuantity, unit2) {
939
+ return baseQuantity / unit2.ratioToBase;
940
+ }
941
+ function availableIn(baseQuantity, unit2) {
942
+ const step = unit2.ratioToBase / 10 ** unit2.precision;
943
+ return Math.floor(baseQuantity / step) / 10 ** unit2.precision;
944
+ }
945
+ function formatQuantity(baseQuantity, unit2) {
946
+ return fromBase(baseQuantity, unit2).toFixed(unit2.precision);
947
+ }
948
+ function formatInUnit(quantity, unit2) {
949
+ return quantity.toFixed(unit2.precision);
950
+ }
951
+ function roundToPrecision(quantity, precision) {
952
+ const scale = 10 ** precision;
953
+ return Math.round(quantity * scale) / scale;
954
+ }
955
+ function extendPrice(priceMinor, quantity) {
956
+ const scale = 10 ** MAX_PRECISION;
957
+ const scaled = BigInt(Math.round(priceMinor)) * BigInt(Math.round(quantity * scale));
958
+ const divisor = BigInt(scale);
959
+ return Number((scaled + divisor / 2n) / divisor);
960
+ }
961
+ function extendPriceFor(priceMinor, quantity, unit2) {
962
+ if (!isValidQuantity(quantity, unit2))
963
+ throw new Error(`${quantity} ${unit2.abbreviation} has more decimals than precision ${unit2.precision} allows.`);
964
+ return extendPrice(priceMinor, quantity);
965
+ }
966
+
720
967
  // utils/order-totals.util.ts
721
968
  function toMinorUnits2(value) {
722
969
  return Number(value) || 0;
@@ -734,7 +981,7 @@ function getSettlementTotal(payments, tipTotal) {
734
981
  return toMinorUnits2(payments?.cardTotal) + toMinorUnits2(payments?.cashTotal) + toMinorUnits2(payments?.customTotal) + toMinorUnits2(tipTotal);
735
982
  }
736
983
  function getLineSubtotal(price, quantity) {
737
- return toMinorUnits2(price) * toMinorUnits2(quantity);
984
+ return extendPrice(toMinorUnits2(price), toMinorUnits2(quantity));
738
985
  }
739
986
  function getGoodsPortion(total, totalTax) {
740
987
  return toMinorUnits2(total) - toMinorUnits2(totalTax);
@@ -1058,6 +1305,31 @@ function normalizeMongoIdString(value) {
1058
1305
  return null;
1059
1306
  }
1060
1307
 
1308
+ // units/family-code.ts
1309
+ var MAX_SLUG_LENGTH = 32;
1310
+ var FALLBACK_SLUG = "family";
1311
+ var SYSTEM_FAMILY_CODES = new Set(SYSTEM_FAMILIES.map((family) => family.familyId));
1312
+ function slugify(name) {
1313
+ const slug = name.normalize("NFKD").replace(/\p{Diacritic}/gu, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, MAX_SLUG_LENGTH).replace(/^-+|-+$/g, "");
1314
+ return slug || FALLBACK_SLUG;
1315
+ }
1316
+ function deriveFamilyCode(name, companyId, takenCodes = []) {
1317
+ if (!companyId) throw new Error(`Cannot derive a family code for "${name}" without a companyId.`);
1318
+ const taken = new Set(takenCodes);
1319
+ const slug = slugify(name);
1320
+ for (let attempt = 1; ; attempt++) {
1321
+ const code = attempt === 1 ? `${slug}-${companyId}` : `${slug}-${attempt}-${companyId}`;
1322
+ if (!taken.has(code) && !isSystemFamilyCode(code)) return code;
1323
+ }
1324
+ }
1325
+ function isSystemFamilyCode(code) {
1326
+ return SYSTEM_FAMILY_CODES.has(code);
1327
+ }
1328
+ function deriveUnitId(abbreviation, documentId) {
1329
+ if (!documentId) throw new Error(`Cannot derive a unit id for "${abbreviation}" without a document id.`);
1330
+ return `${slugify(abbreviation)}-${documentId}`;
1331
+ }
1332
+
1061
1333
  // order-state-machine/types.ts
1062
1334
  var PAYMENT_STATES = [
1063
1335
  "unpaid",
@@ -2487,6 +2759,7 @@ export {
2487
2759
  DEFAULT_CROSS_AXIS_RULES,
2488
2760
  DEFAULT_DISPLAY_STATE_MAP,
2489
2761
  DEFAULT_RESOLVED_CONFIG,
2762
+ DEFAULT_UNIT_ID,
2490
2763
  DualWriteStrategy,
2491
2764
  EUFixedRates,
2492
2765
  EUROPE,
@@ -2499,6 +2772,7 @@ export {
2499
2772
  InvoiceStatus,
2500
2773
  LegacyWriteStrategy,
2501
2774
  LibraryItemType,
2775
+ MAX_PRECISION,
2502
2776
  MenuProject,
2503
2777
  NAFixedRates,
2504
2778
  NORTH_AMERICA,
@@ -2531,6 +2805,10 @@ export {
2531
2805
  ResidualStatus,
2532
2806
  STATE_SCHEMA,
2533
2807
  STRIPE_SUPPORTED_COUNTRIES,
2808
+ SYSTEM_FAMILIES,
2809
+ SYSTEM_FAMILY_IDS,
2810
+ SYSTEM_UNITS,
2811
+ SYSTEM_UNIT_IDS,
2534
2812
  StationStatus,
2535
2813
  StripeCurrencyCode,
2536
2814
  StripeStatus,
@@ -2539,7 +2817,11 @@ export {
2539
2817
  UserTypes,
2540
2818
  addAmounts,
2541
2819
  amountToString,
2820
+ assertUnitSane,
2821
+ availableIn,
2822
+ baseFor,
2542
2823
  baseFulfillmentDefinitions,
2824
+ baseOf,
2543
2825
  basePaymentDefinitions,
2544
2826
  buildOrderContext,
2545
2827
  buildPosPresetConfig,
@@ -2551,12 +2833,19 @@ export {
2551
2833
  createAmount,
2552
2834
  crossAxisRuleApplies,
2553
2835
  deriveDisplayState,
2836
+ deriveFamilyCode,
2837
+ deriveUnitId,
2554
2838
  displayRuleMatchesPair,
2555
2839
  divideAmount,
2556
2840
  evaluateCondition,
2557
2841
  evaluateConditionSet,
2842
+ extendPrice,
2843
+ extendPriceFor,
2558
2844
  formatCurrency,
2845
+ formatInUnit,
2559
2846
  formatMinorUnitsToString,
2847
+ formatQuantity,
2848
+ fromBase,
2560
2849
  fromMinorUnits,
2561
2850
  generateMongoID,
2562
2851
  getAvailableTransitions,
@@ -2578,7 +2867,11 @@ export {
2578
2867
  isFulfillmentState,
2579
2868
  isPaymentState,
2580
2869
  isSupportedCurrency,
2870
+ isSystemFamilyCode,
2871
+ isValidQuantity,
2872
+ isWhole,
2581
2873
  mapToLegacyStatus,
2874
+ maxPrecisionFor,
2582
2875
  mergeFragment,
2583
2876
  multiplyAmount,
2584
2877
  normalizeMongoIdString,
@@ -2586,11 +2879,16 @@ export {
2586
2879
  parseCurrencyCode,
2587
2880
  parseStripeCurrencyCode,
2588
2881
  removeFragment,
2882
+ resolveFamily,
2589
2883
  resolveTemplate,
2884
+ resolveUnit,
2885
+ roundToPrecision,
2886
+ sameFamily,
2590
2887
  setOrderState,
2591
2888
  signReportForDisplay,
2592
2889
  stringifyMinorUnits,
2593
2890
  subtractAmounts,
2891
+ toBase,
2594
2892
  toMinorUnits,
2595
2893
  validateConfig,
2596
2894
  validateFragmentCompatibility,