@infuro/cms-core 1.0.14 → 1.0.15
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/admin.cjs +232 -4
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.js +232 -4
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +390 -58
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.js +390 -58
- package/dist/api.js.map +1 -1
- package/dist/{index-Be8NLxu-.d.cts → index-BQnqJ7EO.d.cts} +2 -0
- package/dist/{index-CjBf9dAb.d.ts → index-BiagwMjV.d.ts} +2 -0
- package/dist/index.cjs +498 -198
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +498 -198
- package/dist/index.js.map +1 -1
- package/dist/migrations/1775000000000-ProductUomTypeOrderItemSnapshots.ts +29 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -38,6 +38,153 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
38
38
|
return result;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
// src/plugins/erp/erp-queue.ts
|
|
42
|
+
async function queueErp(cms, payload) {
|
|
43
|
+
const queue = cms.getPlugin("queue");
|
|
44
|
+
if (!queue) return;
|
|
45
|
+
await queue.add(ERP_QUEUE_NAME, payload);
|
|
46
|
+
}
|
|
47
|
+
function registerErpQueueProcessor(cms) {
|
|
48
|
+
const queue = cms.getPlugin("queue");
|
|
49
|
+
if (!queue) return;
|
|
50
|
+
queue.registerProcessor(ERP_QUEUE_NAME, async (data) => {
|
|
51
|
+
const erp = cms.getPlugin("erp");
|
|
52
|
+
if (!erp) return;
|
|
53
|
+
const payload = data;
|
|
54
|
+
if (payload.kind === "lead") {
|
|
55
|
+
await erp.submission.submitContact(payload.contact);
|
|
56
|
+
} else if (payload.kind === "formOpportunity") {
|
|
57
|
+
await erp.submission.submitFormOpportunity(payload.contact);
|
|
58
|
+
} else if (payload.kind === "createContact") {
|
|
59
|
+
await erp.submission.submitCreateContact(payload.contact);
|
|
60
|
+
} else if (payload.kind === "order") {
|
|
61
|
+
await erp.submission.submitOrder(payload.order);
|
|
62
|
+
} else if (payload.kind === "productUpsert") {
|
|
63
|
+
await erp.submission.submitProductUpsert(payload.product);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
var ERP_QUEUE_NAME;
|
|
68
|
+
var init_erp_queue = __esm({
|
|
69
|
+
"src/plugins/erp/erp-queue.ts"() {
|
|
70
|
+
"use strict";
|
|
71
|
+
ERP_QUEUE_NAME = "erp";
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// src/plugins/erp/paid-order-erp.ts
|
|
76
|
+
var paid_order_erp_exports = {};
|
|
77
|
+
__export(paid_order_erp_exports, {
|
|
78
|
+
queueErpPaidOrderForOrderId: () => queueErpPaidOrderForOrderId
|
|
79
|
+
});
|
|
80
|
+
function roundMoney(major) {
|
|
81
|
+
return Math.round(major * 100) / 100;
|
|
82
|
+
}
|
|
83
|
+
function addressToWebhookDto(a) {
|
|
84
|
+
if (!a) return {};
|
|
85
|
+
return {
|
|
86
|
+
line1: a.line1 ?? "",
|
|
87
|
+
line2: a.line2 ?? "",
|
|
88
|
+
city: a.city ?? "",
|
|
89
|
+
state: a.state ?? "",
|
|
90
|
+
postalCode: a.postalCode ?? "",
|
|
91
|
+
country: a.country ?? ""
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function orderStatusLabel(status) {
|
|
95
|
+
const s = (status || "").toLowerCase();
|
|
96
|
+
if (s === "confirmed") return "Confirmed";
|
|
97
|
+
if (s === "pending") return "Pending";
|
|
98
|
+
if (!status) return "Pending";
|
|
99
|
+
return status.charAt(0).toUpperCase() + status.slice(1);
|
|
100
|
+
}
|
|
101
|
+
function paymentRowToWebhookDto(p, amountMajorOverride) {
|
|
102
|
+
const currency = String(p.currency || "INR");
|
|
103
|
+
const amountMajor = amountMajorOverride != null && Number.isFinite(amountMajorOverride) ? amountMajorOverride : Number(p.amount);
|
|
104
|
+
const meta = { ...p.metadata || {} };
|
|
105
|
+
delete meta.amount;
|
|
106
|
+
delete meta.currency;
|
|
107
|
+
return {
|
|
108
|
+
id: String(p.externalReference || `payment_${p.id}`),
|
|
109
|
+
amount: roundMoney(amountMajor),
|
|
110
|
+
currency_code: currency,
|
|
111
|
+
captured_at: p.paidAt ? new Date(p.paidAt).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
112
|
+
provider_id: String(p.method || "unknown"),
|
|
113
|
+
data: { status: "captured", ...meta }
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId) {
|
|
117
|
+
try {
|
|
118
|
+
const configRepo = dataSource.getRepository(entityMap.configs);
|
|
119
|
+
const cfgRows = await configRepo.find({ where: { settings: "erp", deleted: false } });
|
|
120
|
+
for (const row of cfgRows) {
|
|
121
|
+
const r = row;
|
|
122
|
+
if (r.key === "enabled" && r.value === "false") return;
|
|
123
|
+
}
|
|
124
|
+
if (!cms.getPlugin("erp")) return;
|
|
125
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
126
|
+
const ord = await orderRepo.findOne({
|
|
127
|
+
where: { id: orderId },
|
|
128
|
+
relations: ["items", "items.product", "contact", "billingAddress", "shippingAddress", "payments"]
|
|
129
|
+
});
|
|
130
|
+
if (!ord) return;
|
|
131
|
+
const o = ord;
|
|
132
|
+
const okKind = o.orderKind === void 0 || o.orderKind === null || o.orderKind === "sale";
|
|
133
|
+
if (!okKind) return;
|
|
134
|
+
const rawPayments = o.payments ?? [];
|
|
135
|
+
const completedPayments = rawPayments.filter((pay) => pay.status === "completed" && pay.deleted !== true);
|
|
136
|
+
if (!completedPayments.length) return;
|
|
137
|
+
const rawItems = o.items ?? [];
|
|
138
|
+
const lines = rawItems.filter((it) => it.product).map((it) => {
|
|
139
|
+
const p = it.product;
|
|
140
|
+
const sku = p.sku || `SKU-${p.id}`;
|
|
141
|
+
const itemType = typeof it.productType === "string" && it.productType.trim() ? String(it.productType).trim() : p.type === "service" ? "service" : "product";
|
|
142
|
+
return {
|
|
143
|
+
sku,
|
|
144
|
+
quantity: Number(it.quantity) || 1,
|
|
145
|
+
unitPrice: Number(it.unitPrice),
|
|
146
|
+
title: p.name || sku,
|
|
147
|
+
discount: 0,
|
|
148
|
+
tax: Number(it.tax) || 0,
|
|
149
|
+
uom: (typeof it.uom === "string" && it.uom.trim() ? it.uom : p.uom) || void 0,
|
|
150
|
+
tax_code: typeof it.taxCode === "string" && it.taxCode.trim() ? String(it.taxCode).trim() : void 0,
|
|
151
|
+
hsn_number: (typeof it.hsn === "string" && it.hsn.trim() ? it.hsn : p.hsn) || void 0,
|
|
152
|
+
type: itemType
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
if (!lines.length) return;
|
|
156
|
+
const contact = o.contact;
|
|
157
|
+
const orderTotalMajor = Number(o.total);
|
|
158
|
+
const paymentDtos = completedPayments.length === 1 && Number.isFinite(orderTotalMajor) ? [paymentRowToWebhookDto(completedPayments[0], orderTotalMajor)] : completedPayments.map((pay) => paymentRowToWebhookDto(pay));
|
|
159
|
+
const baseMeta = o.metadata && typeof o.metadata === "object" && !Array.isArray(o.metadata) ? { ...o.metadata } : {};
|
|
160
|
+
const orderDto = {
|
|
161
|
+
platformType: "website",
|
|
162
|
+
platformOrderId: String(o.orderNumber),
|
|
163
|
+
platformOrderNumber: String(o.orderNumber),
|
|
164
|
+
order_date: o.createdAt ? new Date(o.createdAt).toISOString() : void 0,
|
|
165
|
+
status: orderStatusLabel(o.status),
|
|
166
|
+
customer: {
|
|
167
|
+
name: contact?.name || "",
|
|
168
|
+
email: contact?.email || "",
|
|
169
|
+
phone: contact?.phone || ""
|
|
170
|
+
},
|
|
171
|
+
shippingAddress: addressToWebhookDto(o.shippingAddress),
|
|
172
|
+
billingAddress: addressToWebhookDto(o.billingAddress),
|
|
173
|
+
items: lines,
|
|
174
|
+
payments: paymentDtos,
|
|
175
|
+
metadata: { ...baseMeta, source: "storefront" }
|
|
176
|
+
};
|
|
177
|
+
await queueErp(cms, { kind: "order", order: orderDto });
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
var init_paid_order_erp = __esm({
|
|
182
|
+
"src/plugins/erp/paid-order-erp.ts"() {
|
|
183
|
+
"use strict";
|
|
184
|
+
init_erp_queue();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
41
188
|
// src/plugins/erp/erp-response-map.ts
|
|
42
189
|
function pickString(o, keys) {
|
|
43
190
|
for (const k of keys) {
|
|
@@ -114,6 +261,10 @@ var init_erp_response_map = __esm({
|
|
|
114
261
|
});
|
|
115
262
|
|
|
116
263
|
// src/plugins/erp/erp-config-enabled.ts
|
|
264
|
+
var erp_config_enabled_exports = {};
|
|
265
|
+
__export(erp_config_enabled_exports, {
|
|
266
|
+
isErpIntegrationEnabled: () => isErpIntegrationEnabled
|
|
267
|
+
});
|
|
117
268
|
async function isErpIntegrationEnabled(cms, dataSource, entityMap) {
|
|
118
269
|
if (!cms.getPlugin("erp")) return false;
|
|
119
270
|
const configRepo = dataSource.getRepository(entityMap.configs);
|
|
@@ -228,14 +379,31 @@ async function queueEmail(cms, payload) {
|
|
|
228
379
|
}
|
|
229
380
|
}
|
|
230
381
|
async function queueOrderPlacedEmails(cms, payload) {
|
|
231
|
-
const {
|
|
382
|
+
const {
|
|
383
|
+
orderNumber,
|
|
384
|
+
total,
|
|
385
|
+
subtotal,
|
|
386
|
+
tax,
|
|
387
|
+
currency,
|
|
388
|
+
customerName,
|
|
389
|
+
customerEmail,
|
|
390
|
+
salesTeamEmails,
|
|
391
|
+
companyDetails,
|
|
392
|
+
lineItems,
|
|
393
|
+
billingAddress,
|
|
394
|
+
shippingAddress
|
|
395
|
+
} = payload;
|
|
232
396
|
const base = {
|
|
233
397
|
orderNumber,
|
|
234
398
|
total: total != null ? String(total) : void 0,
|
|
399
|
+
subtotal: subtotal != null ? String(subtotal) : void 0,
|
|
400
|
+
tax: tax != null ? String(tax) : void 0,
|
|
235
401
|
currency,
|
|
236
402
|
customerName,
|
|
237
403
|
companyDetails: companyDetails ?? {},
|
|
238
|
-
lineItems: lineItems ?? []
|
|
404
|
+
lineItems: lineItems ?? [],
|
|
405
|
+
billingAddress,
|
|
406
|
+
shippingAddress
|
|
239
407
|
};
|
|
240
408
|
const customerLower = customerEmail?.trim().toLowerCase() ?? "";
|
|
241
409
|
const jobs = [];
|
|
@@ -881,149 +1049,12 @@ var ERPSubmissionService = class {
|
|
|
881
1049
|
}
|
|
882
1050
|
};
|
|
883
1051
|
|
|
884
|
-
// src/plugins/erp/
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
const queue = cms.getPlugin("queue");
|
|
888
|
-
if (!queue) return;
|
|
889
|
-
await queue.add(ERP_QUEUE_NAME, payload);
|
|
890
|
-
}
|
|
891
|
-
function registerErpQueueProcessor(cms) {
|
|
892
|
-
const queue = cms.getPlugin("queue");
|
|
893
|
-
if (!queue) return;
|
|
894
|
-
queue.registerProcessor(ERP_QUEUE_NAME, async (data) => {
|
|
895
|
-
const erp = cms.getPlugin("erp");
|
|
896
|
-
if (!erp) return;
|
|
897
|
-
const payload = data;
|
|
898
|
-
if (payload.kind === "lead") {
|
|
899
|
-
await erp.submission.submitContact(payload.contact);
|
|
900
|
-
} else if (payload.kind === "formOpportunity") {
|
|
901
|
-
await erp.submission.submitFormOpportunity(payload.contact);
|
|
902
|
-
} else if (payload.kind === "createContact") {
|
|
903
|
-
await erp.submission.submitCreateContact(payload.contact);
|
|
904
|
-
} else if (payload.kind === "order") {
|
|
905
|
-
await erp.submission.submitOrder(payload.order);
|
|
906
|
-
} else if (payload.kind === "productUpsert") {
|
|
907
|
-
await erp.submission.submitProductUpsert(payload.product);
|
|
908
|
-
}
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
// src/plugins/erp/paid-order-erp.ts
|
|
913
|
-
function amountToMinorUnits(major, currency) {
|
|
914
|
-
const c = currency.toUpperCase();
|
|
915
|
-
const zeroDecimal = /* @__PURE__ */ new Set([
|
|
916
|
-
"BIF",
|
|
917
|
-
"CLP",
|
|
918
|
-
"DJF",
|
|
919
|
-
"GNF",
|
|
920
|
-
"JPY",
|
|
921
|
-
"KMF",
|
|
922
|
-
"KRW",
|
|
923
|
-
"MGA",
|
|
924
|
-
"PYG",
|
|
925
|
-
"RWF",
|
|
926
|
-
"UGX",
|
|
927
|
-
"VND",
|
|
928
|
-
"VUV",
|
|
929
|
-
"XAF",
|
|
930
|
-
"XOF",
|
|
931
|
-
"XPF"
|
|
932
|
-
]);
|
|
933
|
-
if (zeroDecimal.has(c)) return Math.round(major);
|
|
934
|
-
return Math.round(major * 100);
|
|
935
|
-
}
|
|
936
|
-
function addressToWebhookDto(a) {
|
|
937
|
-
if (!a) return {};
|
|
938
|
-
return {
|
|
939
|
-
line1: a.line1 ?? "",
|
|
940
|
-
line2: a.line2 ?? "",
|
|
941
|
-
city: a.city ?? "",
|
|
942
|
-
state: a.state ?? "",
|
|
943
|
-
postalCode: a.postalCode ?? "",
|
|
944
|
-
country: a.country ?? ""
|
|
945
|
-
};
|
|
946
|
-
}
|
|
947
|
-
function orderStatusLabel(status) {
|
|
948
|
-
const s = (status || "").toLowerCase();
|
|
949
|
-
if (s === "confirmed") return "Confirmed";
|
|
950
|
-
if (s === "pending") return "Pending";
|
|
951
|
-
if (!status) return "Pending";
|
|
952
|
-
return status.charAt(0).toUpperCase() + status.slice(1);
|
|
953
|
-
}
|
|
954
|
-
function paymentRowToWebhookDto(p) {
|
|
955
|
-
const currency = String(p.currency || "INR");
|
|
956
|
-
const amountMajor = Number(p.amount);
|
|
957
|
-
const meta = p.metadata || {};
|
|
958
|
-
return {
|
|
959
|
-
id: String(p.externalReference || `payment_${p.id}`),
|
|
960
|
-
amount: amountToMinorUnits(amountMajor, currency),
|
|
961
|
-
currency_code: currency,
|
|
962
|
-
captured_at: p.paidAt ? new Date(p.paidAt).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
963
|
-
provider_id: String(p.method || "unknown"),
|
|
964
|
-
data: { status: "captured", ...meta }
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId) {
|
|
968
|
-
try {
|
|
969
|
-
const configRepo = dataSource.getRepository(entityMap.configs);
|
|
970
|
-
const cfgRows = await configRepo.find({ where: { settings: "erp", deleted: false } });
|
|
971
|
-
for (const row of cfgRows) {
|
|
972
|
-
const r = row;
|
|
973
|
-
if (r.key === "enabled" && r.value === "false") return;
|
|
974
|
-
}
|
|
975
|
-
if (!cms.getPlugin("erp")) return;
|
|
976
|
-
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
977
|
-
const ord = await orderRepo.findOne({
|
|
978
|
-
where: { id: orderId },
|
|
979
|
-
relations: ["items", "items.product", "contact", "billingAddress", "shippingAddress", "payments"]
|
|
980
|
-
});
|
|
981
|
-
if (!ord) return;
|
|
982
|
-
const o = ord;
|
|
983
|
-
const okKind = o.orderKind === void 0 || o.orderKind === null || o.orderKind === "sale";
|
|
984
|
-
if (!okKind) return;
|
|
985
|
-
const rawPayments = o.payments ?? [];
|
|
986
|
-
const completedPayments = rawPayments.filter((pay) => pay.status === "completed" && pay.deleted !== true);
|
|
987
|
-
if (!completedPayments.length) return;
|
|
988
|
-
const rawItems = o.items ?? [];
|
|
989
|
-
const lines = rawItems.filter((it) => it.product).map((it) => {
|
|
990
|
-
const p = it.product;
|
|
991
|
-
const sku = p.sku || `SKU-${p.id}`;
|
|
992
|
-
return {
|
|
993
|
-
sku,
|
|
994
|
-
quantity: Number(it.quantity) || 1,
|
|
995
|
-
unitPrice: Number(it.unitPrice),
|
|
996
|
-
title: p.name || sku,
|
|
997
|
-
discount: 0,
|
|
998
|
-
tax: Number(it.tax) || 0
|
|
999
|
-
};
|
|
1000
|
-
});
|
|
1001
|
-
if (!lines.length) return;
|
|
1002
|
-
const contact = o.contact;
|
|
1003
|
-
const paymentDtos = completedPayments.map((pay) => paymentRowToWebhookDto(pay));
|
|
1004
|
-
const baseMeta = o.metadata && typeof o.metadata === "object" && !Array.isArray(o.metadata) ? { ...o.metadata } : {};
|
|
1005
|
-
const orderDto = {
|
|
1006
|
-
platformType: "website",
|
|
1007
|
-
platformOrderId: String(o.orderNumber),
|
|
1008
|
-
platformOrderNumber: String(o.orderNumber),
|
|
1009
|
-
status: orderStatusLabel(o.status),
|
|
1010
|
-
customer: {
|
|
1011
|
-
name: contact?.name || "",
|
|
1012
|
-
email: contact?.email || "",
|
|
1013
|
-
phone: contact?.phone || ""
|
|
1014
|
-
},
|
|
1015
|
-
shippingAddress: addressToWebhookDto(o.shippingAddress),
|
|
1016
|
-
billingAddress: addressToWebhookDto(o.billingAddress),
|
|
1017
|
-
items: lines,
|
|
1018
|
-
payments: paymentDtos,
|
|
1019
|
-
metadata: { ...baseMeta, source: "storefront" }
|
|
1020
|
-
};
|
|
1021
|
-
await queueErp(cms, { kind: "order", order: orderDto });
|
|
1022
|
-
} catch {
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1052
|
+
// src/plugins/erp/index.ts
|
|
1053
|
+
init_erp_queue();
|
|
1054
|
+
init_paid_order_erp();
|
|
1025
1055
|
|
|
1026
1056
|
// src/plugins/erp/erp-contact-sync.ts
|
|
1057
|
+
init_erp_queue();
|
|
1027
1058
|
function splitName(full) {
|
|
1028
1059
|
const t = (full || "").trim();
|
|
1029
1060
|
if (!t) return { firstName: "Contact", lastName: "" };
|
|
@@ -1267,6 +1298,7 @@ init_erp_order_invoice();
|
|
|
1267
1298
|
init_erp_config_enabled();
|
|
1268
1299
|
|
|
1269
1300
|
// src/plugins/erp/erp-product-sync.ts
|
|
1301
|
+
init_erp_queue();
|
|
1270
1302
|
init_erp_config_enabled();
|
|
1271
1303
|
async function queueErpProductUpsertIfEnabled(cms, dataSource, entityMap, product) {
|
|
1272
1304
|
try {
|
|
@@ -1274,14 +1306,21 @@ async function queueErpProductUpsertIfEnabled(cms, dataSource, entityMap, produc
|
|
|
1274
1306
|
if (!sku) return;
|
|
1275
1307
|
const on = await isErpIntegrationEnabled(cms, dataSource, entityMap);
|
|
1276
1308
|
if (!on) return;
|
|
1309
|
+
const rawMeta = product.metadata;
|
|
1310
|
+
let metadata;
|
|
1311
|
+
if (rawMeta && typeof rawMeta === "object" && !Array.isArray(rawMeta)) {
|
|
1312
|
+
const { description: _d, ...rest } = rawMeta;
|
|
1313
|
+
metadata = Object.keys(rest).length ? rest : void 0;
|
|
1314
|
+
}
|
|
1277
1315
|
const payload = {
|
|
1278
1316
|
sku,
|
|
1279
1317
|
title: product.name || sku,
|
|
1280
1318
|
name: product.name,
|
|
1281
|
-
description: typeof product.metadata === "object" && product.metadata && "description" in product.metadata ? String(product.metadata.description ?? "") : void 0,
|
|
1282
1319
|
hsn_number: product.hsn,
|
|
1320
|
+
uom: product.uom != null && String(product.uom).trim() ? String(product.uom).trim() : void 0,
|
|
1321
|
+
type: product.type === "service" ? "service" : "product",
|
|
1283
1322
|
is_active: product.status === "available",
|
|
1284
|
-
metadata
|
|
1323
|
+
metadata
|
|
1285
1324
|
};
|
|
1286
1325
|
await queueErp(cms, { kind: "productUpsert", product: payload });
|
|
1287
1326
|
} catch {
|
|
@@ -1567,8 +1606,10 @@ function renderLineItemsHtml(items, currency) {
|
|
|
1567
1606
|
const rows = items.map((it) => {
|
|
1568
1607
|
const name = escapeHtml2(it.productName);
|
|
1569
1608
|
const sku = it.sku && String(it.sku).trim() ? `<span style="font-size:12px;color:#888;"> (${escapeHtml2(String(it.sku).trim())})</span>` : "";
|
|
1609
|
+
const hsn = it.hsn && String(it.hsn).trim() ? `<br/><span style="font-size:11px;color:#888;">HSN: ${escapeHtml2(String(it.hsn).trim())}</span>` : "";
|
|
1610
|
+
const taxNote = it.tax != null && String(it.tax).trim() && Number(it.tax) !== 0 ? `<br/><span style="font-size:11px;color:#888;">Tax: ${escapeHtml2(formatMoney(it.tax, currency))}</span>` : "";
|
|
1570
1611
|
return `<tr>
|
|
1571
|
-
<td style="padding:10px 8px 10px 0;border-bottom:1px solid #eee;vertical-align:top;font-size:14px;color:#333;">${name}${sku}</td>
|
|
1612
|
+
<td style="padding:10px 8px 10px 0;border-bottom:1px solid #eee;vertical-align:top;font-size:14px;color:#333;">${name}${sku}${hsn}${taxNote}</td>
|
|
1572
1613
|
<td align="right" style="padding:10px 8px;border-bottom:1px solid #eee;vertical-align:top;font-size:14px;color:#333;white-space:nowrap;">${escapeHtml2(String(it.quantity))}</td>
|
|
1573
1614
|
<td align="right" style="padding:10px 8px;border-bottom:1px solid #eee;vertical-align:top;font-size:14px;color:#333;white-space:nowrap;">${escapeHtml2(formatMoney(it.unitPrice, currency))}</td>
|
|
1574
1615
|
<td align="right" style="padding:10px 0 10px 8px;border-bottom:1px solid #eee;vertical-align:top;font-size:14px;color:#333;white-space:nowrap;">${escapeHtml2(formatMoney(it.lineTotal, currency))}</td>
|
|
@@ -1587,24 +1628,64 @@ ${rows}
|
|
|
1587
1628
|
}
|
|
1588
1629
|
function renderLineItemsText(items, currency) {
|
|
1589
1630
|
if (!items.length) return "";
|
|
1590
|
-
const lines = items.map(
|
|
1591
|
-
|
|
1592
|
-
|
|
1631
|
+
const lines = items.map((it) => {
|
|
1632
|
+
let s = `- ${it.productName} \xD7 ${it.quantity} @ ${formatMoney(it.unitPrice, currency)} = ${formatMoney(it.lineTotal, currency)}`;
|
|
1633
|
+
if (it.sku) s += ` [${it.sku}]`;
|
|
1634
|
+
if (it.hsn && String(it.hsn).trim()) s += ` HSN:${it.hsn}`;
|
|
1635
|
+
if (it.tax != null && Number(it.tax) !== 0) s += ` tax:${formatMoney(it.tax, currency)}`;
|
|
1636
|
+
return s;
|
|
1637
|
+
});
|
|
1593
1638
|
return ["Items:", ...lines].join("\n");
|
|
1594
1639
|
}
|
|
1640
|
+
function formatAddressLines(addr) {
|
|
1641
|
+
if (!addr || typeof addr !== "object") return [];
|
|
1642
|
+
const parts = [addr.line1, addr.line2, [addr.city, addr.state].filter(Boolean).join(", "), addr.postalCode, addr.country].filter(
|
|
1643
|
+
(x) => x != null && String(x).trim() !== ""
|
|
1644
|
+
);
|
|
1645
|
+
return parts.map((p) => String(p).trim()).filter(Boolean);
|
|
1646
|
+
}
|
|
1647
|
+
function renderAddressesHtml(billing, shipping) {
|
|
1648
|
+
const bLines = formatAddressLines(billing);
|
|
1649
|
+
const sLines = formatAddressLines(shipping);
|
|
1650
|
+
if (!bLines.length && !sLines.length) return "";
|
|
1651
|
+
let html = '<p style="margin:16px 0 8px 0;font-size:14px;font-weight:600;color:#111;">Addresses</p>';
|
|
1652
|
+
if (bLines.length) {
|
|
1653
|
+
html += `<p style="margin:0 0 4px 0;font-size:12px;font-weight:600;color:#555;">Billing</p><p style="margin:0 0 12px 0;font-size:14px;line-height:1.5;color:#333;">${bLines.map((l) => escapeHtml2(l)).join("<br/>")}</p>`;
|
|
1654
|
+
}
|
|
1655
|
+
if (sLines.length) {
|
|
1656
|
+
html += `<p style="margin:0 0 4px 0;font-size:12px;font-weight:600;color:#555;">Shipping</p><p style="margin:0 0 0 0;font-size:14px;line-height:1.5;color:#333;">${sLines.map((l) => escapeHtml2(l)).join("<br/>")}</p>`;
|
|
1657
|
+
}
|
|
1658
|
+
return html;
|
|
1659
|
+
}
|
|
1660
|
+
function renderAddressesText(billing, shipping) {
|
|
1661
|
+
const bLines = formatAddressLines(billing);
|
|
1662
|
+
const sLines = formatAddressLines(shipping);
|
|
1663
|
+
const out = [];
|
|
1664
|
+
if (bLines.length) out.push("Billing:", ...bLines.map((l) => ` ${l}`));
|
|
1665
|
+
if (sLines.length) out.push("Shipping:", ...sLines.map((l) => ` ${l}`));
|
|
1666
|
+
return out.length ? out.join("\n") : "";
|
|
1667
|
+
}
|
|
1595
1668
|
function render4(ctx) {
|
|
1596
1669
|
const {
|
|
1597
1670
|
orderNumber,
|
|
1598
1671
|
total,
|
|
1672
|
+
subtotal,
|
|
1673
|
+
tax,
|
|
1599
1674
|
currency,
|
|
1600
1675
|
customerName,
|
|
1601
1676
|
companyDetails,
|
|
1602
1677
|
audience = "customer",
|
|
1603
1678
|
internalCustomerEmail,
|
|
1604
|
-
lineItems = []
|
|
1679
|
+
lineItems = [],
|
|
1680
|
+
billingAddress,
|
|
1681
|
+
shippingAddress
|
|
1605
1682
|
} = ctx;
|
|
1606
1683
|
const itemsHtml = renderLineItemsHtml(lineItems, currency);
|
|
1607
1684
|
const itemsText = renderLineItemsText(lineItems, currency);
|
|
1685
|
+
const addrHtml = renderAddressesHtml(billingAddress, shippingAddress);
|
|
1686
|
+
const addrText = renderAddressesText(billingAddress, shippingAddress);
|
|
1687
|
+
const subtotalLine = subtotal != null && String(subtotal).trim() !== "" ? `<p style="margin:8px 0 0 0;font-size:14px;line-height:1.5;color:#333;"><strong>Subtotal:</strong> ${escapeHtml2(String(subtotal))}${currency ? ` ${escapeHtml2(currency)}` : ""}</p>` : "";
|
|
1688
|
+
const taxLine = tax != null && String(tax).trim() !== "" && Number(tax) !== 0 ? `<p style="margin:4px 0 0 0;font-size:14px;line-height:1.5;color:#333;"><strong>Tax:</strong> ${escapeHtml2(String(tax))}${currency ? ` ${escapeHtml2(currency)}` : ""}</p>` : "";
|
|
1608
1689
|
const totalLine = total != null && String(total).trim() !== "" ? `<p style="margin:12px 0 0 0;font-size:15px;line-height:1.5;color:#333;"><strong>Order total:</strong> ${escapeHtml2(String(total))}${currency ? ` ${escapeHtml2(currency)}` : ""}</p>` : "";
|
|
1609
1690
|
let subject;
|
|
1610
1691
|
let bodyHtml;
|
|
@@ -1615,13 +1696,17 @@ function render4(ctx) {
|
|
|
1615
1696
|
bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">A new order has been placed and payment completed.</p>
|
|
1616
1697
|
<p style="margin:0 0 8px 0;font-size:15px;line-height:1.5;color:#333;"><strong>Order number:</strong> ${escapeHtml2(orderNumber)}</p>
|
|
1617
1698
|
${who}
|
|
1699
|
+
${addrHtml}
|
|
1618
1700
|
${itemsHtml}
|
|
1619
|
-
${totalLine}`;
|
|
1701
|
+
${subtotalLine}${taxLine}${totalLine}`;
|
|
1620
1702
|
text = [
|
|
1621
1703
|
`New order #${orderNumber}`,
|
|
1622
1704
|
customerName ? `Customer: ${customerName}` : "",
|
|
1623
1705
|
internalCustomerEmail ? `Email: ${internalCustomerEmail}` : "",
|
|
1706
|
+
addrText,
|
|
1624
1707
|
itemsText,
|
|
1708
|
+
subtotal != null ? `Subtotal: ${subtotal}${currency ? ` ${currency}` : ""}` : "",
|
|
1709
|
+
tax != null && Number(tax) !== 0 ? `Tax: ${tax}${currency ? ` ${currency}` : ""}` : "",
|
|
1625
1710
|
total != null ? `Order total: ${total}${currency ? ` ${currency}` : ""}` : ""
|
|
1626
1711
|
].filter(Boolean).join("\n\n");
|
|
1627
1712
|
} else {
|
|
@@ -1630,14 +1715,18 @@ ${totalLine}`;
|
|
|
1630
1715
|
const thanksHtml = `${escapeHtml2(thanksPlain)} We\u2019ve received your order and will process it shortly.`;
|
|
1631
1716
|
bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${thanksHtml}</p>
|
|
1632
1717
|
<p style="margin:0 0 8px 0;font-size:15px;line-height:1.5;color:#333;"><strong>Order number:</strong> ${escapeHtml2(orderNumber)}</p>
|
|
1718
|
+
${addrHtml}
|
|
1633
1719
|
${itemsHtml}
|
|
1634
|
-
${totalLine}
|
|
1720
|
+
${subtotalLine}${taxLine}${totalLine}
|
|
1635
1721
|
<p style="margin:16px 0 0 0;font-size:13px;line-height:1.5;color:#666;">If you have questions, reply to this email or contact us using the details below.</p>`;
|
|
1636
1722
|
text = [
|
|
1637
1723
|
`Order confirmed #${orderNumber}`,
|
|
1638
1724
|
`${thanksPlain} We\u2019ve received your order and will process it shortly.`,
|
|
1639
1725
|
"",
|
|
1726
|
+
addrText,
|
|
1640
1727
|
itemsText,
|
|
1728
|
+
subtotal != null ? `Subtotal: ${subtotal}${currency ? ` ${currency}` : ""}` : "",
|
|
1729
|
+
tax != null && Number(tax) !== 0 ? `Tax: ${tax}${currency ? ` ${currency}` : ""}` : "",
|
|
1641
1730
|
total != null ? `Order total: ${total}${currency ? ` ${currency}` : ""}` : "",
|
|
1642
1731
|
"",
|
|
1643
1732
|
"We will process your order shortly."
|
|
@@ -5026,6 +5115,8 @@ var Product = class {
|
|
|
5026
5115
|
categoryId;
|
|
5027
5116
|
sku;
|
|
5028
5117
|
hsn;
|
|
5118
|
+
uom;
|
|
5119
|
+
type;
|
|
5029
5120
|
slug;
|
|
5030
5121
|
name;
|
|
5031
5122
|
price;
|
|
@@ -5067,6 +5158,12 @@ __decorateClass([
|
|
|
5067
5158
|
__decorateClass([
|
|
5068
5159
|
(0, import_typeorm29.Column)("varchar", { nullable: true })
|
|
5069
5160
|
], Product.prototype, "hsn", 2);
|
|
5161
|
+
__decorateClass([
|
|
5162
|
+
(0, import_typeorm29.Column)("varchar", { nullable: true })
|
|
5163
|
+
], Product.prototype, "uom", 2);
|
|
5164
|
+
__decorateClass([
|
|
5165
|
+
(0, import_typeorm29.Column)("varchar", { default: "product" })
|
|
5166
|
+
], Product.prototype, "type", 2);
|
|
5070
5167
|
__decorateClass([
|
|
5071
5168
|
(0, import_typeorm29.Column)("varchar", { unique: true, nullable: true })
|
|
5072
5169
|
], Product.prototype, "slug", 2);
|
|
@@ -5375,6 +5472,11 @@ var OrderItem = class {
|
|
|
5375
5472
|
unitPrice;
|
|
5376
5473
|
tax;
|
|
5377
5474
|
total;
|
|
5475
|
+
hsn;
|
|
5476
|
+
uom;
|
|
5477
|
+
productType;
|
|
5478
|
+
taxRate;
|
|
5479
|
+
taxCode;
|
|
5378
5480
|
metadata;
|
|
5379
5481
|
createdAt;
|
|
5380
5482
|
updatedAt;
|
|
@@ -5402,6 +5504,21 @@ __decorateClass([
|
|
|
5402
5504
|
__decorateClass([
|
|
5403
5505
|
(0, import_typeorm34.Column)("decimal", { precision: 12, scale: 2 })
|
|
5404
5506
|
], OrderItem.prototype, "total", 2);
|
|
5507
|
+
__decorateClass([
|
|
5508
|
+
(0, import_typeorm34.Column)("varchar", { nullable: true })
|
|
5509
|
+
], OrderItem.prototype, "hsn", 2);
|
|
5510
|
+
__decorateClass([
|
|
5511
|
+
(0, import_typeorm34.Column)("varchar", { nullable: true })
|
|
5512
|
+
], OrderItem.prototype, "uom", 2);
|
|
5513
|
+
__decorateClass([
|
|
5514
|
+
(0, import_typeorm34.Column)("varchar", { nullable: true })
|
|
5515
|
+
], OrderItem.prototype, "productType", 2);
|
|
5516
|
+
__decorateClass([
|
|
5517
|
+
(0, import_typeorm34.Column)("decimal", { precision: 5, scale: 2, nullable: true })
|
|
5518
|
+
], OrderItem.prototype, "taxRate", 2);
|
|
5519
|
+
__decorateClass([
|
|
5520
|
+
(0, import_typeorm34.Column)("varchar", { nullable: true })
|
|
5521
|
+
], OrderItem.prototype, "taxCode", 2);
|
|
5405
5522
|
__decorateClass([
|
|
5406
5523
|
(0, import_typeorm34.Column)("jsonb", { nullable: true })
|
|
5407
5524
|
], OrderItem.prototype, "metadata", 2);
|
|
@@ -6298,6 +6415,24 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
6298
6415
|
} else if (search) {
|
|
6299
6416
|
where = buildSearchWhereClause(repo, search);
|
|
6300
6417
|
}
|
|
6418
|
+
const intFilterKeys = ["productId", "attributeId", "taxId"];
|
|
6419
|
+
const extraWhere = {};
|
|
6420
|
+
for (const key of intFilterKeys) {
|
|
6421
|
+
const v = searchParams.get(key);
|
|
6422
|
+
if (v != null && v !== "" && columnNames.has(key)) {
|
|
6423
|
+
const n = Number(v);
|
|
6424
|
+
if (Number.isFinite(n)) extraWhere[key] = n;
|
|
6425
|
+
}
|
|
6426
|
+
}
|
|
6427
|
+
if (Object.keys(extraWhere).length > 0) {
|
|
6428
|
+
if (Array.isArray(where)) {
|
|
6429
|
+
where = where.map((w) => ({ ...w, ...extraWhere }));
|
|
6430
|
+
} else if (where && typeof where === "object" && Object.keys(where).length > 0) {
|
|
6431
|
+
where = { ...where, ...extraWhere };
|
|
6432
|
+
} else {
|
|
6433
|
+
where = extraWhere;
|
|
6434
|
+
}
|
|
6435
|
+
}
|
|
6301
6436
|
const [data, total] = await repo.findAndCount({
|
|
6302
6437
|
skip,
|
|
6303
6438
|
take: limit,
|
|
@@ -6755,6 +6890,7 @@ function createUserAuthApiRouter(config) {
|
|
|
6755
6890
|
// src/api/cms-handlers.ts
|
|
6756
6891
|
var import_typeorm42 = require("typeorm");
|
|
6757
6892
|
init_email_queue();
|
|
6893
|
+
init_erp_queue();
|
|
6758
6894
|
function createDashboardStatsHandler(config) {
|
|
6759
6895
|
const { dataSource, entityMap, json, requireAuth, requirePermission, requireEntityPermission } = config;
|
|
6760
6896
|
return async function GET(req) {
|
|
@@ -8156,6 +8292,29 @@ function createCmsApiHandler(config) {
|
|
|
8156
8292
|
if (!Number.isFinite(oid)) return config.json({ error: "Invalid id" }, { status: 400 });
|
|
8157
8293
|
return streamOrderInvoicePdf2(cms, dataSource, entityMap, oid, {});
|
|
8158
8294
|
}
|
|
8295
|
+
if (path[0] === "orders" && path.length === 3 && path[2] === "repost-erp" && getCms) {
|
|
8296
|
+
const a = await config.requireAuth(req);
|
|
8297
|
+
if (a) return a;
|
|
8298
|
+
if (perm) {
|
|
8299
|
+
const pe = await perm(req, "orders", method === "GET" ? "read" : "update");
|
|
8300
|
+
if (pe) return pe;
|
|
8301
|
+
}
|
|
8302
|
+
const oid = Number(path[1]);
|
|
8303
|
+
if (!Number.isFinite(oid)) return config.json({ error: "Invalid id" }, { status: 400 });
|
|
8304
|
+
const cms = await getCms();
|
|
8305
|
+
const { isErpIntegrationEnabled: isErpIntegrationEnabled3 } = await Promise.resolve().then(() => (init_erp_config_enabled(), erp_config_enabled_exports));
|
|
8306
|
+
const enabled = await isErpIntegrationEnabled3(cms, dataSource, entityMap);
|
|
8307
|
+
if (method === "GET") {
|
|
8308
|
+
return config.json({ enabled });
|
|
8309
|
+
}
|
|
8310
|
+
if (method === "POST") {
|
|
8311
|
+
if (!enabled) return config.json({ error: "ERP integration is disabled" }, { status: 409 });
|
|
8312
|
+
const { queueErpPaidOrderForOrderId: queueErpPaidOrderForOrderId2 } = await Promise.resolve().then(() => (init_paid_order_erp(), paid_order_erp_exports));
|
|
8313
|
+
await queueErpPaidOrderForOrderId2(cms, dataSource, entityMap, oid);
|
|
8314
|
+
return config.json({ ok: true });
|
|
8315
|
+
}
|
|
8316
|
+
return config.json({ error: "Method not allowed" }, { status: 405 });
|
|
8317
|
+
}
|
|
8159
8318
|
if (path.length === 0) return config.json({ error: "Not found" }, { status: 404 });
|
|
8160
8319
|
const resource = resolveResource(path[0]);
|
|
8161
8320
|
if (!crudResources.includes(resource)) return config.json({ error: "Invalid resource" }, { status: 400 });
|
|
@@ -8252,6 +8411,152 @@ function createStorefrontApiHandler(config) {
|
|
|
8252
8411
|
const tokenRepo = () => dataSource.getRepository(entityMap.password_reset_tokens);
|
|
8253
8412
|
const collectionRepo = () => dataSource.getRepository(entityMap.collections);
|
|
8254
8413
|
const groupRepo = () => dataSource.getRepository(entityMap.user_groups);
|
|
8414
|
+
const configRepo = () => dataSource.getRepository(entityMap.configs);
|
|
8415
|
+
const CART_CHECKOUT_RELATIONS = ["items", "items.product", "items.product.taxes", "items.product.taxes.tax"];
|
|
8416
|
+
function roundMoney2(n) {
|
|
8417
|
+
return Math.round(n * 100) / 100;
|
|
8418
|
+
}
|
|
8419
|
+
async function getStoreDefaultTaxRate() {
|
|
8420
|
+
const rows = await configRepo().find({ where: { settings: "store", deleted: false } });
|
|
8421
|
+
for (const row of rows) {
|
|
8422
|
+
const r = row;
|
|
8423
|
+
if (r.key === "defaultTaxRate") {
|
|
8424
|
+
const n = parseFloat(String(r.value ?? "").trim());
|
|
8425
|
+
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
8426
|
+
}
|
|
8427
|
+
}
|
|
8428
|
+
return null;
|
|
8429
|
+
}
|
|
8430
|
+
function computeTaxForProductLine(p, lineSubtotal, defaultRate) {
|
|
8431
|
+
const pts = p.taxes ?? [];
|
|
8432
|
+
const activePts = pts.filter((pt) => {
|
|
8433
|
+
const t = pt.tax;
|
|
8434
|
+
return t != null && t.active !== false;
|
|
8435
|
+
});
|
|
8436
|
+
if (activePts.length) {
|
|
8437
|
+
let sumRate = 0;
|
|
8438
|
+
const slugs = [];
|
|
8439
|
+
for (const pt of activePts) {
|
|
8440
|
+
const t = pt.tax;
|
|
8441
|
+
const r = Number(pt.rate != null && pt.rate !== "" ? pt.rate : t.rate ?? 0);
|
|
8442
|
+
if (Number.isFinite(r)) sumRate += r;
|
|
8443
|
+
const slug = String(t.slug ?? "").trim();
|
|
8444
|
+
if (slug) slugs.push(slug);
|
|
8445
|
+
}
|
|
8446
|
+
const tax = roundMoney2(lineSubtotal * sumRate / 100);
|
|
8447
|
+
return {
|
|
8448
|
+
tax,
|
|
8449
|
+
taxRate: sumRate > 0 ? roundMoney2(sumRate) : null,
|
|
8450
|
+
taxCode: slugs.length ? [...new Set(slugs)].sort().join(",") : null
|
|
8451
|
+
};
|
|
8452
|
+
}
|
|
8453
|
+
if (defaultRate != null && defaultRate > 0) {
|
|
8454
|
+
return {
|
|
8455
|
+
tax: roundMoney2(lineSubtotal * defaultRate / 100),
|
|
8456
|
+
taxRate: roundMoney2(defaultRate),
|
|
8457
|
+
taxCode: null
|
|
8458
|
+
};
|
|
8459
|
+
}
|
|
8460
|
+
return { tax: 0, taxRate: null, taxCode: null };
|
|
8461
|
+
}
|
|
8462
|
+
function parseInlineAddress(raw) {
|
|
8463
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
8464
|
+
const o = raw;
|
|
8465
|
+
const line1 = String(o.line1 ?? "").trim();
|
|
8466
|
+
if (!line1) return null;
|
|
8467
|
+
return {
|
|
8468
|
+
line1,
|
|
8469
|
+
line2: o.line2 != null ? String(o.line2) : "",
|
|
8470
|
+
city: o.city != null ? String(o.city) : "",
|
|
8471
|
+
state: o.state != null ? String(o.state) : "",
|
|
8472
|
+
postalCode: o.postalCode != null ? String(o.postalCode) : "",
|
|
8473
|
+
country: o.country != null ? String(o.country) : ""
|
|
8474
|
+
};
|
|
8475
|
+
}
|
|
8476
|
+
function intFromBody(v) {
|
|
8477
|
+
if (typeof v === "number" && Number.isInteger(v)) return v;
|
|
8478
|
+
if (typeof v === "string" && /^\d+$/.test(v)) return parseInt(v, 10);
|
|
8479
|
+
return void 0;
|
|
8480
|
+
}
|
|
8481
|
+
async function resolveCheckoutAddress(contactId, idVal, inlineVal) {
|
|
8482
|
+
const aid = intFromBody(idVal);
|
|
8483
|
+
if (aid != null) {
|
|
8484
|
+
const existing = await addressRepo().findOne({
|
|
8485
|
+
where: { id: aid, contactId }
|
|
8486
|
+
});
|
|
8487
|
+
if (!existing) return { id: null, error: "Address not found" };
|
|
8488
|
+
return { id: aid };
|
|
8489
|
+
}
|
|
8490
|
+
const addr = parseInlineAddress(inlineVal);
|
|
8491
|
+
if (addr) {
|
|
8492
|
+
const saved = await addressRepo().save(
|
|
8493
|
+
addressRepo().create({
|
|
8494
|
+
contactId,
|
|
8495
|
+
line1: addr.line1,
|
|
8496
|
+
line2: addr.line2?.trim() ? addr.line2 : null,
|
|
8497
|
+
city: addr.city?.trim() ? addr.city : null,
|
|
8498
|
+
state: addr.state?.trim() ? addr.state : null,
|
|
8499
|
+
postalCode: addr.postalCode?.trim() ? addr.postalCode : null,
|
|
8500
|
+
country: addr.country?.trim() ? addr.country : null
|
|
8501
|
+
})
|
|
8502
|
+
);
|
|
8503
|
+
return { id: saved.id };
|
|
8504
|
+
}
|
|
8505
|
+
return { id: null };
|
|
8506
|
+
}
|
|
8507
|
+
async function prepareCheckoutFromCart(b, cart, contactId) {
|
|
8508
|
+
const defaultRate = await getStoreDefaultTaxRate();
|
|
8509
|
+
const lines = [];
|
|
8510
|
+
let subtotal = 0;
|
|
8511
|
+
let orderTax = 0;
|
|
8512
|
+
let needsShipping = false;
|
|
8513
|
+
for (const it of cart.items || []) {
|
|
8514
|
+
const p = it.product;
|
|
8515
|
+
if (!p || p.deleted || p.status !== "available") continue;
|
|
8516
|
+
const unit = Number(p.price);
|
|
8517
|
+
const qty = it.quantity || 1;
|
|
8518
|
+
const lineSubtotal = unit * qty;
|
|
8519
|
+
const pType = p.type === "service" ? "service" : "product";
|
|
8520
|
+
if (pType === "product") needsShipping = true;
|
|
8521
|
+
const { tax, taxRate, taxCode } = computeTaxForProductLine(p, lineSubtotal, defaultRate);
|
|
8522
|
+
const lineTotal = roundMoney2(lineSubtotal + tax);
|
|
8523
|
+
subtotal = roundMoney2(subtotal + lineSubtotal);
|
|
8524
|
+
orderTax = roundMoney2(orderTax + tax);
|
|
8525
|
+
lines.push({
|
|
8526
|
+
productId: p.id,
|
|
8527
|
+
quantity: qty,
|
|
8528
|
+
unitPrice: unit,
|
|
8529
|
+
tax,
|
|
8530
|
+
total: lineTotal,
|
|
8531
|
+
hsn: p.hsn ?? null,
|
|
8532
|
+
uom: p.uom ?? null,
|
|
8533
|
+
productType: pType,
|
|
8534
|
+
taxRate,
|
|
8535
|
+
taxCode
|
|
8536
|
+
});
|
|
8537
|
+
}
|
|
8538
|
+
if (!lines.length) return { ok: false, status: 400, message: "No available items in cart" };
|
|
8539
|
+
const bill = await resolveCheckoutAddress(contactId, b.billingAddressId, b.billingAddress);
|
|
8540
|
+
if (bill.error) return { ok: false, status: 400, message: bill.error };
|
|
8541
|
+
if (bill.id == null) return { ok: false, status: 400, message: "Billing address required" };
|
|
8542
|
+
const ship = await resolveCheckoutAddress(contactId, b.shippingAddressId, b.shippingAddress);
|
|
8543
|
+
if (ship.error) return { ok: false, status: 400, message: ship.error };
|
|
8544
|
+
let shippingAddressId = ship.id;
|
|
8545
|
+
if (needsShipping && shippingAddressId == null) shippingAddressId = bill.id;
|
|
8546
|
+
if (needsShipping && shippingAddressId == null) {
|
|
8547
|
+
return { ok: false, status: 400, message: "Shipping address required" };
|
|
8548
|
+
}
|
|
8549
|
+
const orderTotal = roundMoney2(subtotal + orderTax);
|
|
8550
|
+
return {
|
|
8551
|
+
ok: true,
|
|
8552
|
+
lines,
|
|
8553
|
+
subtotal,
|
|
8554
|
+
orderTax,
|
|
8555
|
+
orderTotal,
|
|
8556
|
+
billingAddressId: bill.id,
|
|
8557
|
+
shippingAddressId
|
|
8558
|
+
};
|
|
8559
|
+
}
|
|
8255
8560
|
async function syncContactToErp(contact) {
|
|
8256
8561
|
if (!getCms) return;
|
|
8257
8562
|
try {
|
|
@@ -8377,6 +8682,7 @@ function createStorefrontApiHandler(config) {
|
|
|
8377
8682
|
slug: p.slug,
|
|
8378
8683
|
price: p.price,
|
|
8379
8684
|
sku: p.sku,
|
|
8685
|
+
type: p.type === "service" ? "service" : "product",
|
|
8380
8686
|
image: primaryProductImageUrl(p.metadata)
|
|
8381
8687
|
} : null
|
|
8382
8688
|
};
|
|
@@ -8404,6 +8710,8 @@ function createStorefrontApiHandler(config) {
|
|
|
8404
8710
|
slug: p.slug,
|
|
8405
8711
|
sku: p.sku,
|
|
8406
8712
|
hsn: p.hsn,
|
|
8713
|
+
uom: p.uom ?? null,
|
|
8714
|
+
type: p.type === "service" ? "service" : "product",
|
|
8407
8715
|
price: p.price,
|
|
8408
8716
|
compareAtPrice: p.compareAtPrice,
|
|
8409
8717
|
status: p.status,
|
|
@@ -9105,7 +9413,7 @@ function createStorefrontApiHandler(config) {
|
|
|
9105
9413
|
contactId = contact.id;
|
|
9106
9414
|
cart = await cartRepo().findOne({
|
|
9107
9415
|
where: { contactId },
|
|
9108
|
-
relations: [
|
|
9416
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
9109
9417
|
});
|
|
9110
9418
|
} else {
|
|
9111
9419
|
const email = String(b.email ?? "").trim();
|
|
@@ -9138,25 +9446,14 @@ function createStorefrontApiHandler(config) {
|
|
|
9138
9446
|
if (!guestToken) return json({ error: "Cart not found" }, { status: 400 });
|
|
9139
9447
|
cart = await cartRepo().findOne({
|
|
9140
9448
|
where: { guestToken },
|
|
9141
|
-
relations: [
|
|
9449
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
9142
9450
|
});
|
|
9143
9451
|
}
|
|
9144
9452
|
if (!cart || !(cart.items || []).length) {
|
|
9145
9453
|
return json({ error: "Cart is empty" }, { status: 400 });
|
|
9146
9454
|
}
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
for (const it of cart.items || []) {
|
|
9150
|
-
const p = it.product;
|
|
9151
|
-
if (!p || p.deleted || p.status !== "available") continue;
|
|
9152
|
-
const unit = Number(p.price);
|
|
9153
|
-
const qty = it.quantity || 1;
|
|
9154
|
-
const lineTotal = unit * qty;
|
|
9155
|
-
subtotal += lineTotal;
|
|
9156
|
-
lines.push({ productId: p.id, quantity: qty, unitPrice: unit, tax: 0, total: lineTotal });
|
|
9157
|
-
}
|
|
9158
|
-
if (!lines.length) return json({ error: "No available items in cart" }, { status: 400 });
|
|
9159
|
-
const total = subtotal;
|
|
9455
|
+
const prepOrd = await prepareCheckoutFromCart(b, cart, contactId);
|
|
9456
|
+
if (!prepOrd.ok) return json({ error: prepOrd.message }, { status: prepOrd.status });
|
|
9160
9457
|
const cartId = cart.id;
|
|
9161
9458
|
const ord = await orderRepo().save(
|
|
9162
9459
|
orderRepo().create({
|
|
@@ -9164,13 +9461,13 @@ function createStorefrontApiHandler(config) {
|
|
|
9164
9461
|
orderKind: "sale",
|
|
9165
9462
|
parentOrderId: null,
|
|
9166
9463
|
contactId,
|
|
9167
|
-
billingAddressId:
|
|
9168
|
-
shippingAddressId:
|
|
9464
|
+
billingAddressId: prepOrd.billingAddressId,
|
|
9465
|
+
shippingAddressId: prepOrd.shippingAddressId,
|
|
9169
9466
|
status: "pending",
|
|
9170
|
-
subtotal,
|
|
9171
|
-
tax:
|
|
9467
|
+
subtotal: prepOrd.subtotal,
|
|
9468
|
+
tax: prepOrd.orderTax,
|
|
9172
9469
|
discount: 0,
|
|
9173
|
-
total,
|
|
9470
|
+
total: prepOrd.orderTotal,
|
|
9174
9471
|
currency: cart.currency || "INR",
|
|
9175
9472
|
metadata: { cartId }
|
|
9176
9473
|
})
|
|
@@ -9179,7 +9476,7 @@ function createStorefrontApiHandler(config) {
|
|
|
9179
9476
|
await orderRepo().update(oid, {
|
|
9180
9477
|
orderNumber: buildCanonicalOrderNumber("sale", oid, ord.createdAt ?? /* @__PURE__ */ new Date())
|
|
9181
9478
|
});
|
|
9182
|
-
for (const line of lines) {
|
|
9479
|
+
for (const line of prepOrd.lines) {
|
|
9183
9480
|
await orderItemRepo().save(
|
|
9184
9481
|
orderItemRepo().create({
|
|
9185
9482
|
orderId: oid,
|
|
@@ -9187,14 +9484,21 @@ function createStorefrontApiHandler(config) {
|
|
|
9187
9484
|
quantity: line.quantity,
|
|
9188
9485
|
unitPrice: line.unitPrice,
|
|
9189
9486
|
tax: line.tax,
|
|
9190
|
-
total: line.total
|
|
9487
|
+
total: line.total,
|
|
9488
|
+
hsn: line.hsn,
|
|
9489
|
+
uom: line.uom,
|
|
9490
|
+
productType: line.productType,
|
|
9491
|
+
taxRate: line.taxRate,
|
|
9492
|
+
taxCode: line.taxCode
|
|
9191
9493
|
})
|
|
9192
9494
|
);
|
|
9193
9495
|
}
|
|
9194
9496
|
return json({
|
|
9195
9497
|
orderId: oid,
|
|
9196
9498
|
orderNumber: ord.orderNumber,
|
|
9197
|
-
|
|
9499
|
+
subtotal: prepOrd.subtotal,
|
|
9500
|
+
tax: prepOrd.orderTax,
|
|
9501
|
+
total: prepOrd.orderTotal,
|
|
9198
9502
|
currency: cart.currency || "INR"
|
|
9199
9503
|
});
|
|
9200
9504
|
}
|
|
@@ -9212,7 +9516,7 @@ function createStorefrontApiHandler(config) {
|
|
|
9212
9516
|
contactId = contact.id;
|
|
9213
9517
|
cart = await cartRepo().findOne({
|
|
9214
9518
|
where: { contactId },
|
|
9215
|
-
relations: [
|
|
9519
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
9216
9520
|
});
|
|
9217
9521
|
} else {
|
|
9218
9522
|
const email = String(b.email ?? "").trim();
|
|
@@ -9245,38 +9549,27 @@ function createStorefrontApiHandler(config) {
|
|
|
9245
9549
|
if (!guestToken) return json({ error: "Cart not found" }, { status: 400 });
|
|
9246
9550
|
cart = await cartRepo().findOne({
|
|
9247
9551
|
where: { guestToken },
|
|
9248
|
-
relations: [
|
|
9552
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
9249
9553
|
});
|
|
9250
9554
|
}
|
|
9251
9555
|
if (!cart || !(cart.items || []).length) {
|
|
9252
9556
|
return json({ error: "Cart is empty" }, { status: 400 });
|
|
9253
9557
|
}
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
for (const it of cart.items || []) {
|
|
9257
|
-
const p = it.product;
|
|
9258
|
-
if (!p || p.deleted || p.status !== "available") continue;
|
|
9259
|
-
const unit = Number(p.price);
|
|
9260
|
-
const qty = it.quantity || 1;
|
|
9261
|
-
const lineTotal = unit * qty;
|
|
9262
|
-
subtotal += lineTotal;
|
|
9263
|
-
lines.push({ productId: p.id, quantity: qty, unitPrice: unit, tax: 0, total: lineTotal });
|
|
9264
|
-
}
|
|
9265
|
-
if (!lines.length) return json({ error: "No available items in cart" }, { status: 400 });
|
|
9266
|
-
const total = subtotal;
|
|
9558
|
+
const prepChk = await prepareCheckoutFromCart(b, cart, contactId);
|
|
9559
|
+
if (!prepChk.ok) return json({ error: prepChk.message }, { status: prepChk.status });
|
|
9267
9560
|
const ord = await orderRepo().save(
|
|
9268
9561
|
orderRepo().create({
|
|
9269
9562
|
orderNumber: temporaryOrderNumberPlaceholder(),
|
|
9270
9563
|
orderKind: "sale",
|
|
9271
9564
|
parentOrderId: null,
|
|
9272
9565
|
contactId,
|
|
9273
|
-
billingAddressId:
|
|
9274
|
-
shippingAddressId:
|
|
9566
|
+
billingAddressId: prepChk.billingAddressId,
|
|
9567
|
+
shippingAddressId: prepChk.shippingAddressId,
|
|
9275
9568
|
status: "pending",
|
|
9276
|
-
subtotal,
|
|
9277
|
-
tax:
|
|
9569
|
+
subtotal: prepChk.subtotal,
|
|
9570
|
+
tax: prepChk.orderTax,
|
|
9278
9571
|
discount: 0,
|
|
9279
|
-
total,
|
|
9572
|
+
total: prepChk.orderTotal,
|
|
9280
9573
|
currency: cart.currency || "INR"
|
|
9281
9574
|
})
|
|
9282
9575
|
);
|
|
@@ -9284,7 +9577,7 @@ function createStorefrontApiHandler(config) {
|
|
|
9284
9577
|
await orderRepo().update(oid, {
|
|
9285
9578
|
orderNumber: buildCanonicalOrderNumber("sale", oid, ord.createdAt ?? /* @__PURE__ */ new Date())
|
|
9286
9579
|
});
|
|
9287
|
-
for (const line of lines) {
|
|
9580
|
+
for (const line of prepChk.lines) {
|
|
9288
9581
|
await orderItemRepo().save(
|
|
9289
9582
|
orderItemRepo().create({
|
|
9290
9583
|
orderId: oid,
|
|
@@ -9292,7 +9585,12 @@ function createStorefrontApiHandler(config) {
|
|
|
9292
9585
|
quantity: line.quantity,
|
|
9293
9586
|
unitPrice: line.unitPrice,
|
|
9294
9587
|
tax: line.tax,
|
|
9295
|
-
total: line.total
|
|
9588
|
+
total: line.total,
|
|
9589
|
+
hsn: line.hsn,
|
|
9590
|
+
uom: line.uom,
|
|
9591
|
+
productType: line.productType,
|
|
9592
|
+
taxRate: line.taxRate,
|
|
9593
|
+
taxCode: line.taxCode
|
|
9296
9594
|
})
|
|
9297
9595
|
);
|
|
9298
9596
|
}
|
|
@@ -9301,7 +9599,9 @@ function createStorefrontApiHandler(config) {
|
|
|
9301
9599
|
return json({
|
|
9302
9600
|
orderId: oid,
|
|
9303
9601
|
orderNumber: ord.orderNumber,
|
|
9304
|
-
|
|
9602
|
+
subtotal: prepChk.subtotal,
|
|
9603
|
+
tax: prepChk.orderTax,
|
|
9604
|
+
total: prepChk.orderTotal
|
|
9305
9605
|
});
|
|
9306
9606
|
}
|
|
9307
9607
|
if (path[0] === "orders" && path.length === 1 && method === "GET") {
|