@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/api.cjs
CHANGED
|
@@ -30,7 +30,25 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
));
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
|
+
// src/plugins/erp/erp-queue.ts
|
|
34
|
+
async function queueErp(cms, payload) {
|
|
35
|
+
const queue = cms.getPlugin("queue");
|
|
36
|
+
if (!queue) return;
|
|
37
|
+
await queue.add(ERP_QUEUE_NAME, payload);
|
|
38
|
+
}
|
|
39
|
+
var ERP_QUEUE_NAME;
|
|
40
|
+
var init_erp_queue = __esm({
|
|
41
|
+
"src/plugins/erp/erp-queue.ts"() {
|
|
42
|
+
"use strict";
|
|
43
|
+
ERP_QUEUE_NAME = "erp";
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
33
47
|
// src/plugins/erp/erp-config-enabled.ts
|
|
48
|
+
var erp_config_enabled_exports = {};
|
|
49
|
+
__export(erp_config_enabled_exports, {
|
|
50
|
+
isErpIntegrationEnabled: () => isErpIntegrationEnabled
|
|
51
|
+
});
|
|
34
52
|
async function isErpIntegrationEnabled(cms, dataSource, entityMap) {
|
|
35
53
|
if (!cms.getPlugin("erp")) return false;
|
|
36
54
|
const configRepo = dataSource.getRepository(entityMap.configs);
|
|
@@ -85,14 +103,31 @@ async function queueEmail(cms, payload) {
|
|
|
85
103
|
}
|
|
86
104
|
}
|
|
87
105
|
async function queueOrderPlacedEmails(cms, payload) {
|
|
88
|
-
const {
|
|
106
|
+
const {
|
|
107
|
+
orderNumber,
|
|
108
|
+
total,
|
|
109
|
+
subtotal,
|
|
110
|
+
tax,
|
|
111
|
+
currency,
|
|
112
|
+
customerName,
|
|
113
|
+
customerEmail,
|
|
114
|
+
salesTeamEmails,
|
|
115
|
+
companyDetails,
|
|
116
|
+
lineItems,
|
|
117
|
+
billingAddress,
|
|
118
|
+
shippingAddress
|
|
119
|
+
} = payload;
|
|
89
120
|
const base = {
|
|
90
121
|
orderNumber,
|
|
91
122
|
total: total != null ? String(total) : void 0,
|
|
123
|
+
subtotal: subtotal != null ? String(subtotal) : void 0,
|
|
124
|
+
tax: tax != null ? String(tax) : void 0,
|
|
92
125
|
currency,
|
|
93
126
|
customerName,
|
|
94
127
|
companyDetails: companyDetails ?? {},
|
|
95
|
-
lineItems: lineItems ?? []
|
|
128
|
+
lineItems: lineItems ?? [],
|
|
129
|
+
billingAddress,
|
|
130
|
+
shippingAddress
|
|
96
131
|
};
|
|
97
132
|
const customerLower = customerEmail?.trim().toLowerCase() ?? "";
|
|
98
133
|
const jobs = [];
|
|
@@ -270,6 +305,119 @@ var init_erp_order_invoice = __esm({
|
|
|
270
305
|
}
|
|
271
306
|
});
|
|
272
307
|
|
|
308
|
+
// src/plugins/erp/paid-order-erp.ts
|
|
309
|
+
var paid_order_erp_exports = {};
|
|
310
|
+
__export(paid_order_erp_exports, {
|
|
311
|
+
queueErpPaidOrderForOrderId: () => queueErpPaidOrderForOrderId
|
|
312
|
+
});
|
|
313
|
+
function roundMoney(major) {
|
|
314
|
+
return Math.round(major * 100) / 100;
|
|
315
|
+
}
|
|
316
|
+
function addressToWebhookDto(a) {
|
|
317
|
+
if (!a) return {};
|
|
318
|
+
return {
|
|
319
|
+
line1: a.line1 ?? "",
|
|
320
|
+
line2: a.line2 ?? "",
|
|
321
|
+
city: a.city ?? "",
|
|
322
|
+
state: a.state ?? "",
|
|
323
|
+
postalCode: a.postalCode ?? "",
|
|
324
|
+
country: a.country ?? ""
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function orderStatusLabel(status) {
|
|
328
|
+
const s = (status || "").toLowerCase();
|
|
329
|
+
if (s === "confirmed") return "Confirmed";
|
|
330
|
+
if (s === "pending") return "Pending";
|
|
331
|
+
if (!status) return "Pending";
|
|
332
|
+
return status.charAt(0).toUpperCase() + status.slice(1);
|
|
333
|
+
}
|
|
334
|
+
function paymentRowToWebhookDto(p, amountMajorOverride) {
|
|
335
|
+
const currency = String(p.currency || "INR");
|
|
336
|
+
const amountMajor = amountMajorOverride != null && Number.isFinite(amountMajorOverride) ? amountMajorOverride : Number(p.amount);
|
|
337
|
+
const meta = { ...p.metadata || {} };
|
|
338
|
+
delete meta.amount;
|
|
339
|
+
delete meta.currency;
|
|
340
|
+
return {
|
|
341
|
+
id: String(p.externalReference || `payment_${p.id}`),
|
|
342
|
+
amount: roundMoney(amountMajor),
|
|
343
|
+
currency_code: currency,
|
|
344
|
+
captured_at: p.paidAt ? new Date(p.paidAt).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
345
|
+
provider_id: String(p.method || "unknown"),
|
|
346
|
+
data: { status: "captured", ...meta }
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId) {
|
|
350
|
+
try {
|
|
351
|
+
const configRepo = dataSource.getRepository(entityMap.configs);
|
|
352
|
+
const cfgRows = await configRepo.find({ where: { settings: "erp", deleted: false } });
|
|
353
|
+
for (const row of cfgRows) {
|
|
354
|
+
const r = row;
|
|
355
|
+
if (r.key === "enabled" && r.value === "false") return;
|
|
356
|
+
}
|
|
357
|
+
if (!cms.getPlugin("erp")) return;
|
|
358
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
359
|
+
const ord = await orderRepo.findOne({
|
|
360
|
+
where: { id: orderId },
|
|
361
|
+
relations: ["items", "items.product", "contact", "billingAddress", "shippingAddress", "payments"]
|
|
362
|
+
});
|
|
363
|
+
if (!ord) return;
|
|
364
|
+
const o = ord;
|
|
365
|
+
const okKind = o.orderKind === void 0 || o.orderKind === null || o.orderKind === "sale";
|
|
366
|
+
if (!okKind) return;
|
|
367
|
+
const rawPayments = o.payments ?? [];
|
|
368
|
+
const completedPayments = rawPayments.filter((pay) => pay.status === "completed" && pay.deleted !== true);
|
|
369
|
+
if (!completedPayments.length) return;
|
|
370
|
+
const rawItems = o.items ?? [];
|
|
371
|
+
const lines = rawItems.filter((it) => it.product).map((it) => {
|
|
372
|
+
const p = it.product;
|
|
373
|
+
const sku = p.sku || `SKU-${p.id}`;
|
|
374
|
+
const itemType = typeof it.productType === "string" && it.productType.trim() ? String(it.productType).trim() : p.type === "service" ? "service" : "product";
|
|
375
|
+
return {
|
|
376
|
+
sku,
|
|
377
|
+
quantity: Number(it.quantity) || 1,
|
|
378
|
+
unitPrice: Number(it.unitPrice),
|
|
379
|
+
title: p.name || sku,
|
|
380
|
+
discount: 0,
|
|
381
|
+
tax: Number(it.tax) || 0,
|
|
382
|
+
uom: (typeof it.uom === "string" && it.uom.trim() ? it.uom : p.uom) || void 0,
|
|
383
|
+
tax_code: typeof it.taxCode === "string" && it.taxCode.trim() ? String(it.taxCode).trim() : void 0,
|
|
384
|
+
hsn_number: (typeof it.hsn === "string" && it.hsn.trim() ? it.hsn : p.hsn) || void 0,
|
|
385
|
+
type: itemType
|
|
386
|
+
};
|
|
387
|
+
});
|
|
388
|
+
if (!lines.length) return;
|
|
389
|
+
const contact = o.contact;
|
|
390
|
+
const orderTotalMajor = Number(o.total);
|
|
391
|
+
const paymentDtos = completedPayments.length === 1 && Number.isFinite(orderTotalMajor) ? [paymentRowToWebhookDto(completedPayments[0], orderTotalMajor)] : completedPayments.map((pay) => paymentRowToWebhookDto(pay));
|
|
392
|
+
const baseMeta = o.metadata && typeof o.metadata === "object" && !Array.isArray(o.metadata) ? { ...o.metadata } : {};
|
|
393
|
+
const orderDto = {
|
|
394
|
+
platformType: "website",
|
|
395
|
+
platformOrderId: String(o.orderNumber),
|
|
396
|
+
platformOrderNumber: String(o.orderNumber),
|
|
397
|
+
order_date: o.createdAt ? new Date(o.createdAt).toISOString() : void 0,
|
|
398
|
+
status: orderStatusLabel(o.status),
|
|
399
|
+
customer: {
|
|
400
|
+
name: contact?.name || "",
|
|
401
|
+
email: contact?.email || "",
|
|
402
|
+
phone: contact?.phone || ""
|
|
403
|
+
},
|
|
404
|
+
shippingAddress: addressToWebhookDto(o.shippingAddress),
|
|
405
|
+
billingAddress: addressToWebhookDto(o.billingAddress),
|
|
406
|
+
items: lines,
|
|
407
|
+
payments: paymentDtos,
|
|
408
|
+
metadata: { ...baseMeta, source: "storefront" }
|
|
409
|
+
};
|
|
410
|
+
await queueErp(cms, { kind: "order", order: orderDto });
|
|
411
|
+
} catch {
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
var init_paid_order_erp = __esm({
|
|
415
|
+
"src/plugins/erp/paid-order-erp.ts"() {
|
|
416
|
+
"use strict";
|
|
417
|
+
init_erp_queue();
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
273
421
|
// src/api/index.ts
|
|
274
422
|
var api_exports = {};
|
|
275
423
|
__export(api_exports, {
|
|
@@ -298,15 +446,8 @@ module.exports = __toCommonJS(api_exports);
|
|
|
298
446
|
// src/api/crud.ts
|
|
299
447
|
var import_typeorm = require("typeorm");
|
|
300
448
|
|
|
301
|
-
// src/plugins/erp/erp-queue.ts
|
|
302
|
-
var ERP_QUEUE_NAME = "erp";
|
|
303
|
-
async function queueErp(cms, payload) {
|
|
304
|
-
const queue = cms.getPlugin("queue");
|
|
305
|
-
if (!queue) return;
|
|
306
|
-
await queue.add(ERP_QUEUE_NAME, payload);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
449
|
// src/plugins/erp/erp-contact-sync.ts
|
|
450
|
+
init_erp_queue();
|
|
310
451
|
function splitName(full) {
|
|
311
452
|
const t = (full || "").trim();
|
|
312
453
|
if (!t) return { firstName: "Contact", lastName: "" };
|
|
@@ -344,6 +485,7 @@ async function queueErpCreateContactIfEnabled(cms, dataSource, entityMap, input)
|
|
|
344
485
|
}
|
|
345
486
|
|
|
346
487
|
// src/plugins/erp/erp-product-sync.ts
|
|
488
|
+
init_erp_queue();
|
|
347
489
|
init_erp_config_enabled();
|
|
348
490
|
async function queueErpProductUpsertIfEnabled(cms, dataSource, entityMap, product) {
|
|
349
491
|
try {
|
|
@@ -351,14 +493,21 @@ async function queueErpProductUpsertIfEnabled(cms, dataSource, entityMap, produc
|
|
|
351
493
|
if (!sku) return;
|
|
352
494
|
const on = await isErpIntegrationEnabled(cms, dataSource, entityMap);
|
|
353
495
|
if (!on) return;
|
|
496
|
+
const rawMeta = product.metadata;
|
|
497
|
+
let metadata;
|
|
498
|
+
if (rawMeta && typeof rawMeta === "object" && !Array.isArray(rawMeta)) {
|
|
499
|
+
const { description: _d, ...rest } = rawMeta;
|
|
500
|
+
metadata = Object.keys(rest).length ? rest : void 0;
|
|
501
|
+
}
|
|
354
502
|
const payload = {
|
|
355
503
|
sku,
|
|
356
504
|
title: product.name || sku,
|
|
357
505
|
name: product.name,
|
|
358
|
-
description: typeof product.metadata === "object" && product.metadata && "description" in product.metadata ? String(product.metadata.description ?? "") : void 0,
|
|
359
506
|
hsn_number: product.hsn,
|
|
507
|
+
uom: product.uom != null && String(product.uom).trim() ? String(product.uom).trim() : void 0,
|
|
508
|
+
type: product.type === "service" ? "service" : "product",
|
|
360
509
|
is_active: product.status === "available",
|
|
361
|
-
metadata
|
|
510
|
+
metadata
|
|
362
511
|
};
|
|
363
512
|
await queueErp(cms, { kind: "productUpsert", product: payload });
|
|
364
513
|
} catch {
|
|
@@ -618,6 +767,24 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
618
767
|
} else if (search) {
|
|
619
768
|
where = buildSearchWhereClause(repo, search);
|
|
620
769
|
}
|
|
770
|
+
const intFilterKeys = ["productId", "attributeId", "taxId"];
|
|
771
|
+
const extraWhere = {};
|
|
772
|
+
for (const key of intFilterKeys) {
|
|
773
|
+
const v = searchParams.get(key);
|
|
774
|
+
if (v != null && v !== "" && columnNames.has(key)) {
|
|
775
|
+
const n = Number(v);
|
|
776
|
+
if (Number.isFinite(n)) extraWhere[key] = n;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (Object.keys(extraWhere).length > 0) {
|
|
780
|
+
if (Array.isArray(where)) {
|
|
781
|
+
where = where.map((w) => ({ ...w, ...extraWhere }));
|
|
782
|
+
} else if (where && typeof where === "object" && Object.keys(where).length > 0) {
|
|
783
|
+
where = { ...where, ...extraWhere };
|
|
784
|
+
} else {
|
|
785
|
+
where = extraWhere;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
621
788
|
const [data, total] = await repo.findAndCount({
|
|
622
789
|
skip,
|
|
623
790
|
take: limit,
|
|
@@ -1085,6 +1252,7 @@ function createUserAuthApiRouter(config) {
|
|
|
1085
1252
|
// src/api/cms-handlers.ts
|
|
1086
1253
|
var import_typeorm3 = require("typeorm");
|
|
1087
1254
|
init_email_queue();
|
|
1255
|
+
init_erp_queue();
|
|
1088
1256
|
|
|
1089
1257
|
// src/plugins/captcha/assert.ts
|
|
1090
1258
|
async function assertCaptchaOk(getCms, body, req, json) {
|
|
@@ -2563,6 +2731,29 @@ function createCmsApiHandler(config) {
|
|
|
2563
2731
|
if (!Number.isFinite(oid)) return config.json({ error: "Invalid id" }, { status: 400 });
|
|
2564
2732
|
return streamOrderInvoicePdf2(cms, dataSource, entityMap, oid, {});
|
|
2565
2733
|
}
|
|
2734
|
+
if (path[0] === "orders" && path.length === 3 && path[2] === "repost-erp" && getCms) {
|
|
2735
|
+
const a = await config.requireAuth(req);
|
|
2736
|
+
if (a) return a;
|
|
2737
|
+
if (perm) {
|
|
2738
|
+
const pe = await perm(req, "orders", method === "GET" ? "read" : "update");
|
|
2739
|
+
if (pe) return pe;
|
|
2740
|
+
}
|
|
2741
|
+
const oid = Number(path[1]);
|
|
2742
|
+
if (!Number.isFinite(oid)) return config.json({ error: "Invalid id" }, { status: 400 });
|
|
2743
|
+
const cms = await getCms();
|
|
2744
|
+
const { isErpIntegrationEnabled: isErpIntegrationEnabled3 } = await Promise.resolve().then(() => (init_erp_config_enabled(), erp_config_enabled_exports));
|
|
2745
|
+
const enabled = await isErpIntegrationEnabled3(cms, dataSource, entityMap);
|
|
2746
|
+
if (method === "GET") {
|
|
2747
|
+
return config.json({ enabled });
|
|
2748
|
+
}
|
|
2749
|
+
if (method === "POST") {
|
|
2750
|
+
if (!enabled) return config.json({ error: "ERP integration is disabled" }, { status: 409 });
|
|
2751
|
+
const { queueErpPaidOrderForOrderId: queueErpPaidOrderForOrderId2 } = await Promise.resolve().then(() => (init_paid_order_erp(), paid_order_erp_exports));
|
|
2752
|
+
await queueErpPaidOrderForOrderId2(cms, dataSource, entityMap, oid);
|
|
2753
|
+
return config.json({ ok: true });
|
|
2754
|
+
}
|
|
2755
|
+
return config.json({ error: "Method not allowed" }, { status: 405 });
|
|
2756
|
+
}
|
|
2566
2757
|
if (path.length === 0) return config.json({ error: "Not found" }, { status: 404 });
|
|
2567
2758
|
const resource = resolveResource(path[0]);
|
|
2568
2759
|
if (!crudResources.includes(resource)) return config.json({ error: "Invalid resource" }, { status: 400 });
|
|
@@ -2991,6 +3182,152 @@ function createStorefrontApiHandler(config) {
|
|
|
2991
3182
|
const tokenRepo = () => dataSource.getRepository(entityMap.password_reset_tokens);
|
|
2992
3183
|
const collectionRepo = () => dataSource.getRepository(entityMap.collections);
|
|
2993
3184
|
const groupRepo = () => dataSource.getRepository(entityMap.user_groups);
|
|
3185
|
+
const configRepo = () => dataSource.getRepository(entityMap.configs);
|
|
3186
|
+
const CART_CHECKOUT_RELATIONS = ["items", "items.product", "items.product.taxes", "items.product.taxes.tax"];
|
|
3187
|
+
function roundMoney2(n) {
|
|
3188
|
+
return Math.round(n * 100) / 100;
|
|
3189
|
+
}
|
|
3190
|
+
async function getStoreDefaultTaxRate() {
|
|
3191
|
+
const rows = await configRepo().find({ where: { settings: "store", deleted: false } });
|
|
3192
|
+
for (const row of rows) {
|
|
3193
|
+
const r = row;
|
|
3194
|
+
if (r.key === "defaultTaxRate") {
|
|
3195
|
+
const n = parseFloat(String(r.value ?? "").trim());
|
|
3196
|
+
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
return null;
|
|
3200
|
+
}
|
|
3201
|
+
function computeTaxForProductLine(p, lineSubtotal, defaultRate) {
|
|
3202
|
+
const pts = p.taxes ?? [];
|
|
3203
|
+
const activePts = pts.filter((pt) => {
|
|
3204
|
+
const t = pt.tax;
|
|
3205
|
+
return t != null && t.active !== false;
|
|
3206
|
+
});
|
|
3207
|
+
if (activePts.length) {
|
|
3208
|
+
let sumRate = 0;
|
|
3209
|
+
const slugs = [];
|
|
3210
|
+
for (const pt of activePts) {
|
|
3211
|
+
const t = pt.tax;
|
|
3212
|
+
const r = Number(pt.rate != null && pt.rate !== "" ? pt.rate : t.rate ?? 0);
|
|
3213
|
+
if (Number.isFinite(r)) sumRate += r;
|
|
3214
|
+
const slug = String(t.slug ?? "").trim();
|
|
3215
|
+
if (slug) slugs.push(slug);
|
|
3216
|
+
}
|
|
3217
|
+
const tax = roundMoney2(lineSubtotal * sumRate / 100);
|
|
3218
|
+
return {
|
|
3219
|
+
tax,
|
|
3220
|
+
taxRate: sumRate > 0 ? roundMoney2(sumRate) : null,
|
|
3221
|
+
taxCode: slugs.length ? [...new Set(slugs)].sort().join(",") : null
|
|
3222
|
+
};
|
|
3223
|
+
}
|
|
3224
|
+
if (defaultRate != null && defaultRate > 0) {
|
|
3225
|
+
return {
|
|
3226
|
+
tax: roundMoney2(lineSubtotal * defaultRate / 100),
|
|
3227
|
+
taxRate: roundMoney2(defaultRate),
|
|
3228
|
+
taxCode: null
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3231
|
+
return { tax: 0, taxRate: null, taxCode: null };
|
|
3232
|
+
}
|
|
3233
|
+
function parseInlineAddress(raw) {
|
|
3234
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
3235
|
+
const o = raw;
|
|
3236
|
+
const line1 = String(o.line1 ?? "").trim();
|
|
3237
|
+
if (!line1) return null;
|
|
3238
|
+
return {
|
|
3239
|
+
line1,
|
|
3240
|
+
line2: o.line2 != null ? String(o.line2) : "",
|
|
3241
|
+
city: o.city != null ? String(o.city) : "",
|
|
3242
|
+
state: o.state != null ? String(o.state) : "",
|
|
3243
|
+
postalCode: o.postalCode != null ? String(o.postalCode) : "",
|
|
3244
|
+
country: o.country != null ? String(o.country) : ""
|
|
3245
|
+
};
|
|
3246
|
+
}
|
|
3247
|
+
function intFromBody(v) {
|
|
3248
|
+
if (typeof v === "number" && Number.isInteger(v)) return v;
|
|
3249
|
+
if (typeof v === "string" && /^\d+$/.test(v)) return parseInt(v, 10);
|
|
3250
|
+
return void 0;
|
|
3251
|
+
}
|
|
3252
|
+
async function resolveCheckoutAddress(contactId, idVal, inlineVal) {
|
|
3253
|
+
const aid = intFromBody(idVal);
|
|
3254
|
+
if (aid != null) {
|
|
3255
|
+
const existing = await addressRepo().findOne({
|
|
3256
|
+
where: { id: aid, contactId }
|
|
3257
|
+
});
|
|
3258
|
+
if (!existing) return { id: null, error: "Address not found" };
|
|
3259
|
+
return { id: aid };
|
|
3260
|
+
}
|
|
3261
|
+
const addr = parseInlineAddress(inlineVal);
|
|
3262
|
+
if (addr) {
|
|
3263
|
+
const saved = await addressRepo().save(
|
|
3264
|
+
addressRepo().create({
|
|
3265
|
+
contactId,
|
|
3266
|
+
line1: addr.line1,
|
|
3267
|
+
line2: addr.line2?.trim() ? addr.line2 : null,
|
|
3268
|
+
city: addr.city?.trim() ? addr.city : null,
|
|
3269
|
+
state: addr.state?.trim() ? addr.state : null,
|
|
3270
|
+
postalCode: addr.postalCode?.trim() ? addr.postalCode : null,
|
|
3271
|
+
country: addr.country?.trim() ? addr.country : null
|
|
3272
|
+
})
|
|
3273
|
+
);
|
|
3274
|
+
return { id: saved.id };
|
|
3275
|
+
}
|
|
3276
|
+
return { id: null };
|
|
3277
|
+
}
|
|
3278
|
+
async function prepareCheckoutFromCart(b, cart, contactId) {
|
|
3279
|
+
const defaultRate = await getStoreDefaultTaxRate();
|
|
3280
|
+
const lines = [];
|
|
3281
|
+
let subtotal = 0;
|
|
3282
|
+
let orderTax = 0;
|
|
3283
|
+
let needsShipping = false;
|
|
3284
|
+
for (const it of cart.items || []) {
|
|
3285
|
+
const p = it.product;
|
|
3286
|
+
if (!p || p.deleted || p.status !== "available") continue;
|
|
3287
|
+
const unit = Number(p.price);
|
|
3288
|
+
const qty = it.quantity || 1;
|
|
3289
|
+
const lineSubtotal = unit * qty;
|
|
3290
|
+
const pType = p.type === "service" ? "service" : "product";
|
|
3291
|
+
if (pType === "product") needsShipping = true;
|
|
3292
|
+
const { tax, taxRate, taxCode } = computeTaxForProductLine(p, lineSubtotal, defaultRate);
|
|
3293
|
+
const lineTotal = roundMoney2(lineSubtotal + tax);
|
|
3294
|
+
subtotal = roundMoney2(subtotal + lineSubtotal);
|
|
3295
|
+
orderTax = roundMoney2(orderTax + tax);
|
|
3296
|
+
lines.push({
|
|
3297
|
+
productId: p.id,
|
|
3298
|
+
quantity: qty,
|
|
3299
|
+
unitPrice: unit,
|
|
3300
|
+
tax,
|
|
3301
|
+
total: lineTotal,
|
|
3302
|
+
hsn: p.hsn ?? null,
|
|
3303
|
+
uom: p.uom ?? null,
|
|
3304
|
+
productType: pType,
|
|
3305
|
+
taxRate,
|
|
3306
|
+
taxCode
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
if (!lines.length) return { ok: false, status: 400, message: "No available items in cart" };
|
|
3310
|
+
const bill = await resolveCheckoutAddress(contactId, b.billingAddressId, b.billingAddress);
|
|
3311
|
+
if (bill.error) return { ok: false, status: 400, message: bill.error };
|
|
3312
|
+
if (bill.id == null) return { ok: false, status: 400, message: "Billing address required" };
|
|
3313
|
+
const ship = await resolveCheckoutAddress(contactId, b.shippingAddressId, b.shippingAddress);
|
|
3314
|
+
if (ship.error) return { ok: false, status: 400, message: ship.error };
|
|
3315
|
+
let shippingAddressId = ship.id;
|
|
3316
|
+
if (needsShipping && shippingAddressId == null) shippingAddressId = bill.id;
|
|
3317
|
+
if (needsShipping && shippingAddressId == null) {
|
|
3318
|
+
return { ok: false, status: 400, message: "Shipping address required" };
|
|
3319
|
+
}
|
|
3320
|
+
const orderTotal = roundMoney2(subtotal + orderTax);
|
|
3321
|
+
return {
|
|
3322
|
+
ok: true,
|
|
3323
|
+
lines,
|
|
3324
|
+
subtotal,
|
|
3325
|
+
orderTax,
|
|
3326
|
+
orderTotal,
|
|
3327
|
+
billingAddressId: bill.id,
|
|
3328
|
+
shippingAddressId
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
2994
3331
|
async function syncContactToErp(contact) {
|
|
2995
3332
|
if (!getCms) return;
|
|
2996
3333
|
try {
|
|
@@ -3116,6 +3453,7 @@ function createStorefrontApiHandler(config) {
|
|
|
3116
3453
|
slug: p.slug,
|
|
3117
3454
|
price: p.price,
|
|
3118
3455
|
sku: p.sku,
|
|
3456
|
+
type: p.type === "service" ? "service" : "product",
|
|
3119
3457
|
image: primaryProductImageUrl(p.metadata)
|
|
3120
3458
|
} : null
|
|
3121
3459
|
};
|
|
@@ -3143,6 +3481,8 @@ function createStorefrontApiHandler(config) {
|
|
|
3143
3481
|
slug: p.slug,
|
|
3144
3482
|
sku: p.sku,
|
|
3145
3483
|
hsn: p.hsn,
|
|
3484
|
+
uom: p.uom ?? null,
|
|
3485
|
+
type: p.type === "service" ? "service" : "product",
|
|
3146
3486
|
price: p.price,
|
|
3147
3487
|
compareAtPrice: p.compareAtPrice,
|
|
3148
3488
|
status: p.status,
|
|
@@ -3844,7 +4184,7 @@ function createStorefrontApiHandler(config) {
|
|
|
3844
4184
|
contactId = contact.id;
|
|
3845
4185
|
cart = await cartRepo().findOne({
|
|
3846
4186
|
where: { contactId },
|
|
3847
|
-
relations: [
|
|
4187
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
3848
4188
|
});
|
|
3849
4189
|
} else {
|
|
3850
4190
|
const email = String(b.email ?? "").trim();
|
|
@@ -3877,25 +4217,14 @@ function createStorefrontApiHandler(config) {
|
|
|
3877
4217
|
if (!guestToken) return json({ error: "Cart not found" }, { status: 400 });
|
|
3878
4218
|
cart = await cartRepo().findOne({
|
|
3879
4219
|
where: { guestToken },
|
|
3880
|
-
relations: [
|
|
4220
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
3881
4221
|
});
|
|
3882
4222
|
}
|
|
3883
4223
|
if (!cart || !(cart.items || []).length) {
|
|
3884
4224
|
return json({ error: "Cart is empty" }, { status: 400 });
|
|
3885
4225
|
}
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
for (const it of cart.items || []) {
|
|
3889
|
-
const p = it.product;
|
|
3890
|
-
if (!p || p.deleted || p.status !== "available") continue;
|
|
3891
|
-
const unit = Number(p.price);
|
|
3892
|
-
const qty = it.quantity || 1;
|
|
3893
|
-
const lineTotal = unit * qty;
|
|
3894
|
-
subtotal += lineTotal;
|
|
3895
|
-
lines.push({ productId: p.id, quantity: qty, unitPrice: unit, tax: 0, total: lineTotal });
|
|
3896
|
-
}
|
|
3897
|
-
if (!lines.length) return json({ error: "No available items in cart" }, { status: 400 });
|
|
3898
|
-
const total = subtotal;
|
|
4226
|
+
const prepOrd = await prepareCheckoutFromCart(b, cart, contactId);
|
|
4227
|
+
if (!prepOrd.ok) return json({ error: prepOrd.message }, { status: prepOrd.status });
|
|
3899
4228
|
const cartId = cart.id;
|
|
3900
4229
|
const ord = await orderRepo().save(
|
|
3901
4230
|
orderRepo().create({
|
|
@@ -3903,13 +4232,13 @@ function createStorefrontApiHandler(config) {
|
|
|
3903
4232
|
orderKind: "sale",
|
|
3904
4233
|
parentOrderId: null,
|
|
3905
4234
|
contactId,
|
|
3906
|
-
billingAddressId:
|
|
3907
|
-
shippingAddressId:
|
|
4235
|
+
billingAddressId: prepOrd.billingAddressId,
|
|
4236
|
+
shippingAddressId: prepOrd.shippingAddressId,
|
|
3908
4237
|
status: "pending",
|
|
3909
|
-
subtotal,
|
|
3910
|
-
tax:
|
|
4238
|
+
subtotal: prepOrd.subtotal,
|
|
4239
|
+
tax: prepOrd.orderTax,
|
|
3911
4240
|
discount: 0,
|
|
3912
|
-
total,
|
|
4241
|
+
total: prepOrd.orderTotal,
|
|
3913
4242
|
currency: cart.currency || "INR",
|
|
3914
4243
|
metadata: { cartId }
|
|
3915
4244
|
})
|
|
@@ -3918,7 +4247,7 @@ function createStorefrontApiHandler(config) {
|
|
|
3918
4247
|
await orderRepo().update(oid, {
|
|
3919
4248
|
orderNumber: buildCanonicalOrderNumber("sale", oid, ord.createdAt ?? /* @__PURE__ */ new Date())
|
|
3920
4249
|
});
|
|
3921
|
-
for (const line of lines) {
|
|
4250
|
+
for (const line of prepOrd.lines) {
|
|
3922
4251
|
await orderItemRepo().save(
|
|
3923
4252
|
orderItemRepo().create({
|
|
3924
4253
|
orderId: oid,
|
|
@@ -3926,14 +4255,21 @@ function createStorefrontApiHandler(config) {
|
|
|
3926
4255
|
quantity: line.quantity,
|
|
3927
4256
|
unitPrice: line.unitPrice,
|
|
3928
4257
|
tax: line.tax,
|
|
3929
|
-
total: line.total
|
|
4258
|
+
total: line.total,
|
|
4259
|
+
hsn: line.hsn,
|
|
4260
|
+
uom: line.uom,
|
|
4261
|
+
productType: line.productType,
|
|
4262
|
+
taxRate: line.taxRate,
|
|
4263
|
+
taxCode: line.taxCode
|
|
3930
4264
|
})
|
|
3931
4265
|
);
|
|
3932
4266
|
}
|
|
3933
4267
|
return json({
|
|
3934
4268
|
orderId: oid,
|
|
3935
4269
|
orderNumber: ord.orderNumber,
|
|
3936
|
-
|
|
4270
|
+
subtotal: prepOrd.subtotal,
|
|
4271
|
+
tax: prepOrd.orderTax,
|
|
4272
|
+
total: prepOrd.orderTotal,
|
|
3937
4273
|
currency: cart.currency || "INR"
|
|
3938
4274
|
});
|
|
3939
4275
|
}
|
|
@@ -3951,7 +4287,7 @@ function createStorefrontApiHandler(config) {
|
|
|
3951
4287
|
contactId = contact.id;
|
|
3952
4288
|
cart = await cartRepo().findOne({
|
|
3953
4289
|
where: { contactId },
|
|
3954
|
-
relations: [
|
|
4290
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
3955
4291
|
});
|
|
3956
4292
|
} else {
|
|
3957
4293
|
const email = String(b.email ?? "").trim();
|
|
@@ -3984,38 +4320,27 @@ function createStorefrontApiHandler(config) {
|
|
|
3984
4320
|
if (!guestToken) return json({ error: "Cart not found" }, { status: 400 });
|
|
3985
4321
|
cart = await cartRepo().findOne({
|
|
3986
4322
|
where: { guestToken },
|
|
3987
|
-
relations: [
|
|
4323
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
3988
4324
|
});
|
|
3989
4325
|
}
|
|
3990
4326
|
if (!cart || !(cart.items || []).length) {
|
|
3991
4327
|
return json({ error: "Cart is empty" }, { status: 400 });
|
|
3992
4328
|
}
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
for (const it of cart.items || []) {
|
|
3996
|
-
const p = it.product;
|
|
3997
|
-
if (!p || p.deleted || p.status !== "available") continue;
|
|
3998
|
-
const unit = Number(p.price);
|
|
3999
|
-
const qty = it.quantity || 1;
|
|
4000
|
-
const lineTotal = unit * qty;
|
|
4001
|
-
subtotal += lineTotal;
|
|
4002
|
-
lines.push({ productId: p.id, quantity: qty, unitPrice: unit, tax: 0, total: lineTotal });
|
|
4003
|
-
}
|
|
4004
|
-
if (!lines.length) return json({ error: "No available items in cart" }, { status: 400 });
|
|
4005
|
-
const total = subtotal;
|
|
4329
|
+
const prepChk = await prepareCheckoutFromCart(b, cart, contactId);
|
|
4330
|
+
if (!prepChk.ok) return json({ error: prepChk.message }, { status: prepChk.status });
|
|
4006
4331
|
const ord = await orderRepo().save(
|
|
4007
4332
|
orderRepo().create({
|
|
4008
4333
|
orderNumber: temporaryOrderNumberPlaceholder(),
|
|
4009
4334
|
orderKind: "sale",
|
|
4010
4335
|
parentOrderId: null,
|
|
4011
4336
|
contactId,
|
|
4012
|
-
billingAddressId:
|
|
4013
|
-
shippingAddressId:
|
|
4337
|
+
billingAddressId: prepChk.billingAddressId,
|
|
4338
|
+
shippingAddressId: prepChk.shippingAddressId,
|
|
4014
4339
|
status: "pending",
|
|
4015
|
-
subtotal,
|
|
4016
|
-
tax:
|
|
4340
|
+
subtotal: prepChk.subtotal,
|
|
4341
|
+
tax: prepChk.orderTax,
|
|
4017
4342
|
discount: 0,
|
|
4018
|
-
total,
|
|
4343
|
+
total: prepChk.orderTotal,
|
|
4019
4344
|
currency: cart.currency || "INR"
|
|
4020
4345
|
})
|
|
4021
4346
|
);
|
|
@@ -4023,7 +4348,7 @@ function createStorefrontApiHandler(config) {
|
|
|
4023
4348
|
await orderRepo().update(oid, {
|
|
4024
4349
|
orderNumber: buildCanonicalOrderNumber("sale", oid, ord.createdAt ?? /* @__PURE__ */ new Date())
|
|
4025
4350
|
});
|
|
4026
|
-
for (const line of lines) {
|
|
4351
|
+
for (const line of prepChk.lines) {
|
|
4027
4352
|
await orderItemRepo().save(
|
|
4028
4353
|
orderItemRepo().create({
|
|
4029
4354
|
orderId: oid,
|
|
@@ -4031,7 +4356,12 @@ function createStorefrontApiHandler(config) {
|
|
|
4031
4356
|
quantity: line.quantity,
|
|
4032
4357
|
unitPrice: line.unitPrice,
|
|
4033
4358
|
tax: line.tax,
|
|
4034
|
-
total: line.total
|
|
4359
|
+
total: line.total,
|
|
4360
|
+
hsn: line.hsn,
|
|
4361
|
+
uom: line.uom,
|
|
4362
|
+
productType: line.productType,
|
|
4363
|
+
taxRate: line.taxRate,
|
|
4364
|
+
taxCode: line.taxCode
|
|
4035
4365
|
})
|
|
4036
4366
|
);
|
|
4037
4367
|
}
|
|
@@ -4040,7 +4370,9 @@ function createStorefrontApiHandler(config) {
|
|
|
4040
4370
|
return json({
|
|
4041
4371
|
orderId: oid,
|
|
4042
4372
|
orderNumber: ord.orderNumber,
|
|
4043
|
-
|
|
4373
|
+
subtotal: prepChk.subtotal,
|
|
4374
|
+
tax: prepChk.orderTax,
|
|
4375
|
+
total: prepChk.orderTotal
|
|
4044
4376
|
});
|
|
4045
4377
|
}
|
|
4046
4378
|
if (path[0] === "orders" && path.length === 1 && method === "GET") {
|