@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/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 { orderNumber, total, currency, customerName, customerEmail, salesTeamEmails, companyDetails, lineItems } = payload;
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: product.metadata ?? void 0
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: ["items", "items.product"]
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: ["items", "items.product"]
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
- let subtotal = 0;
3887
- const lines = [];
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: typeof b.billingAddressId === "number" ? b.billingAddressId : null,
3907
- shippingAddressId: typeof b.shippingAddressId === "number" ? b.shippingAddressId : null,
4235
+ billingAddressId: prepOrd.billingAddressId,
4236
+ shippingAddressId: prepOrd.shippingAddressId,
3908
4237
  status: "pending",
3909
- subtotal,
3910
- tax: 0,
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
- total,
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: ["items", "items.product"]
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: ["items", "items.product"]
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
- let subtotal = 0;
3994
- const lines = [];
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: typeof b.billingAddressId === "number" ? b.billingAddressId : null,
4013
- shippingAddressId: typeof b.shippingAddressId === "number" ? b.shippingAddressId : null,
4337
+ billingAddressId: prepChk.billingAddressId,
4338
+ shippingAddressId: prepChk.shippingAddressId,
4014
4339
  status: "pending",
4015
- subtotal,
4016
- tax: 0,
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
- total
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") {