@darkpos/pricing 1.0.101 → 1.0.103

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.
@@ -1316,4 +1316,245 @@ describe('Item actions', () => {
1316
1316
  expect(newItem.modifiers.length).toBe(1);
1317
1317
  expect(newItem.modifiers[0].name).toBe('General Repair Department');
1318
1318
  });
1319
+
1320
+ test('1- Item total should not be below 0, having price override modifiers', () => {
1321
+ const order = {
1322
+ _id: '685c2d2bd7f68fccd07af8c7',
1323
+ tax: 0,
1324
+ modifiers: [],
1325
+ discount: 0,
1326
+ total: -2.28,
1327
+ fee: 0,
1328
+ items: [
1329
+ {
1330
+ name: 'Pants',
1331
+ description: null,
1332
+ pieces: 1,
1333
+ modifiersTotalAmount: 0,
1334
+ total: -2.28,
1335
+ totalPaid: 0,
1336
+ price: 0,
1337
+ quantity: 1,
1338
+ notes: [],
1339
+ modifiers: [
1340
+ {
1341
+ _id: '685c2d64d7f68fccd07af8cd',
1342
+ attributes: ['override'],
1343
+ modifierId: '67fea241af24e570c032b40d',
1344
+ _parentId: null,
1345
+ locked: false,
1346
+ name: 'No Clean',
1347
+ tags: ['default'],
1348
+ order: 0,
1349
+ included: false,
1350
+ direct: true,
1351
+ default: true,
1352
+ code: null,
1353
+ properties: {
1354
+ override: {
1355
+ field: 'price',
1356
+ type: 'fixed',
1357
+ multiplier: true,
1358
+ fixedValue: 0,
1359
+ },
1360
+ group: null,
1361
+ department: null,
1362
+ url: null,
1363
+ color: null,
1364
+ label: {
1365
+ backgroundColor: null,
1366
+ align: null,
1367
+ },
1368
+ sort: null,
1369
+ },
1370
+ compute: null,
1371
+ __typename: 'Modifier',
1372
+ addModifiers: [],
1373
+ delModifiers: [],
1374
+ },
1375
+ {
1376
+ _id: '685c2d65d7f68fccd07af8ce',
1377
+ attributes: [],
1378
+ modifierId: '67fea241af24e570c032b324',
1379
+ _parentId: null,
1380
+ locked: false,
1381
+ name: '30% Family Discount',
1382
+ type: 'discount',
1383
+ tags: ['default'],
1384
+ order: 0,
1385
+ included: false,
1386
+ direct: true,
1387
+ hidden: false,
1388
+ print: true,
1389
+ required: false,
1390
+ recommended: false,
1391
+ default: false,
1392
+ code: null,
1393
+ properties: {
1394
+ group: null,
1395
+ department: null,
1396
+ url: null,
1397
+ color: null,
1398
+ label: {
1399
+ backgroundColor: null,
1400
+ align: null,
1401
+ },
1402
+ sort: null,
1403
+ isQuantityMultiplier: true,
1404
+ },
1405
+ compute: {
1406
+ type: 'percentage',
1407
+ action: 'subtract',
1408
+ amount: 30,
1409
+ },
1410
+ __typename: 'Modifier',
1411
+ addModifiers: [],
1412
+ delModifiers: [],
1413
+ },
1414
+ ],
1415
+ weight: 0,
1416
+ properties: {
1417
+ overwrittenPrice: 7.59,
1418
+ },
1419
+ },
1420
+ ],
1421
+ customer: {
1422
+ _id: '67feae8eeb5aa1e1848374b9',
1423
+ tags: ['default'],
1424
+ },
1425
+ };
1426
+
1427
+ const calculatedOrder = pricingService.order.calculate(order);
1428
+
1429
+ expect(calculatedOrder.total).toBe(0);
1430
+ expect(calculatedOrder.items[0].price).toBe(0);
1431
+ expect(calculatedOrder.items[0].total).toBe(0);
1432
+ expect(calculatedOrder.items[0].modifiers[0]._computed.description).toBe(
1433
+ 'No Clean (0 Unit @ $0.00/Unit)'
1434
+ );
1435
+ expect(calculatedOrder.items[0].modifiers[1]._computed.description).toBe(
1436
+ '30% Family Discount'
1437
+ );
1438
+ });
1439
+
1440
+ test('2- Item total should not be below 0, having price override modifiers', () => {
1441
+ const order = {
1442
+ _id: '685c2d2bd7f68fccd07af8c7',
1443
+ tax: 0,
1444
+ modifiers: [],
1445
+ discount: 0,
1446
+ total: -2.28,
1447
+ fee: 0,
1448
+ items: [
1449
+ {
1450
+ name: 'Pants',
1451
+ description: null,
1452
+ pieces: 1,
1453
+ modifiersTotalAmount: 0,
1454
+ total: -2.28,
1455
+ totalPaid: 0,
1456
+ price: 0,
1457
+ quantity: 1,
1458
+ notes: [],
1459
+ modifiers: [
1460
+ {
1461
+ _id: '685c2d65d7f68fccd07af8ce',
1462
+ attributes: [],
1463
+ modifierId: '67fea241af24e570c032b324',
1464
+ _parentId: null,
1465
+ locked: false,
1466
+ name: '30% Family Discount',
1467
+ type: 'discount',
1468
+ tags: ['default'],
1469
+ order: 0,
1470
+ included: false,
1471
+ direct: true,
1472
+ hidden: false,
1473
+ print: true,
1474
+ required: false,
1475
+ recommended: false,
1476
+ default: false,
1477
+ code: null,
1478
+ properties: {
1479
+ group: null,
1480
+ department: null,
1481
+ url: null,
1482
+ color: null,
1483
+ label: {
1484
+ backgroundColor: null,
1485
+ align: null,
1486
+ },
1487
+ sort: null,
1488
+ isQuantityMultiplier: true,
1489
+ },
1490
+ compute: {
1491
+ type: 'percentage',
1492
+ action: 'subtract',
1493
+ amount: 30,
1494
+ },
1495
+ __typename: 'Modifier',
1496
+ addModifiers: [],
1497
+ delModifiers: [],
1498
+ },
1499
+ {
1500
+ _id: '685c2d64d7f68fccd07af8cd',
1501
+ attributes: ['override'],
1502
+ modifierId: '67fea241af24e570c032b40d',
1503
+ _parentId: null,
1504
+ locked: false,
1505
+ name: 'No Clean',
1506
+ tags: ['default'],
1507
+ order: 0,
1508
+ included: false,
1509
+ direct: true,
1510
+ default: true,
1511
+ code: null,
1512
+ properties: {
1513
+ override: {
1514
+ field: 'price',
1515
+ type: 'fixed',
1516
+ multiplier: true,
1517
+ fixedValue: 0,
1518
+ },
1519
+ group: null,
1520
+ department: null,
1521
+ url: null,
1522
+ color: null,
1523
+ label: {
1524
+ backgroundColor: null,
1525
+ align: null,
1526
+ },
1527
+ sort: null,
1528
+ },
1529
+ compute: null,
1530
+ __typename: 'Modifier',
1531
+ addModifiers: [],
1532
+ delModifiers: [],
1533
+ },
1534
+ ],
1535
+ weight: 0,
1536
+ properties: {
1537
+ overwrittenPrice: 7.59,
1538
+ },
1539
+ },
1540
+ ],
1541
+ customer: {
1542
+ _id: '67feae8eeb5aa1e1848374b9',
1543
+ tags: ['default'],
1544
+ },
1545
+ };
1546
+
1547
+ const calculatedOrder = pricingService.order.calculate(order);
1548
+
1549
+ expect(calculatedOrder.total).toBe(0);
1550
+ expect(calculatedOrder.items[0].price).toBe(0);
1551
+ expect(calculatedOrder.items[0].total).toBe(0);
1552
+
1553
+ expect(calculatedOrder.items[0].modifiers[0]._computed.description).toBe(
1554
+ '30% Family Discount'
1555
+ );
1556
+ expect(calculatedOrder.items[0].modifiers[1]._computed.description).toBe(
1557
+ 'No Clean (0 Unit @ $0.00/Unit)'
1558
+ );
1559
+ });
1319
1560
  });
@@ -676,7 +676,7 @@ describe('Modifier actions', () => {
676
676
  properties: {
677
677
  isManual: true,
678
678
  },
679
- _id: 'ead6eb247a93bca8f802f9b2',
679
+ _id: '5d97805a91c31603f8fc6ab0',
680
680
  }),
681
681
  expect.objectContaining({
682
682
  name: 'Blue Label',
@@ -685,7 +685,7 @@ describe('Modifier actions', () => {
685
685
  properties: {
686
686
  isManual: true,
687
687
  },
688
- _id: '4e23edf8fa734884f4618003',
688
+ _id: '49327d26c324cb709456a279',
689
689
  }),
690
690
  ])
691
691
  );
@@ -739,21 +739,21 @@ describe('Modifier actions', () => {
739
739
  expect(result).toStrictEqual([
740
740
  {
741
741
  name: 'Red Tag',
742
- _id: 'ead6eb247a93bca8f802f9b2',
742
+ _id: '5d97805a91c31603f8fc6ab0',
743
743
  direct: true,
744
744
  group: 'colorGroup1',
745
745
  properties: { isManual: true, spreadFrom: 'abc123' },
746
746
  },
747
747
  {
748
748
  name: 'White Shirt',
749
- _id: '4e23edf8fa734884f4618003',
749
+ _id: '3b115e8ff1d00d9ae4ea3601',
750
750
  direct: true,
751
751
  group: 'colorGroup1',
752
752
  properties: { isManual: true, spreadFrom: 'abc123' },
753
753
  },
754
754
  {
755
755
  name: 'Blue Label',
756
- _id: '94b02ee7ba009b09a913a786',
756
+ _id: '49327d26c324cb709456a279',
757
757
  direct: true,
758
758
  group: 'colorGroup1',
759
759
  properties: { isManual: true, spreadFrom: 'abc123' },
@@ -815,21 +815,21 @@ describe('Modifier actions', () => {
815
815
  expect(result).toStrictEqual([
816
816
  {
817
817
  name: 'Red Tag',
818
- _id: 'ead6eb247a93bca8f802f9b2',
818
+ _id: '5d97805a91c31603f8fc6ab0',
819
819
  direct: true,
820
820
  group: 'colorGroup1',
821
821
  properties: { isManual: true, spreadFrom: 'abc123' },
822
822
  },
823
823
  {
824
824
  name: 'White Shirt',
825
- _id: '4e23edf8fa734884f4618003',
825
+ _id: '3b115e8ff1d00d9ae4ea3601',
826
826
  direct: true,
827
827
  group: 'colorGroup1',
828
828
  properties: { isManual: true, spreadFrom: 'abc123' },
829
829
  },
830
830
  {
831
831
  name: 'Blue Label',
832
- _id: '94b02ee7ba009b09a913a786',
832
+ _id: '49327d26c324cb709456a279',
833
833
  direct: true,
834
834
  group: 'colorGroup1',
835
835
  properties: { isManual: true, spreadFrom: 'abc123' },
@@ -2847,7 +2847,7 @@ describe('Order actions', () => {
2847
2847
  });
2848
2848
 
2849
2849
  expect(calculatedItem.modifiers[1]).toHaveProperty('_computed', {
2850
- amount: -0,
2850
+ amount: 0,
2851
2851
  description: '100% discount',
2852
2852
  });
2853
2853
  });
@@ -2890,7 +2890,7 @@ describe('Order actions', () => {
2890
2890
  });
2891
2891
 
2892
2892
  expect(calculatedItem.modifiers[1]).toHaveProperty('_computed', {
2893
- amount: -0,
2893
+ amount: 0,
2894
2894
  description: '100 USD discount',
2895
2895
  });
2896
2896
  });
@@ -2939,7 +2939,7 @@ describe('Order actions', () => {
2939
2939
  });
2940
2940
 
2941
2941
  expect(calculatedItem.modifiers[1]).toHaveProperty('_computed', {
2942
- amount: -0,
2942
+ amount: 0,
2943
2943
  description: '100% discount',
2944
2944
  });
2945
2945
  });
@@ -3001,7 +3001,7 @@ describe('Order actions', () => {
3001
3001
  });
3002
3002
 
3003
3003
  expect(calculatedItem.modifiers[1]).toHaveProperty('_computed', {
3004
- amount: -0,
3004
+ amount: 0,
3005
3005
  description: '100 USD discount',
3006
3006
  });
3007
3007
  });
@@ -3108,7 +3108,7 @@ describe('Order actions', () => {
3108
3108
  });
3109
3109
 
3110
3110
  expect(calculatedItem.modifiers[1]).toHaveProperty('_computed', {
3111
- amount: -0,
3111
+ amount: 0,
3112
3112
  description: '12 USD discount',
3113
3113
  });
3114
3114
  });
@@ -0,0 +1,44 @@
1
+ module.exports = ({ utils, actions, modifierActions }) => {
2
+ const { math } = utils;
3
+
4
+ return function adjustNegativeTotal({ modifiers, subTotals, total, price }) {
5
+ let excess = math.abs(total);
6
+
7
+ for (let i = modifiers.length - 1; i >= 0 && excess > 0; i -= 1) {
8
+ const mod = modifiers[i];
9
+
10
+ if (
11
+ mod.compute &&
12
+ mod.compute.action === 'subtract' &&
13
+ typeof mod._computed.amount === 'number' &&
14
+ math.lt(mod._computed.amount, 0)
15
+ ) {
16
+ const currentAmount = math.abs(mod._computed.amount);
17
+ const reduction = math.min(currentAmount, excess);
18
+
19
+ mod._computed.amount += reduction;
20
+ mod._computed.description = modifierActions.createDescription({
21
+ modifier: mod,
22
+ price,
23
+ });
24
+
25
+ excess = math.sub(excess, reduction);
26
+
27
+ const result = actions.getTotals({
28
+ subTotals,
29
+ amountToAdd: reduction,
30
+ included: mod.included,
31
+ type: mod.type,
32
+ direct: mod.direct,
33
+ });
34
+
35
+ return {
36
+ subTotals: result.subTotals,
37
+ total: result.total,
38
+ };
39
+ }
40
+ }
41
+
42
+ return { subTotals, total };
43
+ };
44
+ };
@@ -0,0 +1,15 @@
1
+ module.exports = ({ actions, modifierActions }) =>
2
+ function applyNoteOverridesToItem({ item, validatedModifiers }) {
3
+ const localItem = item;
4
+ validatedModifiers.forEach(mod => {
5
+ if (
6
+ modifierActions.isNotesOverride(mod) &&
7
+ modifierActions.isValid(mod)
8
+ ) {
9
+ localItem.notes = actions.overrideNotes({
10
+ item,
11
+ message: modifierActions.getOverrideNote(mod),
12
+ }).notes;
13
+ }
14
+ });
15
+ };
@@ -90,38 +90,18 @@ module.exports = ({ _, utils, actions, modifierActions }) => {
90
90
  );
91
91
  });
92
92
 
93
- const modifiersToNotCompute = validatedModifiers
94
- .filter(
95
- each =>
96
- modifierActions.isPaymentModifier(each) ||
97
- !modifiersToCompute.find(
98
- ceach => ceach.modifierId === each.modifierId
99
- ) ||
100
- modifierActions.isNotesOverride(each)
101
- )
102
- .map(each =>
103
- modifierActions.calculate(each, {
104
- price,
105
- quantity,
106
- skip: true,
107
- basePrice: actions.getBasePrice(item),
108
- })
109
- );
93
+ const modifiersToNotCompute = actions.getModifiersToNotCompute({
94
+ item,
95
+ validatedModifiers,
96
+ price,
97
+ quantity,
98
+ modifiersToCompute,
99
+ });
110
100
 
111
101
  let computedPrice = price;
112
102
  let total = math.mul(computedPrice, quantity);
113
103
 
114
- validatedModifiers.forEach(mod => {
115
- if (
116
- modifierActions.isNotesOverride(mod) &&
117
- modifierActions.isValid(mod)
118
- ) {
119
- item.notes = actions.overrideNotes({
120
- item,
121
- message: modifierActions.getOverrideNote(mod),
122
- }).notes;
123
- }
124
- });
104
+ actions.applyNotesOverridesToItem({ item, validatedModifiers });
125
105
 
126
106
  if (modifiersToCompute.length || paymentModifiersToCompute.length) {
127
107
  // sort modifiers based on sort
@@ -217,16 +197,29 @@ module.exports = ({ _, utils, actions, modifierActions }) => {
217
197
  accumulatedAmount = math.add(accumulatedAmount, _computed.amount);
218
198
  prvSort = sort;
219
199
 
200
+ let nextSubTotals = {};
201
+
202
+ ({ subTotals: nextSubTotals, total } = actions.getTotals({
203
+ subTotals,
204
+ amountToAdd: computedAmount,
205
+ included,
206
+ type,
207
+ direct,
208
+ }));
209
+
220
210
  if (
221
211
  !modifierActions.isOverride(modifier) ||
222
212
  modifierActions.isOverrideSubtotal(modifier)
223
213
  ) {
224
- ({ subTotals, total } = actions.getTotals({
214
+ subTotals = nextSubTotals;
215
+ }
216
+
217
+ if (math.lt(total, 0)) {
218
+ ({ subTotals, total } = actions.adjustNegativeTotal({
219
+ modifiers,
225
220
  subTotals,
226
- amountToAdd: computedAmount,
227
- included,
228
- type,
229
- direct,
221
+ total,
222
+ price,
230
223
  }));
231
224
  }
232
225
  }
@@ -0,0 +1,26 @@
1
+ module.exports = ({ actions, modifierActions }) =>
2
+ function getModifiersToNotCompute({
3
+ item,
4
+ validatedModifiers,
5
+ price,
6
+ quantity,
7
+ modifiersToCompute,
8
+ }) {
9
+ return validatedModifiers
10
+ .filter(
11
+ each =>
12
+ modifierActions.isPaymentModifier(each) ||
13
+ !modifiersToCompute.find(
14
+ ceach => ceach.modifierId === each.modifierId
15
+ ) ||
16
+ modifierActions.isNotesOverride(each)
17
+ )
18
+ .map(each =>
19
+ modifierActions.calculate(each, {
20
+ price,
21
+ quantity,
22
+ skip: true,
23
+ basePrice: actions.getBasePrice(item),
24
+ })
25
+ );
26
+ };
File without changes
package/lib/item/index.js CHANGED
@@ -66,6 +66,9 @@ const isOverwrittenPrice = require('./isOverwrittenPrice');
66
66
  const isOverwrittenQuantity = require('./isOverwrittenQuantity');
67
67
  const overrideNotes = require('./overrideNotes');
68
68
  const validateModifiers = require('./validateModifiers');
69
+ const adjustNegativeTotal = require('./adjustNegativeTotal');
70
+ const applyNotesOverridesToItem = require('./applyNotesOverridesToItem');
71
+ const getModifiersToNotCompute = require('./getModifiersToNotCompute');
69
72
 
70
73
  const itemActions = (deps = {}) => {
71
74
  const actions = {};
@@ -145,6 +148,9 @@ const itemActions = (deps = {}) => {
145
148
  isOverwrittenQuantity: isOverwrittenQuantity(innerDeps),
146
149
  overrideNotes: overrideNotes(innerDeps),
147
150
  validateModifiers: validateModifiers(innerDeps),
151
+ adjustNegativeTotal: adjustNegativeTotal(innerDeps),
152
+ applyNotesOverridesToItem: applyNotesOverridesToItem(innerDeps),
153
+ getModifiersToNotCompute: getModifiersToNotCompute(innerDeps),
148
154
  });
149
155
 
150
156
  Object.keys(freezedActions).forEach(actionName => {
@@ -70,15 +70,6 @@ module.exports = ({ _, constants, utils, actions }) => {
70
70
  _computed.amount = math.mul(_computed.amount, options.quantity);
71
71
  }
72
72
 
73
- if (
74
- actions.isSubtract(_modifier) &&
75
- typeof _computed.amount === 'number' &&
76
- typeof options.maxDiscountAmount === 'number' &&
77
- math.gt(math.abs(_computed.amount), options.maxDiscountAmount)
78
- ) {
79
- _computed.amount = math.mul(options.maxDiscountAmount, -1);
80
- }
81
-
82
73
  if (actions.isTotalOverride(_modifier)) {
83
74
  _computed.amount = math.sub(compute.amount, options.maxDiscountAmount);
84
75
  }
@@ -0,0 +1,48 @@
1
+ module.exports = ({ sha256 }) => {
2
+ function combineHashParts(partA, partB) {
3
+ let combined = '';
4
+ try {
5
+ combined = partA
6
+ .split('')
7
+ .map((char, index) => {
8
+ const digitA = parseInt(char, 16);
9
+ const digitB = parseInt(partB[index], 16);
10
+
11
+ // eslint-disable-next-line no-bitwise
12
+ return (digitA ^ digitB).toString(16);
13
+ })
14
+ .join('')
15
+ .padStart(partA.length, '0');
16
+ } catch (err) {
17
+ console.log(JSON.stringify(err));
18
+ }
19
+
20
+ return combined;
21
+ }
22
+
23
+ function generateManualModifierId(parentId, name) {
24
+ const input = `${parentId}-${name}`;
25
+ const hash = sha256(input).toString(); // 64 hex characters
26
+
27
+ const firstPart = hash.slice(0, 24);
28
+ const lastPart = hash.slice(-24);
29
+
30
+ return combineHashParts(firstPart, lastPart); // 24-char hex string
31
+ }
32
+
33
+ return function createManualModifier({ parentId, name, group }) {
34
+ const _id = generateManualModifierId(parentId, name);
35
+
36
+ const manualModifier = {
37
+ name,
38
+ _id,
39
+ direct: true,
40
+ group,
41
+ properties: {
42
+ isManual: true,
43
+ },
44
+ };
45
+
46
+ return manualModifier;
47
+ };
48
+ };
@@ -1,4 +1,4 @@
1
- module.exports = ({ sha256 }) => {
1
+ module.exports = ({ actions }) => {
2
2
  const csvToArray = input =>
3
3
  input
4
4
  .split(',')
@@ -39,23 +39,13 @@ module.exports = ({ sha256 }) => {
39
39
  ).filter(modName => otherModifiers.every(mod => mod.name !== modName));
40
40
 
41
41
  modifiers.push(
42
- ...manualModifiersNames.map((each, index) => {
43
- const hash = sha256(`${index}-${modifier._id}`).toString();
44
-
45
- const _id = hash.substring(0, 24);
46
-
47
- const manualModifier = {
42
+ ...manualModifiersNames.map(each =>
43
+ actions.createManualModifier({
44
+ parentId: modifier._id,
48
45
  name: each,
49
- _id,
50
- direct: true,
51
46
  group: modifier.group,
52
- properties: {
53
- isManual: true,
54
- },
55
- };
56
-
57
- return manualModifier;
58
- })
47
+ })
48
+ )
59
49
  );
60
50
  }
61
51
 
@@ -166,6 +166,7 @@ const getOverrideNote = require('./getOverrideNote');
166
166
  const isNotesOverride = require('./isNotesOverride');
167
167
  const isOverrideSubtotal = require('./isOverrideSubtotal');
168
168
  const validatePaymentCondition = require('./validatePaymentCondition');
169
+ const createManualModifier = require('./createManualModifier');
169
170
 
170
171
  const modifierActions = (deps = {}) => {
171
172
  const actions = {};
@@ -345,6 +346,7 @@ const modifierActions = (deps = {}) => {
345
346
  isNotesOverride: isNotesOverride(innerDeps),
346
347
  isOverrideSubtotal: isOverrideSubtotal(innerDeps),
347
348
  validatePaymentCondition: validatePaymentCondition(innerDeps),
349
+ createManualModifier: createManualModifier(innerDeps),
348
350
  });
349
351
 
350
352
  Object.keys(freezedActions).forEach(actionName => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darkpos/pricing",
3
- "version": "1.0.101",
3
+ "version": "1.0.103",
4
4
  "description": "Pricing calculator",
5
5
  "author": "Dark POS",
6
6
  "license": "ISC",
@@ -54,5 +54,5 @@
54
54
  "supertest": "^6.2.3",
55
55
  "supervisor": "^0.12.0"
56
56
  },
57
- "gitHead": "aad940d770d15b6072fd45e300302002bfc8fc08"
57
+ "gitHead": "032bea629a5c32d78886daca4d2382b847b5d075"
58
58
  }