@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.d.cts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { A as AnalyticsHandlerConfig, c as AuthHandlersConfig, B as BlogBySlugConfig, d as ChangePasswordConfig, e as CmsApiHandlerConfig, f as CmsGetter, g as CrudHandlerOptions, D as DashboardStatsConfig, b as EntityMap, F as ForgotPasswordConfig, h as FormBySlugConfig, G as GetPublicSettingsGroupConfig, i as GetPublicSettingsGroupDataSource, I as InviteAcceptConfig, j as SetPasswordConfig, k as SettingsApiConfig, m as StorefrontApiConfig, n as StorefrontOtpFlags, U as UploadHandlerConfig, o as UserAuthApiConfig, p as UserAvatarConfig, q as UserProfileConfig, r as UsersApiConfig, s as createAnalyticsHandlers, t as createBlogBySlugHandler, u as createChangePasswordHandler, v as createCmsApiHandler, w as createCrudByIdHandler, x as createCrudHandler, y as createDashboardStatsHandler, z as createForgotPasswordHandler, H as createFormBySlugHandler, J as createInviteAcceptHandler, K as createSetPasswordHandler, L as createSettingsApiHandlers, M as createStorefrontApiHandler, N as createUploadHandler, P as createUserAuthApiRouter, Q as createUserAvatarHandler, R as createUserProfileHandler, V as createUsersApiHandlers, X as getPublicSettingsGroup } from './index-
|
|
1
|
+
export { A as AnalyticsHandlerConfig, c as AuthHandlersConfig, B as BlogBySlugConfig, d as ChangePasswordConfig, e as CmsApiHandlerConfig, f as CmsGetter, g as CrudHandlerOptions, D as DashboardStatsConfig, b as EntityMap, F as ForgotPasswordConfig, h as FormBySlugConfig, G as GetPublicSettingsGroupConfig, i as GetPublicSettingsGroupDataSource, I as InviteAcceptConfig, j as SetPasswordConfig, k as SettingsApiConfig, m as StorefrontApiConfig, n as StorefrontOtpFlags, U as UploadHandlerConfig, o as UserAuthApiConfig, p as UserAvatarConfig, q as UserProfileConfig, r as UsersApiConfig, s as createAnalyticsHandlers, t as createBlogBySlugHandler, u as createChangePasswordHandler, v as createCmsApiHandler, w as createCrudByIdHandler, x as createCrudHandler, y as createDashboardStatsHandler, z as createForgotPasswordHandler, H as createFormBySlugHandler, J as createInviteAcceptHandler, K as createSetPasswordHandler, L as createSettingsApiHandlers, M as createStorefrontApiHandler, N as createUploadHandler, P as createUserAuthApiRouter, Q as createUserAvatarHandler, R as createUserProfileHandler, V as createUsersApiHandlers, X as getPublicSettingsGroup } from './index-BQnqJ7EO.cjs';
|
|
2
2
|
import 'typeorm';
|
|
3
3
|
import './helpers-dlrF_49e.cjs';
|
package/dist/api.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { A as AnalyticsHandlerConfig, c as AuthHandlersConfig, B as BlogBySlugConfig, d as ChangePasswordConfig, e as CmsApiHandlerConfig, f as CmsGetter, g as CrudHandlerOptions, D as DashboardStatsConfig, b as EntityMap, F as ForgotPasswordConfig, h as FormBySlugConfig, G as GetPublicSettingsGroupConfig, i as GetPublicSettingsGroupDataSource, I as InviteAcceptConfig, j as SetPasswordConfig, k as SettingsApiConfig, m as StorefrontApiConfig, n as StorefrontOtpFlags, U as UploadHandlerConfig, o as UserAuthApiConfig, p as UserAvatarConfig, q as UserProfileConfig, r as UsersApiConfig, s as createAnalyticsHandlers, t as createBlogBySlugHandler, u as createChangePasswordHandler, v as createCmsApiHandler, w as createCrudByIdHandler, x as createCrudHandler, y as createDashboardStatsHandler, z as createForgotPasswordHandler, H as createFormBySlugHandler, J as createInviteAcceptHandler, K as createSetPasswordHandler, L as createSettingsApiHandlers, M as createStorefrontApiHandler, N as createUploadHandler, P as createUserAuthApiRouter, Q as createUserAvatarHandler, R as createUserProfileHandler, V as createUsersApiHandlers, X as getPublicSettingsGroup } from './index-
|
|
1
|
+
export { A as AnalyticsHandlerConfig, c as AuthHandlersConfig, B as BlogBySlugConfig, d as ChangePasswordConfig, e as CmsApiHandlerConfig, f as CmsGetter, g as CrudHandlerOptions, D as DashboardStatsConfig, b as EntityMap, F as ForgotPasswordConfig, h as FormBySlugConfig, G as GetPublicSettingsGroupConfig, i as GetPublicSettingsGroupDataSource, I as InviteAcceptConfig, j as SetPasswordConfig, k as SettingsApiConfig, m as StorefrontApiConfig, n as StorefrontOtpFlags, U as UploadHandlerConfig, o as UserAuthApiConfig, p as UserAvatarConfig, q as UserProfileConfig, r as UsersApiConfig, s as createAnalyticsHandlers, t as createBlogBySlugHandler, u as createChangePasswordHandler, v as createCmsApiHandler, w as createCrudByIdHandler, x as createCrudHandler, y as createDashboardStatsHandler, z as createForgotPasswordHandler, H as createFormBySlugHandler, J as createInviteAcceptHandler, K as createSetPasswordHandler, L as createSettingsApiHandlers, M as createStorefrontApiHandler, N as createUploadHandler, P as createUserAuthApiRouter, Q as createUserAvatarHandler, R as createUserProfileHandler, V as createUsersApiHandlers, X as getPublicSettingsGroup } from './index-BiagwMjV.js';
|
|
2
2
|
import 'typeorm';
|
|
3
3
|
import './helpers-dlrF_49e.js';
|
package/dist/api.js
CHANGED
|
@@ -8,7 +8,25 @@ var __export = (target, all) => {
|
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// src/plugins/erp/erp-queue.ts
|
|
12
|
+
async function queueErp(cms, payload) {
|
|
13
|
+
const queue = cms.getPlugin("queue");
|
|
14
|
+
if (!queue) return;
|
|
15
|
+
await queue.add(ERP_QUEUE_NAME, payload);
|
|
16
|
+
}
|
|
17
|
+
var ERP_QUEUE_NAME;
|
|
18
|
+
var init_erp_queue = __esm({
|
|
19
|
+
"src/plugins/erp/erp-queue.ts"() {
|
|
20
|
+
"use strict";
|
|
21
|
+
ERP_QUEUE_NAME = "erp";
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
11
25
|
// src/plugins/erp/erp-config-enabled.ts
|
|
26
|
+
var erp_config_enabled_exports = {};
|
|
27
|
+
__export(erp_config_enabled_exports, {
|
|
28
|
+
isErpIntegrationEnabled: () => isErpIntegrationEnabled
|
|
29
|
+
});
|
|
12
30
|
async function isErpIntegrationEnabled(cms, dataSource, entityMap) {
|
|
13
31
|
if (!cms.getPlugin("erp")) return false;
|
|
14
32
|
const configRepo = dataSource.getRepository(entityMap.configs);
|
|
@@ -63,14 +81,31 @@ async function queueEmail(cms, payload) {
|
|
|
63
81
|
}
|
|
64
82
|
}
|
|
65
83
|
async function queueOrderPlacedEmails(cms, payload) {
|
|
66
|
-
const {
|
|
84
|
+
const {
|
|
85
|
+
orderNumber,
|
|
86
|
+
total,
|
|
87
|
+
subtotal,
|
|
88
|
+
tax,
|
|
89
|
+
currency,
|
|
90
|
+
customerName,
|
|
91
|
+
customerEmail,
|
|
92
|
+
salesTeamEmails,
|
|
93
|
+
companyDetails,
|
|
94
|
+
lineItems,
|
|
95
|
+
billingAddress,
|
|
96
|
+
shippingAddress
|
|
97
|
+
} = payload;
|
|
67
98
|
const base = {
|
|
68
99
|
orderNumber,
|
|
69
100
|
total: total != null ? String(total) : void 0,
|
|
101
|
+
subtotal: subtotal != null ? String(subtotal) : void 0,
|
|
102
|
+
tax: tax != null ? String(tax) : void 0,
|
|
70
103
|
currency,
|
|
71
104
|
customerName,
|
|
72
105
|
companyDetails: companyDetails ?? {},
|
|
73
|
-
lineItems: lineItems ?? []
|
|
106
|
+
lineItems: lineItems ?? [],
|
|
107
|
+
billingAddress,
|
|
108
|
+
shippingAddress
|
|
74
109
|
};
|
|
75
110
|
const customerLower = customerEmail?.trim().toLowerCase() ?? "";
|
|
76
111
|
const jobs = [];
|
|
@@ -248,18 +283,124 @@ var init_erp_order_invoice = __esm({
|
|
|
248
283
|
}
|
|
249
284
|
});
|
|
250
285
|
|
|
286
|
+
// src/plugins/erp/paid-order-erp.ts
|
|
287
|
+
var paid_order_erp_exports = {};
|
|
288
|
+
__export(paid_order_erp_exports, {
|
|
289
|
+
queueErpPaidOrderForOrderId: () => queueErpPaidOrderForOrderId
|
|
290
|
+
});
|
|
291
|
+
function roundMoney(major) {
|
|
292
|
+
return Math.round(major * 100) / 100;
|
|
293
|
+
}
|
|
294
|
+
function addressToWebhookDto(a) {
|
|
295
|
+
if (!a) return {};
|
|
296
|
+
return {
|
|
297
|
+
line1: a.line1 ?? "",
|
|
298
|
+
line2: a.line2 ?? "",
|
|
299
|
+
city: a.city ?? "",
|
|
300
|
+
state: a.state ?? "",
|
|
301
|
+
postalCode: a.postalCode ?? "",
|
|
302
|
+
country: a.country ?? ""
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function orderStatusLabel(status) {
|
|
306
|
+
const s = (status || "").toLowerCase();
|
|
307
|
+
if (s === "confirmed") return "Confirmed";
|
|
308
|
+
if (s === "pending") return "Pending";
|
|
309
|
+
if (!status) return "Pending";
|
|
310
|
+
return status.charAt(0).toUpperCase() + status.slice(1);
|
|
311
|
+
}
|
|
312
|
+
function paymentRowToWebhookDto(p, amountMajorOverride) {
|
|
313
|
+
const currency = String(p.currency || "INR");
|
|
314
|
+
const amountMajor = amountMajorOverride != null && Number.isFinite(amountMajorOverride) ? amountMajorOverride : Number(p.amount);
|
|
315
|
+
const meta = { ...p.metadata || {} };
|
|
316
|
+
delete meta.amount;
|
|
317
|
+
delete meta.currency;
|
|
318
|
+
return {
|
|
319
|
+
id: String(p.externalReference || `payment_${p.id}`),
|
|
320
|
+
amount: roundMoney(amountMajor),
|
|
321
|
+
currency_code: currency,
|
|
322
|
+
captured_at: p.paidAt ? new Date(p.paidAt).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
323
|
+
provider_id: String(p.method || "unknown"),
|
|
324
|
+
data: { status: "captured", ...meta }
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId) {
|
|
328
|
+
try {
|
|
329
|
+
const configRepo = dataSource.getRepository(entityMap.configs);
|
|
330
|
+
const cfgRows = await configRepo.find({ where: { settings: "erp", deleted: false } });
|
|
331
|
+
for (const row of cfgRows) {
|
|
332
|
+
const r = row;
|
|
333
|
+
if (r.key === "enabled" && r.value === "false") return;
|
|
334
|
+
}
|
|
335
|
+
if (!cms.getPlugin("erp")) return;
|
|
336
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
337
|
+
const ord = await orderRepo.findOne({
|
|
338
|
+
where: { id: orderId },
|
|
339
|
+
relations: ["items", "items.product", "contact", "billingAddress", "shippingAddress", "payments"]
|
|
340
|
+
});
|
|
341
|
+
if (!ord) return;
|
|
342
|
+
const o = ord;
|
|
343
|
+
const okKind = o.orderKind === void 0 || o.orderKind === null || o.orderKind === "sale";
|
|
344
|
+
if (!okKind) return;
|
|
345
|
+
const rawPayments = o.payments ?? [];
|
|
346
|
+
const completedPayments = rawPayments.filter((pay) => pay.status === "completed" && pay.deleted !== true);
|
|
347
|
+
if (!completedPayments.length) return;
|
|
348
|
+
const rawItems = o.items ?? [];
|
|
349
|
+
const lines = rawItems.filter((it) => it.product).map((it) => {
|
|
350
|
+
const p = it.product;
|
|
351
|
+
const sku = p.sku || `SKU-${p.id}`;
|
|
352
|
+
const itemType = typeof it.productType === "string" && it.productType.trim() ? String(it.productType).trim() : p.type === "service" ? "service" : "product";
|
|
353
|
+
return {
|
|
354
|
+
sku,
|
|
355
|
+
quantity: Number(it.quantity) || 1,
|
|
356
|
+
unitPrice: Number(it.unitPrice),
|
|
357
|
+
title: p.name || sku,
|
|
358
|
+
discount: 0,
|
|
359
|
+
tax: Number(it.tax) || 0,
|
|
360
|
+
uom: (typeof it.uom === "string" && it.uom.trim() ? it.uom : p.uom) || void 0,
|
|
361
|
+
tax_code: typeof it.taxCode === "string" && it.taxCode.trim() ? String(it.taxCode).trim() : void 0,
|
|
362
|
+
hsn_number: (typeof it.hsn === "string" && it.hsn.trim() ? it.hsn : p.hsn) || void 0,
|
|
363
|
+
type: itemType
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
if (!lines.length) return;
|
|
367
|
+
const contact = o.contact;
|
|
368
|
+
const orderTotalMajor = Number(o.total);
|
|
369
|
+
const paymentDtos = completedPayments.length === 1 && Number.isFinite(orderTotalMajor) ? [paymentRowToWebhookDto(completedPayments[0], orderTotalMajor)] : completedPayments.map((pay) => paymentRowToWebhookDto(pay));
|
|
370
|
+
const baseMeta = o.metadata && typeof o.metadata === "object" && !Array.isArray(o.metadata) ? { ...o.metadata } : {};
|
|
371
|
+
const orderDto = {
|
|
372
|
+
platformType: "website",
|
|
373
|
+
platformOrderId: String(o.orderNumber),
|
|
374
|
+
platformOrderNumber: String(o.orderNumber),
|
|
375
|
+
order_date: o.createdAt ? new Date(o.createdAt).toISOString() : void 0,
|
|
376
|
+
status: orderStatusLabel(o.status),
|
|
377
|
+
customer: {
|
|
378
|
+
name: contact?.name || "",
|
|
379
|
+
email: contact?.email || "",
|
|
380
|
+
phone: contact?.phone || ""
|
|
381
|
+
},
|
|
382
|
+
shippingAddress: addressToWebhookDto(o.shippingAddress),
|
|
383
|
+
billingAddress: addressToWebhookDto(o.billingAddress),
|
|
384
|
+
items: lines,
|
|
385
|
+
payments: paymentDtos,
|
|
386
|
+
metadata: { ...baseMeta, source: "storefront" }
|
|
387
|
+
};
|
|
388
|
+
await queueErp(cms, { kind: "order", order: orderDto });
|
|
389
|
+
} catch {
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
var init_paid_order_erp = __esm({
|
|
393
|
+
"src/plugins/erp/paid-order-erp.ts"() {
|
|
394
|
+
"use strict";
|
|
395
|
+
init_erp_queue();
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
251
399
|
// src/api/crud.ts
|
|
252
400
|
import { ILike, Like, MoreThan } from "typeorm";
|
|
253
401
|
|
|
254
|
-
// src/plugins/erp/erp-queue.ts
|
|
255
|
-
var ERP_QUEUE_NAME = "erp";
|
|
256
|
-
async function queueErp(cms, payload) {
|
|
257
|
-
const queue = cms.getPlugin("queue");
|
|
258
|
-
if (!queue) return;
|
|
259
|
-
await queue.add(ERP_QUEUE_NAME, payload);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
402
|
// src/plugins/erp/erp-contact-sync.ts
|
|
403
|
+
init_erp_queue();
|
|
263
404
|
function splitName(full) {
|
|
264
405
|
const t = (full || "").trim();
|
|
265
406
|
if (!t) return { firstName: "Contact", lastName: "" };
|
|
@@ -297,6 +438,7 @@ async function queueErpCreateContactIfEnabled(cms, dataSource, entityMap, input)
|
|
|
297
438
|
}
|
|
298
439
|
|
|
299
440
|
// src/plugins/erp/erp-product-sync.ts
|
|
441
|
+
init_erp_queue();
|
|
300
442
|
init_erp_config_enabled();
|
|
301
443
|
async function queueErpProductUpsertIfEnabled(cms, dataSource, entityMap, product) {
|
|
302
444
|
try {
|
|
@@ -304,14 +446,21 @@ async function queueErpProductUpsertIfEnabled(cms, dataSource, entityMap, produc
|
|
|
304
446
|
if (!sku) return;
|
|
305
447
|
const on = await isErpIntegrationEnabled(cms, dataSource, entityMap);
|
|
306
448
|
if (!on) return;
|
|
449
|
+
const rawMeta = product.metadata;
|
|
450
|
+
let metadata;
|
|
451
|
+
if (rawMeta && typeof rawMeta === "object" && !Array.isArray(rawMeta)) {
|
|
452
|
+
const { description: _d, ...rest } = rawMeta;
|
|
453
|
+
metadata = Object.keys(rest).length ? rest : void 0;
|
|
454
|
+
}
|
|
307
455
|
const payload = {
|
|
308
456
|
sku,
|
|
309
457
|
title: product.name || sku,
|
|
310
458
|
name: product.name,
|
|
311
|
-
description: typeof product.metadata === "object" && product.metadata && "description" in product.metadata ? String(product.metadata.description ?? "") : void 0,
|
|
312
459
|
hsn_number: product.hsn,
|
|
460
|
+
uom: product.uom != null && String(product.uom).trim() ? String(product.uom).trim() : void 0,
|
|
461
|
+
type: product.type === "service" ? "service" : "product",
|
|
313
462
|
is_active: product.status === "available",
|
|
314
|
-
metadata
|
|
463
|
+
metadata
|
|
315
464
|
};
|
|
316
465
|
await queueErp(cms, { kind: "productUpsert", product: payload });
|
|
317
466
|
} catch {
|
|
@@ -571,6 +720,24 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
571
720
|
} else if (search) {
|
|
572
721
|
where = buildSearchWhereClause(repo, search);
|
|
573
722
|
}
|
|
723
|
+
const intFilterKeys = ["productId", "attributeId", "taxId"];
|
|
724
|
+
const extraWhere = {};
|
|
725
|
+
for (const key of intFilterKeys) {
|
|
726
|
+
const v = searchParams.get(key);
|
|
727
|
+
if (v != null && v !== "" && columnNames.has(key)) {
|
|
728
|
+
const n = Number(v);
|
|
729
|
+
if (Number.isFinite(n)) extraWhere[key] = n;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if (Object.keys(extraWhere).length > 0) {
|
|
733
|
+
if (Array.isArray(where)) {
|
|
734
|
+
where = where.map((w) => ({ ...w, ...extraWhere }));
|
|
735
|
+
} else if (where && typeof where === "object" && Object.keys(where).length > 0) {
|
|
736
|
+
where = { ...where, ...extraWhere };
|
|
737
|
+
} else {
|
|
738
|
+
where = extraWhere;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
574
741
|
const [data, total] = await repo.findAndCount({
|
|
575
742
|
skip,
|
|
576
743
|
take: limit,
|
|
@@ -1037,6 +1204,7 @@ function createUserAuthApiRouter(config) {
|
|
|
1037
1204
|
|
|
1038
1205
|
// src/api/cms-handlers.ts
|
|
1039
1206
|
init_email_queue();
|
|
1207
|
+
init_erp_queue();
|
|
1040
1208
|
import { MoreThanOrEqual, ILike as ILike2 } from "typeorm";
|
|
1041
1209
|
|
|
1042
1210
|
// src/plugins/captcha/assert.ts
|
|
@@ -2516,6 +2684,29 @@ function createCmsApiHandler(config) {
|
|
|
2516
2684
|
if (!Number.isFinite(oid)) return config.json({ error: "Invalid id" }, { status: 400 });
|
|
2517
2685
|
return streamOrderInvoicePdf2(cms, dataSource, entityMap, oid, {});
|
|
2518
2686
|
}
|
|
2687
|
+
if (path[0] === "orders" && path.length === 3 && path[2] === "repost-erp" && getCms) {
|
|
2688
|
+
const a = await config.requireAuth(req);
|
|
2689
|
+
if (a) return a;
|
|
2690
|
+
if (perm) {
|
|
2691
|
+
const pe = await perm(req, "orders", method === "GET" ? "read" : "update");
|
|
2692
|
+
if (pe) return pe;
|
|
2693
|
+
}
|
|
2694
|
+
const oid = Number(path[1]);
|
|
2695
|
+
if (!Number.isFinite(oid)) return config.json({ error: "Invalid id" }, { status: 400 });
|
|
2696
|
+
const cms = await getCms();
|
|
2697
|
+
const { isErpIntegrationEnabled: isErpIntegrationEnabled3 } = await Promise.resolve().then(() => (init_erp_config_enabled(), erp_config_enabled_exports));
|
|
2698
|
+
const enabled = await isErpIntegrationEnabled3(cms, dataSource, entityMap);
|
|
2699
|
+
if (method === "GET") {
|
|
2700
|
+
return config.json({ enabled });
|
|
2701
|
+
}
|
|
2702
|
+
if (method === "POST") {
|
|
2703
|
+
if (!enabled) return config.json({ error: "ERP integration is disabled" }, { status: 409 });
|
|
2704
|
+
const { queueErpPaidOrderForOrderId: queueErpPaidOrderForOrderId2 } = await Promise.resolve().then(() => (init_paid_order_erp(), paid_order_erp_exports));
|
|
2705
|
+
await queueErpPaidOrderForOrderId2(cms, dataSource, entityMap, oid);
|
|
2706
|
+
return config.json({ ok: true });
|
|
2707
|
+
}
|
|
2708
|
+
return config.json({ error: "Method not allowed" }, { status: 405 });
|
|
2709
|
+
}
|
|
2519
2710
|
if (path.length === 0) return config.json({ error: "Not found" }, { status: 404 });
|
|
2520
2711
|
const resource = resolveResource(path[0]);
|
|
2521
2712
|
if (!crudResources.includes(resource)) return config.json({ error: "Invalid resource" }, { status: 400 });
|
|
@@ -2944,6 +3135,152 @@ function createStorefrontApiHandler(config) {
|
|
|
2944
3135
|
const tokenRepo = () => dataSource.getRepository(entityMap.password_reset_tokens);
|
|
2945
3136
|
const collectionRepo = () => dataSource.getRepository(entityMap.collections);
|
|
2946
3137
|
const groupRepo = () => dataSource.getRepository(entityMap.user_groups);
|
|
3138
|
+
const configRepo = () => dataSource.getRepository(entityMap.configs);
|
|
3139
|
+
const CART_CHECKOUT_RELATIONS = ["items", "items.product", "items.product.taxes", "items.product.taxes.tax"];
|
|
3140
|
+
function roundMoney2(n) {
|
|
3141
|
+
return Math.round(n * 100) / 100;
|
|
3142
|
+
}
|
|
3143
|
+
async function getStoreDefaultTaxRate() {
|
|
3144
|
+
const rows = await configRepo().find({ where: { settings: "store", deleted: false } });
|
|
3145
|
+
for (const row of rows) {
|
|
3146
|
+
const r = row;
|
|
3147
|
+
if (r.key === "defaultTaxRate") {
|
|
3148
|
+
const n = parseFloat(String(r.value ?? "").trim());
|
|
3149
|
+
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
return null;
|
|
3153
|
+
}
|
|
3154
|
+
function computeTaxForProductLine(p, lineSubtotal, defaultRate) {
|
|
3155
|
+
const pts = p.taxes ?? [];
|
|
3156
|
+
const activePts = pts.filter((pt) => {
|
|
3157
|
+
const t = pt.tax;
|
|
3158
|
+
return t != null && t.active !== false;
|
|
3159
|
+
});
|
|
3160
|
+
if (activePts.length) {
|
|
3161
|
+
let sumRate = 0;
|
|
3162
|
+
const slugs = [];
|
|
3163
|
+
for (const pt of activePts) {
|
|
3164
|
+
const t = pt.tax;
|
|
3165
|
+
const r = Number(pt.rate != null && pt.rate !== "" ? pt.rate : t.rate ?? 0);
|
|
3166
|
+
if (Number.isFinite(r)) sumRate += r;
|
|
3167
|
+
const slug = String(t.slug ?? "").trim();
|
|
3168
|
+
if (slug) slugs.push(slug);
|
|
3169
|
+
}
|
|
3170
|
+
const tax = roundMoney2(lineSubtotal * sumRate / 100);
|
|
3171
|
+
return {
|
|
3172
|
+
tax,
|
|
3173
|
+
taxRate: sumRate > 0 ? roundMoney2(sumRate) : null,
|
|
3174
|
+
taxCode: slugs.length ? [...new Set(slugs)].sort().join(",") : null
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
if (defaultRate != null && defaultRate > 0) {
|
|
3178
|
+
return {
|
|
3179
|
+
tax: roundMoney2(lineSubtotal * defaultRate / 100),
|
|
3180
|
+
taxRate: roundMoney2(defaultRate),
|
|
3181
|
+
taxCode: null
|
|
3182
|
+
};
|
|
3183
|
+
}
|
|
3184
|
+
return { tax: 0, taxRate: null, taxCode: null };
|
|
3185
|
+
}
|
|
3186
|
+
function parseInlineAddress(raw) {
|
|
3187
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
3188
|
+
const o = raw;
|
|
3189
|
+
const line1 = String(o.line1 ?? "").trim();
|
|
3190
|
+
if (!line1) return null;
|
|
3191
|
+
return {
|
|
3192
|
+
line1,
|
|
3193
|
+
line2: o.line2 != null ? String(o.line2) : "",
|
|
3194
|
+
city: o.city != null ? String(o.city) : "",
|
|
3195
|
+
state: o.state != null ? String(o.state) : "",
|
|
3196
|
+
postalCode: o.postalCode != null ? String(o.postalCode) : "",
|
|
3197
|
+
country: o.country != null ? String(o.country) : ""
|
|
3198
|
+
};
|
|
3199
|
+
}
|
|
3200
|
+
function intFromBody(v) {
|
|
3201
|
+
if (typeof v === "number" && Number.isInteger(v)) return v;
|
|
3202
|
+
if (typeof v === "string" && /^\d+$/.test(v)) return parseInt(v, 10);
|
|
3203
|
+
return void 0;
|
|
3204
|
+
}
|
|
3205
|
+
async function resolveCheckoutAddress(contactId, idVal, inlineVal) {
|
|
3206
|
+
const aid = intFromBody(idVal);
|
|
3207
|
+
if (aid != null) {
|
|
3208
|
+
const existing = await addressRepo().findOne({
|
|
3209
|
+
where: { id: aid, contactId }
|
|
3210
|
+
});
|
|
3211
|
+
if (!existing) return { id: null, error: "Address not found" };
|
|
3212
|
+
return { id: aid };
|
|
3213
|
+
}
|
|
3214
|
+
const addr = parseInlineAddress(inlineVal);
|
|
3215
|
+
if (addr) {
|
|
3216
|
+
const saved = await addressRepo().save(
|
|
3217
|
+
addressRepo().create({
|
|
3218
|
+
contactId,
|
|
3219
|
+
line1: addr.line1,
|
|
3220
|
+
line2: addr.line2?.trim() ? addr.line2 : null,
|
|
3221
|
+
city: addr.city?.trim() ? addr.city : null,
|
|
3222
|
+
state: addr.state?.trim() ? addr.state : null,
|
|
3223
|
+
postalCode: addr.postalCode?.trim() ? addr.postalCode : null,
|
|
3224
|
+
country: addr.country?.trim() ? addr.country : null
|
|
3225
|
+
})
|
|
3226
|
+
);
|
|
3227
|
+
return { id: saved.id };
|
|
3228
|
+
}
|
|
3229
|
+
return { id: null };
|
|
3230
|
+
}
|
|
3231
|
+
async function prepareCheckoutFromCart(b, cart, contactId) {
|
|
3232
|
+
const defaultRate = await getStoreDefaultTaxRate();
|
|
3233
|
+
const lines = [];
|
|
3234
|
+
let subtotal = 0;
|
|
3235
|
+
let orderTax = 0;
|
|
3236
|
+
let needsShipping = false;
|
|
3237
|
+
for (const it of cart.items || []) {
|
|
3238
|
+
const p = it.product;
|
|
3239
|
+
if (!p || p.deleted || p.status !== "available") continue;
|
|
3240
|
+
const unit = Number(p.price);
|
|
3241
|
+
const qty = it.quantity || 1;
|
|
3242
|
+
const lineSubtotal = unit * qty;
|
|
3243
|
+
const pType = p.type === "service" ? "service" : "product";
|
|
3244
|
+
if (pType === "product") needsShipping = true;
|
|
3245
|
+
const { tax, taxRate, taxCode } = computeTaxForProductLine(p, lineSubtotal, defaultRate);
|
|
3246
|
+
const lineTotal = roundMoney2(lineSubtotal + tax);
|
|
3247
|
+
subtotal = roundMoney2(subtotal + lineSubtotal);
|
|
3248
|
+
orderTax = roundMoney2(orderTax + tax);
|
|
3249
|
+
lines.push({
|
|
3250
|
+
productId: p.id,
|
|
3251
|
+
quantity: qty,
|
|
3252
|
+
unitPrice: unit,
|
|
3253
|
+
tax,
|
|
3254
|
+
total: lineTotal,
|
|
3255
|
+
hsn: p.hsn ?? null,
|
|
3256
|
+
uom: p.uom ?? null,
|
|
3257
|
+
productType: pType,
|
|
3258
|
+
taxRate,
|
|
3259
|
+
taxCode
|
|
3260
|
+
});
|
|
3261
|
+
}
|
|
3262
|
+
if (!lines.length) return { ok: false, status: 400, message: "No available items in cart" };
|
|
3263
|
+
const bill = await resolveCheckoutAddress(contactId, b.billingAddressId, b.billingAddress);
|
|
3264
|
+
if (bill.error) return { ok: false, status: 400, message: bill.error };
|
|
3265
|
+
if (bill.id == null) return { ok: false, status: 400, message: "Billing address required" };
|
|
3266
|
+
const ship = await resolveCheckoutAddress(contactId, b.shippingAddressId, b.shippingAddress);
|
|
3267
|
+
if (ship.error) return { ok: false, status: 400, message: ship.error };
|
|
3268
|
+
let shippingAddressId = ship.id;
|
|
3269
|
+
if (needsShipping && shippingAddressId == null) shippingAddressId = bill.id;
|
|
3270
|
+
if (needsShipping && shippingAddressId == null) {
|
|
3271
|
+
return { ok: false, status: 400, message: "Shipping address required" };
|
|
3272
|
+
}
|
|
3273
|
+
const orderTotal = roundMoney2(subtotal + orderTax);
|
|
3274
|
+
return {
|
|
3275
|
+
ok: true,
|
|
3276
|
+
lines,
|
|
3277
|
+
subtotal,
|
|
3278
|
+
orderTax,
|
|
3279
|
+
orderTotal,
|
|
3280
|
+
billingAddressId: bill.id,
|
|
3281
|
+
shippingAddressId
|
|
3282
|
+
};
|
|
3283
|
+
}
|
|
2947
3284
|
async function syncContactToErp(contact) {
|
|
2948
3285
|
if (!getCms) return;
|
|
2949
3286
|
try {
|
|
@@ -3069,6 +3406,7 @@ function createStorefrontApiHandler(config) {
|
|
|
3069
3406
|
slug: p.slug,
|
|
3070
3407
|
price: p.price,
|
|
3071
3408
|
sku: p.sku,
|
|
3409
|
+
type: p.type === "service" ? "service" : "product",
|
|
3072
3410
|
image: primaryProductImageUrl(p.metadata)
|
|
3073
3411
|
} : null
|
|
3074
3412
|
};
|
|
@@ -3096,6 +3434,8 @@ function createStorefrontApiHandler(config) {
|
|
|
3096
3434
|
slug: p.slug,
|
|
3097
3435
|
sku: p.sku,
|
|
3098
3436
|
hsn: p.hsn,
|
|
3437
|
+
uom: p.uom ?? null,
|
|
3438
|
+
type: p.type === "service" ? "service" : "product",
|
|
3099
3439
|
price: p.price,
|
|
3100
3440
|
compareAtPrice: p.compareAtPrice,
|
|
3101
3441
|
status: p.status,
|
|
@@ -3797,7 +4137,7 @@ function createStorefrontApiHandler(config) {
|
|
|
3797
4137
|
contactId = contact.id;
|
|
3798
4138
|
cart = await cartRepo().findOne({
|
|
3799
4139
|
where: { contactId },
|
|
3800
|
-
relations: [
|
|
4140
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
3801
4141
|
});
|
|
3802
4142
|
} else {
|
|
3803
4143
|
const email = String(b.email ?? "").trim();
|
|
@@ -3830,25 +4170,14 @@ function createStorefrontApiHandler(config) {
|
|
|
3830
4170
|
if (!guestToken) return json({ error: "Cart not found" }, { status: 400 });
|
|
3831
4171
|
cart = await cartRepo().findOne({
|
|
3832
4172
|
where: { guestToken },
|
|
3833
|
-
relations: [
|
|
4173
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
3834
4174
|
});
|
|
3835
4175
|
}
|
|
3836
4176
|
if (!cart || !(cart.items || []).length) {
|
|
3837
4177
|
return json({ error: "Cart is empty" }, { status: 400 });
|
|
3838
4178
|
}
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
for (const it of cart.items || []) {
|
|
3842
|
-
const p = it.product;
|
|
3843
|
-
if (!p || p.deleted || p.status !== "available") continue;
|
|
3844
|
-
const unit = Number(p.price);
|
|
3845
|
-
const qty = it.quantity || 1;
|
|
3846
|
-
const lineTotal = unit * qty;
|
|
3847
|
-
subtotal += lineTotal;
|
|
3848
|
-
lines.push({ productId: p.id, quantity: qty, unitPrice: unit, tax: 0, total: lineTotal });
|
|
3849
|
-
}
|
|
3850
|
-
if (!lines.length) return json({ error: "No available items in cart" }, { status: 400 });
|
|
3851
|
-
const total = subtotal;
|
|
4179
|
+
const prepOrd = await prepareCheckoutFromCart(b, cart, contactId);
|
|
4180
|
+
if (!prepOrd.ok) return json({ error: prepOrd.message }, { status: prepOrd.status });
|
|
3852
4181
|
const cartId = cart.id;
|
|
3853
4182
|
const ord = await orderRepo().save(
|
|
3854
4183
|
orderRepo().create({
|
|
@@ -3856,13 +4185,13 @@ function createStorefrontApiHandler(config) {
|
|
|
3856
4185
|
orderKind: "sale",
|
|
3857
4186
|
parentOrderId: null,
|
|
3858
4187
|
contactId,
|
|
3859
|
-
billingAddressId:
|
|
3860
|
-
shippingAddressId:
|
|
4188
|
+
billingAddressId: prepOrd.billingAddressId,
|
|
4189
|
+
shippingAddressId: prepOrd.shippingAddressId,
|
|
3861
4190
|
status: "pending",
|
|
3862
|
-
subtotal,
|
|
3863
|
-
tax:
|
|
4191
|
+
subtotal: prepOrd.subtotal,
|
|
4192
|
+
tax: prepOrd.orderTax,
|
|
3864
4193
|
discount: 0,
|
|
3865
|
-
total,
|
|
4194
|
+
total: prepOrd.orderTotal,
|
|
3866
4195
|
currency: cart.currency || "INR",
|
|
3867
4196
|
metadata: { cartId }
|
|
3868
4197
|
})
|
|
@@ -3871,7 +4200,7 @@ function createStorefrontApiHandler(config) {
|
|
|
3871
4200
|
await orderRepo().update(oid, {
|
|
3872
4201
|
orderNumber: buildCanonicalOrderNumber("sale", oid, ord.createdAt ?? /* @__PURE__ */ new Date())
|
|
3873
4202
|
});
|
|
3874
|
-
for (const line of lines) {
|
|
4203
|
+
for (const line of prepOrd.lines) {
|
|
3875
4204
|
await orderItemRepo().save(
|
|
3876
4205
|
orderItemRepo().create({
|
|
3877
4206
|
orderId: oid,
|
|
@@ -3879,14 +4208,21 @@ function createStorefrontApiHandler(config) {
|
|
|
3879
4208
|
quantity: line.quantity,
|
|
3880
4209
|
unitPrice: line.unitPrice,
|
|
3881
4210
|
tax: line.tax,
|
|
3882
|
-
total: line.total
|
|
4211
|
+
total: line.total,
|
|
4212
|
+
hsn: line.hsn,
|
|
4213
|
+
uom: line.uom,
|
|
4214
|
+
productType: line.productType,
|
|
4215
|
+
taxRate: line.taxRate,
|
|
4216
|
+
taxCode: line.taxCode
|
|
3883
4217
|
})
|
|
3884
4218
|
);
|
|
3885
4219
|
}
|
|
3886
4220
|
return json({
|
|
3887
4221
|
orderId: oid,
|
|
3888
4222
|
orderNumber: ord.orderNumber,
|
|
3889
|
-
|
|
4223
|
+
subtotal: prepOrd.subtotal,
|
|
4224
|
+
tax: prepOrd.orderTax,
|
|
4225
|
+
total: prepOrd.orderTotal,
|
|
3890
4226
|
currency: cart.currency || "INR"
|
|
3891
4227
|
});
|
|
3892
4228
|
}
|
|
@@ -3904,7 +4240,7 @@ function createStorefrontApiHandler(config) {
|
|
|
3904
4240
|
contactId = contact.id;
|
|
3905
4241
|
cart = await cartRepo().findOne({
|
|
3906
4242
|
where: { contactId },
|
|
3907
|
-
relations: [
|
|
4243
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
3908
4244
|
});
|
|
3909
4245
|
} else {
|
|
3910
4246
|
const email = String(b.email ?? "").trim();
|
|
@@ -3937,38 +4273,27 @@ function createStorefrontApiHandler(config) {
|
|
|
3937
4273
|
if (!guestToken) return json({ error: "Cart not found" }, { status: 400 });
|
|
3938
4274
|
cart = await cartRepo().findOne({
|
|
3939
4275
|
where: { guestToken },
|
|
3940
|
-
relations: [
|
|
4276
|
+
relations: [...CART_CHECKOUT_RELATIONS]
|
|
3941
4277
|
});
|
|
3942
4278
|
}
|
|
3943
4279
|
if (!cart || !(cart.items || []).length) {
|
|
3944
4280
|
return json({ error: "Cart is empty" }, { status: 400 });
|
|
3945
4281
|
}
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
for (const it of cart.items || []) {
|
|
3949
|
-
const p = it.product;
|
|
3950
|
-
if (!p || p.deleted || p.status !== "available") continue;
|
|
3951
|
-
const unit = Number(p.price);
|
|
3952
|
-
const qty = it.quantity || 1;
|
|
3953
|
-
const lineTotal = unit * qty;
|
|
3954
|
-
subtotal += lineTotal;
|
|
3955
|
-
lines.push({ productId: p.id, quantity: qty, unitPrice: unit, tax: 0, total: lineTotal });
|
|
3956
|
-
}
|
|
3957
|
-
if (!lines.length) return json({ error: "No available items in cart" }, { status: 400 });
|
|
3958
|
-
const total = subtotal;
|
|
4282
|
+
const prepChk = await prepareCheckoutFromCart(b, cart, contactId);
|
|
4283
|
+
if (!prepChk.ok) return json({ error: prepChk.message }, { status: prepChk.status });
|
|
3959
4284
|
const ord = await orderRepo().save(
|
|
3960
4285
|
orderRepo().create({
|
|
3961
4286
|
orderNumber: temporaryOrderNumberPlaceholder(),
|
|
3962
4287
|
orderKind: "sale",
|
|
3963
4288
|
parentOrderId: null,
|
|
3964
4289
|
contactId,
|
|
3965
|
-
billingAddressId:
|
|
3966
|
-
shippingAddressId:
|
|
4290
|
+
billingAddressId: prepChk.billingAddressId,
|
|
4291
|
+
shippingAddressId: prepChk.shippingAddressId,
|
|
3967
4292
|
status: "pending",
|
|
3968
|
-
subtotal,
|
|
3969
|
-
tax:
|
|
4293
|
+
subtotal: prepChk.subtotal,
|
|
4294
|
+
tax: prepChk.orderTax,
|
|
3970
4295
|
discount: 0,
|
|
3971
|
-
total,
|
|
4296
|
+
total: prepChk.orderTotal,
|
|
3972
4297
|
currency: cart.currency || "INR"
|
|
3973
4298
|
})
|
|
3974
4299
|
);
|
|
@@ -3976,7 +4301,7 @@ function createStorefrontApiHandler(config) {
|
|
|
3976
4301
|
await orderRepo().update(oid, {
|
|
3977
4302
|
orderNumber: buildCanonicalOrderNumber("sale", oid, ord.createdAt ?? /* @__PURE__ */ new Date())
|
|
3978
4303
|
});
|
|
3979
|
-
for (const line of lines) {
|
|
4304
|
+
for (const line of prepChk.lines) {
|
|
3980
4305
|
await orderItemRepo().save(
|
|
3981
4306
|
orderItemRepo().create({
|
|
3982
4307
|
orderId: oid,
|
|
@@ -3984,7 +4309,12 @@ function createStorefrontApiHandler(config) {
|
|
|
3984
4309
|
quantity: line.quantity,
|
|
3985
4310
|
unitPrice: line.unitPrice,
|
|
3986
4311
|
tax: line.tax,
|
|
3987
|
-
total: line.total
|
|
4312
|
+
total: line.total,
|
|
4313
|
+
hsn: line.hsn,
|
|
4314
|
+
uom: line.uom,
|
|
4315
|
+
productType: line.productType,
|
|
4316
|
+
taxRate: line.taxRate,
|
|
4317
|
+
taxCode: line.taxCode
|
|
3988
4318
|
})
|
|
3989
4319
|
);
|
|
3990
4320
|
}
|
|
@@ -3993,7 +4323,9 @@ function createStorefrontApiHandler(config) {
|
|
|
3993
4323
|
return json({
|
|
3994
4324
|
orderId: oid,
|
|
3995
4325
|
orderNumber: ord.orderNumber,
|
|
3996
|
-
|
|
4326
|
+
subtotal: prepChk.subtotal,
|
|
4327
|
+
tax: prepChk.orderTax,
|
|
4328
|
+
total: prepChk.orderTotal
|
|
3997
4329
|
});
|
|
3998
4330
|
}
|
|
3999
4331
|
if (path[0] === "orders" && path.length === 1 && method === "GET") {
|