@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.js
CHANGED
|
@@ -17,6 +17,153 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
17
17
|
return result;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
// src/plugins/erp/erp-queue.ts
|
|
21
|
+
async function queueErp(cms, payload) {
|
|
22
|
+
const queue = cms.getPlugin("queue");
|
|
23
|
+
if (!queue) return;
|
|
24
|
+
await queue.add(ERP_QUEUE_NAME, payload);
|
|
25
|
+
}
|
|
26
|
+
function registerErpQueueProcessor(cms) {
|
|
27
|
+
const queue = cms.getPlugin("queue");
|
|
28
|
+
if (!queue) return;
|
|
29
|
+
queue.registerProcessor(ERP_QUEUE_NAME, async (data) => {
|
|
30
|
+
const erp = cms.getPlugin("erp");
|
|
31
|
+
if (!erp) return;
|
|
32
|
+
const payload = data;
|
|
33
|
+
if (payload.kind === "lead") {
|
|
34
|
+
await erp.submission.submitContact(payload.contact);
|
|
35
|
+
} else if (payload.kind === "formOpportunity") {
|
|
36
|
+
await erp.submission.submitFormOpportunity(payload.contact);
|
|
37
|
+
} else if (payload.kind === "createContact") {
|
|
38
|
+
await erp.submission.submitCreateContact(payload.contact);
|
|
39
|
+
} else if (payload.kind === "order") {
|
|
40
|
+
await erp.submission.submitOrder(payload.order);
|
|
41
|
+
} else if (payload.kind === "productUpsert") {
|
|
42
|
+
await erp.submission.submitProductUpsert(payload.product);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
var ERP_QUEUE_NAME;
|
|
47
|
+
var init_erp_queue = __esm({
|
|
48
|
+
"src/plugins/erp/erp-queue.ts"() {
|
|
49
|
+
"use strict";
|
|
50
|
+
ERP_QUEUE_NAME = "erp";
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// src/plugins/erp/paid-order-erp.ts
|
|
55
|
+
var paid_order_erp_exports = {};
|
|
56
|
+
__export(paid_order_erp_exports, {
|
|
57
|
+
queueErpPaidOrderForOrderId: () => queueErpPaidOrderForOrderId
|
|
58
|
+
});
|
|
59
|
+
function roundMoney(major) {
|
|
60
|
+
return Math.round(major * 100) / 100;
|
|
61
|
+
}
|
|
62
|
+
function addressToWebhookDto(a) {
|
|
63
|
+
if (!a) return {};
|
|
64
|
+
return {
|
|
65
|
+
line1: a.line1 ?? "",
|
|
66
|
+
line2: a.line2 ?? "",
|
|
67
|
+
city: a.city ?? "",
|
|
68
|
+
state: a.state ?? "",
|
|
69
|
+
postalCode: a.postalCode ?? "",
|
|
70
|
+
country: a.country ?? ""
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function orderStatusLabel(status) {
|
|
74
|
+
const s = (status || "").toLowerCase();
|
|
75
|
+
if (s === "confirmed") return "Confirmed";
|
|
76
|
+
if (s === "pending") return "Pending";
|
|
77
|
+
if (!status) return "Pending";
|
|
78
|
+
return status.charAt(0).toUpperCase() + status.slice(1);
|
|
79
|
+
}
|
|
80
|
+
function paymentRowToWebhookDto(p, amountMajorOverride) {
|
|
81
|
+
const currency = String(p.currency || "INR");
|
|
82
|
+
const amountMajor = amountMajorOverride != null && Number.isFinite(amountMajorOverride) ? amountMajorOverride : Number(p.amount);
|
|
83
|
+
const meta = { ...p.metadata || {} };
|
|
84
|
+
delete meta.amount;
|
|
85
|
+
delete meta.currency;
|
|
86
|
+
return {
|
|
87
|
+
id: String(p.externalReference || `payment_${p.id}`),
|
|
88
|
+
amount: roundMoney(amountMajor),
|
|
89
|
+
currency_code: currency,
|
|
90
|
+
captured_at: p.paidAt ? new Date(p.paidAt).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
91
|
+
provider_id: String(p.method || "unknown"),
|
|
92
|
+
data: { status: "captured", ...meta }
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId) {
|
|
96
|
+
try {
|
|
97
|
+
const configRepo = dataSource.getRepository(entityMap.configs);
|
|
98
|
+
const cfgRows = await configRepo.find({ where: { settings: "erp", deleted: false } });
|
|
99
|
+
for (const row of cfgRows) {
|
|
100
|
+
const r = row;
|
|
101
|
+
if (r.key === "enabled" && r.value === "false") return;
|
|
102
|
+
}
|
|
103
|
+
if (!cms.getPlugin("erp")) return;
|
|
104
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
105
|
+
const ord = await orderRepo.findOne({
|
|
106
|
+
where: { id: orderId },
|
|
107
|
+
relations: ["items", "items.product", "contact", "billingAddress", "shippingAddress", "payments"]
|
|
108
|
+
});
|
|
109
|
+
if (!ord) return;
|
|
110
|
+
const o = ord;
|
|
111
|
+
const okKind = o.orderKind === void 0 || o.orderKind === null || o.orderKind === "sale";
|
|
112
|
+
if (!okKind) return;
|
|
113
|
+
const rawPayments = o.payments ?? [];
|
|
114
|
+
const completedPayments = rawPayments.filter((pay) => pay.status === "completed" && pay.deleted !== true);
|
|
115
|
+
if (!completedPayments.length) return;
|
|
116
|
+
const rawItems = o.items ?? [];
|
|
117
|
+
const lines = rawItems.filter((it) => it.product).map((it) => {
|
|
118
|
+
const p = it.product;
|
|
119
|
+
const sku = p.sku || `SKU-${p.id}`;
|
|
120
|
+
const itemType = typeof it.productType === "string" && it.productType.trim() ? String(it.productType).trim() : p.type === "service" ? "service" : "product";
|
|
121
|
+
return {
|
|
122
|
+
sku,
|
|
123
|
+
quantity: Number(it.quantity) || 1,
|
|
124
|
+
unitPrice: Number(it.unitPrice),
|
|
125
|
+
title: p.name || sku,
|
|
126
|
+
discount: 0,
|
|
127
|
+
tax: Number(it.tax) || 0,
|
|
128
|
+
uom: (typeof it.uom === "string" && it.uom.trim() ? it.uom : p.uom) || void 0,
|
|
129
|
+
tax_code: typeof it.taxCode === "string" && it.taxCode.trim() ? String(it.taxCode).trim() : void 0,
|
|
130
|
+
hsn_number: (typeof it.hsn === "string" && it.hsn.trim() ? it.hsn : p.hsn) || void 0,
|
|
131
|
+
type: itemType
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
if (!lines.length) return;
|
|
135
|
+
const contact = o.contact;
|
|
136
|
+
const orderTotalMajor = Number(o.total);
|
|
137
|
+
const paymentDtos = completedPayments.length === 1 && Number.isFinite(orderTotalMajor) ? [paymentRowToWebhookDto(completedPayments[0], orderTotalMajor)] : completedPayments.map((pay) => paymentRowToWebhookDto(pay));
|
|
138
|
+
const baseMeta = o.metadata && typeof o.metadata === "object" && !Array.isArray(o.metadata) ? { ...o.metadata } : {};
|
|
139
|
+
const orderDto = {
|
|
140
|
+
platformType: "website",
|
|
141
|
+
platformOrderId: String(o.orderNumber),
|
|
142
|
+
platformOrderNumber: String(o.orderNumber),
|
|
143
|
+
order_date: o.createdAt ? new Date(o.createdAt).toISOString() : void 0,
|
|
144
|
+
status: orderStatusLabel(o.status),
|
|
145
|
+
customer: {
|
|
146
|
+
name: contact?.name || "",
|
|
147
|
+
email: contact?.email || "",
|
|
148
|
+
phone: contact?.phone || ""
|
|
149
|
+
},
|
|
150
|
+
shippingAddress: addressToWebhookDto(o.shippingAddress),
|
|
151
|
+
billingAddress: addressToWebhookDto(o.billingAddress),
|
|
152
|
+
items: lines,
|
|
153
|
+
payments: paymentDtos,
|
|
154
|
+
metadata: { ...baseMeta, source: "storefront" }
|
|
155
|
+
};
|
|
156
|
+
await queueErp(cms, { kind: "order", order: orderDto });
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
var init_paid_order_erp = __esm({
|
|
161
|
+
"src/plugins/erp/paid-order-erp.ts"() {
|
|
162
|
+
"use strict";
|
|
163
|
+
init_erp_queue();
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
20
167
|
// src/plugins/erp/erp-response-map.ts
|
|
21
168
|
function pickString(o, keys) {
|
|
22
169
|
for (const k of keys) {
|
|
@@ -93,6 +240,10 @@ var init_erp_response_map = __esm({
|
|
|
93
240
|
});
|
|
94
241
|
|
|
95
242
|
// src/plugins/erp/erp-config-enabled.ts
|
|
243
|
+
var erp_config_enabled_exports = {};
|
|
244
|
+
__export(erp_config_enabled_exports, {
|
|
245
|
+
isErpIntegrationEnabled: () => isErpIntegrationEnabled
|
|
246
|
+
});
|
|
96
247
|
async function isErpIntegrationEnabled(cms, dataSource, entityMap) {
|
|
97
248
|
if (!cms.getPlugin("erp")) return false;
|
|
98
249
|
const configRepo = dataSource.getRepository(entityMap.configs);
|
|
@@ -207,14 +358,31 @@ async function queueEmail(cms, payload) {
|
|
|
207
358
|
}
|
|
208
359
|
}
|
|
209
360
|
async function queueOrderPlacedEmails(cms, payload) {
|
|
210
|
-
const {
|
|
361
|
+
const {
|
|
362
|
+
orderNumber,
|
|
363
|
+
total,
|
|
364
|
+
subtotal,
|
|
365
|
+
tax,
|
|
366
|
+
currency,
|
|
367
|
+
customerName,
|
|
368
|
+
customerEmail,
|
|
369
|
+
salesTeamEmails,
|
|
370
|
+
companyDetails,
|
|
371
|
+
lineItems,
|
|
372
|
+
billingAddress,
|
|
373
|
+
shippingAddress
|
|
374
|
+
} = payload;
|
|
211
375
|
const base = {
|
|
212
376
|
orderNumber,
|
|
213
377
|
total: total != null ? String(total) : void 0,
|
|
378
|
+
subtotal: subtotal != null ? String(subtotal) : void 0,
|
|
379
|
+
tax: tax != null ? String(tax) : void 0,
|
|
214
380
|
currency,
|
|
215
381
|
customerName,
|
|
216
382
|
companyDetails: companyDetails ?? {},
|
|
217
|
-
lineItems: lineItems ?? []
|
|
383
|
+
lineItems: lineItems ?? [],
|
|
384
|
+
billingAddress,
|
|
385
|
+
shippingAddress
|
|
218
386
|
};
|
|
219
387
|
const customerLower = customerEmail?.trim().toLowerCase() ?? "";
|
|
220
388
|
const jobs = [];
|
|
@@ -725,149 +893,12 @@ var ERPSubmissionService = class {
|
|
|
725
893
|
}
|
|
726
894
|
};
|
|
727
895
|
|
|
728
|
-
// src/plugins/erp/
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
const queue = cms.getPlugin("queue");
|
|
732
|
-
if (!queue) return;
|
|
733
|
-
await queue.add(ERP_QUEUE_NAME, payload);
|
|
734
|
-
}
|
|
735
|
-
function registerErpQueueProcessor(cms) {
|
|
736
|
-
const queue = cms.getPlugin("queue");
|
|
737
|
-
if (!queue) return;
|
|
738
|
-
queue.registerProcessor(ERP_QUEUE_NAME, async (data) => {
|
|
739
|
-
const erp = cms.getPlugin("erp");
|
|
740
|
-
if (!erp) return;
|
|
741
|
-
const payload = data;
|
|
742
|
-
if (payload.kind === "lead") {
|
|
743
|
-
await erp.submission.submitContact(payload.contact);
|
|
744
|
-
} else if (payload.kind === "formOpportunity") {
|
|
745
|
-
await erp.submission.submitFormOpportunity(payload.contact);
|
|
746
|
-
} else if (payload.kind === "createContact") {
|
|
747
|
-
await erp.submission.submitCreateContact(payload.contact);
|
|
748
|
-
} else if (payload.kind === "order") {
|
|
749
|
-
await erp.submission.submitOrder(payload.order);
|
|
750
|
-
} else if (payload.kind === "productUpsert") {
|
|
751
|
-
await erp.submission.submitProductUpsert(payload.product);
|
|
752
|
-
}
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// src/plugins/erp/paid-order-erp.ts
|
|
757
|
-
function amountToMinorUnits(major, currency) {
|
|
758
|
-
const c = currency.toUpperCase();
|
|
759
|
-
const zeroDecimal = /* @__PURE__ */ new Set([
|
|
760
|
-
"BIF",
|
|
761
|
-
"CLP",
|
|
762
|
-
"DJF",
|
|
763
|
-
"GNF",
|
|
764
|
-
"JPY",
|
|
765
|
-
"KMF",
|
|
766
|
-
"KRW",
|
|
767
|
-
"MGA",
|
|
768
|
-
"PYG",
|
|
769
|
-
"RWF",
|
|
770
|
-
"UGX",
|
|
771
|
-
"VND",
|
|
772
|
-
"VUV",
|
|
773
|
-
"XAF",
|
|
774
|
-
"XOF",
|
|
775
|
-
"XPF"
|
|
776
|
-
]);
|
|
777
|
-
if (zeroDecimal.has(c)) return Math.round(major);
|
|
778
|
-
return Math.round(major * 100);
|
|
779
|
-
}
|
|
780
|
-
function addressToWebhookDto(a) {
|
|
781
|
-
if (!a) return {};
|
|
782
|
-
return {
|
|
783
|
-
line1: a.line1 ?? "",
|
|
784
|
-
line2: a.line2 ?? "",
|
|
785
|
-
city: a.city ?? "",
|
|
786
|
-
state: a.state ?? "",
|
|
787
|
-
postalCode: a.postalCode ?? "",
|
|
788
|
-
country: a.country ?? ""
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
function orderStatusLabel(status) {
|
|
792
|
-
const s = (status || "").toLowerCase();
|
|
793
|
-
if (s === "confirmed") return "Confirmed";
|
|
794
|
-
if (s === "pending") return "Pending";
|
|
795
|
-
if (!status) return "Pending";
|
|
796
|
-
return status.charAt(0).toUpperCase() + status.slice(1);
|
|
797
|
-
}
|
|
798
|
-
function paymentRowToWebhookDto(p) {
|
|
799
|
-
const currency = String(p.currency || "INR");
|
|
800
|
-
const amountMajor = Number(p.amount);
|
|
801
|
-
const meta = p.metadata || {};
|
|
802
|
-
return {
|
|
803
|
-
id: String(p.externalReference || `payment_${p.id}`),
|
|
804
|
-
amount: amountToMinorUnits(amountMajor, currency),
|
|
805
|
-
currency_code: currency,
|
|
806
|
-
captured_at: p.paidAt ? new Date(p.paidAt).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
807
|
-
provider_id: String(p.method || "unknown"),
|
|
808
|
-
data: { status: "captured", ...meta }
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId) {
|
|
812
|
-
try {
|
|
813
|
-
const configRepo = dataSource.getRepository(entityMap.configs);
|
|
814
|
-
const cfgRows = await configRepo.find({ where: { settings: "erp", deleted: false } });
|
|
815
|
-
for (const row of cfgRows) {
|
|
816
|
-
const r = row;
|
|
817
|
-
if (r.key === "enabled" && r.value === "false") return;
|
|
818
|
-
}
|
|
819
|
-
if (!cms.getPlugin("erp")) return;
|
|
820
|
-
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
821
|
-
const ord = await orderRepo.findOne({
|
|
822
|
-
where: { id: orderId },
|
|
823
|
-
relations: ["items", "items.product", "contact", "billingAddress", "shippingAddress", "payments"]
|
|
824
|
-
});
|
|
825
|
-
if (!ord) return;
|
|
826
|
-
const o = ord;
|
|
827
|
-
const okKind = o.orderKind === void 0 || o.orderKind === null || o.orderKind === "sale";
|
|
828
|
-
if (!okKind) return;
|
|
829
|
-
const rawPayments = o.payments ?? [];
|
|
830
|
-
const completedPayments = rawPayments.filter((pay) => pay.status === "completed" && pay.deleted !== true);
|
|
831
|
-
if (!completedPayments.length) return;
|
|
832
|
-
const rawItems = o.items ?? [];
|
|
833
|
-
const lines = rawItems.filter((it) => it.product).map((it) => {
|
|
834
|
-
const p = it.product;
|
|
835
|
-
const sku = p.sku || `SKU-${p.id}`;
|
|
836
|
-
return {
|
|
837
|
-
sku,
|
|
838
|
-
quantity: Number(it.quantity) || 1,
|
|
839
|
-
unitPrice: Number(it.unitPrice),
|
|
840
|
-
title: p.name || sku,
|
|
841
|
-
discount: 0,
|
|
842
|
-
tax: Number(it.tax) || 0
|
|
843
|
-
};
|
|
844
|
-
});
|
|
845
|
-
if (!lines.length) return;
|
|
846
|
-
const contact = o.contact;
|
|
847
|
-
const paymentDtos = completedPayments.map((pay) => paymentRowToWebhookDto(pay));
|
|
848
|
-
const baseMeta = o.metadata && typeof o.metadata === "object" && !Array.isArray(o.metadata) ? { ...o.metadata } : {};
|
|
849
|
-
const orderDto = {
|
|
850
|
-
platformType: "website",
|
|
851
|
-
platformOrderId: String(o.orderNumber),
|
|
852
|
-
platformOrderNumber: String(o.orderNumber),
|
|
853
|
-
status: orderStatusLabel(o.status),
|
|
854
|
-
customer: {
|
|
855
|
-
name: contact?.name || "",
|
|
856
|
-
email: contact?.email || "",
|
|
857
|
-
phone: contact?.phone || ""
|
|
858
|
-
},
|
|
859
|
-
shippingAddress: addressToWebhookDto(o.shippingAddress),
|
|
860
|
-
billingAddress: addressToWebhookDto(o.billingAddress),
|
|
861
|
-
items: lines,
|
|
862
|
-
payments: paymentDtos,
|
|
863
|
-
metadata: { ...baseMeta, source: "storefront" }
|
|
864
|
-
};
|
|
865
|
-
await queueErp(cms, { kind: "order", order: orderDto });
|
|
866
|
-
} catch {
|
|
867
|
-
}
|
|
868
|
-
}
|
|
896
|
+
// src/plugins/erp/index.ts
|
|
897
|
+
init_erp_queue();
|
|
898
|
+
init_paid_order_erp();
|
|
869
899
|
|
|
870
900
|
// src/plugins/erp/erp-contact-sync.ts
|
|
901
|
+
init_erp_queue();
|
|
871
902
|
function splitName(full) {
|
|
872
903
|
const t = (full || "").trim();
|
|
873
904
|
if (!t) return { firstName: "Contact", lastName: "" };
|
|
@@ -1111,6 +1142,7 @@ init_erp_order_invoice();
|
|
|
1111
1142
|
init_erp_config_enabled();
|
|
1112
1143
|
|
|
1113
1144
|
// src/plugins/erp/erp-product-sync.ts
|
|
1145
|
+
init_erp_queue();
|
|
1114
1146
|
init_erp_config_enabled();
|
|
1115
1147
|
async function queueErpProductUpsertIfEnabled(cms, dataSource, entityMap, product) {
|
|
1116
1148
|
try {
|
|
@@ -1118,14 +1150,21 @@ async function queueErpProductUpsertIfEnabled(cms, dataSource, entityMap, produc
|
|
|
1118
1150
|
if (!sku) return;
|
|
1119
1151
|
const on = await isErpIntegrationEnabled(cms, dataSource, entityMap);
|
|
1120
1152
|
if (!on) return;
|
|
1153
|
+
const rawMeta = product.metadata;
|
|
1154
|
+
let metadata;
|
|
1155
|
+
if (rawMeta && typeof rawMeta === "object" && !Array.isArray(rawMeta)) {
|
|
1156
|
+
const { description: _d, ...rest } = rawMeta;
|
|
1157
|
+
metadata = Object.keys(rest).length ? rest : void 0;
|
|
1158
|
+
}
|
|
1121
1159
|
const payload = {
|
|
1122
1160
|
sku,
|
|
1123
1161
|
title: product.name || sku,
|
|
1124
1162
|
name: product.name,
|
|
1125
|
-
description: typeof product.metadata === "object" && product.metadata && "description" in product.metadata ? String(product.metadata.description ?? "") : void 0,
|
|
1126
1163
|
hsn_number: product.hsn,
|
|
1164
|
+
uom: product.uom != null && String(product.uom).trim() ? String(product.uom).trim() : void 0,
|
|
1165
|
+
type: product.type === "service" ? "service" : "product",
|
|
1127
1166
|
is_active: product.status === "available",
|
|
1128
|
-
metadata
|
|
1167
|
+
metadata
|
|
1129
1168
|
};
|
|
1130
1169
|
await queueErp(cms, { kind: "productUpsert", product: payload });
|
|
1131
1170
|
} catch {
|
|
@@ -1411,8 +1450,10 @@ function renderLineItemsHtml(items, currency) {
|
|
|
1411
1450
|
const rows = items.map((it) => {
|
|
1412
1451
|
const name = escapeHtml2(it.productName);
|
|
1413
1452
|
const sku = it.sku && String(it.sku).trim() ? `<span style="font-size:12px;color:#888;"> (${escapeHtml2(String(it.sku).trim())})</span>` : "";
|
|
1453
|
+
const hsn = it.hsn && String(it.hsn).trim() ? `<br/><span style="font-size:11px;color:#888;">HSN: ${escapeHtml2(String(it.hsn).trim())}</span>` : "";
|
|
1454
|
+
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>` : "";
|
|
1414
1455
|
return `<tr>
|
|
1415
|
-
<td style="padding:10px 8px 10px 0;border-bottom:1px solid #eee;vertical-align:top;font-size:14px;color:#333;">${name}${sku}</td>
|
|
1456
|
+
<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>
|
|
1416
1457
|
<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>
|
|
1417
1458
|
<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>
|
|
1418
1459
|
<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>
|
|
@@ -1431,24 +1472,64 @@ ${rows}
|
|
|
1431
1472
|
}
|
|
1432
1473
|
function renderLineItemsText(items, currency) {
|
|
1433
1474
|
if (!items.length) return "";
|
|
1434
|
-
const lines = items.map(
|
|
1435
|
-
|
|
1436
|
-
|
|
1475
|
+
const lines = items.map((it) => {
|
|
1476
|
+
let s = `- ${it.productName} \xD7 ${it.quantity} @ ${formatMoney(it.unitPrice, currency)} = ${formatMoney(it.lineTotal, currency)}`;
|
|
1477
|
+
if (it.sku) s += ` [${it.sku}]`;
|
|
1478
|
+
if (it.hsn && String(it.hsn).trim()) s += ` HSN:${it.hsn}`;
|
|
1479
|
+
if (it.tax != null && Number(it.tax) !== 0) s += ` tax:${formatMoney(it.tax, currency)}`;
|
|
1480
|
+
return s;
|
|
1481
|
+
});
|
|
1437
1482
|
return ["Items:", ...lines].join("\n");
|
|
1438
1483
|
}
|
|
1484
|
+
function formatAddressLines(addr) {
|
|
1485
|
+
if (!addr || typeof addr !== "object") return [];
|
|
1486
|
+
const parts = [addr.line1, addr.line2, [addr.city, addr.state].filter(Boolean).join(", "), addr.postalCode, addr.country].filter(
|
|
1487
|
+
(x) => x != null && String(x).trim() !== ""
|
|
1488
|
+
);
|
|
1489
|
+
return parts.map((p) => String(p).trim()).filter(Boolean);
|
|
1490
|
+
}
|
|
1491
|
+
function renderAddressesHtml(billing, shipping) {
|
|
1492
|
+
const bLines = formatAddressLines(billing);
|
|
1493
|
+
const sLines = formatAddressLines(shipping);
|
|
1494
|
+
if (!bLines.length && !sLines.length) return "";
|
|
1495
|
+
let html = '<p style="margin:16px 0 8px 0;font-size:14px;font-weight:600;color:#111;">Addresses</p>';
|
|
1496
|
+
if (bLines.length) {
|
|
1497
|
+
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>`;
|
|
1498
|
+
}
|
|
1499
|
+
if (sLines.length) {
|
|
1500
|
+
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>`;
|
|
1501
|
+
}
|
|
1502
|
+
return html;
|
|
1503
|
+
}
|
|
1504
|
+
function renderAddressesText(billing, shipping) {
|
|
1505
|
+
const bLines = formatAddressLines(billing);
|
|
1506
|
+
const sLines = formatAddressLines(shipping);
|
|
1507
|
+
const out = [];
|
|
1508
|
+
if (bLines.length) out.push("Billing:", ...bLines.map((l) => ` ${l}`));
|
|
1509
|
+
if (sLines.length) out.push("Shipping:", ...sLines.map((l) => ` ${l}`));
|
|
1510
|
+
return out.length ? out.join("\n") : "";
|
|
1511
|
+
}
|
|
1439
1512
|
function render4(ctx) {
|
|
1440
1513
|
const {
|
|
1441
1514
|
orderNumber,
|
|
1442
1515
|
total,
|
|
1516
|
+
subtotal,
|
|
1517
|
+
tax,
|
|
1443
1518
|
currency,
|
|
1444
1519
|
customerName,
|
|
1445
1520
|
companyDetails,
|
|
1446
1521
|
audience = "customer",
|
|
1447
1522
|
internalCustomerEmail,
|
|
1448
|
-
lineItems = []
|
|
1523
|
+
lineItems = [],
|
|
1524
|
+
billingAddress,
|
|
1525
|
+
shippingAddress
|
|
1449
1526
|
} = ctx;
|
|
1450
1527
|
const itemsHtml = renderLineItemsHtml(lineItems, currency);
|
|
1451
1528
|
const itemsText = renderLineItemsText(lineItems, currency);
|
|
1529
|
+
const addrHtml = renderAddressesHtml(billingAddress, shippingAddress);
|
|
1530
|
+
const addrText = renderAddressesText(billingAddress, shippingAddress);
|
|
1531
|
+
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>` : "";
|
|
1532
|
+
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>` : "";
|
|
1452
1533
|
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>` : "";
|
|
1453
1534
|
let subject;
|
|
1454
1535
|
let bodyHtml;
|
|
@@ -1459,13 +1540,17 @@ function render4(ctx) {
|
|
|
1459
1540
|
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>
|
|
1460
1541
|
<p style="margin:0 0 8px 0;font-size:15px;line-height:1.5;color:#333;"><strong>Order number:</strong> ${escapeHtml2(orderNumber)}</p>
|
|
1461
1542
|
${who}
|
|
1543
|
+
${addrHtml}
|
|
1462
1544
|
${itemsHtml}
|
|
1463
|
-
${totalLine}`;
|
|
1545
|
+
${subtotalLine}${taxLine}${totalLine}`;
|
|
1464
1546
|
text = [
|
|
1465
1547
|
`New order #${orderNumber}`,
|
|
1466
1548
|
customerName ? `Customer: ${customerName}` : "",
|
|
1467
1549
|
internalCustomerEmail ? `Email: ${internalCustomerEmail}` : "",
|
|
1550
|
+
addrText,
|
|
1468
1551
|
itemsText,
|
|
1552
|
+
subtotal != null ? `Subtotal: ${subtotal}${currency ? ` ${currency}` : ""}` : "",
|
|
1553
|
+
tax != null && Number(tax) !== 0 ? `Tax: ${tax}${currency ? ` ${currency}` : ""}` : "",
|
|
1469
1554
|
total != null ? `Order total: ${total}${currency ? ` ${currency}` : ""}` : ""
|
|
1470
1555
|
].filter(Boolean).join("\n\n");
|
|
1471
1556
|
} else {
|
|
@@ -1474,14 +1559,18 @@ ${totalLine}`;
|
|
|
1474
1559
|
const thanksHtml = `${escapeHtml2(thanksPlain)} We\u2019ve received your order and will process it shortly.`;
|
|
1475
1560
|
bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${thanksHtml}</p>
|
|
1476
1561
|
<p style="margin:0 0 8px 0;font-size:15px;line-height:1.5;color:#333;"><strong>Order number:</strong> ${escapeHtml2(orderNumber)}</p>
|
|
1562
|
+
${addrHtml}
|
|
1477
1563
|
${itemsHtml}
|
|
1478
|
-
${totalLine}
|
|
1564
|
+
${subtotalLine}${taxLine}${totalLine}
|
|
1479
1565
|
<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>`;
|
|
1480
1566
|
text = [
|
|
1481
1567
|
`Order confirmed #${orderNumber}`,
|
|
1482
1568
|
`${thanksPlain} We\u2019ve received your order and will process it shortly.`,
|
|
1483
1569
|
"",
|
|
1570
|
+
addrText,
|
|
1484
1571
|
itemsText,
|
|
1572
|
+
subtotal != null ? `Subtotal: ${subtotal}${currency ? ` ${currency}` : ""}` : "",
|
|
1573
|
+
tax != null && Number(tax) !== 0 ? `Tax: ${tax}${currency ? ` ${currency}` : ""}` : "",
|
|
1485
1574
|
total != null ? `Order total: ${total}${currency ? ` ${currency}` : ""}` : "",
|
|
1486
1575
|
"",
|
|
1487
1576
|
"We will process your order shortly."
|
|
@@ -4879,6 +4968,8 @@ var Product = class {
|
|
|
4879
4968
|
categoryId;
|
|
4880
4969
|
sku;
|
|
4881
4970
|
hsn;
|
|
4971
|
+
uom;
|
|
4972
|
+
type;
|
|
4882
4973
|
slug;
|
|
4883
4974
|
name;
|
|
4884
4975
|
price;
|
|
@@ -4920,6 +5011,12 @@ __decorateClass([
|
|
|
4920
5011
|
__decorateClass([
|
|
4921
5012
|
Column27("varchar", { nullable: true })
|
|
4922
5013
|
], Product.prototype, "hsn", 2);
|
|
5014
|
+
__decorateClass([
|
|
5015
|
+
Column27("varchar", { nullable: true })
|
|
5016
|
+
], Product.prototype, "uom", 2);
|
|
5017
|
+
__decorateClass([
|
|
5018
|
+
Column27("varchar", { default: "product" })
|
|
5019
|
+
], Product.prototype, "type", 2);
|
|
4923
5020
|
__decorateClass([
|
|
4924
5021
|
Column27("varchar", { unique: true, nullable: true })
|
|
4925
5022
|
], Product.prototype, "slug", 2);
|
|
@@ -5228,6 +5325,11 @@ var OrderItem = class {
|
|
|
5228
5325
|
unitPrice;
|
|
5229
5326
|
tax;
|
|
5230
5327
|
total;
|
|
5328
|
+
hsn;
|
|
5329
|
+
uom;
|
|
5330
|
+
productType;
|
|
5331
|
+
taxRate;
|
|
5332
|
+
taxCode;
|
|
5231
5333
|
metadata;
|
|
5232
5334
|
createdAt;
|
|
5233
5335
|
updatedAt;
|
|
@@ -5255,6 +5357,21 @@ __decorateClass([
|
|
|
5255
5357
|
__decorateClass([
|
|
5256
5358
|
Column32("decimal", { precision: 12, scale: 2 })
|
|
5257
5359
|
], OrderItem.prototype, "total", 2);
|
|
5360
|
+
__decorateClass([
|
|
5361
|
+
Column32("varchar", { nullable: true })
|
|
5362
|
+
], OrderItem.prototype, "hsn", 2);
|
|
5363
|
+
__decorateClass([
|
|
5364
|
+
Column32("varchar", { nullable: true })
|
|
5365
|
+
], OrderItem.prototype, "uom", 2);
|
|
5366
|
+
__decorateClass([
|
|
5367
|
+
Column32("varchar", { nullable: true })
|
|
5368
|
+
], OrderItem.prototype, "productType", 2);
|
|
5369
|
+
__decorateClass([
|
|
5370
|
+
Column32("decimal", { precision: 5, scale: 2, nullable: true })
|
|
5371
|
+
], OrderItem.prototype, "taxRate", 2);
|
|
5372
|
+
__decorateClass([
|
|
5373
|
+
Column32("varchar", { nullable: true })
|
|
5374
|
+
], OrderItem.prototype, "taxCode", 2);
|
|
5258
5375
|
__decorateClass([
|
|
5259
5376
|
Column32("jsonb", { nullable: true })
|
|
5260
5377
|
], OrderItem.prototype, "metadata", 2);
|
|
@@ -6151,6 +6268,24 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
6151
6268
|
} else if (search) {
|
|
6152
6269
|
where = buildSearchWhereClause(repo, search);
|
|
6153
6270
|
}
|
|
6271
|
+
const intFilterKeys = ["productId", "attributeId", "taxId"];
|
|
6272
|
+
const extraWhere = {};
|
|
6273
|
+
for (const key of intFilterKeys) {
|
|
6274
|
+
const v = searchParams.get(key);
|
|
6275
|
+
if (v != null && v !== "" && columnNames.has(key)) {
|
|
6276
|
+
const n = Number(v);
|
|
6277
|
+
if (Number.isFinite(n)) extraWhere[key] = n;
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
if (Object.keys(extraWhere).length > 0) {
|
|
6281
|
+
if (Array.isArray(where)) {
|
|
6282
|
+
where = where.map((w) => ({ ...w, ...extraWhere }));
|
|
6283
|
+
} else if (where && typeof where === "object" && Object.keys(where).length > 0) {
|
|
6284
|
+
where = { ...where, ...extraWhere };
|
|
6285
|
+
} else {
|
|
6286
|
+
where = extraWhere;
|
|
6287
|
+
}
|
|
6288
|
+
}
|
|
6154
6289
|
const [data, total] = await repo.findAndCount({
|
|
6155
6290
|
skip,
|
|
6156
6291
|
take: limit,
|
|
@@ -6607,6 +6742,7 @@ function createUserAuthApiRouter(config) {
|
|
|
6607
6742
|
|
|
6608
6743
|
// src/api/cms-handlers.ts
|
|
6609
6744
|
init_email_queue();
|
|
6745
|
+
init_erp_queue();
|
|
6610
6746
|
import { MoreThanOrEqual, ILike as ILike2 } from "typeorm";
|
|
6611
6747
|
function createDashboardStatsHandler(config) {
|
|
6612
6748
|
const { dataSource, entityMap, json, requireAuth, requirePermission, requireEntityPermission } = config;
|
|
@@ -8009,6 +8145,29 @@ function createCmsApiHandler(config) {
|
|
|
8009
8145
|
if (!Number.isFinite(oid)) return config.json({ error: "Invalid id" }, { status: 400 });
|
|
8010
8146
|
return streamOrderInvoicePdf2(cms, dataSource, entityMap, oid, {});
|
|
8011
8147
|
}
|
|
8148
|
+
if (path[0] === "orders" && path.length === 3 && path[2] === "repost-erp" && getCms) {
|
|
8149
|
+
const a = await config.requireAuth(req);
|
|
8150
|
+
if (a) return a;
|
|
8151
|
+
if (perm) {
|
|
8152
|
+
const pe = await perm(req, "orders", method === "GET" ? "read" : "update");
|
|
8153
|
+
if (pe) return pe;
|
|
8154
|
+
}
|
|
8155
|
+
const oid = Number(path[1]);
|
|
8156
|
+
if (!Number.isFinite(oid)) return config.json({ error: "Invalid id" }, { status: 400 });
|
|
8157
|
+
const cms = await getCms();
|
|
8158
|
+
const { isErpIntegrationEnabled: isErpIntegrationEnabled3 } = await Promise.resolve().then(() => (init_erp_config_enabled(), erp_config_enabled_exports));
|
|
8159
|
+
const enabled = await isErpIntegrationEnabled3(cms, dataSource, entityMap);
|
|
8160
|
+
if (method === "GET") {
|
|
8161
|
+
return config.json({ enabled });
|
|
8162
|
+
}
|
|
8163
|
+
if (method === "POST") {
|
|
8164
|
+
if (!enabled) return config.json({ error: "ERP integration is disabled" }, { status: 409 });
|
|
8165
|
+
const { queueErpPaidOrderForOrderId: queueErpPaidOrderForOrderId2 } = await Promise.resolve().then(() => (init_paid_order_erp(), paid_order_erp_exports));
|
|
8166
|
+
await queueErpPaidOrderForOrderId2(cms, dataSource, entityMap, oid);
|
|
8167
|
+
return config.json({ ok: true });
|
|
8168
|
+
}
|
|
8169
|
+
return config.json({ error: "Method not allowed" }, { status: 405 });
|
|
8170
|
+
}
|
|
8012
8171
|
if (path.length === 0) return config.json({ error: "Not found" }, { status: 404 });
|
|
8013
8172
|
const resource = resolveResource(path[0]);
|
|
8014
8173
|
if (!crudResources.includes(resource)) return config.json({ error: "Invalid resource" }, { status: 400 });
|
|
@@ -8105,6 +8264,152 @@ function createStorefrontApiHandler(config) {
|
|
|
8105
8264
|
const tokenRepo = () => dataSource.getRepository(entityMap.password_reset_tokens);
|
|
8106
8265
|
const collectionRepo = () => dataSource.getRepository(entityMap.collections);
|
|
8107
8266
|
const groupRepo = () => dataSource.getRepository(entityMap.user_groups);
|
|
8267
|
+
const configRepo = () => dataSource.getRepository(entityMap.configs);
|
|
8268
|
+
const CART_CHECKOUT_RELATIONS = ["items", "items.product", "items.product.taxes", "items.product.taxes.tax"];
|
|
8269
|
+
function roundMoney2(n) {
|
|
8270
|
+
return Math.round(n * 100) / 100;
|
|
8271
|
+
}
|
|
8272
|
+
async function getStoreDefaultTaxRate() {
|
|
8273
|
+
const rows = await configRepo().find({ where: { settings: "store", deleted: false } });
|
|
8274
|
+
for (const row of rows) {
|
|
8275
|
+
const r = row;
|
|
8276
|
+
if (r.key === "defaultTaxRate") {
|
|
8277
|
+
const n = parseFloat(String(r.value ?? "").trim());
|
|
8278
|
+
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
8279
|
+
}
|
|
8280
|
+
}
|
|
8281
|
+
return null;
|
|
8282
|
+
}
|
|
8283
|
+
function computeTaxForProductLine(p, lineSubtotal, defaultRate) {
|
|
8284
|
+
const pts = p.taxes ?? [];
|
|
8285
|
+
const activePts = pts.filter((pt) => {
|
|
8286
|
+
const t = pt.tax;
|
|
8287
|
+
return t != null && t.active !== false;
|
|
8288
|
+
});
|
|
8289
|
+
if (activePts.length) {
|
|
8290
|
+
let sumRate = 0;
|
|
8291
|
+
const slugs = [];
|
|
8292
|
+
for (const pt of activePts) {
|
|
8293
|
+
const t = pt.tax;
|
|
8294
|
+
const r = Number(pt.rate != null && pt.rate !== "" ? pt.rate : t.rate ?? 0);
|
|
8295
|
+
if (Number.isFinite(r)) sumRate += r;
|
|
8296
|
+
const slug = String(t.slug ?? "").trim();
|
|
8297
|
+
if (slug) slugs.push(slug);
|
|
8298
|
+
}
|
|
8299
|
+
const tax = roundMoney2(lineSubtotal * sumRate / 100);
|
|
8300
|
+
return {
|
|
8301
|
+
tax,
|
|
8302
|
+
taxRate: sumRate > 0 ? roundMoney2(sumRate) : null,
|
|
8303
|
+
taxCode: slugs.length ? [...new Set(slugs)].sort().join(",") : null
|
|
8304
|
+
};
|
|
8305
|
+
}
|
|
8306
|
+
if (defaultRate != null && defaultRate > 0) {
|
|
8307
|
+
return {
|
|
8308
|
+
tax: roundMoney2(lineSubtotal * defaultRate / 100),
|
|
8309
|
+
taxRate: roundMoney2(defaultRate),
|
|
8310
|
+
taxCode: null
|
|
8311
|
+
};
|
|
8312
|
+
}
|
|
8313
|
+
return { tax: 0, taxRate: null, taxCode: null };
|
|
8314
|
+
}
|
|
8315
|
+
function parseInlineAddress(raw) {
|
|
8316
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
8317
|
+
const o = raw;
|
|
8318
|
+
const line1 = String(o.line1 ?? "").trim();
|
|
8319
|
+
if (!line1) return null;
|
|
8320
|
+
return {
|
|
8321
|
+
line1,
|
|
8322
|
+
line2: o.line2 != null ? String(o.line2) : "",
|
|
8323
|
+
city: o.city != null ? String(o.city) : "",
|
|
8324
|
+
state: o.state != null ? String(o.state) : "",
|
|
8325
|
+
postalCode: o.postalCode != null ? String(o.postalCode) : "",
|
|
8326
|
+
country: o.country != null ? String(o.country) : ""
|
|
8327
|
+
};
|
|
8328
|
+
}
|
|
8329
|
+
function intFromBody(v) {
|
|
8330
|
+
if (typeof v === "number" && Number.isInteger(v)) return v;
|
|
8331
|
+
if (typeof v === "string" && /^\d+$/.test(v)) return parseInt(v, 10);
|
|
8332
|
+
return void 0;
|
|
8333
|
+
}
|
|
8334
|
+
async function resolveCheckoutAddress(contactId, idVal, inlineVal) {
|
|
8335
|
+
const aid = intFromBody(idVal);
|
|
8336
|
+
if (aid != null) {
|
|
8337
|
+
const existing = await addressRepo().findOne({
|
|
8338
|
+
where: { id: aid, contactId }
|
|
8339
|
+
});
|
|
8340
|
+
if (!existing) return { id: null, error: "Address not found" };
|
|
8341
|
+
return { id: aid };
|
|
8342
|
+
}
|
|
8343
|
+
const addr = parseInlineAddress(inlineVal);
|
|
8344
|
+
if (addr) {
|
|
8345
|
+
const saved = await addressRepo().save(
|
|
8346
|
+
addressRepo().create({
|
|
8347
|
+
contactId,
|
|
8348
|
+
line1: addr.line1,
|
|
8349
|
+
line2: addr.line2?.trim() ? addr.line2 : null,
|
|
8350
|
+
city: addr.city?.trim() ? addr.city : null,
|
|
8351
|
+
state: addr.state?.trim() ? addr.state : null,
|
|
8352
|
+
postalCode: addr.postalCode?.trim() ? addr.postalCode : null,
|
|
8353
|
+
country: addr.country?.trim() ? addr.country : null
|
|
8354
|
+
})
|
|
8355
|
+
);
|
|
8356
|
+
return { id: saved.id };
|
|
8357
|
+
}
|
|
8358
|
+
return { id: null };
|
|
8359
|
+
}
|
|
8360
|
+
async function prepareCheckoutFromCart(b, cart, contactId) {
|
|
8361
|
+
const defaultRate = await getStoreDefaultTaxRate();
|
|
8362
|
+
const lines = [];
|
|
8363
|
+
let subtotal = 0;
|
|
8364
|
+
let orderTax = 0;
|
|
8365
|
+
let needsShipping = false;
|
|
8366
|
+
for (const it of cart.items || []) {
|
|
8367
|
+
const p = it.product;
|
|
8368
|
+
if (!p || p.deleted || p.status !== "available") continue;
|
|
8369
|
+
const unit = Number(p.price);
|
|
8370
|
+
const qty = it.quantity || 1;
|
|
8371
|
+
const lineSubtotal = unit * qty;
|
|
8372
|
+
const pType = p.type === "service" ? "service" : "product";
|
|
8373
|
+
if (pType === "product") needsShipping = true;
|
|
8374
|
+
const { tax, taxRate, taxCode } = computeTaxForProductLine(p, lineSubtotal, defaultRate);
|
|
8375
|
+
const lineTotal = roundMoney2(lineSubtotal + tax);
|
|
8376
|
+
subtotal = roundMoney2(subtotal + lineSubtotal);
|
|
8377
|
+
orderTax = roundMoney2(orderTax + tax);
|
|
8378
|
+
lines.push({
|
|
8379
|
+
productId: p.id,
|
|
8380
|
+
quantity: qty,
|
|
8381
|
+
unitPrice: unit,
|
|
8382
|
+
tax,
|
|
8383
|
+
total: lineTotal,
|
|
8384
|
+
hsn: p.hsn ?? null,
|
|
8385
|
+
uom: p.uom ?? null,
|
|
8386
|
+
productType: pType,
|
|
8387
|
+
taxRate,
|
|
8388
|
+
taxCode
|
|
8389
|
+
});
|
|
8390
|
+
}
|
|
8391
|
+
if (!lines.length) return { ok: false, status: 400, message: "No available items in cart" };
|
|
8392
|
+
const bill = await resolveCheckoutAddress(contactId, b.billingAddressId, b.billingAddress);
|
|
8393
|
+
if (bill.error) return { ok: false, status: 400, message: bill.error };
|
|
8394
|
+
if (bill.id == null) return { ok: false, status: 400, message: "Billing address required" };
|
|
8395
|
+
const ship = await resolveCheckoutAddress(contactId, b.shippingAddressId, b.shippingAddress);
|
|
8396
|
+
if (ship.error) return { ok: false, status: 400, message: ship.error };
|
|
8397
|
+
let shippingAddressId = ship.id;
|
|
8398
|
+
if (needsShipping && shippingAddressId == null) shippingAddressId = bill.id;
|
|
8399
|
+
if (needsShipping && shippingAddressId == null) {
|
|
8400
|
+
return { ok: false, status: 400, message: "Shipping address required" };
|
|
8401
|
+
}
|
|
8402
|
+
const orderTotal = roundMoney2(subtotal + orderTax);
|
|
8403
|
+
return {
|
|
8404
|
+
ok: true,
|
|
8405
|
+
lines,
|
|
8406
|
+
subtotal,
|
|
8407
|
+
orderTax,
|
|
8408
|
+
orderTotal,
|
|
8409
|
+
billingAddressId: bill.id,
|
|
8410
|
+
shippingAddressId
|
|
8411
|
+
};
|
|
8412
|
+
}
|
|
8108
8413
|
async function syncContactToErp(contact) {
|
|
8109
8414
|
if (!getCms) return;
|
|
8110
8415
|
try {
|
|
@@ -8230,6 +8535,7 @@ function createStorefrontApiHandler(config) {
|
|
|
8230
8535
|
slug: p.slug,
|
|
8231
8536
|
price: p.price,
|
|
8232
8537
|
sku: p.sku,
|
|
8538
|
+
type: p.type === "service" ? "service" : "product",
|
|
8233
8539
|
image: primaryProductImageUrl(p.metadata)
|
|
8234
8540
|
} : null
|
|
8235
8541
|
};
|
|
@@ -8257,6 +8563,8 @@ function createStorefrontApiHandler(config) {
|
|
|
8257
8563
|
slug: p.slug,
|
|
8258
8564
|
sku: p.sku,
|
|
8259
8565
|
hsn: p.hsn,
|
|
8566
|
+
uom: p.uom ?? null,
|
|
8567
|
+
type: p.type === "service" ? "service" : "product",
|
|
8260
8568
|
price: p.price,
|
|
8261
8569
|
compareAtPrice: p.compareAtPrice,
|
|
8262
8570
|
status: p.status,
|
|
@@ -8958,7 +9266,7 @@ function createStorefrontApiHandler(config) {
|
|
|
8958
9266
|
contactId = contact.id;
|
|
8959
9267
|
cart = await cartRepo().findOne({
|
|
8960
9268
|
where: { contactId },
|
|
8961
|
-
relations: [
|
|
9269
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
8962
9270
|
});
|
|
8963
9271
|
} else {
|
|
8964
9272
|
const email = String(b.email ?? "").trim();
|
|
@@ -8991,25 +9299,14 @@ function createStorefrontApiHandler(config) {
|
|
|
8991
9299
|
if (!guestToken) return json({ error: "Cart not found" }, { status: 400 });
|
|
8992
9300
|
cart = await cartRepo().findOne({
|
|
8993
9301
|
where: { guestToken },
|
|
8994
|
-
relations: [
|
|
9302
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
8995
9303
|
});
|
|
8996
9304
|
}
|
|
8997
9305
|
if (!cart || !(cart.items || []).length) {
|
|
8998
9306
|
return json({ error: "Cart is empty" }, { status: 400 });
|
|
8999
9307
|
}
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
for (const it of cart.items || []) {
|
|
9003
|
-
const p = it.product;
|
|
9004
|
-
if (!p || p.deleted || p.status !== "available") continue;
|
|
9005
|
-
const unit = Number(p.price);
|
|
9006
|
-
const qty = it.quantity || 1;
|
|
9007
|
-
const lineTotal = unit * qty;
|
|
9008
|
-
subtotal += lineTotal;
|
|
9009
|
-
lines.push({ productId: p.id, quantity: qty, unitPrice: unit, tax: 0, total: lineTotal });
|
|
9010
|
-
}
|
|
9011
|
-
if (!lines.length) return json({ error: "No available items in cart" }, { status: 400 });
|
|
9012
|
-
const total = subtotal;
|
|
9308
|
+
const prepOrd = await prepareCheckoutFromCart(b, cart, contactId);
|
|
9309
|
+
if (!prepOrd.ok) return json({ error: prepOrd.message }, { status: prepOrd.status });
|
|
9013
9310
|
const cartId = cart.id;
|
|
9014
9311
|
const ord = await orderRepo().save(
|
|
9015
9312
|
orderRepo().create({
|
|
@@ -9017,13 +9314,13 @@ function createStorefrontApiHandler(config) {
|
|
|
9017
9314
|
orderKind: "sale",
|
|
9018
9315
|
parentOrderId: null,
|
|
9019
9316
|
contactId,
|
|
9020
|
-
billingAddressId:
|
|
9021
|
-
shippingAddressId:
|
|
9317
|
+
billingAddressId: prepOrd.billingAddressId,
|
|
9318
|
+
shippingAddressId: prepOrd.shippingAddressId,
|
|
9022
9319
|
status: "pending",
|
|
9023
|
-
subtotal,
|
|
9024
|
-
tax:
|
|
9320
|
+
subtotal: prepOrd.subtotal,
|
|
9321
|
+
tax: prepOrd.orderTax,
|
|
9025
9322
|
discount: 0,
|
|
9026
|
-
total,
|
|
9323
|
+
total: prepOrd.orderTotal,
|
|
9027
9324
|
currency: cart.currency || "INR",
|
|
9028
9325
|
metadata: { cartId }
|
|
9029
9326
|
})
|
|
@@ -9032,7 +9329,7 @@ function createStorefrontApiHandler(config) {
|
|
|
9032
9329
|
await orderRepo().update(oid, {
|
|
9033
9330
|
orderNumber: buildCanonicalOrderNumber("sale", oid, ord.createdAt ?? /* @__PURE__ */ new Date())
|
|
9034
9331
|
});
|
|
9035
|
-
for (const line of lines) {
|
|
9332
|
+
for (const line of prepOrd.lines) {
|
|
9036
9333
|
await orderItemRepo().save(
|
|
9037
9334
|
orderItemRepo().create({
|
|
9038
9335
|
orderId: oid,
|
|
@@ -9040,14 +9337,21 @@ function createStorefrontApiHandler(config) {
|
|
|
9040
9337
|
quantity: line.quantity,
|
|
9041
9338
|
unitPrice: line.unitPrice,
|
|
9042
9339
|
tax: line.tax,
|
|
9043
|
-
total: line.total
|
|
9340
|
+
total: line.total,
|
|
9341
|
+
hsn: line.hsn,
|
|
9342
|
+
uom: line.uom,
|
|
9343
|
+
productType: line.productType,
|
|
9344
|
+
taxRate: line.taxRate,
|
|
9345
|
+
taxCode: line.taxCode
|
|
9044
9346
|
})
|
|
9045
9347
|
);
|
|
9046
9348
|
}
|
|
9047
9349
|
return json({
|
|
9048
9350
|
orderId: oid,
|
|
9049
9351
|
orderNumber: ord.orderNumber,
|
|
9050
|
-
|
|
9352
|
+
subtotal: prepOrd.subtotal,
|
|
9353
|
+
tax: prepOrd.orderTax,
|
|
9354
|
+
total: prepOrd.orderTotal,
|
|
9051
9355
|
currency: cart.currency || "INR"
|
|
9052
9356
|
});
|
|
9053
9357
|
}
|
|
@@ -9065,7 +9369,7 @@ function createStorefrontApiHandler(config) {
|
|
|
9065
9369
|
contactId = contact.id;
|
|
9066
9370
|
cart = await cartRepo().findOne({
|
|
9067
9371
|
where: { contactId },
|
|
9068
|
-
relations: [
|
|
9372
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
9069
9373
|
});
|
|
9070
9374
|
} else {
|
|
9071
9375
|
const email = String(b.email ?? "").trim();
|
|
@@ -9098,38 +9402,27 @@ function createStorefrontApiHandler(config) {
|
|
|
9098
9402
|
if (!guestToken) return json({ error: "Cart not found" }, { status: 400 });
|
|
9099
9403
|
cart = await cartRepo().findOne({
|
|
9100
9404
|
where: { guestToken },
|
|
9101
|
-
relations: [
|
|
9405
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
9102
9406
|
});
|
|
9103
9407
|
}
|
|
9104
9408
|
if (!cart || !(cart.items || []).length) {
|
|
9105
9409
|
return json({ error: "Cart is empty" }, { status: 400 });
|
|
9106
9410
|
}
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
for (const it of cart.items || []) {
|
|
9110
|
-
const p = it.product;
|
|
9111
|
-
if (!p || p.deleted || p.status !== "available") continue;
|
|
9112
|
-
const unit = Number(p.price);
|
|
9113
|
-
const qty = it.quantity || 1;
|
|
9114
|
-
const lineTotal = unit * qty;
|
|
9115
|
-
subtotal += lineTotal;
|
|
9116
|
-
lines.push({ productId: p.id, quantity: qty, unitPrice: unit, tax: 0, total: lineTotal });
|
|
9117
|
-
}
|
|
9118
|
-
if (!lines.length) return json({ error: "No available items in cart" }, { status: 400 });
|
|
9119
|
-
const total = subtotal;
|
|
9411
|
+
const prepChk = await prepareCheckoutFromCart(b, cart, contactId);
|
|
9412
|
+
if (!prepChk.ok) return json({ error: prepChk.message }, { status: prepChk.status });
|
|
9120
9413
|
const ord = await orderRepo().save(
|
|
9121
9414
|
orderRepo().create({
|
|
9122
9415
|
orderNumber: temporaryOrderNumberPlaceholder(),
|
|
9123
9416
|
orderKind: "sale",
|
|
9124
9417
|
parentOrderId: null,
|
|
9125
9418
|
contactId,
|
|
9126
|
-
billingAddressId:
|
|
9127
|
-
shippingAddressId:
|
|
9419
|
+
billingAddressId: prepChk.billingAddressId,
|
|
9420
|
+
shippingAddressId: prepChk.shippingAddressId,
|
|
9128
9421
|
status: "pending",
|
|
9129
|
-
subtotal,
|
|
9130
|
-
tax:
|
|
9422
|
+
subtotal: prepChk.subtotal,
|
|
9423
|
+
tax: prepChk.orderTax,
|
|
9131
9424
|
discount: 0,
|
|
9132
|
-
total,
|
|
9425
|
+
total: prepChk.orderTotal,
|
|
9133
9426
|
currency: cart.currency || "INR"
|
|
9134
9427
|
})
|
|
9135
9428
|
);
|
|
@@ -9137,7 +9430,7 @@ function createStorefrontApiHandler(config) {
|
|
|
9137
9430
|
await orderRepo().update(oid, {
|
|
9138
9431
|
orderNumber: buildCanonicalOrderNumber("sale", oid, ord.createdAt ?? /* @__PURE__ */ new Date())
|
|
9139
9432
|
});
|
|
9140
|
-
for (const line of lines) {
|
|
9433
|
+
for (const line of prepChk.lines) {
|
|
9141
9434
|
await orderItemRepo().save(
|
|
9142
9435
|
orderItemRepo().create({
|
|
9143
9436
|
orderId: oid,
|
|
@@ -9145,7 +9438,12 @@ function createStorefrontApiHandler(config) {
|
|
|
9145
9438
|
quantity: line.quantity,
|
|
9146
9439
|
unitPrice: line.unitPrice,
|
|
9147
9440
|
tax: line.tax,
|
|
9148
|
-
total: line.total
|
|
9441
|
+
total: line.total,
|
|
9442
|
+
hsn: line.hsn,
|
|
9443
|
+
uom: line.uom,
|
|
9444
|
+
productType: line.productType,
|
|
9445
|
+
taxRate: line.taxRate,
|
|
9446
|
+
taxCode: line.taxCode
|
|
9149
9447
|
})
|
|
9150
9448
|
);
|
|
9151
9449
|
}
|
|
@@ -9154,7 +9452,9 @@ function createStorefrontApiHandler(config) {
|
|
|
9154
9452
|
return json({
|
|
9155
9453
|
orderId: oid,
|
|
9156
9454
|
orderNumber: ord.orderNumber,
|
|
9157
|
-
|
|
9455
|
+
subtotal: prepChk.subtotal,
|
|
9456
|
+
tax: prepChk.orderTax,
|
|
9457
|
+
total: prepChk.orderTotal
|
|
9158
9458
|
});
|
|
9159
9459
|
}
|
|
9160
9460
|
if (path[0] === "orders" && path.length === 1 && method === "GET") {
|