@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.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-Be8NLxu-.cjs';
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-CjBf9dAb.js';
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 { orderNumber, total, currency, customerName, customerEmail, salesTeamEmails, companyDetails, lineItems } = payload;
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: product.metadata ?? void 0
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: ["items", "items.product"]
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: ["items", "items.product"]
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
- let subtotal = 0;
3840
- const lines = [];
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: typeof b.billingAddressId === "number" ? b.billingAddressId : null,
3860
- shippingAddressId: typeof b.shippingAddressId === "number" ? b.shippingAddressId : null,
4188
+ billingAddressId: prepOrd.billingAddressId,
4189
+ shippingAddressId: prepOrd.shippingAddressId,
3861
4190
  status: "pending",
3862
- subtotal,
3863
- tax: 0,
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
- total,
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: ["items", "items.product"]
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: ["items", "items.product"]
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
- let subtotal = 0;
3947
- const lines = [];
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: typeof b.billingAddressId === "number" ? b.billingAddressId : null,
3966
- shippingAddressId: typeof b.shippingAddressId === "number" ? b.shippingAddressId : null,
4290
+ billingAddressId: prepChk.billingAddressId,
4291
+ shippingAddressId: prepChk.shippingAddressId,
3967
4292
  status: "pending",
3968
- subtotal,
3969
- tax: 0,
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
- total
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") {