@cloudcommerce/app-discounts 2.29.9 → 2.29.11
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/lib-mjs/apply-discount.mjs +428 -191
- package/lib-mjs/helpers.mjs +132 -31
- package/package.json +3 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
1
2
|
import ecomUtils from '@ecomplus/utils';
|
|
2
3
|
import api from '@cloudcommerce/api';
|
|
3
4
|
import {
|
|
@@ -6,38 +7,95 @@ import {
|
|
|
6
7
|
checkOpenPromotion,
|
|
7
8
|
getValidDiscountRules,
|
|
8
9
|
matchDiscountRule,
|
|
9
|
-
|
|
10
|
+
matchFreebieRule,
|
|
11
|
+
mapCampaignProducts,
|
|
10
12
|
} from './helpers.mjs';
|
|
11
13
|
|
|
12
14
|
export default async ({ params, application }) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// setup response object
|
|
20
|
-
// https://apx-mods.e-com.plus/api/v1/apply_discount/response_schema.json?store_id=100
|
|
15
|
+
const config = { ...application.data, ...application.hidden_data };
|
|
16
|
+
if (config.advanced && typeof config.advanced === 'object') {
|
|
17
|
+
Object.assign(config, config.advanced);
|
|
18
|
+
}
|
|
21
19
|
const response = {};
|
|
20
|
+
const discountPerSku = {};
|
|
22
21
|
const respondSuccess = () => {
|
|
23
22
|
if (response.available_extra_discount && !response.available_extra_discount.value) {
|
|
24
23
|
delete response.available_extra_discount;
|
|
25
24
|
}
|
|
25
|
+
if (response.discount_rule) {
|
|
26
|
+
if (!response.discount_rule.extra_discount?.value) {
|
|
27
|
+
delete response.discount_rule;
|
|
28
|
+
} else if (!response.discount_rule.description && config.describe_discounted_items) {
|
|
29
|
+
const discountedSkus = Object.keys(discountPerSku);
|
|
30
|
+
if (discountedSkus.length) {
|
|
31
|
+
response.discount_rule.description = `
|
|
32
|
+
Descontos por SKU:
|
|
33
|
+
---
|
|
34
|
+
${discountedSkus.map((sku) => `\n${sku}: ${discountPerSku[sku].toFixed(2)}`)}
|
|
35
|
+
`.substring(0, 999);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return response;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const checkUsageLimit = async (discountRule, label) => {
|
|
43
|
+
const { customer } = params;
|
|
44
|
+
if (!label) {
|
|
45
|
+
label = discountRule.label;
|
|
46
|
+
}
|
|
26
47
|
if (
|
|
27
|
-
|
|
28
|
-
|
|
48
|
+
label
|
|
49
|
+
&& customer && (customer._id || customer.doc_number)
|
|
50
|
+
&& (discountRule.usage_limit > 0 || discountRule.total_usage_limit > 0)
|
|
29
51
|
) {
|
|
30
|
-
|
|
52
|
+
// list orders to check discount usage limits
|
|
53
|
+
const endpoint = 'orders?fields=status'
|
|
54
|
+
+ `&extra_discount.app.label${(discountRule.case_insensitive ? '%=' : '=')}`
|
|
55
|
+
+ encodeURIComponent(label);
|
|
56
|
+
const usageLimits = [{
|
|
57
|
+
// limit by customer
|
|
58
|
+
query: customer.doc_number
|
|
59
|
+
? `&buyers.doc_number=${customer.doc_number}`
|
|
60
|
+
: `&buyers._id=${customer._id}`,
|
|
61
|
+
max: discountRule.usage_limit,
|
|
62
|
+
}, {
|
|
63
|
+
// total limit
|
|
64
|
+
query: '',
|
|
65
|
+
max: discountRule.total_usage_limit,
|
|
66
|
+
}];
|
|
67
|
+
for (let i = 0; i < usageLimits.length; i++) {
|
|
68
|
+
const { query, max } = usageLimits[i];
|
|
69
|
+
if (max) {
|
|
70
|
+
// send Store API request to list orders with filters
|
|
71
|
+
const { data } = await api.get(`${endpoint}${query}`);
|
|
72
|
+
const countOrders = data.result
|
|
73
|
+
.filter(({ status }) => status !== 'cancelled')
|
|
74
|
+
.length;
|
|
75
|
+
if (countOrders >= max) {
|
|
76
|
+
// limit reached
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
31
81
|
}
|
|
32
|
-
return
|
|
82
|
+
return true;
|
|
33
83
|
};
|
|
34
84
|
|
|
35
|
-
const
|
|
85
|
+
const getDiscountValue = (discount, maxDiscount) => {
|
|
36
86
|
let value;
|
|
37
87
|
if (typeof maxDiscount !== 'number') {
|
|
38
|
-
|
|
88
|
+
const applyAt = discount.apply_at || 'total';
|
|
89
|
+
maxDiscount = params.amount[applyAt];
|
|
90
|
+
if (applyAt === 'total' && response.discount_rule) {
|
|
91
|
+
maxDiscount -= response.discount_rule.extra_discount.value;
|
|
92
|
+
}
|
|
93
|
+
if (applyAt !== 'freight') {
|
|
94
|
+
// eslint-disable-next-line
|
|
95
|
+
maxDiscount -= getFreebiesPreview().value;
|
|
96
|
+
}
|
|
39
97
|
}
|
|
40
|
-
if (maxDiscount) {
|
|
98
|
+
if (maxDiscount > 0) {
|
|
41
99
|
// update amount discount and total
|
|
42
100
|
if (discount.type === 'percentage') {
|
|
43
101
|
value = maxDiscount * (discount.value / 100);
|
|
@@ -48,7 +106,11 @@ export default async ({ params, application }) => {
|
|
|
48
106
|
value = maxDiscount;
|
|
49
107
|
}
|
|
50
108
|
}
|
|
109
|
+
return value !== undefined ? Number(value) : value;
|
|
110
|
+
};
|
|
51
111
|
|
|
112
|
+
const addDiscount = (discount, flag, label, maxDiscount) => {
|
|
113
|
+
const value = getDiscountValue(discount, maxDiscount);
|
|
52
114
|
if (value) {
|
|
53
115
|
if (response.discount_rule) {
|
|
54
116
|
// accumulate discount
|
|
@@ -66,9 +128,145 @@ export default async ({ params, application }) => {
|
|
|
66
128
|
},
|
|
67
129
|
};
|
|
68
130
|
}
|
|
69
|
-
return
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const pointDiscountToSku = (discountValue, sku) => {
|
|
137
|
+
if (!discountValue || !sku) return;
|
|
138
|
+
if (!discountPerSku[sku]) discountPerSku[sku] = 0;
|
|
139
|
+
discountPerSku[sku] += discountValue;
|
|
140
|
+
};
|
|
141
|
+
const pointDiscountToEachItem = (discountValue, filteredItems) => {
|
|
142
|
+
const itemsAmount = filteredItems.reduce(
|
|
143
|
+
(amount, item) => amount + (ecomUtils.price(item) * (item.quantity || 1)),
|
|
144
|
+
0,
|
|
145
|
+
);
|
|
146
|
+
const discountMultiplier = discountValue / itemsAmount;
|
|
147
|
+
filteredItems.forEach((item) => {
|
|
148
|
+
const discountPerItem = discountMultiplier * (ecomUtils.price(item) * (item.quantity || 1));
|
|
149
|
+
return pointDiscountToSku(discountPerItem, item.sku);
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const getFreebiesPreview = () => {
|
|
154
|
+
if (params.items && params.items.length) {
|
|
155
|
+
// gift products (freebies) campaings
|
|
156
|
+
if (Array.isArray(config.freebies_rules)) {
|
|
157
|
+
const validFreebiesRules = config.freebies_rules.filter((rule) => {
|
|
158
|
+
return validateDateRange(rule)
|
|
159
|
+
&& validateCustomerId(rule, params)
|
|
160
|
+
&& mapCampaignProducts({ product_ids: rule.check_product_ids }, params).valid
|
|
161
|
+
&& Array.isArray(rule.product_ids)
|
|
162
|
+
&& rule.product_ids.length
|
|
163
|
+
&& matchFreebieRule(rule, params);
|
|
164
|
+
});
|
|
165
|
+
if (validFreebiesRules) {
|
|
166
|
+
const cumulativeRules = [];
|
|
167
|
+
let bestRule;
|
|
168
|
+
let discountValue = 0;
|
|
169
|
+
for (let i = 0; i < validFreebiesRules.length; i++) {
|
|
170
|
+
const rule = validFreebiesRules[i];
|
|
171
|
+
let subtotal = 0;
|
|
172
|
+
const subtotalItems = [];
|
|
173
|
+
params.items.forEach((item) => {
|
|
174
|
+
if (Array.isArray(rule.category_ids)) {
|
|
175
|
+
if (Array.isArray(item.categories)) {
|
|
176
|
+
for (let ii = 0; ii < item.categories.length; ii++) {
|
|
177
|
+
const category = item.categories[ii];
|
|
178
|
+
if (rule.category_ids.indexOf(category._id) > -1) {
|
|
179
|
+
subtotal += (item.quantity * ecomUtils.price(item));
|
|
180
|
+
subtotalItems.push(item);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
subtotal += (item.quantity * ecomUtils.price(item));
|
|
188
|
+
subtotalItems.push(item);
|
|
189
|
+
});
|
|
190
|
+
if (subtotal <= 0) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// start calculating discount
|
|
194
|
+
let value = 0;
|
|
195
|
+
let fixedSubtotal = subtotal;
|
|
196
|
+
rule.product_ids.forEach((productId) => {
|
|
197
|
+
const item = params.items.find((_item) => productId === _item.product_id);
|
|
198
|
+
if (item) {
|
|
199
|
+
const price = ecomUtils.price(item);
|
|
200
|
+
value += price;
|
|
201
|
+
if (subtotalItems.find((_item) => productId === _item.product_id)) {
|
|
202
|
+
fixedSubtotal -= price;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
if (rule.deduct_discounts) {
|
|
207
|
+
if (response.discount_rule) {
|
|
208
|
+
fixedSubtotal -= response.discount_rule.extra_discount.value;
|
|
209
|
+
}
|
|
210
|
+
if (params.amount.discount) {
|
|
211
|
+
fixedSubtotal -= params.amount.discount;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (rule.cumulative_freebie === true && !(rule.min_subtotal > fixedSubtotal)) {
|
|
215
|
+
cumulativeRules.push({ rule, value });
|
|
216
|
+
}
|
|
217
|
+
if (!bestRule || value > discountValue || bestRule.min_subtotal < rule.min_subtotal) {
|
|
218
|
+
if (!(rule.min_subtotal > fixedSubtotal)) {
|
|
219
|
+
bestRule = rule;
|
|
220
|
+
discountValue = value;
|
|
221
|
+
} else if (!discountValue && fixedSubtotal >= rule.min_subtotal) {
|
|
222
|
+
// discount not applicable yet but additional freebies are available
|
|
223
|
+
bestRule = rule;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (bestRule) {
|
|
229
|
+
// provide freebie products \o/
|
|
230
|
+
response.freebie_product_ids = bestRule.product_ids;
|
|
231
|
+
if (discountValue) {
|
|
232
|
+
if (bestRule.cumulative_freebie === true) {
|
|
233
|
+
cumulativeRules.forEach(({ rule, value }) => {
|
|
234
|
+
for (let i = 0; i < response.freebie_product_ids.length; i++) {
|
|
235
|
+
const productId = response.freebie_product_ids[i];
|
|
236
|
+
if (rule.product_ids.includes(productId)) {
|
|
237
|
+
// ignoring cumulative freebie rules with repeated products
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
discountValue += value;
|
|
242
|
+
rule.product_ids.forEach((productId) => {
|
|
243
|
+
response.freebie_product_ids.push(productId);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
value: discountValue,
|
|
249
|
+
label: bestRule.label,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return { value: 0 };
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const addFreebies = () => {
|
|
260
|
+
const { value, label } = getFreebiesPreview();
|
|
261
|
+
if (value) {
|
|
262
|
+
const maxDiscount = Math.min(value, params.amount.total || 0);
|
|
263
|
+
addDiscount(
|
|
264
|
+
{ type: 'fixed', value },
|
|
265
|
+
'FREEBIES',
|
|
266
|
+
label,
|
|
267
|
+
maxDiscount,
|
|
268
|
+
);
|
|
70
269
|
}
|
|
71
|
-
return false;
|
|
72
270
|
};
|
|
73
271
|
|
|
74
272
|
if (params.items && params.items.length) {
|
|
@@ -76,7 +274,7 @@ export default async ({ params, application }) => {
|
|
|
76
274
|
if (Array.isArray(config.product_kit_discounts)) {
|
|
77
275
|
config.product_kit_discounts = config.product_kit_discounts.map((kitDiscount) => {
|
|
78
276
|
if (!kitDiscount.product_ids) {
|
|
79
|
-
// kit with any items
|
|
277
|
+
// kit with any items (or per category)
|
|
80
278
|
kitDiscount.product_ids = [];
|
|
81
279
|
}
|
|
82
280
|
return kitDiscount;
|
|
@@ -91,41 +289,60 @@ export default async ({ params, application }) => {
|
|
|
91
289
|
}
|
|
92
290
|
if (a.min_quantity > b.min_quantity) {
|
|
93
291
|
return -1;
|
|
94
|
-
}
|
|
95
|
-
if (b.min_quantity > a.min_quantity) {
|
|
292
|
+
} if (b.min_quantity > a.min_quantity) {
|
|
96
293
|
return 1;
|
|
97
|
-
}
|
|
98
|
-
if (a.discount.min_amount > b.discount.min_amount) {
|
|
294
|
+
} if (a.discount.min_amount > b.discount.min_amount) {
|
|
99
295
|
return -1;
|
|
100
|
-
}
|
|
101
|
-
if (b.discount.min_amount > a.discount.min_amount) {
|
|
296
|
+
} if (b.discount.min_amount > a.discount.min_amount) {
|
|
102
297
|
return 1;
|
|
103
298
|
}
|
|
104
299
|
return 0;
|
|
105
300
|
});
|
|
106
|
-
|
|
301
|
+
// prevent applying duplicated kit discount for same items
|
|
107
302
|
let discountedItemIds = [];
|
|
108
303
|
// check buy together recommendations
|
|
109
304
|
const buyTogether = [];
|
|
110
305
|
|
|
111
|
-
kitDiscounts.
|
|
306
|
+
for (let index = 0; index < kitDiscounts.length; index++) {
|
|
307
|
+
const kitDiscount = kitDiscounts[index];
|
|
112
308
|
if (kitDiscount) {
|
|
113
309
|
const productIds = Array.isArray(kitDiscount.product_ids)
|
|
114
310
|
? kitDiscount.product_ids
|
|
115
311
|
: [];
|
|
116
|
-
|
|
117
|
-
?
|
|
118
|
-
:
|
|
119
|
-
kitItems =
|
|
312
|
+
const categoryIds = Array.isArray(kitDiscount.category_ids)
|
|
313
|
+
? kitDiscount.category_ids
|
|
314
|
+
: [];
|
|
315
|
+
let kitItems = [];
|
|
316
|
+
if (productIds.length) {
|
|
317
|
+
kitItems = params.items.filter((item) => productIds.indexOf(item.product_id) > -1);
|
|
318
|
+
} else if (categoryIds.length) {
|
|
319
|
+
kitItems = params.items.filter((item) => {
|
|
320
|
+
if (Array.isArray(item.categories)) {
|
|
321
|
+
for (let i = 0; i < item.categories.length; i++) {
|
|
322
|
+
const category = item.categories[i];
|
|
323
|
+
if (categoryIds.indexOf(category._id) > -1) {
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return false;
|
|
329
|
+
});
|
|
330
|
+
} else {
|
|
331
|
+
kitItems = [...params.items];
|
|
332
|
+
}
|
|
333
|
+
// eslint-disable-next-line no-loop-func
|
|
334
|
+
kitItems = kitItems.filter((item) => {
|
|
335
|
+
return item.quantity && discountedItemIds.indexOf(item.product_id) === -1;
|
|
336
|
+
});
|
|
120
337
|
if (!kitItems.length) {
|
|
121
|
-
|
|
338
|
+
continue;
|
|
122
339
|
}
|
|
123
340
|
|
|
124
341
|
const recommendBuyTogether = () => {
|
|
125
342
|
if (
|
|
126
343
|
params.items.length === 1
|
|
127
|
-
|
|
128
|
-
|
|
344
|
+
&& productIds.length <= 4
|
|
345
|
+
&& buyTogether.length < 300
|
|
129
346
|
) {
|
|
130
347
|
const baseProductId = params.items[0].product_id;
|
|
131
348
|
if (productIds.indexOf(baseProductId) === -1) {
|
|
@@ -145,8 +362,8 @@ export default async ({ params, application }) => {
|
|
|
145
362
|
buyTogether.push({
|
|
146
363
|
products: buyTogetherProducts,
|
|
147
364
|
discount: {
|
|
148
|
-
type: kitDiscount.discount.type,
|
|
149
|
-
value: kitDiscount.discount.value,
|
|
365
|
+
type: kitDiscount.originalDiscount?.type || kitDiscount.discount.type,
|
|
366
|
+
value: kitDiscount.originalDiscount?.value || kitDiscount.discount.value,
|
|
150
367
|
},
|
|
151
368
|
});
|
|
152
369
|
}
|
|
@@ -166,18 +383,25 @@ export default async ({ params, application }) => {
|
|
|
166
383
|
if (totalQuantity < kitDiscount.min_quantity) {
|
|
167
384
|
if (productIds.length > 1 && kitDiscount.check_all_items !== false) {
|
|
168
385
|
recommendBuyTogether();
|
|
169
|
-
return;
|
|
170
386
|
}
|
|
171
|
-
|
|
387
|
+
continue;
|
|
172
388
|
}
|
|
173
|
-
if (
|
|
389
|
+
if (
|
|
390
|
+
discount.type === 'fixed'
|
|
391
|
+
&& kitDiscount.cumulative_discount !== false
|
|
392
|
+
&& !kitDiscount.usage_limit
|
|
393
|
+
) {
|
|
174
394
|
discount.value *= Math.floor(totalQuantity / kitDiscount.min_quantity);
|
|
175
395
|
}
|
|
176
396
|
}
|
|
177
397
|
}
|
|
178
398
|
|
|
179
|
-
if (
|
|
399
|
+
if (
|
|
400
|
+
!params.amount
|
|
401
|
+
|| !(discount.min_amount > params.amount.total - getFreebiesPreview().value)
|
|
402
|
+
) {
|
|
180
403
|
if (kitDiscount.check_all_items !== false) {
|
|
404
|
+
let isSkip = false;
|
|
181
405
|
for (let i = 0; i < productIds.length; i++) {
|
|
182
406
|
const productId = productIds[i];
|
|
183
407
|
if (
|
|
@@ -186,99 +410,103 @@ export default async ({ params, application }) => {
|
|
|
186
410
|
) {
|
|
187
411
|
// product not on current cart
|
|
188
412
|
recommendBuyTogether();
|
|
189
|
-
|
|
413
|
+
isSkip = true;
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (categoryIds.length) {
|
|
418
|
+
for (let i = 0; i < kitItems.length; i++) {
|
|
419
|
+
const { categories } = kitItems[i];
|
|
420
|
+
let hasListedCategory = false;
|
|
421
|
+
if (categories) {
|
|
422
|
+
for (let ii = 0; ii < categories.length; ii++) {
|
|
423
|
+
const category = categories[ii];
|
|
424
|
+
if (categoryIds.find((categoryId) => categoryId === category._id)) {
|
|
425
|
+
hasListedCategory = true;
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (!hasListedCategory) {
|
|
431
|
+
recommendBuyTogether();
|
|
432
|
+
isSkip = true;
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
190
435
|
}
|
|
191
436
|
}
|
|
437
|
+
if (isSkip) continue;
|
|
192
438
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
const isAvailable = await checkUsageLimit(kitDiscount);
|
|
442
|
+
if (isAvailable) {
|
|
443
|
+
// apply cumulative discount \o/
|
|
444
|
+
if (kitDiscount.same_product_quantity) {
|
|
445
|
+
kitItems.forEach((item, i) => {
|
|
446
|
+
const discountValue = addDiscount(
|
|
447
|
+
discount,
|
|
448
|
+
`KIT-${(index + 1)}-${i}`,
|
|
449
|
+
kitDiscount.label,
|
|
450
|
+
ecomUtils.price(item) * (item.quantity || 1),
|
|
451
|
+
);
|
|
452
|
+
pointDiscountToSku(discountValue, item.sku);
|
|
453
|
+
});
|
|
454
|
+
} else {
|
|
455
|
+
const discountValue = addDiscount(discount, `KIT-${(index + 1)}`, kitDiscount.label);
|
|
456
|
+
pointDiscountToEachItem(discountValue, kitItems);
|
|
457
|
+
}
|
|
458
|
+
discountedItemIds = discountedItemIds.concat(kitItems.map((item) => item.product_id));
|
|
459
|
+
}
|
|
460
|
+
} catch (err) {
|
|
461
|
+
return {
|
|
462
|
+
error: 'CANT_CHECK_USAGE_LIMITS',
|
|
463
|
+
message: err.message,
|
|
464
|
+
};
|
|
205
465
|
}
|
|
206
|
-
discountedItemIds = discountedItemIds.concat(kitItems.map((item) => item.product_id));
|
|
207
466
|
}
|
|
208
467
|
}
|
|
209
|
-
}
|
|
468
|
+
}
|
|
210
469
|
if (buyTogether.length) {
|
|
211
470
|
response.buy_together = buyTogether;
|
|
212
471
|
}
|
|
472
|
+
}
|
|
213
473
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
474
|
+
// additional discount coupons for API manipualation with
|
|
475
|
+
// PATCH https://api.e-com.plus/v1/applications/<discounts_app_id>/hidden_data.json { COUPON }
|
|
476
|
+
if (!config.discount_rules) {
|
|
477
|
+
config.discount_rules = [];
|
|
478
|
+
}
|
|
479
|
+
Object.keys(config).forEach((configField) => {
|
|
480
|
+
switch (configField) {
|
|
481
|
+
case 'freebies_rules':
|
|
482
|
+
case 'product_kit_discounts':
|
|
483
|
+
case 'discount_rules':
|
|
484
|
+
return;
|
|
485
|
+
default:
|
|
486
|
+
}
|
|
487
|
+
const configObj = config[configField];
|
|
488
|
+
if (configObj && configObj.discount) {
|
|
489
|
+
config.discount_rules.push({
|
|
490
|
+
...configObj,
|
|
491
|
+
discount_coupon: configField,
|
|
222
492
|
});
|
|
223
|
-
if (validFreebiesRules) {
|
|
224
|
-
let subtotal = 0;
|
|
225
|
-
params.items.forEach((item) => {
|
|
226
|
-
subtotal += (item.quantity * ecomUtils.price(item));
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
let bestRule;
|
|
230
|
-
let discountValue = 0;
|
|
231
|
-
for (let i = 0; i < validFreebiesRules.length; i++) {
|
|
232
|
-
const rule = validFreebiesRules[i];
|
|
233
|
-
// start calculating discount
|
|
234
|
-
let value = 0;
|
|
235
|
-
rule.product_ids.forEach((productId) => {
|
|
236
|
-
const item = params.items.find((_item) => productId === _item.product_id);
|
|
237
|
-
if (item) {
|
|
238
|
-
value += ecomUtils.price(item);
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
const fixedSubtotal = subtotal - value;
|
|
242
|
-
if (!bestRule || value > discountValue || bestRule.min_subtotal < rule.min_subtotal) {
|
|
243
|
-
if (!(rule.min_subtotal > fixedSubtotal)) {
|
|
244
|
-
bestRule = rule;
|
|
245
|
-
discountValue = value;
|
|
246
|
-
} else if (!discountValue && subtotal >= rule.min_subtotal) {
|
|
247
|
-
// discount not applicable yet but additional freebies are available
|
|
248
|
-
bestRule = rule;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (bestRule) {
|
|
254
|
-
// provide freebie products \o/
|
|
255
|
-
response.freebie_product_ids = bestRule.product_ids;
|
|
256
|
-
if (discountValue) {
|
|
257
|
-
addDiscount(
|
|
258
|
-
{
|
|
259
|
-
type: 'fixed',
|
|
260
|
-
value: discountValue,
|
|
261
|
-
},
|
|
262
|
-
'FREEBIES',
|
|
263
|
-
bestRule.label,
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
493
|
}
|
|
269
|
-
}
|
|
494
|
+
});
|
|
270
495
|
|
|
271
496
|
const discountRules = getValidDiscountRules(config.discount_rules, params);
|
|
272
497
|
if (discountRules.length) {
|
|
273
498
|
const { discountRule, discountMatchEnum } = matchDiscountRule(discountRules, params);
|
|
274
499
|
if (discountRule) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
500
|
+
const {
|
|
501
|
+
valid: isValidByItems,
|
|
502
|
+
items: filteredItems,
|
|
503
|
+
} = mapCampaignProducts(discountRule, params);
|
|
504
|
+
if (!isValidByItems) {
|
|
505
|
+
addFreebies();
|
|
506
|
+
response.invalid_coupon_message = params.lang === 'pt_br'
|
|
507
|
+
? 'Nenhum produto da promoção está incluído no carrinho'
|
|
508
|
+
: 'No promotion products are included in the cart';
|
|
509
|
+
return respondSuccess();
|
|
282
510
|
}
|
|
283
511
|
|
|
284
512
|
const excludedProducts = discountRule.excluded_product_ids;
|
|
@@ -287,64 +515,104 @@ export default async ({ params, application }) => {
|
|
|
287
515
|
for (let i = 0; i < params.items.length; i++) {
|
|
288
516
|
const item = params.items[i];
|
|
289
517
|
if (item.quantity && excludedProducts.includes(item.product_id)) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
};
|
|
518
|
+
addFreebies();
|
|
519
|
+
response.invalid_coupon_message = params.lang === 'pt_br'
|
|
520
|
+
? `Promoção é inválida para o produto ${item.name}`
|
|
521
|
+
: `Invalid promotion for product ${item.name}`;
|
|
522
|
+
return respondSuccess();
|
|
296
523
|
}
|
|
297
524
|
}
|
|
298
525
|
}
|
|
299
526
|
|
|
300
|
-
let { label } = discountRule;
|
|
301
527
|
const { discount } = discountRule;
|
|
528
|
+
let { label } = discountRule;
|
|
302
529
|
if (typeof label !== 'string' || !label) {
|
|
303
530
|
label = params.discount_coupon || `DISCOUNT ${discountMatchEnum}`;
|
|
304
531
|
}
|
|
305
532
|
if (
|
|
306
533
|
discount.apply_at !== 'freight'
|
|
307
|
-
|
|
308
|
-
|
|
534
|
+
&& (!response.available_extra_discount || !response.available_extra_discount.value
|
|
535
|
+
|| discountRule.default_discount === true || checkOpenPromotion(discountRule))
|
|
309
536
|
) {
|
|
310
537
|
// show current discount rule as available discount to apply
|
|
311
538
|
response.available_extra_discount = {
|
|
312
539
|
label: label.substring(0, 50),
|
|
540
|
+
type: discount.type,
|
|
313
541
|
};
|
|
314
|
-
['min_amount', '
|
|
542
|
+
['min_amount', 'value'].forEach((field) => {
|
|
315
543
|
if (discount[field]) {
|
|
316
|
-
response.available_extra_discount[field] = discount[field];
|
|
544
|
+
response.available_extra_discount[field] = Number(discount[field]);
|
|
317
545
|
}
|
|
318
546
|
});
|
|
319
547
|
}
|
|
320
548
|
|
|
321
549
|
// params object follows list payments request schema:
|
|
322
550
|
// https://apx-mods.e-com.plus/api/v1/apply_discount/schema.json?store_id=100
|
|
551
|
+
let checkAmount;
|
|
552
|
+
if (params.amount) {
|
|
553
|
+
checkAmount = params.amount[discountRule.discount.amount_field || 'total'];
|
|
554
|
+
if (discountRule.discount.amount_field !== 'freight') {
|
|
555
|
+
checkAmount -= getFreebiesPreview().value;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
323
558
|
if (
|
|
324
559
|
params.amount && params.amount.total > 0
|
|
325
|
-
|
|
560
|
+
&& !(discountRule.discount.min_amount > checkAmount)
|
|
326
561
|
) {
|
|
327
562
|
if (
|
|
328
563
|
discountRule.cumulative_discount === false
|
|
329
|
-
|
|
564
|
+
&& (response.discount_rule || params.amount.discount)
|
|
330
565
|
) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
566
|
+
if (
|
|
567
|
+
response.discount_rule?.extra_discount
|
|
568
|
+
&& !params.amount.discount
|
|
569
|
+
&& getDiscountValue(discount) > response.discount_rule.extra_discount.value
|
|
570
|
+
) {
|
|
571
|
+
// replace discount with new bigger one
|
|
572
|
+
delete response.discount_rule;
|
|
573
|
+
} else {
|
|
574
|
+
// explain discount can't be applied :(
|
|
575
|
+
// https://apx-mods.e-com.plus/api/v1/apply_discount/response_schema.json?store_id=100
|
|
576
|
+
addFreebies();
|
|
577
|
+
response.invalid_coupon_message = params.lang === 'pt_br'
|
|
335
578
|
? 'A promoção não pôde ser aplicada porque este desconto não é cumulativo'
|
|
336
|
-
: 'This discount is not cumulative'
|
|
337
|
-
|
|
579
|
+
: 'This discount is not cumulative';
|
|
580
|
+
return respondSuccess();
|
|
581
|
+
}
|
|
338
582
|
}
|
|
339
583
|
|
|
340
584
|
// we have a discount to apply \o/
|
|
341
|
-
|
|
585
|
+
const discountValue = addDiscount(discountRule.discount, discountMatchEnum);
|
|
586
|
+
if (discountValue) {
|
|
587
|
+
if (filteredItems?.length) {
|
|
588
|
+
pointDiscountToEachItem(discountValue, filteredItems);
|
|
589
|
+
}
|
|
342
590
|
// add discount label and description if any
|
|
343
591
|
response.discount_rule.label = label;
|
|
344
592
|
if (discountRule.description) {
|
|
345
593
|
response.discount_rule.description = discountRule.description;
|
|
346
594
|
}
|
|
347
595
|
if (!checkOpenPromotion(discountRule)) {
|
|
596
|
+
if (discountRule.cumulative_discount !== false) {
|
|
597
|
+
// check for additional same-rule discount on different amount
|
|
598
|
+
const {
|
|
599
|
+
discountRule: secondDiscountRule,
|
|
600
|
+
discountMatchEnum: secondDiscountMatchEnum,
|
|
601
|
+
} = matchDiscountRule(discountRules, params, discountRule.discount.apply_at || 'total');
|
|
602
|
+
if (secondDiscountRule) {
|
|
603
|
+
let _checkAmount = params.amount[secondDiscountRule.discount.amount_field || 'total'];
|
|
604
|
+
if (secondDiscountRule.discount.amount_field !== 'freight') {
|
|
605
|
+
_checkAmount -= getFreebiesPreview().value;
|
|
606
|
+
}
|
|
607
|
+
if (
|
|
608
|
+
secondDiscountRule.cumulative_discount !== false
|
|
609
|
+
&& !(secondDiscountRule.discount.min_amount > _checkAmount)
|
|
610
|
+
) {
|
|
611
|
+
addDiscount(secondDiscountRule.discount, secondDiscountMatchEnum + '-2');
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
348
616
|
// check for additional open discount
|
|
349
617
|
const {
|
|
350
618
|
discountRule: openDiscountRule,
|
|
@@ -352,78 +620,47 @@ export default async ({ params, application }) => {
|
|
|
352
620
|
} = matchDiscountRule(discountRules, {});
|
|
353
621
|
if (
|
|
354
622
|
openDiscountRule
|
|
355
|
-
|
|
356
|
-
|
|
623
|
+
&& openDiscountRule.cumulative_discount !== false
|
|
624
|
+
&& openDiscountRule.discount.min_amount
|
|
357
625
|
) {
|
|
358
|
-
let
|
|
359
|
-
if (
|
|
626
|
+
let _checkAmount = params.amount[openDiscountRule.discount.amount_field || 'total'];
|
|
627
|
+
if (_checkAmount) {
|
|
360
628
|
// subtract current discount to validate cumulative open discount min amount
|
|
361
629
|
if (response.discount_rule) {
|
|
362
|
-
|
|
630
|
+
_checkAmount -= response.discount_rule.extra_discount.value;
|
|
363
631
|
}
|
|
364
|
-
if (openDiscountRule.discount.
|
|
632
|
+
if (openDiscountRule.discount.amount_field !== 'freight') {
|
|
633
|
+
_checkAmount -= getFreebiesPreview().value;
|
|
634
|
+
}
|
|
635
|
+
if (openDiscountRule.discount.min_amount <= _checkAmount) {
|
|
365
636
|
addDiscount(openDiscountRule.discount, openDiscountMatchEnum);
|
|
366
637
|
}
|
|
367
638
|
}
|
|
368
639
|
}
|
|
369
640
|
}
|
|
370
641
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const endpoint = 'orders?fields=status'
|
|
379
|
-
+ `&extra_discount.app.label${(discountRule.case_insensitive ? '%=' : '=')}`
|
|
380
|
-
+ encodeURIComponent(label);
|
|
381
|
-
const usageLimits = [{
|
|
382
|
-
// limit by customer
|
|
383
|
-
query: customer.doc_number
|
|
384
|
-
? `&buyers.doc_number=${customer.doc_number}`
|
|
385
|
-
: `&buyers._id=${customer._id}`,
|
|
386
|
-
max: discountRule.usage_limit,
|
|
387
|
-
}, {
|
|
388
|
-
// total limit
|
|
389
|
-
query: '',
|
|
390
|
-
max: discountRule.total_usage_limit,
|
|
391
|
-
}];
|
|
392
|
-
for (let i = 0; i < usageLimits.length; i++) {
|
|
393
|
-
const { query, max } = usageLimits[i];
|
|
394
|
-
if (max) {
|
|
395
|
-
let countOrders;
|
|
396
|
-
try {
|
|
397
|
-
// send Store API request to list orders with filters
|
|
398
|
-
// eslint-disable-next-line no-await-in-loop
|
|
399
|
-
const { data } = await api.get(`${endpoint}${query}`);
|
|
400
|
-
countOrders = data.result
|
|
401
|
-
.filter(({ status }) => status !== 'cancelled')
|
|
402
|
-
.length;
|
|
403
|
-
} catch (err) {
|
|
404
|
-
return {
|
|
405
|
-
error: 'CANT_CHECK_USAGE_LIMITS',
|
|
406
|
-
message: err.message,
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if (countOrders >= max) {
|
|
411
|
-
// limit reached
|
|
412
|
-
return {
|
|
413
|
-
invalid_coupon_message: params.lang === 'pt_br'
|
|
414
|
-
? 'A promoção não pôde ser aplicada porque já atingiu o limite de usos'
|
|
415
|
-
: 'The promotion could not be applied because it has already reached the usage limit',
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
}
|
|
642
|
+
try {
|
|
643
|
+
const isAvailable = await checkUsageLimit(discountRule, label);
|
|
644
|
+
if (!isAvailable) {
|
|
645
|
+
delete response.discount_rule;
|
|
646
|
+
response.invalid_coupon_message = params.lang === 'pt_br'
|
|
647
|
+
? 'A promoção não pôde ser aplicada porque já atingiu o limite de usos'
|
|
648
|
+
: 'The promotion could not be applied because it has already reached the usage limit';
|
|
419
649
|
}
|
|
650
|
+
addFreebies();
|
|
651
|
+
return respondSuccess();
|
|
652
|
+
} catch (err) {
|
|
653
|
+
return {
|
|
654
|
+
error: 'CANT_CHECK_USAGE_LIMITS',
|
|
655
|
+
message: err.message,
|
|
656
|
+
};
|
|
420
657
|
}
|
|
421
|
-
return respondSuccess();
|
|
422
658
|
}
|
|
423
659
|
}
|
|
424
660
|
}
|
|
425
661
|
}
|
|
426
662
|
|
|
663
|
+
addFreebies();
|
|
427
664
|
// response with no error nor discount applied
|
|
428
665
|
return respondSuccess();
|
|
429
666
|
};
|
package/lib-mjs/helpers.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable default-param-last, no-use-before-define */
|
|
1
2
|
import ecomUtils from '@ecomplus/utils';
|
|
2
3
|
|
|
3
4
|
const validateDateRange = (rule) => {
|
|
@@ -15,6 +16,19 @@ const validateDateRange = (rule) => {
|
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
const validateCustomerId = (rule, params) => {
|
|
19
|
+
if (
|
|
20
|
+
typeof rule.customer_ids === 'object'
|
|
21
|
+
&& rule.customer_ids
|
|
22
|
+
&& !Array.isArray(rule.customer_ids)
|
|
23
|
+
) {
|
|
24
|
+
const customerIds = [];
|
|
25
|
+
Object.keys(rule.customer_ids).forEach((key) => {
|
|
26
|
+
if (rule.customer_ids[key]) {
|
|
27
|
+
customerIds.push(rule.customer_ids[key]);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
rule.customer_ids = customerIds;
|
|
31
|
+
}
|
|
18
32
|
if (
|
|
19
33
|
Array.isArray(rule.customer_ids)
|
|
20
34
|
&& rule.customer_ids.length
|
|
@@ -26,27 +40,74 @@ const validateCustomerId = (rule, params) => {
|
|
|
26
40
|
return true;
|
|
27
41
|
};
|
|
28
42
|
|
|
43
|
+
const matchFreebieRule = (rule, params = {}) => {
|
|
44
|
+
const coupon = params.discount_coupon;
|
|
45
|
+
const utm = params.utm && params.utm.campaign;
|
|
46
|
+
if (rule.domain && rule.domain !== params.domain) {
|
|
47
|
+
if (params.domain !== `${rule.domain}.skip-open`) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (rule.freebie_coupon && rule.freebie_utm) {
|
|
52
|
+
return coupon?.toUpperCase() === rule.freebie_coupon?.toUpperCase()
|
|
53
|
+
|| (utm?.toUpperCase() === rule.freebie_utm?.toUpperCase());
|
|
54
|
+
}
|
|
55
|
+
if (rule.freebie_coupon) {
|
|
56
|
+
return coupon?.toUpperCase() === rule.freebie_coupon?.toUpperCase();
|
|
57
|
+
}
|
|
58
|
+
if (rule.freebie_utm) {
|
|
59
|
+
return (utm?.toUpperCase() === rule.freebie_utm?.toUpperCase());
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
};
|
|
63
|
+
|
|
29
64
|
const checkOpenPromotion = (rule) => {
|
|
30
65
|
return !rule.discount_coupon && !rule.utm_campaign
|
|
31
66
|
&& (!Array.isArray(rule.customer_ids) || !rule.customer_ids.length);
|
|
32
67
|
};
|
|
33
68
|
|
|
34
|
-
const getValidDiscountRules = (discountRules, params,
|
|
69
|
+
const getValidDiscountRules = (discountRules, params, itemsForKit) => {
|
|
35
70
|
if (Array.isArray(discountRules) && discountRules.length) {
|
|
36
71
|
// validate rules objects
|
|
37
72
|
return discountRules.filter((rule) => {
|
|
38
73
|
if (!rule || !validateCustomerId(rule, params)) {
|
|
39
74
|
return false;
|
|
40
75
|
}
|
|
41
|
-
|
|
42
|
-
|
|
76
|
+
const isKitDiscount = Array.isArray(itemsForKit)
|
|
77
|
+
&& (Array.isArray(rule.product_ids) || Array.isArray(rule.category_ids));
|
|
78
|
+
if (rule.domain && rule.domain !== params.domain) {
|
|
79
|
+
if (params.domain === `${rule.domain}.skip-open`) {
|
|
80
|
+
if (!isKitDiscount && checkOpenPromotion(rule)) return false;
|
|
81
|
+
} else {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (isKitDiscount) {
|
|
43
86
|
const checkProductId = (item) => {
|
|
44
|
-
|
|
87
|
+
if (
|
|
88
|
+
!(rule.product_ids && rule.product_ids.length)
|
|
89
|
+
&& Array.isArray(rule.category_ids)
|
|
90
|
+
&& rule.category_ids.length
|
|
91
|
+
) {
|
|
92
|
+
if (Array.isArray(item.categories)) {
|
|
93
|
+
for (let i = 0; i < item.categories.length; i++) {
|
|
94
|
+
const category = item.categories[i];
|
|
95
|
+
if (rule.category_ids.indexOf(category._id) > -1) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
return (
|
|
103
|
+
!(rule.product_ids && rule.product_ids.length)
|
|
104
|
+
|| rule.product_ids.indexOf(item.product_id) > -1
|
|
105
|
+
);
|
|
45
106
|
};
|
|
46
107
|
// set/add discount value from lowest item price
|
|
47
108
|
let value;
|
|
48
109
|
if (rule.discount_lowest_price) {
|
|
49
|
-
|
|
110
|
+
itemsForKit.forEach((item) => {
|
|
50
111
|
const price = ecomUtils.price(item);
|
|
51
112
|
if (price > 0 && checkProductId(item) && (!value || value > price)) {
|
|
52
113
|
value = price;
|
|
@@ -54,7 +115,7 @@ const getValidDiscountRules = (discountRules, params, items) => {
|
|
|
54
115
|
});
|
|
55
116
|
} else if (rule.discount_kit_subtotal) {
|
|
56
117
|
value = 0;
|
|
57
|
-
|
|
118
|
+
itemsForKit.forEach((item) => {
|
|
58
119
|
const price = ecomUtils.price(item);
|
|
59
120
|
if (price > 0 && checkProductId(item)) {
|
|
60
121
|
value += price * item.quantity;
|
|
@@ -71,6 +132,7 @@ const getValidDiscountRules = (discountRules, params, items) => {
|
|
|
71
132
|
value = Math.min(value, rule.discount.value);
|
|
72
133
|
}
|
|
73
134
|
}
|
|
135
|
+
rule.originalDiscount = rule.discount;
|
|
74
136
|
rule.discount = {
|
|
75
137
|
...rule.discount,
|
|
76
138
|
type: 'fixed',
|
|
@@ -81,21 +143,31 @@ const getValidDiscountRules = (discountRules, params, items) => {
|
|
|
81
143
|
if (!rule.discount || !rule.discount.value) {
|
|
82
144
|
return false;
|
|
83
145
|
}
|
|
84
|
-
|
|
85
146
|
return validateDateRange(rule);
|
|
86
147
|
});
|
|
87
148
|
}
|
|
88
|
-
|
|
89
149
|
// returns array anyway
|
|
90
150
|
return [];
|
|
91
151
|
};
|
|
92
152
|
|
|
93
|
-
const matchDiscountRule = (
|
|
153
|
+
const matchDiscountRule = (_discountRules, params = {}, skipApplyAt) => {
|
|
154
|
+
const validItemsDiscountRules = _discountRules.filter((rule) => {
|
|
155
|
+
return mapCampaignProducts(rule, params).valid;
|
|
156
|
+
});
|
|
157
|
+
const discountRules = validItemsDiscountRules.length
|
|
158
|
+
? validItemsDiscountRules
|
|
159
|
+
: _discountRules;
|
|
160
|
+
const filteredRules = skipApplyAt
|
|
161
|
+
? discountRules.filter((rule) => {
|
|
162
|
+
const applyAt = (rule.discount && rule.discount.apply_at) || 'total';
|
|
163
|
+
return applyAt !== skipApplyAt;
|
|
164
|
+
})
|
|
165
|
+
: discountRules;
|
|
94
166
|
// try to match a promotion
|
|
95
167
|
if (params.discount_coupon) {
|
|
96
168
|
// match only by discount coupon
|
|
97
169
|
return {
|
|
98
|
-
discountRule:
|
|
170
|
+
discountRule: filteredRules.find((rule) => {
|
|
99
171
|
return rule.case_insensitive
|
|
100
172
|
? typeof rule.discount_coupon === 'string'
|
|
101
173
|
&& rule.discount_coupon.toUpperCase() === params.discount_coupon.toUpperCase()
|
|
@@ -104,10 +176,9 @@ const matchDiscountRule = (discountRules, params = {}) => {
|
|
|
104
176
|
discountMatchEnum: 'COUPON',
|
|
105
177
|
};
|
|
106
178
|
}
|
|
107
|
-
|
|
108
179
|
// try to match by UTM campaign first
|
|
109
180
|
if (params.utm && params.utm.campaign) {
|
|
110
|
-
const discountRule =
|
|
181
|
+
const discountRule = filteredRules.find((rule) => {
|
|
111
182
|
return rule.case_insensitive
|
|
112
183
|
? typeof rule.utm_campaign === 'string'
|
|
113
184
|
&& rule.utm_campaign.toUpperCase() === params.utm.campaign.toUpperCase()
|
|
@@ -120,11 +191,12 @@ const matchDiscountRule = (discountRules, params = {}) => {
|
|
|
120
191
|
};
|
|
121
192
|
}
|
|
122
193
|
}
|
|
123
|
-
|
|
124
194
|
// then try to match by customer
|
|
125
195
|
if (params.customer && params.customer._id) {
|
|
126
|
-
const discountRule =
|
|
127
|
-
|
|
196
|
+
const discountRule = filteredRules.find((rule) => {
|
|
197
|
+
return Array.isArray(rule.customer_ids)
|
|
198
|
+
&& rule.customer_ids.indexOf(params.customer._id) > -1;
|
|
199
|
+
});
|
|
128
200
|
if (discountRule) {
|
|
129
201
|
return {
|
|
130
202
|
discountRule,
|
|
@@ -132,31 +204,59 @@ const matchDiscountRule = (discountRules, params = {}) => {
|
|
|
132
204
|
};
|
|
133
205
|
}
|
|
134
206
|
}
|
|
135
|
-
|
|
207
|
+
// then try to match by domain
|
|
208
|
+
if (params.domain) {
|
|
209
|
+
const discountRule = filteredRules.find((rule) => {
|
|
210
|
+
return rule.domain === params.domain || params.domain === `${rule.domain}.skip-open`;
|
|
211
|
+
});
|
|
212
|
+
if (discountRule) {
|
|
213
|
+
return {
|
|
214
|
+
discountRule,
|
|
215
|
+
discountMatchEnum: 'DOMAIN',
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
136
219
|
// last try to match by open promotions
|
|
137
220
|
return {
|
|
138
|
-
discountRule:
|
|
221
|
+
discountRule: filteredRules.find(checkOpenPromotion),
|
|
139
222
|
discountMatchEnum: 'OPEN',
|
|
140
223
|
};
|
|
141
224
|
};
|
|
142
225
|
|
|
143
|
-
const
|
|
144
|
-
if (Array.isArray(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
226
|
+
const mapCampaignProducts = (rule, params) => {
|
|
227
|
+
if (Array.isArray(rule.product_ids) && rule.product_ids.length) {
|
|
228
|
+
const items = params.items?.filter((item) => {
|
|
229
|
+
return item.quantity && rule.product_ids.includes(item.product_id);
|
|
230
|
+
}) || [];
|
|
231
|
+
return { valid: items.length, items };
|
|
232
|
+
}
|
|
233
|
+
if (Array.isArray(rule.category_ids) && rule.category_ids.length) {
|
|
234
|
+
let discountValue = 0;
|
|
235
|
+
const items = params.items?.filter((item) => {
|
|
236
|
+
const isValidItem = item.quantity && item.categories?.some((category) => {
|
|
237
|
+
return rule.category_ids.includes(category._id);
|
|
238
|
+
});
|
|
239
|
+
if (isValidItem) {
|
|
240
|
+
const price = ecomUtils.price(item);
|
|
241
|
+
if (price > 0) {
|
|
242
|
+
discountValue += (price * item.quantity);
|
|
152
243
|
}
|
|
153
244
|
}
|
|
245
|
+
return isValidItem;
|
|
246
|
+
}) || [];
|
|
247
|
+
// direct "fix" rule discount value limiting by category items
|
|
248
|
+
if (rule.discount?.value) {
|
|
249
|
+
if (rule.discount.type === 'percentage') {
|
|
250
|
+
discountValue *= rule.discount.value / 100;
|
|
251
|
+
} else {
|
|
252
|
+
discountValue = Math.min(discountValue, rule.discount.value);
|
|
253
|
+
}
|
|
254
|
+
rule.discount.type = 'fixed';
|
|
255
|
+
rule.discount.value = discountValue;
|
|
154
256
|
}
|
|
155
|
-
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
257
|
+
return { valid: items.length, items };
|
|
158
258
|
}
|
|
159
|
-
return true;
|
|
259
|
+
return { valid: true, items: [] };
|
|
160
260
|
};
|
|
161
261
|
|
|
162
262
|
export {
|
|
@@ -165,5 +265,6 @@ export {
|
|
|
165
265
|
checkOpenPromotion,
|
|
166
266
|
getValidDiscountRules,
|
|
167
267
|
matchDiscountRule,
|
|
168
|
-
|
|
268
|
+
matchFreebieRule,
|
|
269
|
+
mapCampaignProducts,
|
|
169
270
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudcommerce/app-discounts",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.29.
|
|
4
|
+
"version": "2.29.11",
|
|
5
5
|
"description": "e-com.plus Cloud Commerce app for complex discount rules",
|
|
6
6
|
"main": "lib/discounts.js",
|
|
7
7
|
"files": [
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"homepage": "https://github.com/ecomplus/cloud-commerce/tree/main/packages/apps/discounts#readme",
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@ecomplus/utils": "1.5.0-rc.6",
|
|
26
|
-
"@cloudcommerce/api": "2.29.
|
|
26
|
+
"@cloudcommerce/api": "2.29.11"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@cloudcommerce/types": "2.29.
|
|
29
|
+
"@cloudcommerce/types": "2.29.11"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "bash ../../../scripts/build-lib.sh"
|