@shushed/helpers 0.0.202-v2-20251208085030 → 0.0.202
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/dist/cjs/contracts/asset.schema.json +78 -0
- package/dist/cjs/contracts/category.schema.json +86 -0
- package/dist/cjs/contracts/country.schema.json +258 -0
- package/dist/cjs/contracts/currency.schema.json +177 -0
- package/dist/cjs/contracts/customer-segment.schema.json +32 -0
- package/dist/cjs/contracts/development-colour.schema.json +97 -0
- package/dist/cjs/contracts/index.js +48 -0
- package/dist/cjs/contracts/marketing-preferences.schema.json +41 -0
- package/dist/cjs/contracts/messages/ean-change.schema.json +22 -0
- package/dist/cjs/contracts/messages/index.js +29 -0
- package/dist/cjs/contracts/messages/order/delivered.schema.json +26 -0
- package/dist/cjs/contracts/messages/order/index.js +18 -0
- package/dist/cjs/contracts/messages/order/new.schema.json +32 -0
- package/dist/cjs/contracts/messages/order/processed.schema.json +15 -0
- package/dist/cjs/contracts/messages/order/return-initiated.schema.json +26 -0
- package/dist/cjs/contracts/messages/order/returned.schema.json +25 -0
- package/dist/cjs/contracts/messages/order/shipped.schema.json +26 -0
- package/dist/cjs/contracts/messages/product-category.schema.json +21 -0
- package/dist/cjs/contracts/messages/product-draft.schema.json +66 -0
- package/dist/cjs/contracts/messages/product.schema.json +29 -0
- package/dist/cjs/contracts/money.schema.json +54 -0
- package/dist/cjs/contracts/order/address.schema.json +127 -0
- package/dist/cjs/contracts/order/customer.schema.json +149 -0
- package/dist/cjs/contracts/order/index.js +31 -0
- package/dist/cjs/contracts/order/item.schema.json +98 -0
- package/dist/cjs/contracts/order/payment.schema.json +115 -0
- package/dist/cjs/contracts/order/shipment/index.js +29 -0
- package/dist/cjs/contracts/order/shipment/item/index.js +8 -0
- package/dist/cjs/contracts/order/shipment/item/returned.schema.json +30 -0
- package/dist/cjs/contracts/order/shipment/item.schema.json +22 -0
- package/dist/cjs/contracts/order/shipment/pos/index.js +10 -0
- package/dist/cjs/contracts/order/shipment/pos/outbound.schema.json +37 -0
- package/dist/cjs/contracts/order/shipment/pos/return.schema.json +31 -0
- package/dist/cjs/contracts/order/shipment/pos.schema.json +48 -0
- package/dist/cjs/contracts/order/shipment/shipped/index.js +10 -0
- package/dist/cjs/contracts/order/shipment/shipped/outbound.schema.json +34 -0
- package/dist/cjs/contracts/order/shipment/shipped/return.schema.json +43 -0
- package/dist/cjs/contracts/order/shipment/shipped.schema.json +52 -0
- package/dist/cjs/contracts/order/shipment.schema.json +58 -0
- package/dist/cjs/contracts/order.schema.json +231 -0
- package/dist/cjs/contracts/product-category.schema.json +34 -0
- package/dist/cjs/contracts/product-draft.schema.json +782 -0
- package/dist/cjs/contracts/product.schema.json +816 -0
- package/dist/cjs/contracts/total.schema.json +41 -0
- package/dist/cjs/dist-dereferenced/messages/order/delivered.js +1 -1
- package/dist/cjs/dist-dereferenced/messages/order/new.js +1 -1
- package/dist/cjs/dist-dereferenced/messages/order/processed.js +1 -1
- package/dist/cjs/dist-dereferenced/messages/order/return-initiated.js +1 -1
- package/dist/cjs/dist-dereferenced/messages/order/returned.js +1 -1
- package/dist/cjs/dist-dereferenced/messages/order/shipped.js +1 -1
- package/dist/cjs/dist-dereferenced/order/customer.js +1 -1
- package/dist/cjs/dist-dereferenced/order/item.js +1 -1
- package/dist/cjs/dist-dereferenced/order/orderMain.js +1 -1
- package/dist/cjs/dist-dereferenced/order/payment.js +1 -1
- package/dist/cjs/dist-dereferenced/order/shipment/pos/outbound.js +1 -1
- package/dist/cjs/dist-dereferenced/order/shipment/pos/return.js +1 -1
- package/dist/cjs/src-public/bcOrder.js +943 -0
- package/dist/cjs/src-public/dato.js +10 -287
- package/dist/cjs/src-public/index.js +3 -1
- package/dist/cjs/src-public/validate.js +34 -0
- package/dist/types/contracts/index.d.ts +15 -0
- package/dist/types/contracts/messages/index.d.ts +5 -0
- package/dist/types/contracts/messages/order/index.d.ts +6 -0
- package/dist/types/contracts/order/index.d.ts +6 -0
- package/dist/types/contracts/order/shipment/index.d.ts +6 -0
- package/dist/types/contracts/order/shipment/item/index.d.ts +1 -0
- package/dist/types/contracts/order/shipment/pos/index.d.ts +2 -0
- package/dist/types/contracts/order/shipment/shipped/index.d.ts +2 -0
- package/dist/types/dist-dereferenced/messages/order/delivered.d.ts +34 -10
- package/dist/types/dist-dereferenced/messages/order/new.d.ts +34 -10
- package/dist/types/dist-dereferenced/messages/order/processed.d.ts +34 -10
- package/dist/types/dist-dereferenced/messages/order/return-initiated.d.ts +34 -10
- package/dist/types/dist-dereferenced/messages/order/returned.d.ts +34 -10
- package/dist/types/dist-dereferenced/messages/order/shipped.d.ts +34 -10
- package/dist/types/dist-dereferenced/order/customer.d.ts +3 -0
- package/dist/types/dist-dereferenced/order/index.d.ts +34 -10
- package/dist/types/dist-dereferenced/order/item.d.ts +6 -3
- package/dist/types/dist-dereferenced/order/orderMain.d.ts +34 -10
- package/dist/types/dist-dereferenced/order/payment.d.ts +16 -0
- package/dist/types/dist-dereferenced/order/shipment/pos/outbound.d.ts +0 -1
- package/dist/types/dist-dereferenced/order/shipment/pos/return.d.ts +0 -1
- package/dist/types/dist-types/messages/order/delivered.d.ts +13 -5
- package/dist/types/dist-types/messages/order/new.d.ts +13 -5
- package/dist/types/dist-types/messages/order/processed.d.ts +13 -5
- package/dist/types/dist-types/messages/order/return-initiated.d.ts +13 -5
- package/dist/types/dist-types/messages/order/returned.d.ts +13 -5
- package/dist/types/dist-types/messages/order/shipped.d.ts +13 -5
- package/dist/types/dist-types/order/customer.d.ts +1 -0
- package/dist/types/dist-types/order/item.d.ts +4 -3
- package/dist/types/dist-types/order/orderMain.d.ts +13 -5
- package/dist/types/dist-types/order/payment.d.ts +4 -0
- package/dist/types/src-public/bcOrder.d.ts +84 -0
- package/dist/types/src-public/dato.d.ts +1 -34
- package/dist/types/src-public/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,943 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BCOrderHelper = void 0;
|
|
7
|
+
exports.buildLineGrouping = buildLineGrouping;
|
|
8
|
+
exports.transformMasterOrder = transformMasterOrder;
|
|
9
|
+
exports.validateOrder = validateOrder;
|
|
10
|
+
const order_schema_json_1 = __importDefault(require("../contracts/order.schema.json"));
|
|
11
|
+
const runtime_1 = __importDefault(require("./runtime"));
|
|
12
|
+
const validate_1 = __importDefault(require("./validate"));
|
|
13
|
+
const GBP = "GBP";
|
|
14
|
+
function toMinorUnits(amount) {
|
|
15
|
+
if (typeof amount !== "number" || !isFinite(amount))
|
|
16
|
+
return 0;
|
|
17
|
+
return Math.round(amount * 100);
|
|
18
|
+
}
|
|
19
|
+
function money(value) {
|
|
20
|
+
const v = toMinorUnits(value);
|
|
21
|
+
return {
|
|
22
|
+
value: v,
|
|
23
|
+
currency: GBP,
|
|
24
|
+
decimal_places: 2,
|
|
25
|
+
lcy_value: v,
|
|
26
|
+
lcy_currency: GBP,
|
|
27
|
+
lcy_decimal_places: 2,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function moneyFromMinor(minor) {
|
|
31
|
+
return {
|
|
32
|
+
value: Math.round(minor),
|
|
33
|
+
currency: GBP,
|
|
34
|
+
decimal_places: 2,
|
|
35
|
+
lcy_value: Math.round(minor),
|
|
36
|
+
lcy_currency: GBP,
|
|
37
|
+
lcy_decimal_places: 2,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function buildLineGrouping(payload) {
|
|
41
|
+
const group = {};
|
|
42
|
+
const makeKey = (orderNo, lineNo) => `${orderNo}:${lineNo}`;
|
|
43
|
+
(payload.sales || []).forEach((sale) => {
|
|
44
|
+
const orderNo = String(sale?.no || sale?.document_no || payload.no || "");
|
|
45
|
+
(sale.sale_lines || []).forEach((l) => {
|
|
46
|
+
if (String(l?.type || '').toLowerCase() !== 'item')
|
|
47
|
+
return;
|
|
48
|
+
const lineNo = Number(l?.line_no || 0);
|
|
49
|
+
if (!orderNo || !lineNo)
|
|
50
|
+
return;
|
|
51
|
+
const key = makeKey(orderNo, lineNo);
|
|
52
|
+
const entry = group[key] || { order_no: orderNo, order_line_no: lineNo };
|
|
53
|
+
entry.sale = { document_no: orderNo };
|
|
54
|
+
group[key] = entry;
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
(payload.invoices || []).forEach((inv) => {
|
|
58
|
+
const documentNo = String(inv?.no || inv?.document_no || "");
|
|
59
|
+
(inv.invoice_lines || []).forEach((l) => {
|
|
60
|
+
if (String(l?.type || '').toLowerCase() !== 'item')
|
|
61
|
+
return;
|
|
62
|
+
const orderNo = String(l?.order_no || inv?.order_no || payload.no || "");
|
|
63
|
+
const lineNo = Number(l?.order_line_no || l?.line_no || 0);
|
|
64
|
+
if (!orderNo || !lineNo)
|
|
65
|
+
return;
|
|
66
|
+
const key = makeKey(orderNo, lineNo);
|
|
67
|
+
const entry = group[key] || { order_no: orderNo, order_line_no: lineNo };
|
|
68
|
+
entry.invoice = { document_no: documentNo || orderNo };
|
|
69
|
+
group[key] = entry;
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
(payload.shipments || []).forEach((sh) => {
|
|
73
|
+
const documentNo = String(sh?.no || sh?.document_no || "");
|
|
74
|
+
(sh.shipment_lines || []).forEach((l) => {
|
|
75
|
+
const orderNo = String(l?.order_no || payload.no || "");
|
|
76
|
+
const lineNo = Number(l?.order_line_no || l?.line_no || 0);
|
|
77
|
+
if (!orderNo || !lineNo)
|
|
78
|
+
return;
|
|
79
|
+
const key = makeKey(orderNo, lineNo);
|
|
80
|
+
const entry = group[key] || { order_no: orderNo, order_line_no: lineNo };
|
|
81
|
+
entry.shipment = { document_no: documentNo || orderNo };
|
|
82
|
+
group[key] = entry;
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
(payload.credit_memos || []).forEach((cm) => {
|
|
86
|
+
const documentNo = String(cm?.no || cm?.document_no || "");
|
|
87
|
+
(cm.credit_memo_lines || []).forEach((l) => {
|
|
88
|
+
const orderNo = String(l?.order_no || payload.no || "");
|
|
89
|
+
const lineNo = Number(l?.order_line_no || l?.line_no || 0);
|
|
90
|
+
if (!orderNo || !lineNo)
|
|
91
|
+
return;
|
|
92
|
+
const key = makeKey(orderNo, lineNo);
|
|
93
|
+
const entry = group[key] || { order_no: orderNo, order_line_no: lineNo };
|
|
94
|
+
entry.credit_memo = { document_no: documentNo || orderNo };
|
|
95
|
+
group[key] = entry;
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
return group;
|
|
99
|
+
}
|
|
100
|
+
function transformMasterOrder(payload) {
|
|
101
|
+
const erp_order_id = payload.no || payload.system_id || "";
|
|
102
|
+
const sale0 = (payload.sales || [])[0];
|
|
103
|
+
const invoice0 = (payload.invoices || [])[0];
|
|
104
|
+
const ordered_at_candidates = [];
|
|
105
|
+
if (sale0?.system_created_at)
|
|
106
|
+
ordered_at_candidates.push(sale0.system_created_at);
|
|
107
|
+
if (Array.isArray(payload.invoices) && payload.invoices.length > 0) {
|
|
108
|
+
const invDates = payload.invoices.map((i) => i.system_created_at).filter(Boolean);
|
|
109
|
+
ordered_at_candidates.push(...invDates);
|
|
110
|
+
}
|
|
111
|
+
if (payload.ordered_at)
|
|
112
|
+
ordered_at_candidates.push(String(payload.ordered_at));
|
|
113
|
+
if (payload.created_at)
|
|
114
|
+
ordered_at_candidates.push(String(payload.created_at));
|
|
115
|
+
const orderedAtRaw = ordered_at_candidates.length > 0
|
|
116
|
+
? ordered_at_candidates.sort()[0]
|
|
117
|
+
: new Date().toISOString();
|
|
118
|
+
let ordered_at;
|
|
119
|
+
try {
|
|
120
|
+
const dateStr = typeof orderedAtRaw === 'string' && orderedAtRaw.includes('T')
|
|
121
|
+
? orderedAtRaw
|
|
122
|
+
: `${orderedAtRaw}T00:00:00Z`;
|
|
123
|
+
const date = new Date(dateStr);
|
|
124
|
+
if (isNaN(date.getTime())) {
|
|
125
|
+
ordered_at = new Date().toISOString();
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
ordered_at = date.toISOString();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
ordered_at = new Date().toISOString();
|
|
133
|
+
}
|
|
134
|
+
const created_by_raw = sale0?.created_by ?? invoice0?.created_by ?? "";
|
|
135
|
+
const system_created_by = created_by_raw === "SITOO" ? "POS" : created_by_raw === "STOREFRONT" ? "ECOMMERCE" : "SYSTEM";
|
|
136
|
+
const is_gift = Boolean((sale0?.gift_message && String(sale0.gift_message).trim().length > 0) ||
|
|
137
|
+
(payload.invoices?.some((i) => (i.gift_message || "").trim().length > 0)));
|
|
138
|
+
const invoiceLinesForReturn = payload.invoices?.flatMap((i) => i.invoice_lines || []) || [];
|
|
139
|
+
const salesLinesFallbackForReturn = (sale0?.sale_lines || []);
|
|
140
|
+
const sourceLinesForReturn = invoiceLinesForReturn.length > 0 ? invoiceLinesForReturn : salesLinesFallbackForReturn;
|
|
141
|
+
const skuByDescription = {};
|
|
142
|
+
sourceLinesForReturn.forEach((line) => {
|
|
143
|
+
if ((line?.type || "") !== "Item")
|
|
144
|
+
return;
|
|
145
|
+
const itemNo = String(line?.no || "");
|
|
146
|
+
const variant = String(line?.variant_code || "");
|
|
147
|
+
const desc = String(line?.description || "");
|
|
148
|
+
const sku = variant ? `${itemNo}-${variant}` : itemNo;
|
|
149
|
+
if (desc)
|
|
150
|
+
skuByDescription[desc] = sku;
|
|
151
|
+
});
|
|
152
|
+
const isItemish = (type, itemNo, desc) => {
|
|
153
|
+
const t = String(type || '').toLowerCase();
|
|
154
|
+
return t.includes('item') || itemNo.includes('-') || desc.includes(' - ');
|
|
155
|
+
};
|
|
156
|
+
const returnedQtyBySku = {};
|
|
157
|
+
(payload.credit_memos || []).forEach((cm) => {
|
|
158
|
+
(cm.credit_memo_lines || []).forEach((l) => {
|
|
159
|
+
const itemNo = String(l?.item_no || l?.no || '');
|
|
160
|
+
const desc = String(l?.description || '');
|
|
161
|
+
if (itemNo.toUpperCase() === 'X DELCHR' || itemNo.toUpperCase() === 'REFUND')
|
|
162
|
+
return;
|
|
163
|
+
if ((l?.quantity || 0) <= 0)
|
|
164
|
+
return;
|
|
165
|
+
const typeStr = String(l?.type || '').toLowerCase();
|
|
166
|
+
const isTypeItem = typeStr.includes('item');
|
|
167
|
+
if (isTypeItem) {
|
|
168
|
+
const variant = String(l?.variant_code || '');
|
|
169
|
+
const sku = variant ? `${itemNo}-${variant}` : itemNo;
|
|
170
|
+
returnedQtyBySku[sku] = (returnedQtyBySku[sku] || 0) + (l.quantity || 0);
|
|
171
|
+
}
|
|
172
|
+
else if (desc && skuByDescription[desc]) {
|
|
173
|
+
const sku = skuByDescription[desc];
|
|
174
|
+
returnedQtyBySku[sku] = (returnedQtyBySku[sku] || 0) + (l.quantity || 0);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
(payload.sales || []).forEach((sale) => {
|
|
179
|
+
const docTypeRaw = String(sale?.document_type || sale?.documentType || '');
|
|
180
|
+
if (!docTypeRaw.toLowerCase().includes('return'))
|
|
181
|
+
return;
|
|
182
|
+
(sale.sale_lines || []).forEach((l) => {
|
|
183
|
+
const itemNo = String(l?.item_no || l?.no || '');
|
|
184
|
+
const desc = String(l?.description || '');
|
|
185
|
+
if (itemNo.toUpperCase() === 'X DELCHR' || itemNo.toUpperCase() === 'REFUND')
|
|
186
|
+
return;
|
|
187
|
+
if ((l?.quantity || 0) <= 0)
|
|
188
|
+
return;
|
|
189
|
+
if (!isItemish(l?.type, itemNo, desc))
|
|
190
|
+
return;
|
|
191
|
+
const variant = String(l?.variant_code || '');
|
|
192
|
+
const sku = variant ? `${itemNo}-${variant}` : itemNo;
|
|
193
|
+
returnedQtyBySku[sku] = (returnedQtyBySku[sku] || 0) + (l.quantity || 0);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
const invoiceLines = payload.invoices?.flatMap((i) => i.invoice_lines || []) || [];
|
|
197
|
+
const salesLinesFallback = (sale0?.sale_lines || []).map((l) => ({
|
|
198
|
+
type: l.type || "Item",
|
|
199
|
+
no: l.no,
|
|
200
|
+
variant_code: l.variant_code,
|
|
201
|
+
quantity: l.quantity,
|
|
202
|
+
amount: l.amount,
|
|
203
|
+
amount_including_vat: l.amount_including_vat,
|
|
204
|
+
price_update_reason: l.price_update_reason,
|
|
205
|
+
salesforce_promotion_id: l.salesforce_promotion_id,
|
|
206
|
+
line_discount_amount: l.line_discount_amount,
|
|
207
|
+
line_discount_percent: l.line_discount_percent,
|
|
208
|
+
unit_cost_lcy: l.unit_cost_lcy,
|
|
209
|
+
unit_price: l.unit_price,
|
|
210
|
+
vat_percent: l.vat_percent,
|
|
211
|
+
order_line_no: l.line_no,
|
|
212
|
+
}));
|
|
213
|
+
const sourceLines = invoiceLines.length > 0 ? invoiceLines : salesLinesFallback;
|
|
214
|
+
const headerPricesInclVat = invoiceLines.length > 0
|
|
215
|
+
? Boolean(invoice0?.prices_including_vat)
|
|
216
|
+
: Boolean(sale0?.prices_including_vat);
|
|
217
|
+
const shippedQtyBySku = {};
|
|
218
|
+
(payload.shipments || []).forEach((s) => {
|
|
219
|
+
(s.shipment_lines || []).forEach((l) => {
|
|
220
|
+
const itemNo = String(l?.item_no || l?.no || "");
|
|
221
|
+
if (itemNo.toUpperCase() === 'X DELCHR')
|
|
222
|
+
return;
|
|
223
|
+
const desc = String(l?.description || '');
|
|
224
|
+
const looksProduct = itemNo.includes('-') || desc.includes(' - ');
|
|
225
|
+
const qty = Number(l?.quantity || 0);
|
|
226
|
+
if (!looksProduct)
|
|
227
|
+
return;
|
|
228
|
+
if (qty <= 0)
|
|
229
|
+
return;
|
|
230
|
+
const variant = String(l?.variant_code || "");
|
|
231
|
+
const sku = variant ? `${itemNo}-${variant}` : itemNo;
|
|
232
|
+
shippedQtyBySku[sku] = (shippedQtyBySku[sku] || 0) + qty;
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
const shippedRemainingBySku = { ...shippedQtyBySku };
|
|
236
|
+
const returnedTotalBySkuCapped = {};
|
|
237
|
+
Object.keys(returnedQtyBySku).forEach((k) => {
|
|
238
|
+
const totalReturned = Number(returnedQtyBySku[k] || 0);
|
|
239
|
+
const totalShipped = Number(shippedQtyBySku[k] || 0);
|
|
240
|
+
returnedTotalBySkuCapped[k] = totalShipped ? Math.min(totalReturned, totalShipped) : totalReturned;
|
|
241
|
+
});
|
|
242
|
+
const returnedRemainingBySku = { ...returnedTotalBySkuCapped };
|
|
243
|
+
const itemsList = [];
|
|
244
|
+
sourceLines
|
|
245
|
+
.filter((line) => line.type === "Item")
|
|
246
|
+
.forEach((line) => {
|
|
247
|
+
const itemNo = line.no || "";
|
|
248
|
+
const variant = line.variant_code || "";
|
|
249
|
+
const quantity = Number(line.quantity || 0);
|
|
250
|
+
const sku = variant ? `${itemNo}-${variant}` : itemNo;
|
|
251
|
+
const parts = itemNo.split("-");
|
|
252
|
+
const style_id = parts.length >= 2 && parts[0] ? parts[0] : null;
|
|
253
|
+
const colour_id = parts.length >= 2 && parts[1] ? parts[1] : null;
|
|
254
|
+
const product_id = (() => {
|
|
255
|
+
const s = String(itemNo || "");
|
|
256
|
+
if (!s)
|
|
257
|
+
return null;
|
|
258
|
+
const parts = s.split("-");
|
|
259
|
+
return parts.length >= 2 ? `${parts[0]}-${parts[1]}` : s;
|
|
260
|
+
})();
|
|
261
|
+
const netTotal = Number(line.amount || 0);
|
|
262
|
+
const grossTotal = Number(line.amount_including_vat || 0);
|
|
263
|
+
const unitPriceRaw = Number(line.unit_price || 0);
|
|
264
|
+
const vatPercent = Number(line.vat_percent || 0);
|
|
265
|
+
let vatRate = 0;
|
|
266
|
+
if (vatPercent > 0) {
|
|
267
|
+
vatRate = vatPercent / 100;
|
|
268
|
+
}
|
|
269
|
+
else if (netTotal > 0 && grossTotal > 0) {
|
|
270
|
+
vatRate = Math.max(0, (grossTotal - netTotal) / netTotal);
|
|
271
|
+
}
|
|
272
|
+
let unitNet = 0;
|
|
273
|
+
let unitGross = 0;
|
|
274
|
+
if (unitPriceRaw > 0) {
|
|
275
|
+
if (headerPricesInclVat) {
|
|
276
|
+
unitGross = unitPriceRaw;
|
|
277
|
+
unitNet = vatRate > 0 ? (unitGross / (1 + vatRate)) : unitGross;
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
unitNet = unitPriceRaw;
|
|
281
|
+
unitGross = vatRate > 0 ? (unitNet * (1 + vatRate)) : unitNet;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
unitNet = quantity > 0
|
|
286
|
+
? (netTotal > 0
|
|
287
|
+
? netTotal / quantity
|
|
288
|
+
: (grossTotal > 0 && vatRate > 0 ? (grossTotal / (1 + vatRate)) / quantity : 0))
|
|
289
|
+
: 0;
|
|
290
|
+
unitGross = quantity > 0
|
|
291
|
+
? (grossTotal > 0
|
|
292
|
+
? grossTotal / quantity
|
|
293
|
+
: (netTotal > 0 && vatRate > 0 ? (netTotal * (1 + vatRate)) / quantity : 0))
|
|
294
|
+
: 0;
|
|
295
|
+
}
|
|
296
|
+
const discountCode = (line.salesforce_promotion_id && line.salesforce_promotion_id.trim().length > 0)
|
|
297
|
+
? line.salesforce_promotion_id.trim()
|
|
298
|
+
: null;
|
|
299
|
+
let unit_price_status = "rrp";
|
|
300
|
+
const reason = (line.price_update_reason || "").toUpperCase();
|
|
301
|
+
if (reason === "PERM")
|
|
302
|
+
unit_price_status = "perm";
|
|
303
|
+
else if (reason === "RRP")
|
|
304
|
+
unit_price_status = "rrp";
|
|
305
|
+
else if (reason === "POS")
|
|
306
|
+
unit_price_status = "pos";
|
|
307
|
+
const discountPercent = Number(line.line_discount_percent || 0);
|
|
308
|
+
const discountAmountRaw = Number(line.line_discount_amount || 0);
|
|
309
|
+
let discountNet = 0;
|
|
310
|
+
let discountGross = 0;
|
|
311
|
+
if (discountAmountRaw > 0) {
|
|
312
|
+
if (headerPricesInclVat) {
|
|
313
|
+
discountGross = discountAmountRaw;
|
|
314
|
+
discountNet = vatRate > 0 ? (discountGross / (1 + vatRate)) : discountGross;
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
discountNet = discountAmountRaw;
|
|
318
|
+
discountGross = vatRate > 0 ? (discountNet * (1 + vatRate)) : discountNet;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else if (discountPercent > 0) {
|
|
322
|
+
if (netTotal > 0)
|
|
323
|
+
discountNet = (netTotal * discountPercent) / 100;
|
|
324
|
+
if (grossTotal > 0)
|
|
325
|
+
discountGross = (grossTotal * discountPercent) / 100;
|
|
326
|
+
if (grossTotal === 0 && discountNet > 0 && vatRate > 0)
|
|
327
|
+
discountGross = discountNet * (1 + vatRate);
|
|
328
|
+
if (netTotal === 0 && discountGross > 0 && vatRate > 0)
|
|
329
|
+
discountNet = discountGross / (1 + vatRate);
|
|
330
|
+
}
|
|
331
|
+
const take = Math.min(shippedRemainingBySku[sku] || 0, quantity);
|
|
332
|
+
const remaining = quantity - take;
|
|
333
|
+
const baseFields = {
|
|
334
|
+
sku,
|
|
335
|
+
linked_style_id: null,
|
|
336
|
+
colour_id,
|
|
337
|
+
style_id: (style_id || null),
|
|
338
|
+
unit_price_status,
|
|
339
|
+
unit_price_net: money(unitNet),
|
|
340
|
+
unit_price_gross: money(unitGross),
|
|
341
|
+
cost_net: money(line.unit_cost_lcy || 0),
|
|
342
|
+
size_name: (variant || undefined),
|
|
343
|
+
preorder_date: null,
|
|
344
|
+
discount_code: discountCode,
|
|
345
|
+
order_line_no: line.order_line_no || null,
|
|
346
|
+
product_id,
|
|
347
|
+
};
|
|
348
|
+
const pushItem = (qty, returnedQtyForLine) => {
|
|
349
|
+
itemsList.push({
|
|
350
|
+
...baseFields,
|
|
351
|
+
quantity: qty,
|
|
352
|
+
returned_quantity: Math.max(0, Number(returnedQtyForLine || 0)),
|
|
353
|
+
amount_net: money(unitNet * qty),
|
|
354
|
+
amount_gross: money(unitGross * qty),
|
|
355
|
+
discount_amount_net: money(discountNet * (qty / Math.max(1, quantity))),
|
|
356
|
+
discount_amount_gross: money(discountGross * (qty / Math.max(1, quantity))),
|
|
357
|
+
discount_amount_percent: line.line_discount_percent || 0,
|
|
358
|
+
});
|
|
359
|
+
};
|
|
360
|
+
if (take > 0) {
|
|
361
|
+
const retForShipped = Math.min(Math.max(0, returnedRemainingBySku[sku] || 0), take);
|
|
362
|
+
pushItem(take, retForShipped);
|
|
363
|
+
shippedRemainingBySku[sku] = Math.max(0, (shippedRemainingBySku[sku] || 0) - take);
|
|
364
|
+
if (retForShipped > 0) {
|
|
365
|
+
returnedRemainingBySku[sku] = Math.max(0, (returnedRemainingBySku[sku] || 0) - retForShipped);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (remaining > 0) {
|
|
369
|
+
pushItem(remaining, 0);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
const itemsMap = {};
|
|
373
|
+
itemsList.forEach((it) => {
|
|
374
|
+
if (!itemsMap[it.sku])
|
|
375
|
+
itemsMap[it.sku] = [];
|
|
376
|
+
itemsMap[it.sku].push(it);
|
|
377
|
+
});
|
|
378
|
+
const invoicedLineNos = new Set((payload.invoices || []).flatMap((inv) => (inv?.invoice_lines || []).map((l) => Number(l?.order_line_no || -1))));
|
|
379
|
+
let invoicesNetMinor = toMinorUnits((payload.invoices || []).reduce((acc, inv) => acc + (inv.amount_excl_vat || 0), 0));
|
|
380
|
+
let invoicesGrossMinor = toMinorUnits((payload.invoices || []).reduce((acc, inv) => acc + (inv.amount_incl_vat || 0), 0));
|
|
381
|
+
if (invoicesGrossMinor === 0 && invoicedLineNos.size > 0 && sale0?.sale_lines) {
|
|
382
|
+
const invoicedSaleLines = (sale0.sale_lines || []).filter((l) => {
|
|
383
|
+
const lineNo = Number(l?.line_no || -1);
|
|
384
|
+
const itemNo = String(l?.item_no || l?.no || '').toUpperCase();
|
|
385
|
+
if (itemNo === 'X DELCHR')
|
|
386
|
+
return false;
|
|
387
|
+
if (String(l?.type || '').toLowerCase() !== 'item')
|
|
388
|
+
return false;
|
|
389
|
+
return invoicedLineNos.has(lineNo);
|
|
390
|
+
});
|
|
391
|
+
invoicesNetMinor = toMinorUnits(invoicedSaleLines.reduce((acc, l) => acc + (Number(l?.amount || 0)), 0));
|
|
392
|
+
invoicesGrossMinor = toMinorUnits(invoicedSaleLines.reduce((acc, l) => acc + (Number(l?.amount_including_vat || 0)), 0));
|
|
393
|
+
}
|
|
394
|
+
let nonInvSalesNetMinor = 0;
|
|
395
|
+
let nonInvSalesGrossMinor = 0;
|
|
396
|
+
if (sale0 && (sale0.amount_excl_vat || sale0.amount_incl_vat)) {
|
|
397
|
+
const saleNetMinor = toMinorUnits(sale0.amount_excl_vat || 0);
|
|
398
|
+
const saleGrossMinor = toMinorUnits(sale0.amount_incl_vat || 0);
|
|
399
|
+
if (invoicesGrossMinor > 0) {
|
|
400
|
+
nonInvSalesNetMinor = Math.max(0, saleNetMinor - invoicesNetMinor);
|
|
401
|
+
nonInvSalesGrossMinor = Math.max(0, saleGrossMinor - invoicesGrossMinor);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
nonInvSalesNetMinor = saleNetMinor;
|
|
405
|
+
nonInvSalesGrossMinor = saleGrossMinor;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
const nonInvoicedSales = (sale0?.sale_lines || []).filter((l) => {
|
|
410
|
+
const lineNo = Number(l?.line_no || -1);
|
|
411
|
+
const itemNo = String(l?.item_no || l?.no || '').toUpperCase();
|
|
412
|
+
if (itemNo === 'X DELCHR')
|
|
413
|
+
return false;
|
|
414
|
+
if (String(l?.type || '').toLowerCase() !== 'item')
|
|
415
|
+
return false;
|
|
416
|
+
return !invoicedLineNos.has(lineNo);
|
|
417
|
+
});
|
|
418
|
+
nonInvSalesNetMinor = toMinorUnits(nonInvoicedSales.reduce((acc, l) => acc + (Number(l?.amount || 0)), 0));
|
|
419
|
+
nonInvSalesGrossMinor = toMinorUnits(nonInvoicedSales.reduce((acc, l) => acc + (Number(l?.amount_including_vat || 0)), 0));
|
|
420
|
+
}
|
|
421
|
+
const returnsNetMinor = toMinorUnits((payload.credit_memos || []).reduce((acc, cm) => acc + (cm.amount_excl_vat || 0), 0));
|
|
422
|
+
const returnsGrossMinor = toMinorUnits((payload.credit_memos || []).reduce((acc, cm) => acc + (cm.amount_incl_vat || 0), 0));
|
|
423
|
+
const totalNetMinor = invoicesNetMinor + nonInvSalesNetMinor;
|
|
424
|
+
const totalGrossMinor = invoicesGrossMinor + nonInvSalesGrossMinor;
|
|
425
|
+
const total = {
|
|
426
|
+
amount_net: moneyFromMinor(totalNetMinor),
|
|
427
|
+
amount_gross: moneyFromMinor(totalGrossMinor),
|
|
428
|
+
discount_amount_net: money(0),
|
|
429
|
+
discount_amount_gross: money(0),
|
|
430
|
+
discount_amount_percent: 0,
|
|
431
|
+
};
|
|
432
|
+
const return_total = (payload.credit_memos && payload.credit_memos.length > 0)
|
|
433
|
+
? {
|
|
434
|
+
amount_net: moneyFromMinor(returnsNetMinor),
|
|
435
|
+
amount_gross: moneyFromMinor(returnsGrossMinor),
|
|
436
|
+
discount_amount_net: money(0),
|
|
437
|
+
discount_amount_gross: money(0),
|
|
438
|
+
discount_amount_percent: 0,
|
|
439
|
+
}
|
|
440
|
+
: undefined;
|
|
441
|
+
const net_total = {
|
|
442
|
+
amount_net: moneyFromMinor(Math.max(0, totalNetMinor - returnsNetMinor)),
|
|
443
|
+
amount_gross: moneyFromMinor(Math.max(0, totalGrossMinor - returnsGrossMinor)),
|
|
444
|
+
discount_amount_net: money(0),
|
|
445
|
+
discount_amount_gross: money(0),
|
|
446
|
+
discount_amount_percent: 0,
|
|
447
|
+
};
|
|
448
|
+
const normalizeDateTime = (dateMaybe, fallbackIso) => {
|
|
449
|
+
const source = (typeof dateMaybe === "string" && dateMaybe.length > 0)
|
|
450
|
+
? dateMaybe
|
|
451
|
+
: fallbackIso;
|
|
452
|
+
const d = new Date(source.includes("T") ? source : `${source}T00:00:00Z`);
|
|
453
|
+
return isNaN(d.getTime()) ? new Date().toISOString() : d.toISOString();
|
|
454
|
+
};
|
|
455
|
+
const mappedShipments = (payload.shipments || []).map((s) => {
|
|
456
|
+
const rawLines = (s.shipment_lines || [])
|
|
457
|
+
.filter((l) => (l?.quantity ?? 0) > 0)
|
|
458
|
+
.filter((l) => String(l?.item_no || '').toUpperCase() !== 'X DELCHR')
|
|
459
|
+
.filter((l) => {
|
|
460
|
+
const itemNo = String(l?.item_no || '');
|
|
461
|
+
const desc = String(l?.description || '');
|
|
462
|
+
const looksProduct = itemNo.includes('-') || desc.includes(' - ');
|
|
463
|
+
return looksProduct;
|
|
464
|
+
});
|
|
465
|
+
const qtyBySku = new Map();
|
|
466
|
+
rawLines.forEach((l) => {
|
|
467
|
+
const itemNo = String(l?.item_no || '');
|
|
468
|
+
const variant = String(l?.variant_code || '');
|
|
469
|
+
const sku = variant ? `${itemNo}-${variant}` : itemNo;
|
|
470
|
+
const q = Number(l?.quantity || 0);
|
|
471
|
+
qtyBySku.set(sku, (qtyBySku.get(sku) || 0) + q);
|
|
472
|
+
});
|
|
473
|
+
const skus = Array.from(qtyBySku.keys()).sort();
|
|
474
|
+
const shipmentLines = skus.map((sku, idx) => ({
|
|
475
|
+
lineNumber: idx + 1,
|
|
476
|
+
sku,
|
|
477
|
+
quantity: Math.max(1, qtyBySku.get(sku) || 0),
|
|
478
|
+
}));
|
|
479
|
+
const statusVal = shipmentLines.length > 0 ? "SHIPPED" : "PENDING";
|
|
480
|
+
return {
|
|
481
|
+
erp_id: s.no || s.system_id || "",
|
|
482
|
+
shipped_at: normalizeDateTime(s.shipment_date, ordered_at),
|
|
483
|
+
status: statusVal,
|
|
484
|
+
type: statusVal === "SHIPPED" ? "SHIPPED-OUTBOUND" : undefined,
|
|
485
|
+
shipped_by: "TORQUE",
|
|
486
|
+
items: shipmentLines,
|
|
487
|
+
amount_net: money(0),
|
|
488
|
+
amount_gross: money(0),
|
|
489
|
+
discount_amount_net: money(0),
|
|
490
|
+
discount_amount_gross: money(0),
|
|
491
|
+
discount_amount_percent: 0,
|
|
492
|
+
};
|
|
493
|
+
});
|
|
494
|
+
const returnShipments = (payload.credit_memos || [])
|
|
495
|
+
.filter((cm) => {
|
|
496
|
+
const lines = cm?.credit_memo_lines || [];
|
|
497
|
+
return lines.some((l) => {
|
|
498
|
+
const itemNo = String(l?.item_no || l?.no || "");
|
|
499
|
+
const upper = itemNo.toUpperCase();
|
|
500
|
+
if (upper === 'X DELCHR' || upper === 'REFUND')
|
|
501
|
+
return false;
|
|
502
|
+
const looksProduct = itemNo.includes('-');
|
|
503
|
+
return (l?.quantity || 0) > 0 && looksProduct;
|
|
504
|
+
});
|
|
505
|
+
})
|
|
506
|
+
.map((cm, idx) => {
|
|
507
|
+
const rawLines = (cm.credit_memo_lines || [])
|
|
508
|
+
.filter((l) => (l?.quantity || 0) > 0)
|
|
509
|
+
.filter((l) => {
|
|
510
|
+
const itemNo = String(l?.item_no || l?.no || '');
|
|
511
|
+
const upper = itemNo.toUpperCase();
|
|
512
|
+
if (upper === 'X DELCHR' || upper === 'REFUND')
|
|
513
|
+
return false;
|
|
514
|
+
return itemNo.includes('-');
|
|
515
|
+
});
|
|
516
|
+
const qtyBySku = new Map();
|
|
517
|
+
rawLines.forEach((l) => {
|
|
518
|
+
const itemNo = String(l?.item_no || l?.no || '');
|
|
519
|
+
const variant = String(l?.variant_code || '');
|
|
520
|
+
const sku = variant ? `${itemNo}-${variant}` : itemNo;
|
|
521
|
+
const q = Number(l?.quantity || 0);
|
|
522
|
+
qtyBySku.set(sku, (qtyBySku.get(sku) || 0) + q);
|
|
523
|
+
});
|
|
524
|
+
const skus = Array.from(qtyBySku.keys()).sort();
|
|
525
|
+
const lines = skus.map((sku, i) => ({
|
|
526
|
+
lineNumber: i + 1,
|
|
527
|
+
sku,
|
|
528
|
+
quantity: Math.max(1, qtyBySku.get(sku) || 0),
|
|
529
|
+
returnReasonCode: "R1",
|
|
530
|
+
returnReason: "Return",
|
|
531
|
+
}));
|
|
532
|
+
return {
|
|
533
|
+
erp_id: cm.no || cm.system_id || `${erp_order_id}-RET-${idx + 1}`,
|
|
534
|
+
shipped_at: normalizeDateTime(cm.shipment_date, ordered_at),
|
|
535
|
+
status: "RECEIVED",
|
|
536
|
+
type: "SHIPPED-RETURN",
|
|
537
|
+
shipped_by: "CUSTOMER",
|
|
538
|
+
items: lines,
|
|
539
|
+
amount_net: money(0),
|
|
540
|
+
amount_gross: money(0),
|
|
541
|
+
discount_amount_net: money(0),
|
|
542
|
+
discount_amount_gross: money(0),
|
|
543
|
+
discount_amount_percent: 0,
|
|
544
|
+
};
|
|
545
|
+
});
|
|
546
|
+
const shipments = (mappedShipments.length || returnShipments.length)
|
|
547
|
+
? [...mappedShipments, ...returnShipments]
|
|
548
|
+
: [];
|
|
549
|
+
const shipping_method = (payload.shipments?.[0]?.shipping_agent_service_code) || null;
|
|
550
|
+
const merchReturnGrossFromCreditMemos = (payload.credit_memos || []).reduce((acc, cm) => {
|
|
551
|
+
const lines = cm?.credit_memo_lines || [];
|
|
552
|
+
let hasQualifying = false;
|
|
553
|
+
const sum = lines.reduce((s, l) => {
|
|
554
|
+
const itemNo = String(l?.item_no || l?.no || "");
|
|
555
|
+
const desc = String(l?.description || "");
|
|
556
|
+
const looksProduct = itemNo.includes('-') || desc.includes(' - ');
|
|
557
|
+
const typeStr = String(l?.type || "").toLowerCase();
|
|
558
|
+
const isItemish = typeStr.includes('item') || looksProduct;
|
|
559
|
+
const isRefund = itemNo.toUpperCase() === 'REFUND';
|
|
560
|
+
const isDelivery = itemNo.toUpperCase() === 'X DELCHR';
|
|
561
|
+
if ((l?.quantity || 0) > 0 && isItemish && !isRefund && !isDelivery) {
|
|
562
|
+
hasQualifying = true;
|
|
563
|
+
return s + (l?.amount_including_vat || 0);
|
|
564
|
+
}
|
|
565
|
+
return s;
|
|
566
|
+
}, 0);
|
|
567
|
+
const add = sum > 0 ? sum : (hasQualifying ? (cm?.amount_incl_vat || 0) : 0);
|
|
568
|
+
return acc + add;
|
|
569
|
+
}, 0);
|
|
570
|
+
const merchReturnGrossFromReturnSales = (payload.sales || []).reduce((acc, sale) => {
|
|
571
|
+
const docTypeRaw = String(sale?.document_type || sale?.documentType || "");
|
|
572
|
+
const isReturnOrder = docTypeRaw.toLowerCase().includes('return');
|
|
573
|
+
if (!isReturnOrder)
|
|
574
|
+
return acc;
|
|
575
|
+
const lines = sale?.sale_lines || [];
|
|
576
|
+
let hasQualifying = false;
|
|
577
|
+
const sum = lines.reduce((s, l) => {
|
|
578
|
+
const itemNo = String(l?.item_no || l?.no || "");
|
|
579
|
+
const desc = String(l?.description || "");
|
|
580
|
+
const looksProduct = itemNo.includes('-') || desc.includes(' - ');
|
|
581
|
+
const typeStr = String(l?.type || "").toLowerCase();
|
|
582
|
+
const isItemish = typeStr.includes('item') || looksProduct;
|
|
583
|
+
const isRefund = itemNo.toUpperCase() === 'REFUND';
|
|
584
|
+
const isDelivery = itemNo.toUpperCase() === 'X DELCHR';
|
|
585
|
+
if ((l?.quantity || 0) > 0 && isItemish && !isRefund && !isDelivery) {
|
|
586
|
+
hasQualifying = true;
|
|
587
|
+
return s + (l?.amount_including_vat || 0);
|
|
588
|
+
}
|
|
589
|
+
return s;
|
|
590
|
+
}, 0);
|
|
591
|
+
const add = sum > 0 ? sum : (hasQualifying ? (sale?.amount_incl_vat || sale?.amount_including_vat || 0) : 0);
|
|
592
|
+
return acc + add;
|
|
593
|
+
}, 0);
|
|
594
|
+
const merchReturnGross = merchReturnGrossFromCreditMemos + merchReturnGrossFromReturnSales;
|
|
595
|
+
const hasMerchReturn = merchReturnGross > 0;
|
|
596
|
+
const totalOrderGrossMinor = totalGrossMinor;
|
|
597
|
+
const sourceLineMap = new Map();
|
|
598
|
+
sourceLines.forEach((line) => {
|
|
599
|
+
const lineNo = Number(line.order_line_no || line.line_no || -1);
|
|
600
|
+
if (lineNo > 0) {
|
|
601
|
+
sourceLineMap.set(lineNo, line);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
(sale0?.sale_lines || []).forEach((line) => {
|
|
605
|
+
const lineNo = Number(line.line_no || -1);
|
|
606
|
+
if (lineNo > 0) {
|
|
607
|
+
const existing = sourceLineMap.get(lineNo);
|
|
608
|
+
if (!existing || (!existing.amount_including_vat && !existing.amount_incl_vat)) {
|
|
609
|
+
sourceLineMap.set(lineNo, {
|
|
610
|
+
...line,
|
|
611
|
+
order_line_no: line.line_no,
|
|
612
|
+
amount_including_vat: line.amount_including_vat,
|
|
613
|
+
amount_incl_vat: line.amount_including_vat,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
const sourceLineBySku = new Map();
|
|
619
|
+
sourceLines.forEach((line) => {
|
|
620
|
+
if (line.type === "Item") {
|
|
621
|
+
const itemNo = String(line.no || "");
|
|
622
|
+
const variant = String(line.variant_code || "");
|
|
623
|
+
const sku = variant ? `${itemNo}-${variant}` : itemNo;
|
|
624
|
+
if (!sourceLineBySku.has(sku)) {
|
|
625
|
+
sourceLineBySku.set(sku, line);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
(sale0?.sale_lines || []).forEach((line) => {
|
|
630
|
+
if (line.type === "Item") {
|
|
631
|
+
const itemNo = String(line.no || "");
|
|
632
|
+
const variant = String(line.variant_code || "");
|
|
633
|
+
const sku = variant ? `${itemNo}-${variant}` : itemNo;
|
|
634
|
+
const existing = sourceLineBySku.get(sku);
|
|
635
|
+
if (!existing || (!existing.amount_including_vat && !existing.amount_incl_vat)) {
|
|
636
|
+
sourceLineBySku.set(sku, {
|
|
637
|
+
...line,
|
|
638
|
+
order_line_no: line.line_no,
|
|
639
|
+
amount_including_vat: line.amount_including_vat,
|
|
640
|
+
amount_incl_vat: line.amount_including_vat,
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
const shippedGrossDecimal = (payload.shipments || []).reduce((acc, s) => {
|
|
646
|
+
if (s.amount_incl_vat != null && s.amount_incl_vat > 0) {
|
|
647
|
+
return acc + (s.amount_incl_vat || 0);
|
|
648
|
+
}
|
|
649
|
+
const lineAmounts = (s.shipment_lines || []).reduce((lineAcc, l) => {
|
|
650
|
+
const itemNo = String(l?.item_no || l?.no || "").toUpperCase();
|
|
651
|
+
if (itemNo === 'X DELCHR')
|
|
652
|
+
return lineAcc;
|
|
653
|
+
const orderLineNo = Number(l?.order_line_no || -1);
|
|
654
|
+
let sourceLine = orderLineNo > 0 ? sourceLineMap.get(orderLineNo) : null;
|
|
655
|
+
if (!sourceLine) {
|
|
656
|
+
const variant = String(l?.variant_code || "");
|
|
657
|
+
const sku = variant ? `${itemNo}-${variant}` : itemNo;
|
|
658
|
+
sourceLine = sourceLineBySku.get(sku);
|
|
659
|
+
}
|
|
660
|
+
if (sourceLine) {
|
|
661
|
+
const sourceQty = Number(sourceLine.quantity || 0);
|
|
662
|
+
const sourceGross = Number(sourceLine.amount_including_vat || sourceLine.amount_incl_vat || 0);
|
|
663
|
+
const shippedQty = Number(l?.quantity || 0);
|
|
664
|
+
if (sourceQty > 0 && sourceGross > 0) {
|
|
665
|
+
const unitPrice = sourceGross / sourceQty;
|
|
666
|
+
return lineAcc + (unitPrice * shippedQty);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return lineAcc + (l?.amount_including_vat || l?.amount_incl_vat || 0);
|
|
670
|
+
}, 0);
|
|
671
|
+
return acc + lineAmounts;
|
|
672
|
+
}, 0);
|
|
673
|
+
const shippedGrossMinor = toMinorUnits(shippedGrossDecimal);
|
|
674
|
+
let status = "PROCESSED";
|
|
675
|
+
if (hasMerchReturn) {
|
|
676
|
+
const totalOrderGrossDecimal = totalOrderGrossMinor / 100;
|
|
677
|
+
status = totalOrderGrossDecimal > 0 && merchReturnGross >= totalOrderGrossDecimal ? "RETURNED" : "PARTIALLY-RETURNED";
|
|
678
|
+
}
|
|
679
|
+
else if (shippedGrossMinor > 0 && totalOrderGrossMinor > 0) {
|
|
680
|
+
status = shippedGrossMinor >= totalOrderGrossMinor ? "SHIPPED" : "PARTIALLY-SHIPPED";
|
|
681
|
+
}
|
|
682
|
+
const sfcc_order_id = (sale0?.created_by === "STOREFRONT")
|
|
683
|
+
? (sale0?.no || null)
|
|
684
|
+
: ((invoice0?.created_by === "STOREFRONT") ? (invoice0?.order_no || null) : null);
|
|
685
|
+
const billing_from = invoice0 || sale0 || null;
|
|
686
|
+
const shipping_from = invoice0 || sale0 || null;
|
|
687
|
+
const mapPaymentMethod = (code, card) => {
|
|
688
|
+
const c = String(code || '').toUpperCase();
|
|
689
|
+
if (/(GVOUCHER|VOUCHER|GIFT|EGIFT)/.test(c) || String(card?.service_type || '').toUpperCase().includes('VOUCHER'))
|
|
690
|
+
return 'GIFT-CARD';
|
|
691
|
+
if (c.includes('ADYEN') && !card?.card_number)
|
|
692
|
+
return 'APPLE-PAY';
|
|
693
|
+
if (c.includes('APPLE') || String(card?.service_type || '').toUpperCase().includes('APPLE'))
|
|
694
|
+
return 'APPLE-PAY';
|
|
695
|
+
if (card?.transaction_id || card?.card_number)
|
|
696
|
+
return 'CREDIT-CARD';
|
|
697
|
+
return 'OTHER';
|
|
698
|
+
};
|
|
699
|
+
const journeyFromCreatedBy = (createdBy) => {
|
|
700
|
+
const v = String(createdBy || '').toUpperCase();
|
|
701
|
+
if (v === 'SITOO')
|
|
702
|
+
return 'POS';
|
|
703
|
+
if (v === 'STOREFRONT')
|
|
704
|
+
return 'CHECKOUT';
|
|
705
|
+
if (v === 'CENTRA')
|
|
706
|
+
return 'CHECKOUT';
|
|
707
|
+
return 'SYSTEM';
|
|
708
|
+
};
|
|
709
|
+
const cardPays = (payload.card_payments || []).map((c) => ({
|
|
710
|
+
created_at: c.created_at,
|
|
711
|
+
amount: c.amount,
|
|
712
|
+
type: 'Payment',
|
|
713
|
+
payment_method_code: String(c.service_type || ''),
|
|
714
|
+
transaction_id: c.transaction_id,
|
|
715
|
+
card_number: c.card_number,
|
|
716
|
+
psp_reference: c.psp_reference,
|
|
717
|
+
created_by: invoice0?.created_by || sale0?.created_by,
|
|
718
|
+
}));
|
|
719
|
+
const normalPays = (payload.payments || []).map((p) => ({
|
|
720
|
+
created_at: p.created_at,
|
|
721
|
+
amount: p.amount,
|
|
722
|
+
type: p.type,
|
|
723
|
+
payment_method_code: p.payment_method_code,
|
|
724
|
+
transaction_id: p.ledger_entry_no ? String(p.ledger_entry_no) : undefined,
|
|
725
|
+
card_number: undefined,
|
|
726
|
+
psp_reference: p.psp_reference,
|
|
727
|
+
created_by: p.created_by || invoice0?.created_by || sale0?.created_by,
|
|
728
|
+
}));
|
|
729
|
+
const flat = [...cardPays, ...normalPays];
|
|
730
|
+
const groupsMap = new Map();
|
|
731
|
+
flat.forEach((p, idx) => {
|
|
732
|
+
const isRefund = String(p.type || '').toLowerCase() === 'refund';
|
|
733
|
+
const logicalType = isRefund ? 'REFUND' : 'CHARGE';
|
|
734
|
+
const keyBase = p.transaction_id || `${erp_order_id}-${logicalType}-${idx + 1}`;
|
|
735
|
+
const key = `${logicalType}:${keyBase}`;
|
|
736
|
+
const g = groupsMap.get(key) || { entries: [], type: logicalType, key };
|
|
737
|
+
g.entries.push(p);
|
|
738
|
+
groupsMap.set(key, g);
|
|
739
|
+
});
|
|
740
|
+
const payments = Array.from(groupsMap.values()).map((g, i) => {
|
|
741
|
+
const created_at = g.entries
|
|
742
|
+
.map(e => e.created_at)
|
|
743
|
+
.filter(Boolean)
|
|
744
|
+
.map(d => new Date(d).toISOString())
|
|
745
|
+
.sort()[0] || ordered_at;
|
|
746
|
+
const amount = Math.max(...g.entries.map(e => Number(e.amount || 0)), 0);
|
|
747
|
+
const any = g.entries[0] || {};
|
|
748
|
+
const pm = mapPaymentMethod(any.payment_method_code, any);
|
|
749
|
+
const transaction_id = (any.transaction_id) || `${erp_order_id}-PAY-${i + 1}`;
|
|
750
|
+
const psp_reference = any.psp_reference || transaction_id;
|
|
751
|
+
const created_by = 'CUSTOMER';
|
|
752
|
+
const journey_label = journeyFromCreatedBy(invoice0?.created_by || sale0?.created_by);
|
|
753
|
+
return {
|
|
754
|
+
type: g.type,
|
|
755
|
+
created_at,
|
|
756
|
+
payment_method: pm,
|
|
757
|
+
transaction_id,
|
|
758
|
+
card_number: any.card_number || null,
|
|
759
|
+
card_holder: null,
|
|
760
|
+
card_token: null,
|
|
761
|
+
expiration_month: null,
|
|
762
|
+
expiration_year: null,
|
|
763
|
+
journey_label,
|
|
764
|
+
psp_reference,
|
|
765
|
+
card_brand: null,
|
|
766
|
+
status: 'CAPTURED',
|
|
767
|
+
amount: money(amount),
|
|
768
|
+
created_by,
|
|
769
|
+
};
|
|
770
|
+
});
|
|
771
|
+
const shipmentsWithItems = shipments.filter((s) => s.items && s.items.length > 0);
|
|
772
|
+
shipmentsWithItems.forEach((shipment) => {
|
|
773
|
+
let shipmentNetMinor = 0;
|
|
774
|
+
let shipmentGrossMinor = 0;
|
|
775
|
+
let shipmentDiscountNetMinor = 0;
|
|
776
|
+
let shipmentDiscountGrossMinor = 0;
|
|
777
|
+
let maxDiscountPercent = 0;
|
|
778
|
+
(shipment.items || []).forEach((shipItem) => {
|
|
779
|
+
const sku = shipItem.sku;
|
|
780
|
+
const shipQty = Number(shipItem.quantity || 0);
|
|
781
|
+
const matchingItems = itemsList.filter((item) => item.sku === sku);
|
|
782
|
+
let remainingQty = shipQty;
|
|
783
|
+
for (const item of matchingItems) {
|
|
784
|
+
if (remainingQty <= 0)
|
|
785
|
+
break;
|
|
786
|
+
const itemQty = Number(item.quantity || 0);
|
|
787
|
+
if (itemQty > 0) {
|
|
788
|
+
const takeQty = Math.min(remainingQty, itemQty);
|
|
789
|
+
const ratio = takeQty / itemQty;
|
|
790
|
+
shipmentNetMinor += item.amount_net.value * ratio;
|
|
791
|
+
shipmentGrossMinor += item.amount_gross.value * ratio;
|
|
792
|
+
shipmentDiscountNetMinor += item.discount_amount_net.value * ratio;
|
|
793
|
+
shipmentDiscountGrossMinor += item.discount_amount_gross.value * ratio;
|
|
794
|
+
maxDiscountPercent = Math.max(maxDiscountPercent, item.discount_amount_percent || 0);
|
|
795
|
+
remainingQty -= takeQty;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
shipment.amount_net = moneyFromMinor(shipmentNetMinor);
|
|
800
|
+
shipment.amount_gross = moneyFromMinor(shipmentGrossMinor);
|
|
801
|
+
shipment.discount_amount_net = moneyFromMinor(shipmentDiscountNetMinor);
|
|
802
|
+
shipment.discount_amount_gross = moneyFromMinor(shipmentDiscountGrossMinor);
|
|
803
|
+
shipment.discount_amount_percent = maxDiscountPercent;
|
|
804
|
+
});
|
|
805
|
+
let totalDiscountNetMinor = 0;
|
|
806
|
+
let totalDiscountGrossMinor = 0;
|
|
807
|
+
let maxTotalDiscountPercent = 0;
|
|
808
|
+
itemsList.forEach((item) => {
|
|
809
|
+
totalDiscountNetMinor += item.discount_amount_net.value;
|
|
810
|
+
totalDiscountGrossMinor += item.discount_amount_gross.value;
|
|
811
|
+
maxTotalDiscountPercent = Math.max(maxTotalDiscountPercent, item.discount_amount_percent || 0);
|
|
812
|
+
});
|
|
813
|
+
total.discount_amount_net = moneyFromMinor(totalDiscountNetMinor);
|
|
814
|
+
total.discount_amount_gross = moneyFromMinor(totalDiscountGrossMinor);
|
|
815
|
+
total.discount_amount_percent = maxTotalDiscountPercent;
|
|
816
|
+
net_total.discount_amount_net = moneyFromMinor(totalDiscountNetMinor);
|
|
817
|
+
net_total.discount_amount_gross = moneyFromMinor(totalDiscountGrossMinor);
|
|
818
|
+
net_total.discount_amount_percent = maxTotalDiscountPercent;
|
|
819
|
+
const result = {
|
|
820
|
+
status,
|
|
821
|
+
erp_order_id,
|
|
822
|
+
centra_order_id: null,
|
|
823
|
+
sfcc_order_id,
|
|
824
|
+
globale_order_id: (() => {
|
|
825
|
+
const v = (invoice0?.globale_order_id ?? sale0?.globale_order_id ?? null);
|
|
826
|
+
if (typeof v !== 'string')
|
|
827
|
+
return v;
|
|
828
|
+
const s = v.trim();
|
|
829
|
+
return s.length > 0 ? s : null;
|
|
830
|
+
})(),
|
|
831
|
+
sitoo_order_id: null,
|
|
832
|
+
system_created_by,
|
|
833
|
+
ordered_at,
|
|
834
|
+
is_gift,
|
|
835
|
+
is_pos: created_by_raw === "SITOO",
|
|
836
|
+
billing_address: billing_from ? (() => {
|
|
837
|
+
const billToName = billing_from.bill_to_name || "";
|
|
838
|
+
const firstName = billing_from.bill_to_first_name || (billToName ? billToName.split(' ')[0] : "");
|
|
839
|
+
const lastName = billing_from.bill_to_surname || (billToName ? billToName.split(' ').slice(1).join(' ') : "");
|
|
840
|
+
return {
|
|
841
|
+
first_name: firstName,
|
|
842
|
+
last_name: lastName,
|
|
843
|
+
address1: billing_from.bill_to_address || "",
|
|
844
|
+
address2: billing_from.bill_to_address_2 || null,
|
|
845
|
+
city: billing_from.bill_to_city || "",
|
|
846
|
+
post_code: billing_from.bill_to_post_code || null,
|
|
847
|
+
country_code: billing_from.bill_to_country_region_code || "",
|
|
848
|
+
phone: billing_from.sell_to_phone_no || null,
|
|
849
|
+
created_at: (typeof billing_from.system_created_at === 'string' && billing_from.system_created_at) || undefined,
|
|
850
|
+
};
|
|
851
|
+
})() : null,
|
|
852
|
+
shipping_address: shipping_from ? (() => {
|
|
853
|
+
const shipToName = shipping_from.ship_to_name || "";
|
|
854
|
+
const firstName = shipping_from.ship_to_first_name || (shipToName ? shipToName.split(' ')[0] : "");
|
|
855
|
+
const lastName = shipping_from.ship_to_surname || (shipToName ? shipToName.split(' ').slice(1).join(' ') : "");
|
|
856
|
+
return {
|
|
857
|
+
first_name: firstName,
|
|
858
|
+
last_name: lastName,
|
|
859
|
+
address1: shipping_from.ship_to_address || "",
|
|
860
|
+
address2: shipping_from.ship_to_address_2 || null,
|
|
861
|
+
city: shipping_from.ship_to_city || "",
|
|
862
|
+
post_code: shipping_from.ship_to_post_code || null,
|
|
863
|
+
country_code: shipping_from.ship_to_country_region_code || "",
|
|
864
|
+
phone: shipping_from.ship_to_phone_no || null,
|
|
865
|
+
created_at: (typeof shipping_from.system_created_at === 'string' && shipping_from.system_created_at) || undefined,
|
|
866
|
+
};
|
|
867
|
+
})() : null,
|
|
868
|
+
customer: (() => {
|
|
869
|
+
const normalizeEmailOrNull = (v) => {
|
|
870
|
+
const s = String(v || "").trim().toLowerCase();
|
|
871
|
+
if (!s)
|
|
872
|
+
return null;
|
|
873
|
+
return /.+@.+\..+/.test(s) ? s : null;
|
|
874
|
+
};
|
|
875
|
+
const c = payload.customer;
|
|
876
|
+
if (!c)
|
|
877
|
+
return undefined;
|
|
878
|
+
const mapped = {
|
|
879
|
+
first_name: c.first_name || "",
|
|
880
|
+
last_name: c.surname || "",
|
|
881
|
+
email: normalizeEmailOrNull(c.email),
|
|
882
|
+
};
|
|
883
|
+
if (c.no || payload.customer_no)
|
|
884
|
+
mapped.bc_customer_id = String(c.no || payload.customer_no);
|
|
885
|
+
const buildAddr = (src) => src ? {
|
|
886
|
+
first_name: src.bill_to_first_name || src.ship_to_first_name || "",
|
|
887
|
+
last_name: src.bill_to_surname || src.ship_to_surname || "",
|
|
888
|
+
address1: (src.bill_to_address || src.ship_to_address || ""),
|
|
889
|
+
address2: (src.bill_to_address_2 || src.ship_to_address_2 || null),
|
|
890
|
+
city: (src.bill_to_city || src.ship_to_city || ""),
|
|
891
|
+
post_code: (src.bill_to_post_code || src.ship_to_post_code || null),
|
|
892
|
+
country_code: (src.bill_to_country_region_code || src.ship_to_country_region_code || ""),
|
|
893
|
+
phone: (src.sell_to_phone_no || src.ship_to_phone_no || null),
|
|
894
|
+
created_at: (typeof src.system_created_at === 'string' && src.system_created_at) || undefined,
|
|
895
|
+
} : null;
|
|
896
|
+
const custBilling = buildAddr(billing_from);
|
|
897
|
+
const custShipping = buildAddr(shipping_from);
|
|
898
|
+
const addresses = [custBilling, custShipping].filter(Boolean);
|
|
899
|
+
if (addresses.length > 0)
|
|
900
|
+
mapped.addresses = addresses;
|
|
901
|
+
if (custShipping)
|
|
902
|
+
mapped.default_shipping_address = custShipping;
|
|
903
|
+
const addrPhone = (custShipping?.phone || custBilling?.phone || null);
|
|
904
|
+
const custPhone = (typeof c.phone_no === 'string' && c.phone_no.trim().length > 0) ? c.phone_no : null;
|
|
905
|
+
mapped.phone = custPhone || addrPhone || null;
|
|
906
|
+
const chosenAddr = custShipping || custBilling;
|
|
907
|
+
const custCreatedAt = (typeof c.system_created_at === 'string' && c.system_created_at) || null;
|
|
908
|
+
const addrCreatedAt = (chosenAddr && typeof chosenAddr.created_at === 'string') ? chosenAddr.created_at : null;
|
|
909
|
+
if (custCreatedAt)
|
|
910
|
+
mapped.registered_at = custCreatedAt;
|
|
911
|
+
else if (addrCreatedAt)
|
|
912
|
+
mapped.registered_at = addrCreatedAt;
|
|
913
|
+
return mapped;
|
|
914
|
+
})(),
|
|
915
|
+
items: itemsList,
|
|
916
|
+
total,
|
|
917
|
+
return_total,
|
|
918
|
+
net_total,
|
|
919
|
+
shipping_method,
|
|
920
|
+
payments,
|
|
921
|
+
};
|
|
922
|
+
if (shipmentsWithItems.length > 0) {
|
|
923
|
+
result.shipments = shipmentsWithItems;
|
|
924
|
+
}
|
|
925
|
+
return result;
|
|
926
|
+
}
|
|
927
|
+
function validateOrder(order) {
|
|
928
|
+
const log = { log: () => { }, error: () => { } };
|
|
929
|
+
(0, validate_1.default)(order, order_schema_json_1.default, { log });
|
|
930
|
+
}
|
|
931
|
+
class BCOrderHelper extends runtime_1.default {
|
|
932
|
+
constructor(opts) {
|
|
933
|
+
super(opts);
|
|
934
|
+
}
|
|
935
|
+
transformMasterOrder(payload) {
|
|
936
|
+
return transformMasterOrder(payload);
|
|
937
|
+
}
|
|
938
|
+
validateOrder(order) {
|
|
939
|
+
return validateOrder(order);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
exports.BCOrderHelper = BCOrderHelper;
|
|
943
|
+
exports.default = BCOrderHelper;
|