merchi_sdk_ts 1.27.0 → 1.28.0

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.
@@ -340,6 +340,10 @@ var VariationField = /** @class */ (function (_super) {
340
340
  VariationField.property(),
341
341
  __metadata("design:type", String)
342
342
  ], VariationField.prototype, "areaInputType", void 0);
343
+ __decorate([
344
+ VariationField.property({ type: Number }),
345
+ __metadata("design:type", Object)
346
+ ], VariationField.prototype, "areaStep", void 0);
343
347
  __decorate([
344
348
  VariationField.property(),
345
349
  __metadata("design:type", Boolean)
@@ -12,12 +12,27 @@ var __values = (this && this.__values) || function(o) {
12
12
  import { applyDiscount } from './discount.js';
13
13
  import { roundHalfEven } from './round.js';
14
14
  import { resolveVisibleFields } from './visibility.js';
15
+ import { FieldType } from '../constants/field_types.js';
15
16
  /** Form inputs often supply quantities as strings. Coerce before any arithmetic
16
17
  * so `reduce((a,b)=>a+b)` cannot concatenate ("100"+"1" → "01001" → 1001). */
17
18
  function toQuantity(value) {
18
19
  var n = Number(value);
19
20
  return Number.isFinite(n) ? n : 0;
20
21
  }
22
+ function parseAreaMm(value) {
23
+ if (value === undefined || value === null || value === '')
24
+ return null;
25
+ var parts = String(value).split(',').map(function (p) { return p.trim(); });
26
+ if (parts.length !== 2)
27
+ return null;
28
+ var heightMm = Number(parts[0]);
29
+ var widthMm = Number(parts[1]);
30
+ if (!Number.isFinite(heightMm) || !Number.isFinite(widthMm))
31
+ return null;
32
+ if (heightMm <= 0 || widthMm <= 0)
33
+ return null;
34
+ return { heightMm: heightMm, widthMm: widthMm };
35
+ }
21
36
  function unitPriceAt(rules, qty) {
22
37
  var unitPrice = applyDiscount(rules.product.unitPrice, qty, rules.product.discountGroup);
23
38
  var mop = rules.product.minimumPrice;
@@ -65,9 +80,53 @@ function isEmpty(field, sel) {
65
80
  sel.value === '' ||
66
81
  sel.value === 0);
67
82
  }
83
+ function areaCostFactor(field, sel, groupQuantities) {
84
+ var _a, _b, _c, _d, _e;
85
+ var n = groupQuantities.length;
86
+ var parsed = parseAreaMm((_a = sel === null || sel === void 0 ? void 0 : sel.value) !== null && _a !== void 0 ? _a : null);
87
+ if (!parsed) {
88
+ return { setup: 0, unitList: new Array(n).fill(0) };
89
+ }
90
+ var totalQty = groupQuantities.reduce(function (a, b) { return a + b; }, 0);
91
+ var heightSetup = variationSetupCost({
92
+ variationCost: field.heightVariationCost || 0,
93
+ variationCostDiscountGroup: (_b = field.heightVariationCostDiscountGroup) !== null && _b !== void 0 ? _b : null,
94
+ }, totalQty);
95
+ var widthSetup = variationSetupCost({
96
+ variationCost: field.widthVariationCost || 0,
97
+ variationCostDiscountGroup: (_c = field.widthVariationCostDiscountGroup) !== null && _c !== void 0 ? _c : null,
98
+ }, totalQty);
99
+ var heightUcs = variationUnitCosts({
100
+ variationUnitCost: field.heightVariationUnitCost || 0,
101
+ variationUnitCostDiscountGroup: (_d = field.heightVariationUnitCostDiscountGroup) !== null && _d !== void 0 ? _d : null,
102
+ }, groupQuantities);
103
+ var widthUcs = variationUnitCosts({
104
+ variationUnitCost: field.widthVariationUnitCost || 0,
105
+ variationUnitCostDiscountGroup: (_e = field.widthVariationUnitCostDiscountGroup) !== null && _e !== void 0 ? _e : null,
106
+ }, groupQuantities);
107
+ var areaUnit = (field.areaUnit || 'mm').toLowerCase();
108
+ var mmPerUnit = areaUnit === 'm' ? 1000
109
+ : areaUnit === 'cm' ? 10
110
+ : areaUnit === 'in' || areaUnit === 'inch' || areaUnit === 'inches' ? 25.4
111
+ : areaUnit === 'ft' || areaUnit === 'foot' || areaUnit === 'feet' ? 304.8
112
+ : 1;
113
+ var heightU = parsed.heightMm / mmPerUnit;
114
+ var widthU = parsed.widthMm / mmPerUnit;
115
+ var area = heightU * widthU;
116
+ // onceOff/unit = rateH × rateW × height_u × width_u (in field areaUnit)
117
+ var setup = roundHalfEven(heightSetup * widthSetup * area, 3);
118
+ var unitList = heightUcs.map(function (hu, i) {
119
+ return roundHalfEven(hu * widthUcs[i] * area, 3);
120
+ });
121
+ return { setup: setup, unitList: unitList };
122
+ }
68
123
  function costFactor(field, sel, groupQuantities) {
69
124
  var e_1, _a, e_2, _b;
70
125
  var n = groupQuantities.length;
126
+ // Coerce: JSON/APIs occasionally deliver fieldType as a string.
127
+ if (Number(field.fieldType) === FieldType.AREA) {
128
+ return areaCostFactor(field, sel, groupQuantities);
129
+ }
71
130
  if (isEmpty(field, sel)) {
72
131
  return { setup: 0, unitList: new Array(n).fill(0) };
73
132
  }
@@ -72,6 +72,35 @@ test('hidden conditional field is not costed', function () {
72
72
  var r = estimateQuote(rules, { quantity: 10, fieldValues: { 2: { selectedOptionIds: [201] } } });
73
73
  expect(r.cost).toBe(100);
74
74
  });
75
+ test('area field multiplies height/width rate cards when dimensions set', function () {
76
+ var rules = __assign(__assign({}, base), { product: { unitPrice: 1, minimumPrice: null, discountGroup: null }, fields: [{
77
+ id: 14, originalId: 14, position: 0, fieldType: 14, independent: true,
78
+ isSelectable: false, selectedBy: [],
79
+ variationCost: 0, variationUnitCost: 0,
80
+ variationCostDiscountGroup: null, variationUnitCostDiscountGroup: null,
81
+ areaUnit: 'mm',
82
+ heightVariationCost: 2, widthVariationCost: 2,
83
+ heightVariationUnitCost: 3, widthVariationUnitCost: 4,
84
+ heightVariationCostDiscountGroup: null,
85
+ heightVariationUnitCostDiscountGroup: null,
86
+ widthVariationCostDiscountGroup: null,
87
+ widthVariationUnitCostDiscountGroup: null,
88
+ options: [],
89
+ }] });
90
+ var empty = estimateQuote(rules, {
91
+ quantity: 1, fieldValues: { 14: { value: '' } },
92
+ });
93
+ expect(empty.cost).toBe(1);
94
+ var priced = estimateQuote(rules, {
95
+ quantity: 1, fieldValues: { 14: { value: '2,5' } },
96
+ });
97
+ // area 2*5=10; onceOff 2*2*10=40; unit 3*4*10=120 → cost = 1 + 40 + 120 = 161
98
+ expect(priced.cost).toBe(161);
99
+ expect(priced.totalCost).toBe(177.1); // +10% tax
100
+ // String fieldType (some JSON paths) must still hit the area calculator.
101
+ var asString = estimateQuote(__assign(__assign({}, rules), { fields: [__assign(__assign({}, rules.fields[0]), { fieldType: '14' })] }), { quantity: 1, fieldValues: { 14: { value: '2,5' } } });
102
+ expect(asString.cost).toBe(161);
103
+ });
75
104
  test('non-selectable field with value adds field cost; empty adds nothing', function () {
76
105
  var mk = function (sel) {
77
106
  var rules = __assign(__assign({}, base), { fields: [{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "merchi_sdk_ts",
3
- "version": "1.27.0",
3
+ "version": "1.28.0",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "repository": "git@github.com:merchisdk/merchi_sdk_ts.git",
@@ -174,6 +174,9 @@ export class VariationField extends Entity {
174
174
  @VariationField.property()
175
175
  public areaInputType?: string;
176
176
 
177
+ @VariationField.property({type: Number})
178
+ public areaStep?: number | null;
179
+
177
180
  @VariationField.property()
178
181
  public aspectRatioLock?: boolean;
179
182
 
@@ -79,6 +79,47 @@ test('hidden conditional field is not costed', () => {
79
79
  expect(r.cost).toBe(100);
80
80
  });
81
81
 
82
+ test('area field multiplies height/width rate cards when dimensions set', () => {
83
+ const rules: PricingRules = {
84
+ ...base,
85
+ product: { unitPrice: 1, minimumPrice: null, discountGroup: null },
86
+ fields: [{
87
+ id: 14, originalId: 14, position: 0, fieldType: 14, independent: true,
88
+ isSelectable: false, selectedBy: [],
89
+ variationCost: 0, variationUnitCost: 0,
90
+ variationCostDiscountGroup: null, variationUnitCostDiscountGroup: null,
91
+ areaUnit: 'mm',
92
+ heightVariationCost: 2, widthVariationCost: 2,
93
+ heightVariationUnitCost: 3, widthVariationUnitCost: 4,
94
+ heightVariationCostDiscountGroup: null,
95
+ heightVariationUnitCostDiscountGroup: null,
96
+ widthVariationCostDiscountGroup: null,
97
+ widthVariationUnitCostDiscountGroup: null,
98
+ options: [],
99
+ }],
100
+ };
101
+ const empty = estimateQuote(rules, {
102
+ quantity: 1, fieldValues: { 14: { value: '' } },
103
+ }) as QuoteResult;
104
+ expect(empty.cost).toBe(1);
105
+ const priced = estimateQuote(rules, {
106
+ quantity: 1, fieldValues: { 14: { value: '2,5' } },
107
+ }) as QuoteResult;
108
+ // area 2*5=10; onceOff 2*2*10=40; unit 3*4*10=120 → cost = 1 + 40 + 120 = 161
109
+ expect(priced.cost).toBe(161);
110
+ expect(priced.totalCost).toBe(177.1); // +10% tax
111
+
112
+ // String fieldType (some JSON paths) must still hit the area calculator.
113
+ const asString = estimateQuote(
114
+ {
115
+ ...rules,
116
+ fields: [{ ...rules.fields[0], fieldType: '14' as unknown as number }],
117
+ },
118
+ { quantity: 1, fieldValues: { 14: { value: '2,5' } } },
119
+ ) as QuoteResult;
120
+ expect(asString.cost).toBe(161);
121
+ });
122
+
82
123
  test('non-selectable field with value adds field cost; empty adds nothing', () => {
83
124
  const mk = (sel: any) => {
84
125
  const rules: PricingRules = {
@@ -5,6 +5,7 @@ import {
5
5
  import { applyDiscount } from './discount.js';
6
6
  import { roundHalfEven } from './round.js';
7
7
  import { resolveVisibleFields } from './visibility.js';
8
+ import { FieldType } from '../constants/field_types.js';
8
9
 
9
10
  /** Form inputs often supply quantities as strings. Coerce before any arithmetic
10
11
  * so `reduce((a,b)=>a+b)` cannot concatenate ("100"+"1" → "01001" → 1001). */
@@ -13,6 +14,19 @@ function toQuantity(value: unknown): number {
13
14
  return Number.isFinite(n) ? n : 0;
14
15
  }
15
16
 
17
+ function parseAreaMm(
18
+ value: string | number | null | undefined
19
+ ): { heightMm: number; widthMm: number } | null {
20
+ if (value === undefined || value === null || value === '') return null;
21
+ const parts = String(value).split(',').map((p) => p.trim());
22
+ if (parts.length !== 2) return null;
23
+ const heightMm = Number(parts[0]);
24
+ const widthMm = Number(parts[1]);
25
+ if (!Number.isFinite(heightMm) || !Number.isFinite(widthMm)) return null;
26
+ if (heightMm <= 0 || widthMm <= 0) return null;
27
+ return { heightMm, widthMm };
28
+ }
29
+
16
30
  function unitPriceAt(rules: PricingRules, qty: number): number {
17
31
  let unitPrice = applyDiscount(rules.product.unitPrice, qty, rules.product.discountGroup);
18
32
  const mop = rules.product.minimumPrice;
@@ -69,12 +83,75 @@ function isEmpty(field: PricingField, sel: FieldSelection | undefined): boolean
69
83
  );
70
84
  }
71
85
 
86
+ function areaCostFactor(
87
+ field: PricingField,
88
+ sel: FieldSelection | undefined,
89
+ groupQuantities: number[]
90
+ ): { setup: number; unitList: number[] } {
91
+ const n = groupQuantities.length;
92
+ const parsed = parseAreaMm(sel?.value ?? null);
93
+ if (!parsed) {
94
+ return { setup: 0, unitList: new Array(n).fill(0) };
95
+ }
96
+ const totalQty = groupQuantities.reduce((a, b) => a + b, 0);
97
+ const heightSetup = variationSetupCost(
98
+ {
99
+ variationCost: field.heightVariationCost || 0,
100
+ variationCostDiscountGroup: field.heightVariationCostDiscountGroup ?? null,
101
+ },
102
+ totalQty
103
+ );
104
+ const widthSetup = variationSetupCost(
105
+ {
106
+ variationCost: field.widthVariationCost || 0,
107
+ variationCostDiscountGroup: field.widthVariationCostDiscountGroup ?? null,
108
+ },
109
+ totalQty
110
+ );
111
+ const heightUcs = variationUnitCosts(
112
+ {
113
+ variationUnitCost: field.heightVariationUnitCost || 0,
114
+ variationUnitCostDiscountGroup:
115
+ field.heightVariationUnitCostDiscountGroup ?? null,
116
+ },
117
+ groupQuantities
118
+ );
119
+ const widthUcs = variationUnitCosts(
120
+ {
121
+ variationUnitCost: field.widthVariationUnitCost || 0,
122
+ variationUnitCostDiscountGroup:
123
+ field.widthVariationUnitCostDiscountGroup ?? null,
124
+ },
125
+ groupQuantities
126
+ );
127
+ const areaUnit = (field.areaUnit || 'mm').toLowerCase();
128
+ const mmPerUnit =
129
+ areaUnit === 'm' ? 1000
130
+ : areaUnit === 'cm' ? 10
131
+ : areaUnit === 'in' || areaUnit === 'inch' || areaUnit === 'inches' ? 25.4
132
+ : areaUnit === 'ft' || areaUnit === 'foot' || areaUnit === 'feet' ? 304.8
133
+ : 1;
134
+ const heightU = parsed.heightMm / mmPerUnit;
135
+ const widthU = parsed.widthMm / mmPerUnit;
136
+ const area = heightU * widthU;
137
+ // onceOff/unit = rateH × rateW × height_u × width_u (in field areaUnit)
138
+ const setup = roundHalfEven(heightSetup * widthSetup * area, 3);
139
+ const unitList = heightUcs.map((hu, i) =>
140
+ roundHalfEven(hu * widthUcs[i] * area, 3)
141
+ );
142
+ return { setup, unitList };
143
+ }
144
+
72
145
  function costFactor(
73
146
  field: PricingField,
74
147
  sel: FieldSelection | undefined,
75
148
  groupQuantities: number[]
76
149
  ): { setup: number; unitList: number[] } {
77
150
  const n = groupQuantities.length;
151
+ // Coerce: JSON/APIs occasionally deliver fieldType as a string.
152
+ if (Number(field.fieldType) === FieldType.AREA) {
153
+ return areaCostFactor(field, sel, groupQuantities);
154
+ }
78
155
  if (isEmpty(field, sel)) {
79
156
  return { setup: 0, unitList: new Array(n).fill(0) };
80
157
  }
@@ -35,6 +35,16 @@ export interface PricingField {
35
35
  variationUnitCost: number;
36
36
  variationCostDiscountGroup: DiscountGroup | null;
37
37
  variationUnitCostDiscountGroup: DiscountGroup | null;
38
+ /** Area field (type 14): once-off and per-edge unit costs. */
39
+ areaUnit?: 'mm' | 'cm' | 'm' | string;
40
+ heightVariationCost?: number;
41
+ heightVariationUnitCost?: number;
42
+ widthVariationCost?: number;
43
+ widthVariationUnitCost?: number;
44
+ heightVariationCostDiscountGroup?: DiscountGroup | null;
45
+ heightVariationUnitCostDiscountGroup?: DiscountGroup | null;
46
+ widthVariationCostDiscountGroup?: DiscountGroup | null;
47
+ widthVariationUnitCostDiscountGroup?: DiscountGroup | null;
38
48
  options: PricingOption[];
39
49
  }
40
50