@darkpos/pricing 1.0.100 → 1.0.102

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
  });
@@ -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
  });
@@ -239,4 +239,64 @@ describe('pickEndDate function', () => {
239
239
 
240
240
  expect(result).toBe('2025-05-14T15:00:00Z');
241
241
  });
242
+
243
+ test('pickEndDate - filters out schedules with departments', () => {
244
+ const schedulesWithDepartments = [
245
+ {
246
+ _id: 'schedule-with-departments',
247
+ dayOfWeek: [2],
248
+ recommended: 'auto_recommended',
249
+ addDays: 2,
250
+ cutDay: 1,
251
+ skipDays: [1],
252
+ readyHour: { hour: 18, minute: 0 },
253
+ cutHour: { hour: 16, minute: 0 },
254
+ departments: [{ _id: 'department-id' }],
255
+ },
256
+ {
257
+ _id: 'schedule-without-departments',
258
+ dayOfWeek: [2],
259
+ recommended: 'auto_recommended',
260
+ addDays: 1,
261
+ cutDay: 1,
262
+ skipDays: [1],
263
+ readyHour: { hour: 17, minute: 0 },
264
+ cutHour: { hour: 16, minute: 0 },
265
+ },
266
+ ];
267
+
268
+ const pricingService = usePricing(
269
+ getDefaultSettings(schedulesWithDepartments)
270
+ );
271
+ const now = moment('2025-05-13T15:00:00Z').tz('America/New_York');
272
+ jest.spyOn(moment, 'now').mockImplementation(() => now.valueOf());
273
+
274
+ const result = pricingService.store.pickEndDate();
275
+
276
+ expect(result).toBe('2025-05-14T21:00:00Z');
277
+ });
278
+
279
+ test('pickEndDate - uses provided schedules even with departments', () => {
280
+ const scheduleWithDepartments = [
281
+ {
282
+ _id: 'schedule-with-departments',
283
+ dayOfWeek: [2],
284
+ recommended: 'auto_recommended',
285
+ addDays: 2,
286
+ cutDay: 1,
287
+ skipDays: [1],
288
+ readyHour: { hour: 18, minute: 0 },
289
+ cutHour: { hour: 16, minute: 0 },
290
+ departments: [{ _id: 'department-id' }],
291
+ },
292
+ ];
293
+
294
+ const pricingService = usePricing(getDefaultSettings());
295
+ const now = moment('2025-05-13T15:00:00Z').tz('America/New_York');
296
+ jest.spyOn(moment, 'now').mockImplementation(() => now.valueOf());
297
+
298
+ const result = pricingService.store.pickEndDate(scheduleWithDepartments);
299
+
300
+ expect(result).toBe('2025-05-15T22:00:00Z');
301
+ });
242
302
  });
@@ -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
  }
@@ -1,4 +1,12 @@
1
- module.exports = ({ utils, _, actions, itemActions, modifierActions }) => {
1
+ module.exports = ({
2
+ utils,
3
+ _,
4
+ actions,
5
+ itemActions,
6
+ modifierActions,
7
+ settings,
8
+ storeActions,
9
+ }) => {
2
10
  const { helpers } = utils;
3
11
 
4
12
  const isSplitByPieces = splitUnit => splitUnit === 'pieces';
@@ -26,16 +34,45 @@ module.exports = ({ utils, _, actions, itemActions, modifierActions }) => {
26
34
  return { ...order, items };
27
35
  });
28
36
 
29
- return function splitByDepartments({ parentOrder, newItems }) {
37
+ const getDepartmentSchedules = department => {
38
+ if (!department) return null;
39
+ const allSchedules = _.get(settings, 'order.schedules', []);
40
+ return allSchedules.filter(item =>
41
+ item.departments.some(dep => dep._id === department.modifierId)
42
+ );
43
+ };
44
+
45
+ return function splitByDepartments({
46
+ parentOrder: parentOrderParam,
47
+ newItems,
48
+ }) {
30
49
  const itemsByDepartments = _.groupBy(newItems, getDepartmentName);
31
50
  const itemGroups = Object.values(itemsByDepartments);
32
51
  let splitOrders = [];
52
+ const defaultEndDate = storeActions.pickEndDate();
53
+ const isDefaultEndDate =
54
+ !parentOrderParam.end ||
55
+ !parentOrderParam.end.requestDate ||
56
+ utils.date.isEqual(defaultEndDate, parentOrderParam.end.requestDate);
33
57
 
34
58
  itemGroups.forEach(items => {
35
59
  // Assuming there is one department in the item
36
60
  const department = itemActions.getDepartmentModifiers(items[0])[0];
37
61
  const departmentName = getDepartmentName(items[0]);
38
62
  const maxItems = modifierActions.getDepartmentMaxItems(department);
63
+ const departmentSchedules = getDepartmentSchedules(department);
64
+ let parentOrder = { ...parentOrderParam };
65
+
66
+ if (
67
+ departmentSchedules &&
68
+ departmentSchedules.length &&
69
+ isDefaultEndDate
70
+ ) {
71
+ parentOrder = actions.changeEndDate({
72
+ date: storeActions.pickEndDate(departmentSchedules),
73
+ order: parentOrder,
74
+ });
75
+ }
39
76
 
40
77
  const autoSplit = _.get(
41
78
  department || {},
@@ -1,5 +1,7 @@
1
1
  module.exports = ({ settings, _, moment }) => {
2
- const timezone = _.get(settings, 'localization.timezone', 'America/New_York');
2
+ const timezone =
3
+ _.get(settings, 'localization.timezone', 'America/New_York') ||
4
+ 'America/New_York';
3
5
 
4
6
  const getSchedule = (schedules, isoWeekday) => {
5
7
  const todayTZ = moment().tz(timezone);
@@ -7,7 +9,9 @@ module.exports = ({ settings, _, moment }) => {
7
9
  const _schedules =
8
10
  schedules && schedules.length
9
11
  ? schedules
10
- : _.get(settings, 'order.schedules', []);
12
+ : _.get(settings, 'order.schedules', []).filter(
13
+ item => !item.departments || !item.departments.length
14
+ );
11
15
 
12
16
  if (!_schedules.length) {
13
17
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darkpos/pricing",
3
- "version": "1.0.100",
3
+ "version": "1.0.102",
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": "dbe4e7f639282d1eb04927708dac5195275fe5e4"
57
+ "gitHead": "a4c2dff797325d1c452db5eab1e9bda8f9de4e97"
58
58
  }