@darkpos/pricing 1.0.152 → 1.0.154
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/__TEST__/order/applyRefund.test.js +387 -0
- package/__TEST__/payment.test.js +92 -147
- package/lib/invoice/index.js +0 -4
- package/lib/item/capOverPaidItem.js +14 -0
- package/lib/item/index.js +2 -0
- package/lib/order/applyRefund.js +125 -0
- package/lib/order/getOverPaidAmount.js +13 -0
- package/lib/order/getTotalPaidToRedistribute.js +41 -0
- package/lib/order/index.js +8 -0
- package/lib/order/refundOrderItem.js +29 -0
- package/lib/payment/getOverPaidOrders.js +18 -13
- package/lib/payment/getRefundableOrderAmount.js +9 -22
- package/lib/payment/index.js +2 -4
- package/package.json +2 -2
- package/__TEST__/invoice/refundInvoices.test.js +0 -106
- package/lib/invoice/computeRefundables.js +0 -59
- package/lib/invoice/resolveItemState.js +0 -25
- package/lib/payment/appendIfOverPaid.js +0 -17
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
const usePricing = require('../../index');
|
|
2
|
+
const mockStores = require('../mocks/stores');
|
|
3
|
+
|
|
4
|
+
const session = {
|
|
5
|
+
store: mockStores[0],
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const pricingService = usePricing(session);
|
|
9
|
+
|
|
10
|
+
const createOptions = items => ({
|
|
11
|
+
'order-1': {
|
|
12
|
+
items,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('Apply refund', () => {
|
|
17
|
+
test('keeps applyPayment working for positive amounts', () => {
|
|
18
|
+
const options = createOptions({
|
|
19
|
+
'item-1': {
|
|
20
|
+
total: 8,
|
|
21
|
+
totalPaid: 0,
|
|
22
|
+
status: {},
|
|
23
|
+
},
|
|
24
|
+
'item-2': {
|
|
25
|
+
total: 7,
|
|
26
|
+
totalPaid: 0,
|
|
27
|
+
status: {},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const result = pricingService.order.applyPayment(options, 10);
|
|
32
|
+
|
|
33
|
+
expect(result['order-1'].items['item-1']).toMatchObject({
|
|
34
|
+
orderId: 'order-1',
|
|
35
|
+
amount: 8,
|
|
36
|
+
totalPaid: 8,
|
|
37
|
+
status: {
|
|
38
|
+
paid: {
|
|
39
|
+
value: true,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
expect(result['order-1'].items['item-2']).toMatchObject({
|
|
45
|
+
orderId: 'order-1',
|
|
46
|
+
amount: 2,
|
|
47
|
+
totalPaid: 2,
|
|
48
|
+
status: {
|
|
49
|
+
paid: {
|
|
50
|
+
value: false,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('applies refunds when amount to process is negative', () => {
|
|
57
|
+
const options = createOptions({
|
|
58
|
+
'item-1': {
|
|
59
|
+
total: 8,
|
|
60
|
+
totalPaid: 8,
|
|
61
|
+
status: {
|
|
62
|
+
paid: {
|
|
63
|
+
value: true,
|
|
64
|
+
date: new Date('2024-01-01T00:00:00.000Z'),
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
'item-2': {
|
|
69
|
+
total: 7,
|
|
70
|
+
totalPaid: 7,
|
|
71
|
+
status: {
|
|
72
|
+
paid: {
|
|
73
|
+
value: true,
|
|
74
|
+
date: new Date('2024-01-01T00:00:00.000Z'),
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const result = pricingService.order.applyRefund(options, -10);
|
|
81
|
+
|
|
82
|
+
expect(result.refundedAmount).toBe(10);
|
|
83
|
+
|
|
84
|
+
expect(result.options['order-1'].items['item-1']).toMatchObject({
|
|
85
|
+
orderId: 'order-1',
|
|
86
|
+
amount: -8,
|
|
87
|
+
totalPaid: 0,
|
|
88
|
+
status: {
|
|
89
|
+
paid: {
|
|
90
|
+
value: false,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(result.options['order-1'].items['item-2']).toMatchObject({
|
|
96
|
+
orderId: 'order-1',
|
|
97
|
+
amount: -2,
|
|
98
|
+
totalPaid: 5,
|
|
99
|
+
status: {
|
|
100
|
+
paid: {
|
|
101
|
+
value: false,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('accepts positive refund amounts and only applies the visible overpayment on current items', () => {
|
|
108
|
+
const options = createOptions({
|
|
109
|
+
'item-1': {
|
|
110
|
+
total: 1,
|
|
111
|
+
totalPaid: 5,
|
|
112
|
+
status: {
|
|
113
|
+
paid: {
|
|
114
|
+
value: true,
|
|
115
|
+
date: new Date('2024-01-01T00:00:00.000Z'),
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
'item-2': {
|
|
120
|
+
total: 1,
|
|
121
|
+
totalPaid: 5,
|
|
122
|
+
status: {
|
|
123
|
+
paid: {
|
|
124
|
+
value: true,
|
|
125
|
+
date: new Date('2024-01-01T00:00:00.000Z'),
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const result = pricingService.order.applyRefund(options, 3);
|
|
132
|
+
|
|
133
|
+
expect(result.refundedAmount).toBe(3);
|
|
134
|
+
|
|
135
|
+
expect(result.options['order-1'].items['item-1']).toMatchObject({
|
|
136
|
+
orderId: 'order-1',
|
|
137
|
+
amount: -3,
|
|
138
|
+
totalPaid: 2,
|
|
139
|
+
status: {
|
|
140
|
+
paid: {
|
|
141
|
+
value: true,
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(result.options['order-1'].items['item-2']).toMatchObject({
|
|
147
|
+
orderId: 'order-1',
|
|
148
|
+
amount: 0,
|
|
149
|
+
totalPaid: 5,
|
|
150
|
+
status: {
|
|
151
|
+
paid: {
|
|
152
|
+
value: true,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('normalizes overpaid items after refund so no item keeps totalPaid above total', () => {
|
|
159
|
+
const options = createOptions({
|
|
160
|
+
'698a364799a15511959c9135': {
|
|
161
|
+
status: {
|
|
162
|
+
picked: {
|
|
163
|
+
value: false,
|
|
164
|
+
date: '',
|
|
165
|
+
},
|
|
166
|
+
paid: {
|
|
167
|
+
value: true,
|
|
168
|
+
date: new Date('2026-04-01T16:32:04.514Z'),
|
|
169
|
+
},
|
|
170
|
+
tracker: [],
|
|
171
|
+
},
|
|
172
|
+
total: 1.05,
|
|
173
|
+
totalPaid: 14.7,
|
|
174
|
+
},
|
|
175
|
+
'698a364799a15511959c9130': {
|
|
176
|
+
status: {
|
|
177
|
+
picked: {
|
|
178
|
+
value: false,
|
|
179
|
+
date: '',
|
|
180
|
+
},
|
|
181
|
+
paid: {
|
|
182
|
+
value: false,
|
|
183
|
+
date: new Date('2026-04-01T16:32:04.514Z'),
|
|
184
|
+
},
|
|
185
|
+
tracker: [],
|
|
186
|
+
},
|
|
187
|
+
total: 1.05,
|
|
188
|
+
totalPaid: 0,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const result = pricingService.order.applyRefund(options, -12.6);
|
|
193
|
+
|
|
194
|
+
expect(result.refundedAmount).toBe(12.6);
|
|
195
|
+
|
|
196
|
+
expect(
|
|
197
|
+
result.options['order-1'].items['698a364799a15511959c9135']
|
|
198
|
+
).toMatchObject({
|
|
199
|
+
orderId: 'order-1',
|
|
200
|
+
amount: -13.65,
|
|
201
|
+
totalPaid: 1.05,
|
|
202
|
+
status: {
|
|
203
|
+
paid: {
|
|
204
|
+
value: true,
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
expect(
|
|
210
|
+
result.options['order-1'].items['698a364799a15511959c9130']
|
|
211
|
+
).toMatchObject({
|
|
212
|
+
orderId: 'order-1',
|
|
213
|
+
amount: 1.05,
|
|
214
|
+
totalPaid: 1.05,
|
|
215
|
+
status: {
|
|
216
|
+
paid: {
|
|
217
|
+
value: true,
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('ignores refund amounts larger than the refundable balance', () => {
|
|
224
|
+
const options = createOptions({
|
|
225
|
+
'item-1': {
|
|
226
|
+
total: 8,
|
|
227
|
+
totalPaid: 8,
|
|
228
|
+
status: {
|
|
229
|
+
paid: {
|
|
230
|
+
value: true,
|
|
231
|
+
date: new Date('2024-01-01T00:00:00.000Z'),
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
'item-2': {
|
|
236
|
+
total: 7,
|
|
237
|
+
totalPaid: 2,
|
|
238
|
+
status: {
|
|
239
|
+
paid: {
|
|
240
|
+
value: false,
|
|
241
|
+
date: new Date('2024-01-01T00:00:00.000Z'),
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const result = pricingService.order.applyRefund(options, -100);
|
|
248
|
+
|
|
249
|
+
expect(result.refundedAmount).toBe(10);
|
|
250
|
+
|
|
251
|
+
expect(result.options['order-1'].items['item-1']).toMatchObject({
|
|
252
|
+
orderId: 'order-1',
|
|
253
|
+
amount: -8,
|
|
254
|
+
totalPaid: 0,
|
|
255
|
+
status: {
|
|
256
|
+
paid: {
|
|
257
|
+
value: false,
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
expect(result.options['order-1'].items['item-2']).toMatchObject({
|
|
263
|
+
orderId: 'order-1',
|
|
264
|
+
amount: -2,
|
|
265
|
+
totalPaid: 0,
|
|
266
|
+
status: {
|
|
267
|
+
paid: {
|
|
268
|
+
value: false,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('refunds overpaid zero-total items before touching correctly paid items', () => {
|
|
275
|
+
const options = createOptions({
|
|
276
|
+
shorts: {
|
|
277
|
+
total: 6.3,
|
|
278
|
+
totalPaid: 6.3,
|
|
279
|
+
status: {
|
|
280
|
+
paid: {
|
|
281
|
+
value: true,
|
|
282
|
+
date: new Date('2024-01-01T00:00:00.000Z'),
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
jacket1: {
|
|
287
|
+
total: 0,
|
|
288
|
+
totalPaid: 8.93,
|
|
289
|
+
status: {
|
|
290
|
+
paid: {
|
|
291
|
+
value: true,
|
|
292
|
+
date: new Date('2024-01-01T00:00:00.000Z'),
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
longJacket: {
|
|
297
|
+
total: 0,
|
|
298
|
+
totalPaid: 12.6,
|
|
299
|
+
status: {
|
|
300
|
+
paid: {
|
|
301
|
+
value: true,
|
|
302
|
+
date: new Date('2024-01-01T00:00:00.000Z'),
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
jacket2: {
|
|
307
|
+
total: 0,
|
|
308
|
+
totalPaid: 8.93,
|
|
309
|
+
status: {
|
|
310
|
+
paid: {
|
|
311
|
+
value: true,
|
|
312
|
+
date: new Date('2024-01-01T00:00:00.000Z'),
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
coat: {
|
|
317
|
+
total: 13.65,
|
|
318
|
+
totalPaid: 3.24,
|
|
319
|
+
status: {
|
|
320
|
+
paid: {
|
|
321
|
+
value: false,
|
|
322
|
+
date: new Date('2024-01-01T00:00:00.000Z'),
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const result = pricingService.order.applyRefund(options, 20.05);
|
|
329
|
+
|
|
330
|
+
expect(result.refundedAmount).toBe(20.05);
|
|
331
|
+
|
|
332
|
+
expect(result.options['order-1'].items.shorts).toMatchObject({
|
|
333
|
+
orderId: 'order-1',
|
|
334
|
+
amount: 0,
|
|
335
|
+
totalPaid: 6.3,
|
|
336
|
+
status: {
|
|
337
|
+
paid: {
|
|
338
|
+
value: true,
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
expect(result.options['order-1'].items.jacket1).toMatchObject({
|
|
344
|
+
orderId: 'order-1',
|
|
345
|
+
amount: -8.93,
|
|
346
|
+
totalPaid: 0,
|
|
347
|
+
status: {
|
|
348
|
+
paid: {
|
|
349
|
+
value: true,
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
expect(result.options['order-1'].items.longJacket).toMatchObject({
|
|
355
|
+
orderId: 'order-1',
|
|
356
|
+
amount: -12.6,
|
|
357
|
+
totalPaid: 0,
|
|
358
|
+
status: {
|
|
359
|
+
paid: {
|
|
360
|
+
value: true,
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
expect(result.options['order-1'].items.jacket2).toMatchObject({
|
|
366
|
+
orderId: 'order-1',
|
|
367
|
+
amount: -8.93,
|
|
368
|
+
totalPaid: 0,
|
|
369
|
+
status: {
|
|
370
|
+
paid: {
|
|
371
|
+
value: true,
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
expect(result.options['order-1'].items.coat).toMatchObject({
|
|
377
|
+
orderId: 'order-1',
|
|
378
|
+
amount: 10.41,
|
|
379
|
+
totalPaid: 13.65,
|
|
380
|
+
status: {
|
|
381
|
+
paid: {
|
|
382
|
+
value: true,
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
});
|
package/__TEST__/payment.test.js
CHANGED
|
@@ -62,7 +62,7 @@ describe('getMaxAmountToRefund tests', () => {
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
describe('getRefundableOrderAmount tests', () => {
|
|
65
|
-
test('Returns
|
|
65
|
+
test('Returns 0 when retained order payment still matches the remaining item total', () => {
|
|
66
66
|
const order = {
|
|
67
67
|
_id: 'order-1',
|
|
68
68
|
totalPaid: 5,
|
|
@@ -77,10 +77,10 @@ describe('getRefundableOrderAmount tests', () => {
|
|
|
77
77
|
],
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
expect(pricingService.payment.getRefundableOrderAmount({ order })).toBe(
|
|
80
|
+
expect(pricingService.payment.getRefundableOrderAmount({ order })).toBe(0);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
test('Returns
|
|
83
|
+
test('Returns the net order overpayment (totalPaid minus total), regardless of per-item breakdown', () => {
|
|
84
84
|
const order = {
|
|
85
85
|
_id: 'order-2',
|
|
86
86
|
totalPaid: 14.7,
|
|
@@ -103,177 +103,122 @@ describe('getRefundableOrderAmount tests', () => {
|
|
|
103
103
|
};
|
|
104
104
|
|
|
105
105
|
expect(pricingService.payment.getRefundableOrderAmount({ order })).toBe(
|
|
106
|
-
|
|
106
|
+
12.6
|
|
107
107
|
);
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
-
describe('
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
isParent: true,
|
|
117
|
-
orders: [
|
|
118
|
-
{
|
|
119
|
-
_id: 'child-1',
|
|
120
|
-
totalPaid: 5,
|
|
121
|
-
items: [
|
|
122
|
-
{
|
|
123
|
-
_id: 'item-1',
|
|
124
|
-
total: 5,
|
|
125
|
-
totalPaid: 0,
|
|
126
|
-
status: { paid: { value: false } },
|
|
127
|
-
},
|
|
128
|
-
],
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
_id: 'child-2',
|
|
132
|
-
totalPaid: 5,
|
|
133
|
-
items: [
|
|
134
|
-
{
|
|
135
|
-
_id: 'item-2',
|
|
136
|
-
total: 5,
|
|
137
|
-
totalPaid: 5,
|
|
138
|
-
status: { paid: { value: true } },
|
|
139
|
-
},
|
|
140
|
-
],
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
},
|
|
144
|
-
];
|
|
111
|
+
describe('getOverPaidOrders tests', () => {
|
|
112
|
+
const baseOrder = pricingService.order.calculate({
|
|
113
|
+
_id: 'order-1',
|
|
114
|
+
items: [{ price: 30, quantity: 1, modifiers: [] }],
|
|
115
|
+
});
|
|
145
116
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
totalPaid: 5,
|
|
152
|
-
items: [
|
|
153
|
-
{
|
|
154
|
-
_id: 'item-1',
|
|
155
|
-
total: 5,
|
|
156
|
-
totalPaid: 0,
|
|
157
|
-
status: { paid: { value: false } },
|
|
158
|
-
},
|
|
159
|
-
],
|
|
160
|
-
refundableAmount: 5,
|
|
161
|
-
},
|
|
162
|
-
],
|
|
117
|
+
test('includes order and accumulates overpaid amount when customer paid more than the order total', () => {
|
|
118
|
+
const order = { ...baseOrder, totalPaid: 50 };
|
|
119
|
+
|
|
120
|
+
const result = pricingService.payment.getOverPaidOrders({
|
|
121
|
+
orders: [order],
|
|
163
122
|
});
|
|
123
|
+
|
|
124
|
+
expect(result.overPaidOrders).toHaveLength(1);
|
|
125
|
+
expect(result.overPaidAmount).toBe(20);
|
|
164
126
|
});
|
|
165
127
|
|
|
166
|
-
test('
|
|
167
|
-
const
|
|
168
|
-
null,
|
|
169
|
-
{
|
|
170
|
-
_id: 'order-1',
|
|
171
|
-
totalPaid: 5,
|
|
172
|
-
items: [
|
|
173
|
-
{
|
|
174
|
-
_id: 'item-1',
|
|
175
|
-
total: 5,
|
|
176
|
-
totalPaid: 5,
|
|
177
|
-
status: { paid: { value: true } },
|
|
178
|
-
},
|
|
179
|
-
],
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
isParent: true,
|
|
183
|
-
orders: [
|
|
184
|
-
{
|
|
185
|
-
_id: 'child-1',
|
|
186
|
-
totalPaid: 1,
|
|
187
|
-
items: [
|
|
188
|
-
{
|
|
189
|
-
_id: 'item-2',
|
|
190
|
-
total: 1,
|
|
191
|
-
totalPaid: 1,
|
|
192
|
-
status: { paid: { value: true } },
|
|
193
|
-
},
|
|
194
|
-
],
|
|
195
|
-
},
|
|
196
|
-
],
|
|
197
|
-
},
|
|
198
|
-
];
|
|
128
|
+
test('returns empty list and zero amount when customer paid exactly the order total', () => {
|
|
129
|
+
const order = { ...baseOrder, totalPaid: 30 };
|
|
199
130
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
overPaidOrders: [],
|
|
131
|
+
const result = pricingService.payment.getOverPaidOrders({
|
|
132
|
+
orders: [order],
|
|
203
133
|
});
|
|
134
|
+
|
|
135
|
+
expect(result.overPaidOrders).toHaveLength(0);
|
|
136
|
+
expect(result.overPaidAmount).toBe(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('includes a void order paid exactly to its total as refundable', () => {
|
|
140
|
+
const order = pricingService.order.voidOrder({
|
|
141
|
+
...baseOrder,
|
|
142
|
+
totalPaid: 30,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const result = pricingService.payment.getOverPaidOrders({
|
|
146
|
+
orders: [order],
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(result.overPaidOrders).toHaveLength(1);
|
|
150
|
+
expect(result.overPaidOrders[0].refundableAmount).toBe(30);
|
|
151
|
+
expect(result.overPaidAmount).toBe(30);
|
|
204
152
|
});
|
|
205
153
|
});
|
|
206
154
|
|
|
207
|
-
describe('
|
|
208
|
-
test('
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
).toBe(
|
|
155
|
+
describe('capOverPaidItem tests', () => {
|
|
156
|
+
test('caps totalPaid to item total and returns the excess as amountCapped', () => {
|
|
157
|
+
const item = { total: 10, totalPaid: 15 };
|
|
158
|
+
|
|
159
|
+
const result = pricingService.item.capOverPaidItem({ item });
|
|
160
|
+
|
|
161
|
+
expect(result.item.totalPaid).toBe(10);
|
|
162
|
+
expect(result.amountCapped).toBe(5);
|
|
214
163
|
});
|
|
215
164
|
|
|
216
|
-
test('
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
).toBe(
|
|
165
|
+
test('returns item unchanged and amountCapped 0 when totalPaid does not exceed total', () => {
|
|
166
|
+
const item = { total: 10, totalPaid: 10 };
|
|
167
|
+
|
|
168
|
+
const result = pricingService.item.capOverPaidItem({ item });
|
|
169
|
+
|
|
170
|
+
expect(result.item.totalPaid).toBe(10);
|
|
171
|
+
expect(result.amountCapped).toBe(0);
|
|
222
172
|
});
|
|
223
173
|
});
|
|
224
174
|
|
|
225
|
-
describe('
|
|
226
|
-
test('
|
|
227
|
-
const
|
|
175
|
+
describe('getTotalPaidToRedistribute tests', () => {
|
|
176
|
+
test('returns order with redistributableAmount when totalPaid exceeds item totalPaid sum', () => {
|
|
177
|
+
const order = {
|
|
228
178
|
_id: 'order-1',
|
|
229
|
-
displayId: '101',
|
|
230
179
|
totalPaid: 5,
|
|
231
|
-
items: [
|
|
232
|
-
{
|
|
233
|
-
_id: 'item-2',
|
|
234
|
-
name: 'Simple Item 2',
|
|
235
|
-
total: 5,
|
|
236
|
-
totalPaid: 0,
|
|
237
|
-
status: { paid: { value: false } },
|
|
238
|
-
},
|
|
239
|
-
],
|
|
180
|
+
items: [{ totalPaid: 0 }, { totalPaid: 0 }],
|
|
240
181
|
};
|
|
241
182
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
).
|
|
248
|
-
|
|
249
|
-
{
|
|
250
|
-
...currentOrder,
|
|
251
|
-
refundableAmount: 5,
|
|
252
|
-
},
|
|
253
|
-
]);
|
|
183
|
+
const result = pricingService.order.getTotalPaidToRedistribute({
|
|
184
|
+
orders: [order],
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(result.redistributableOrders).toHaveLength(1);
|
|
188
|
+
expect(result.redistributableOrders[0].redistributableAmount).toBe(5);
|
|
189
|
+
expect(result.redistributableAmount).toBe(5);
|
|
254
190
|
});
|
|
255
191
|
|
|
256
|
-
test('
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
totalPaid:
|
|
261
|
-
items: [
|
|
262
|
-
{
|
|
263
|
-
_id: 'item-1',
|
|
264
|
-
name: 'Simple Item 1',
|
|
265
|
-
total: 5,
|
|
266
|
-
totalPaid: 5,
|
|
267
|
-
status: { paid: { value: true } },
|
|
268
|
-
},
|
|
269
|
-
],
|
|
192
|
+
test('returns empty list and zero amount when all totalPaid is already distributed to items', () => {
|
|
193
|
+
const order = {
|
|
194
|
+
_id: 'order-1',
|
|
195
|
+
totalPaid: 10,
|
|
196
|
+
items: [{ totalPaid: 6 }, { totalPaid: 4 }],
|
|
270
197
|
};
|
|
271
198
|
|
|
199
|
+
const result = pricingService.order.getTotalPaidToRedistribute({
|
|
200
|
+
orders: [order],
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(result.redistributableOrders).toHaveLength(0);
|
|
204
|
+
expect(result.redistributableAmount).toBe(0);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('getMethodLabel tests', () => {
|
|
209
|
+
test('Returns the configured label for the payment provider', () => {
|
|
272
210
|
expect(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
overPaidOrders,
|
|
211
|
+
pricingServiceMethodLabel.payment.getMethodLabel({
|
|
212
|
+
paymentProvider: 'cash',
|
|
276
213
|
})
|
|
277
|
-
).
|
|
214
|
+
).toBe('Cash');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('Returns empty string when the provider is missing', () => {
|
|
218
|
+
expect(
|
|
219
|
+
pricingServiceMethodLabel.payment.getMethodLabel({
|
|
220
|
+
paymentProvider: 'missing-provider',
|
|
221
|
+
})
|
|
222
|
+
).toBe('');
|
|
278
223
|
});
|
|
279
224
|
});
|
package/lib/invoice/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const getStatusByItems = require('./getStatusByItems');
|
|
2
2
|
const getTotalByItems = require('./getTotalByItems');
|
|
3
|
-
const resolveItemState = require('./resolveItemState');
|
|
4
|
-
const computeRefundables = require('./computeRefundables');
|
|
5
3
|
|
|
6
4
|
const invoiceActions = (deps = {}) => {
|
|
7
5
|
const actions = {};
|
|
@@ -14,8 +12,6 @@ const invoiceActions = (deps = {}) => {
|
|
|
14
12
|
const freezedActions = Object.freeze({
|
|
15
13
|
getStatusByItems: getStatusByItems(innerDeps),
|
|
16
14
|
getTotalByItems: getTotalByItems(innerDeps),
|
|
17
|
-
resolveItemState: resolveItemState(innerDeps),
|
|
18
|
-
computeRefundables: computeRefundables(innerDeps),
|
|
19
15
|
});
|
|
20
16
|
|
|
21
17
|
Object.keys(freezedActions).forEach(actionName => {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module.exports = ({ utils }) =>
|
|
2
|
+
function capOverPaidItem({ item }) {
|
|
3
|
+
const total = item.total || 0;
|
|
4
|
+
|
|
5
|
+
const totalPaid = item.totalPaid || 0;
|
|
6
|
+
|
|
7
|
+
if (totalPaid > total)
|
|
8
|
+
return {
|
|
9
|
+
item: { ...item, totalPaid: total },
|
|
10
|
+
amountCapped: utils.math.sub(totalPaid, total),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
return { item, amountCapped: 0 };
|
|
14
|
+
};
|
package/lib/item/index.js
CHANGED
|
@@ -84,6 +84,7 @@ const getAmountToPayById = require('./getAmountToPayById');
|
|
|
84
84
|
const applyPayment = require('./applyPayment');
|
|
85
85
|
const getBalanceForPaymentModifier = require('./getBalanceForPaymentModifier');
|
|
86
86
|
const isOverpaid = require('./isOverpaid');
|
|
87
|
+
const capOverPaidItem = require('./capOverPaidItem');
|
|
87
88
|
|
|
88
89
|
const itemActions = (deps = {}) => {
|
|
89
90
|
const actions = {};
|
|
@@ -181,6 +182,7 @@ const itemActions = (deps = {}) => {
|
|
|
181
182
|
applyPayment: applyPayment(innerDeps),
|
|
182
183
|
getBalanceForPaymentModifier: getBalanceForPaymentModifier(innerDeps),
|
|
183
184
|
isOverpaid: isOverpaid(innerDeps),
|
|
185
|
+
capOverPaidItem: capOverPaidItem(innerDeps),
|
|
184
186
|
});
|
|
185
187
|
|
|
186
188
|
Object.keys(freezedActions).forEach(actionName => {
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
module.exports = ({ utils, actions }) =>
|
|
2
|
+
function applyRefund(options, amountToProcess = 0) {
|
|
3
|
+
const optionsWithAmount = options;
|
|
4
|
+
let amount = Math.abs(amountToProcess || 0);
|
|
5
|
+
let refundedAmount = 0;
|
|
6
|
+
|
|
7
|
+
Object.keys(options).forEach(orderId => {
|
|
8
|
+
const order = options[orderId];
|
|
9
|
+
const { items } = order;
|
|
10
|
+
const itemIds = Object.keys(items);
|
|
11
|
+
|
|
12
|
+
itemIds.forEach(itemId => {
|
|
13
|
+
const item = items[itemId];
|
|
14
|
+
|
|
15
|
+
items[itemId] = {
|
|
16
|
+
...item,
|
|
17
|
+
orderId,
|
|
18
|
+
amount: item.amount || 0,
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
itemIds.forEach(itemId => {
|
|
23
|
+
if (amount <= 0) return;
|
|
24
|
+
|
|
25
|
+
const item = items[itemId];
|
|
26
|
+
const overPaidAmount = utils.math.max(
|
|
27
|
+
utils.math.sub(item.totalPaid || 0, item.total || 0),
|
|
28
|
+
0
|
|
29
|
+
);
|
|
30
|
+
const { refunded, item: updatedItem } = actions.refundOrderItem({
|
|
31
|
+
item,
|
|
32
|
+
orderId,
|
|
33
|
+
amountToRefund: utils.math.min(amount, overPaidAmount),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!updatedItem) return;
|
|
37
|
+
|
|
38
|
+
refundedAmount = utils.math.add(refundedAmount, refunded);
|
|
39
|
+
items[itemId] = updatedItem;
|
|
40
|
+
amount = utils.math.sub(amount, refunded);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
itemIds.forEach(itemId => {
|
|
44
|
+
if (amount <= 0) return;
|
|
45
|
+
|
|
46
|
+
const item = items[itemId];
|
|
47
|
+
const { refunded, item: updatedItem } = actions.refundOrderItem({
|
|
48
|
+
item,
|
|
49
|
+
orderId,
|
|
50
|
+
amountToRefund: amount,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!updatedItem) return;
|
|
54
|
+
|
|
55
|
+
refundedAmount = utils.math.add(refundedAmount, refunded);
|
|
56
|
+
items[itemId] = updatedItem;
|
|
57
|
+
amount = utils.math.sub(amount, refunded);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
itemIds.forEach((itemId, index) => {
|
|
61
|
+
const item = items[itemId];
|
|
62
|
+
let excess = utils.math.max(
|
|
63
|
+
utils.math.sub(item.totalPaid || 0, item.total || 0),
|
|
64
|
+
0
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (excess <= 0) return;
|
|
68
|
+
|
|
69
|
+
for (
|
|
70
|
+
let nextIndex = index + 1;
|
|
71
|
+
nextIndex < itemIds.length;
|
|
72
|
+
nextIndex += 1
|
|
73
|
+
) {
|
|
74
|
+
if (excess <= 0) break;
|
|
75
|
+
|
|
76
|
+
const nextItemId = itemIds[nextIndex];
|
|
77
|
+
const nextItem = items[nextItemId];
|
|
78
|
+
const capacity = utils.math.max(
|
|
79
|
+
utils.math.sub(nextItem.total || 0, nextItem.totalPaid || 0),
|
|
80
|
+
0
|
|
81
|
+
);
|
|
82
|
+
const amountToMove = utils.math.min(excess, capacity);
|
|
83
|
+
|
|
84
|
+
if (amountToMove > 0) {
|
|
85
|
+
items[itemId] = {
|
|
86
|
+
...items[itemId],
|
|
87
|
+
amount: utils.math.sub(items[itemId].amount || 0, amountToMove),
|
|
88
|
+
totalPaid: utils.math.sub(
|
|
89
|
+
items[itemId].totalPaid || 0,
|
|
90
|
+
amountToMove
|
|
91
|
+
),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
items[nextItemId] = {
|
|
95
|
+
...nextItem,
|
|
96
|
+
amount: utils.math.add(nextItem.amount || 0, amountToMove),
|
|
97
|
+
totalPaid: utils.math.add(nextItem.totalPaid || 0, amountToMove),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
excess = utils.math.sub(excess, amountToMove);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
itemIds.forEach(itemId => {
|
|
106
|
+
const item = items[itemId];
|
|
107
|
+
|
|
108
|
+
items[itemId] = {
|
|
109
|
+
...item,
|
|
110
|
+
status: {
|
|
111
|
+
...item.status,
|
|
112
|
+
paid: {
|
|
113
|
+
value: (item.totalPaid || 0) >= (item.total || 0),
|
|
114
|
+
date: new Date(),
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
options: optionsWithAmount,
|
|
123
|
+
refundedAmount,
|
|
124
|
+
};
|
|
125
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module.exports = ({ itemActions, utils }) =>
|
|
2
|
+
function getOverPaidAmount({ order }) {
|
|
3
|
+
if (!order) return 0;
|
|
4
|
+
|
|
5
|
+
const { total: itemsTotal } = itemActions.getItemsTotals(order.items || []);
|
|
6
|
+
|
|
7
|
+
const orderTotalPaid = order.totalPaid || 0;
|
|
8
|
+
|
|
9
|
+
if (orderTotalPaid > itemsTotal)
|
|
10
|
+
return utils.math.sub(orderTotalPaid, itemsTotal);
|
|
11
|
+
|
|
12
|
+
return 0;
|
|
13
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module.exports = ({ itemActions, utils }) =>
|
|
2
|
+
function getTotalPaidToRedistribute({ orders = [] }) {
|
|
3
|
+
const redistributableOrders = [];
|
|
4
|
+
let redistributableAmount = 0;
|
|
5
|
+
|
|
6
|
+
const appendIfRedistributable = currentOrder => {
|
|
7
|
+
const itemsTotalPaid = itemActions.getItemsTotalPaid({
|
|
8
|
+
orderItems: currentOrder.items || [],
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const amount = utils.math.max(
|
|
12
|
+
utils.math.sub(currentOrder.totalPaid || 0, itemsTotalPaid),
|
|
13
|
+
0
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
if (amount > 0) {
|
|
17
|
+
redistributableOrders.push({
|
|
18
|
+
...currentOrder,
|
|
19
|
+
redistributableAmount: amount,
|
|
20
|
+
});
|
|
21
|
+
redistributableAmount = utils.math.add(redistributableAmount, amount);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
(orders || []).forEach(order => {
|
|
26
|
+
if (!order || !order._id) return;
|
|
27
|
+
if (order.isParent) {
|
|
28
|
+
const childOrders = order.orders || [];
|
|
29
|
+
if (childOrders.length > 0) {
|
|
30
|
+
childOrders.forEach(appendIfRedistributable);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
appendIfRedistributable(order);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
redistributableOrders,
|
|
39
|
+
redistributableAmount,
|
|
40
|
+
};
|
|
41
|
+
};
|
package/lib/order/index.js
CHANGED
|
@@ -82,6 +82,7 @@ const createSubOrder = require('./createSubOrder');
|
|
|
82
82
|
const manualSplit = require('./manualSplit');
|
|
83
83
|
const manualSplitByQuantity = require('./manualSplitByQuantity');
|
|
84
84
|
const applyPayment = require('./applyPayment');
|
|
85
|
+
const applyRefund = require('./applyRefund');
|
|
85
86
|
const getBalance = require('./getBalance');
|
|
86
87
|
const getLastLocation = require('./getLastLocation');
|
|
87
88
|
const getModifierRelations = require('./getModifierRelations');
|
|
@@ -101,6 +102,9 @@ const getTaxes = require('./getTaxes');
|
|
|
101
102
|
const getPickedStatus = require('./getPickedStatus');
|
|
102
103
|
const calculateWithPayment = require('./calculateWithPayment');
|
|
103
104
|
const hasSerial = require('./hasSerial');
|
|
105
|
+
const getOverPaidAmount = require('./getOverPaidAmount');
|
|
106
|
+
const getTotalPaidToRedistribute = require('./getTotalPaidToRedistribute');
|
|
107
|
+
const refundOrderItem = require('./refundOrderItem');
|
|
104
108
|
|
|
105
109
|
const orderActions = (deps = {}) => {
|
|
106
110
|
const actions = {};
|
|
@@ -194,6 +198,7 @@ const orderActions = (deps = {}) => {
|
|
|
194
198
|
manualSplit: manualSplit(innerDeps),
|
|
195
199
|
manualSplitByQuantity: manualSplitByQuantity(innerDeps),
|
|
196
200
|
applyPayment: applyPayment(innerDeps),
|
|
201
|
+
applyRefund: applyRefund(innerDeps),
|
|
197
202
|
getOrdersBalance: getOrdersBalance(innerDeps),
|
|
198
203
|
getLastLocation: getLastLocation(innerDeps),
|
|
199
204
|
getModifierRelations: getModifierRelations(innerDeps),
|
|
@@ -213,6 +218,9 @@ const orderActions = (deps = {}) => {
|
|
|
213
218
|
getPickedStatus: getPickedStatus(innerDeps),
|
|
214
219
|
calculateWithPayment: calculateWithPayment(innerDeps),
|
|
215
220
|
hasSerial: hasSerial(innerDeps),
|
|
221
|
+
getOverPaidAmount: getOverPaidAmount(innerDeps),
|
|
222
|
+
getTotalPaidToRedistribute: getTotalPaidToRedistribute(innerDeps),
|
|
223
|
+
refundOrderItem: refundOrderItem(innerDeps),
|
|
216
224
|
});
|
|
217
225
|
|
|
218
226
|
Object.keys(freezedActions).forEach(actionName => {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module.exports = ({ utils }) =>
|
|
2
|
+
function refundOrderItem({ item, orderId, amountToRefund = 0 }) {
|
|
3
|
+
if (amountToRefund <= 0) return { refunded: 0 };
|
|
4
|
+
|
|
5
|
+
const refundableAmount = item.totalPaid || 0;
|
|
6
|
+
const refunded = utils.math.min(amountToRefund, refundableAmount);
|
|
7
|
+
const amountToAssign = utils.math.sub(0, refunded);
|
|
8
|
+
const updatedTotalPaid = utils.math.add(
|
|
9
|
+
item.totalPaid || 0,
|
|
10
|
+
amountToAssign
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
refunded,
|
|
15
|
+
item: {
|
|
16
|
+
...item,
|
|
17
|
+
orderId,
|
|
18
|
+
amount: utils.math.add(item.amount || 0, amountToAssign),
|
|
19
|
+
totalPaid: updatedTotalPaid,
|
|
20
|
+
status: {
|
|
21
|
+
...item.status,
|
|
22
|
+
paid: {
|
|
23
|
+
value: updatedTotalPaid >= (item.total || 0),
|
|
24
|
+
date: new Date(),
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
module.exports = ({
|
|
1
|
+
module.exports = ({ orderActions, utils }) =>
|
|
2
2
|
function getOverPaidOrders({ orders = [] }) {
|
|
3
|
-
|
|
3
|
+
const overPaidOrders = [];
|
|
4
4
|
let overPaidAmount = 0;
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
currentOrder,
|
|
9
|
-
overPaidOrders,
|
|
6
|
+
const appendIfRefundable = currentOrder => {
|
|
7
|
+
const overPaidTotal = orderActions.getOverPaidAmount({
|
|
8
|
+
order: currentOrder,
|
|
10
9
|
});
|
|
10
|
+
const refundableAmount = orderActions.isVoid(currentOrder)
|
|
11
|
+
? currentOrder.totalPaid || 0
|
|
12
|
+
: overPaidTotal;
|
|
13
|
+
|
|
14
|
+
if (refundableAmount > 0) {
|
|
15
|
+
overPaidOrders.push({
|
|
16
|
+
...currentOrder,
|
|
17
|
+
refundableAmount,
|
|
18
|
+
});
|
|
19
|
+
overPaidAmount = utils.math.add(overPaidAmount, refundableAmount);
|
|
20
|
+
}
|
|
11
21
|
};
|
|
12
22
|
|
|
13
23
|
(orders || []).forEach(order => {
|
|
@@ -16,19 +26,14 @@ module.exports = ({ actions }) =>
|
|
|
16
26
|
const childOrders = order.orders || [];
|
|
17
27
|
|
|
18
28
|
if (childOrders.length > 0) {
|
|
19
|
-
childOrders.forEach(
|
|
29
|
+
childOrders.forEach(appendIfRefundable);
|
|
20
30
|
return;
|
|
21
31
|
}
|
|
22
32
|
}
|
|
23
33
|
|
|
24
|
-
|
|
34
|
+
appendIfRefundable(order);
|
|
25
35
|
});
|
|
26
36
|
|
|
27
|
-
overPaidAmount = overPaidOrders.reduce(
|
|
28
|
-
(total, order) => total + Number(order.refundableAmount || 0),
|
|
29
|
-
0
|
|
30
|
-
);
|
|
31
|
-
|
|
32
37
|
return {
|
|
33
38
|
overPaidOrders,
|
|
34
39
|
overPaidAmount,
|
|
@@ -5,30 +5,17 @@ module.exports = ({ utils }) => {
|
|
|
5
5
|
if (!order) return 0;
|
|
6
6
|
|
|
7
7
|
const items = Array.isArray(order.items) ? order.items : [];
|
|
8
|
-
const
|
|
8
|
+
const orderTotal =
|
|
9
|
+
typeof order.total === 'number'
|
|
10
|
+
? order.total
|
|
11
|
+
: items.reduce(
|
|
12
|
+
(acc, item) => math.add(acc, Number(item.total || 0)),
|
|
13
|
+
0
|
|
14
|
+
);
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
return math.max(
|
|
17
|
+
math.sub(Number(order.totalPaid || 0), Number(orderTotal || 0)),
|
|
12
18
|
0
|
|
13
19
|
);
|
|
14
|
-
const paidItemsTotalPaid = paidItems.reduce(
|
|
15
|
-
(acc, item) => math.add(acc, Number(item.totalPaid || 0)),
|
|
16
|
-
0
|
|
17
|
-
);
|
|
18
|
-
const paidItemsTotal = paidItems.reduce(
|
|
19
|
-
(acc, item) => math.add(acc, Number(item.total || 0)),
|
|
20
|
-
0
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
const orphanedPaidAmount = math.max(
|
|
24
|
-
math.sub(order.totalPaid || 0, itemsTotalPaid),
|
|
25
|
-
0
|
|
26
|
-
);
|
|
27
|
-
const refundablePaidItemsAmount = math.max(
|
|
28
|
-
math.sub(paidItemsTotalPaid, paidItemsTotal),
|
|
29
|
-
0
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
return math.add(orphanedPaidAmount, refundablePaidItemsAmount);
|
|
33
20
|
};
|
|
34
21
|
};
|
package/lib/payment/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
//
|
|
2
|
-
const appendIfOverPaid = require('./appendIfOverPaid');
|
|
3
2
|
const getMaxAmountToRefund = require('./getMaxAmountToRefund');
|
|
4
3
|
const getRefundableOrderAmount = require('./getRefundableOrderAmount');
|
|
5
|
-
const getOverPaidOrders = require('./getOverPaidOrders');
|
|
6
4
|
const getMethodLabel = require('./getMethodLabel');
|
|
5
|
+
const getOverPaidOrders = require('./getOverPaidOrders');
|
|
7
6
|
|
|
8
7
|
const orderActions = (deps = {}) => {
|
|
9
8
|
const actions = {};
|
|
@@ -14,11 +13,10 @@ const orderActions = (deps = {}) => {
|
|
|
14
13
|
};
|
|
15
14
|
|
|
16
15
|
const freezedActions = Object.freeze({
|
|
17
|
-
appendIfOverPaid: appendIfOverPaid(innerDeps),
|
|
18
16
|
getMaxAmountToRefund: getMaxAmountToRefund(innerDeps),
|
|
19
17
|
getRefundableOrderAmount: getRefundableOrderAmount(innerDeps),
|
|
20
|
-
getOverPaidOrders: getOverPaidOrders(innerDeps),
|
|
21
18
|
getMethodLabel: getMethodLabel(innerDeps),
|
|
19
|
+
getOverPaidOrders: getOverPaidOrders(innerDeps),
|
|
22
20
|
});
|
|
23
21
|
|
|
24
22
|
Object.keys(freezedActions).forEach(actionName => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@darkpos/pricing",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.154",
|
|
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": "
|
|
57
|
+
"gitHead": "6d06392dd10f6fba50228db77331e4073cf2e3bf"
|
|
58
58
|
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
const usePricing = require('../../index');
|
|
2
|
-
const mockStores = require('../mocks/stores');
|
|
3
|
-
|
|
4
|
-
const pricing = usePricing({ store: mockStores[0] });
|
|
5
|
-
|
|
6
|
-
const ORDER_ID = 'order-1';
|
|
7
|
-
const OVERPAID_ITEM_ID = 'item-0';
|
|
8
|
-
const UNDERPAID_ITEM_ID = 'item-1';
|
|
9
|
-
|
|
10
|
-
const liveOrder = {
|
|
11
|
-
_id: ORDER_ID,
|
|
12
|
-
items: [
|
|
13
|
-
{
|
|
14
|
-
_id: OVERPAID_ITEM_ID,
|
|
15
|
-
total: 10.5,
|
|
16
|
-
totalPaid: 14.7,
|
|
17
|
-
status: { paid: { value: true }, picked: { value: false } },
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
_id: UNDERPAID_ITEM_ID,
|
|
21
|
-
total: 10.5,
|
|
22
|
-
totalPaid: 10.3,
|
|
23
|
-
status: { paid: { value: false }, picked: { value: false } },
|
|
24
|
-
},
|
|
25
|
-
],
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const ordersById = { [ORDER_ID]: liveOrder };
|
|
29
|
-
|
|
30
|
-
const overpaidInvoiceItem = {
|
|
31
|
-
orderId: ORDER_ID,
|
|
32
|
-
orderItemId: OVERPAID_ITEM_ID,
|
|
33
|
-
total: 14.7,
|
|
34
|
-
totalPaid: 14.7,
|
|
35
|
-
amount: 14.7,
|
|
36
|
-
status: { paid: { value: true }, picked: { value: false } },
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const partiallyPaidInvoiceItem = {
|
|
40
|
-
orderId: ORDER_ID,
|
|
41
|
-
orderItemId: UNDERPAID_ITEM_ID,
|
|
42
|
-
total: 13.65,
|
|
43
|
-
totalPaid: 10.3,
|
|
44
|
-
amount: 10.3,
|
|
45
|
-
status: { paid: { value: false }, picked: { value: false } },
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
describe('computeRefundables', () => {
|
|
49
|
-
const overpaidState = pricing.invoice.resolveItemState(
|
|
50
|
-
overpaidInvoiceItem,
|
|
51
|
-
ordersById
|
|
52
|
-
); // excess +4.2
|
|
53
|
-
const underpaidState = pricing.invoice.resolveItemState(
|
|
54
|
-
partiallyPaidInvoiceItem,
|
|
55
|
-
ordersById
|
|
56
|
-
); // excess -0.2
|
|
57
|
-
|
|
58
|
-
test('refunds the full excess from the overpaid item and redistributes the surplus to the underpaid item', () => {
|
|
59
|
-
const [overpaidRefundable, underpaidCredit] =
|
|
60
|
-
pricing.invoice.computeRefundables([overpaidState, underpaidState], 4);
|
|
61
|
-
expect(overpaidRefundable).toBe(4.2); // full overpayment cleared
|
|
62
|
-
expect(underpaidCredit).toBe(-0.2); // 0.2 credit applied to cover the gap
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('net refund amount across all items equals the requested refund total', () => {
|
|
66
|
-
const [overpaidRefundable, underpaidCredit] =
|
|
67
|
-
pricing.invoice.computeRefundables([overpaidState, underpaidState], 4);
|
|
68
|
-
expect(overpaidRefundable + underpaidCredit).toBe(4);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('result is order-independent — same outcome when items are reversed', () => {
|
|
72
|
-
const [underpaidCredit, overpaidRefundable] =
|
|
73
|
-
pricing.invoice.computeRefundables([underpaidState, overpaidState], 4);
|
|
74
|
-
expect(overpaidRefundable).toBe(4.2);
|
|
75
|
-
expect(underpaidCredit).toBe(-0.2);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('never-paid item receives no credit even when overpaid surplus exists', () => {
|
|
79
|
-
const neverPaidState = {
|
|
80
|
-
...pricing.invoice.resolveItemState(partiallyPaidInvoiceItem, ordersById),
|
|
81
|
-
itemTotalPaid: 0,
|
|
82
|
-
excess: -10.5,
|
|
83
|
-
};
|
|
84
|
-
const [overpaidRefundable, neverPaidRefundable] =
|
|
85
|
-
pricing.invoice.computeRefundables([overpaidState, neverPaidState], 4);
|
|
86
|
-
expect(overpaidRefundable).toBe(4); // capped — no surplus to redistribute
|
|
87
|
-
expect(neverPaidRefundable).toBe(0); // never-paid item stays untouched
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('draws directly from totalPaid when no overpaid items exist', () => {
|
|
91
|
-
const exactlyPaidState = { ...overpaidState, excess: 0 };
|
|
92
|
-
const [refundable] = pricing.invoice.computeRefundables(
|
|
93
|
-
[exactlyPaidState],
|
|
94
|
-
5
|
|
95
|
-
);
|
|
96
|
-
expect(refundable).toBe(5);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('returns all zeros when the requested refund amount is 0', () => {
|
|
100
|
-
const refundables = pricing.invoice.computeRefundables(
|
|
101
|
-
[overpaidState, underpaidState],
|
|
102
|
-
0
|
|
103
|
-
);
|
|
104
|
-
expect(refundables).toEqual([0, 0]);
|
|
105
|
-
});
|
|
106
|
-
});
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
module.exports = ({ utils }) => {
|
|
2
|
-
const { math } = utils;
|
|
3
|
-
|
|
4
|
-
return function computeRefundables(itemStates, totalToRefund) {
|
|
5
|
-
const refundables = new Array(itemStates.length).fill(0);
|
|
6
|
-
|
|
7
|
-
if (totalToRefund <= 0) return refundables;
|
|
8
|
-
|
|
9
|
-
// Only partially-paid underpaid items are eligible to absorb redistribution credits.
|
|
10
|
-
const eligibleDeficit = itemStates.reduce(
|
|
11
|
-
(sum, { excess, itemTotalPaid }) => {
|
|
12
|
-
const isPartiallyPaidAndUnderpaid = excess < 0 && itemTotalPaid > 0;
|
|
13
|
-
return isPartiallyPaidAndUnderpaid
|
|
14
|
-
? math.add(sum, math.sub(0, excess))
|
|
15
|
-
: sum;
|
|
16
|
-
},
|
|
17
|
-
0
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
// Cap total drawn from overpaid items so the net refund never exceeds totalToRefund.
|
|
21
|
-
let maxFromOverpaid = math.add(totalToRefund, eligibleDeficit);
|
|
22
|
-
let tempRemaining = totalToRefund;
|
|
23
|
-
|
|
24
|
-
// Pass 1: refund each overpaid item's excess, capped by maxFromOverpaid
|
|
25
|
-
itemStates.forEach(({ excess }, i) => {
|
|
26
|
-
if (excess > 0 && maxFromOverpaid > 0) {
|
|
27
|
-
const refund = excess > maxFromOverpaid ? maxFromOverpaid : excess;
|
|
28
|
-
refundables[i] = refund;
|
|
29
|
-
tempRemaining = math.sub(tempRemaining, refund);
|
|
30
|
-
maxFromOverpaid = math.sub(maxFromOverpaid, refund);
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
if (tempRemaining < 0) {
|
|
35
|
-
// Pass 2a: over-refunded — spread surplus as credits to partially-paid underpaid items
|
|
36
|
-
let surplus = math.sub(0, tempRemaining);
|
|
37
|
-
itemStates.forEach(({ excess, itemTotalPaid }, i) => {
|
|
38
|
-
if (excess < 0 && itemTotalPaid > 0 && surplus > 0) {
|
|
39
|
-
const deficit = math.sub(0, excess);
|
|
40
|
-
const credit = surplus > deficit ? deficit : surplus;
|
|
41
|
-
refundables[i] = -credit;
|
|
42
|
-
surplus = math.sub(surplus, credit);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
} else if (tempRemaining > 0) {
|
|
46
|
-
// Pass 2b: under-refunded — draw remainder from non-overpaid items (totalPaid > 0)
|
|
47
|
-
itemStates.forEach(({ itemTotalPaid, excess }, i) => {
|
|
48
|
-
if (excess <= 0 && itemTotalPaid > 0 && tempRemaining > 0) {
|
|
49
|
-
const refund =
|
|
50
|
-
tempRemaining > itemTotalPaid ? itemTotalPaid : tempRemaining;
|
|
51
|
-
refundables[i] = refund;
|
|
52
|
-
tempRemaining = math.sub(tempRemaining, refund);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return refundables;
|
|
58
|
-
};
|
|
59
|
-
};
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
module.exports = ({ utils }) => {
|
|
2
|
-
const { math } = utils;
|
|
3
|
-
|
|
4
|
-
return function resolveItemState(paymentOrderItem, ordersById) {
|
|
5
|
-
const order = (ordersById && ordersById[paymentOrderItem.orderId]) || {};
|
|
6
|
-
const orderItem = (order.items || []).find(
|
|
7
|
-
each => each._id === paymentOrderItem.orderItemId
|
|
8
|
-
);
|
|
9
|
-
|
|
10
|
-
const itemTotal = orderItem ? orderItem.total : paymentOrderItem.total || 0;
|
|
11
|
-
const itemTotalPaid = orderItem
|
|
12
|
-
? orderItem.totalPaid
|
|
13
|
-
: paymentOrderItem.totalPaid || 0;
|
|
14
|
-
const currentStatus =
|
|
15
|
-
(orderItem && orderItem.status) || paymentOrderItem.status || {};
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
item: paymentOrderItem,
|
|
19
|
-
itemTotal,
|
|
20
|
-
itemTotalPaid,
|
|
21
|
-
currentStatus,
|
|
22
|
-
excess: math.sub(itemTotalPaid, itemTotal),
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
module.exports = ({ actions }) =>
|
|
2
|
-
function appendIfOverPaid({ currentOrder, overPaidOrders = [] }) {
|
|
3
|
-
if (!currentOrder || !currentOrder._id) return overPaidOrders;
|
|
4
|
-
|
|
5
|
-
const refundableAmount = actions.getRefundableOrderAmount({
|
|
6
|
-
order: currentOrder,
|
|
7
|
-
});
|
|
8
|
-
if (refundableAmount <= 0) return overPaidOrders;
|
|
9
|
-
|
|
10
|
-
return [
|
|
11
|
-
...overPaidOrders,
|
|
12
|
-
{
|
|
13
|
-
...currentOrder,
|
|
14
|
-
refundableAmount,
|
|
15
|
-
},
|
|
16
|
-
];
|
|
17
|
-
};
|