@infuro/cms-core 1.0.45 → 1.0.46

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.
Files changed (65) hide show
  1. package/README.md +19 -2
  2. package/dist/admin.cjs +873 -214
  3. package/dist/admin.d.cts +7 -1
  4. package/dist/admin.d.ts +7 -1
  5. package/dist/admin.js +874 -215
  6. package/dist/api.cjs +40 -37
  7. package/dist/api.d.cts +1 -1
  8. package/dist/api.d.ts +1 -1
  9. package/dist/api.js +8 -5
  10. package/dist/auth.cjs +23 -23
  11. package/dist/auth.d.cts +1 -0
  12. package/dist/auth.d.ts +1 -0
  13. package/dist/auth.js +2 -2
  14. package/dist/chunk-2KUCQVAQ.js +936 -0
  15. package/dist/chunk-3K5AIEIZ.js +256 -0
  16. package/dist/{chunk-I6NH72MO.js → chunk-57O3HZPG.js} +9 -1
  17. package/dist/chunk-7PMHZRF3.cjs +872 -0
  18. package/dist/chunk-BXYZDMTZ.cjs +953 -0
  19. package/dist/chunk-CQHXW4TR.js +107 -0
  20. package/dist/{chunk-NNQBWDCI.js → chunk-CRYKVJTO.js} +55 -959
  21. package/dist/chunk-DBPSJYLZ.js +47 -0
  22. package/dist/{chunk-TAHX46EI.cjs → chunk-FBKDMRQL.cjs} +9 -1
  23. package/dist/chunk-GUSHM5HN.js +862 -0
  24. package/dist/chunk-HY3AXLOI.cjs +265 -0
  25. package/dist/chunk-L3525VXI.js +243 -0
  26. package/dist/{chunk-ZMRQ72F6.cjs → chunk-NBY4NVTY.cjs} +3 -3
  27. package/dist/chunk-OY2YTBMP.cjs +50 -0
  28. package/dist/{chunk-BLR6H5GL.js → chunk-SF2XKMCI.js} +3 -3
  29. package/dist/{chunk-22FFHLTO.cjs → chunk-TCSGFAWB.cjs} +56 -970
  30. package/dist/{chunk-F5XFHYTG.cjs → chunk-UDVLFVEC.cjs} +661 -382
  31. package/dist/{chunk-MVXLRANV.js → chunk-UKC3HYXI.js} +591 -323
  32. package/dist/chunk-V25RDUOU.cjs +248 -0
  33. package/dist/chunk-XMRMZ6NQ.cjs +109 -0
  34. package/dist/cli.cjs +9 -4
  35. package/dist/cli.js +9 -4
  36. package/dist/{email-queue-TPZWLTIV.cjs → email-queue-6NJY6HNM.cjs} +7 -5
  37. package/dist/email-queue-OI2HSCGE.js +4 -0
  38. package/dist/erp-order-invoice-3GXDE443.js +3 -0
  39. package/dist/erp-order-invoice-EYYNR526.cjs +24 -0
  40. package/dist/index.cjs +391 -364
  41. package/dist/index.d.cts +267 -14
  42. package/dist/index.d.ts +267 -14
  43. package/dist/index.js +12 -110
  44. package/dist/migrations/1781810000000-OrderItemsVariantId.ts +44 -0
  45. package/dist/migrations/1781900000000-UserInviteStatusToken.ts +46 -0
  46. package/dist/migrations/1782000000000-ComboSlug.ts +58 -0
  47. package/dist/migrations/1782100000000-DiscountDeviceType.ts +20 -0
  48. package/dist/order-inventory-2MYRKEPC.cjs +39 -0
  49. package/dist/order-inventory-76V4XIGW.js +2 -0
  50. package/dist/order-notification-dispatcher-SBQAMM5V.cjs +21 -0
  51. package/dist/{order-notification-dispatcher-XWIJYDGA.js → order-notification-dispatcher-ZJZB2HUN.js} +5 -2
  52. package/dist/retire-soft-deleted-unique-5VXBA5XX.cjs +15 -0
  53. package/dist/retire-soft-deleted-unique-NXQIH4BC.js +2 -0
  54. package/dist/send-order-placed-emails-B5ZVJT7T.cjs +15 -0
  55. package/dist/send-order-placed-emails-WPI37KRZ.js +6 -0
  56. package/package.json +5 -4
  57. package/src/admin/admin.css +27 -24
  58. package/dist/chunk-5ELSW2UP.js +0 -150
  59. package/dist/chunk-PNOKNSG2.cjs +0 -222
  60. package/dist/chunk-WEMDMVHQ.js +0 -216
  61. package/dist/chunk-XW5ATPRW.cjs +0 -155
  62. package/dist/email-queue-CGZBVVAS.js +0 -2
  63. package/dist/erp-order-invoice-3YISSOWW.js +0 -3
  64. package/dist/erp-order-invoice-ZR5F2KTA.cjs +0 -12
  65. package/dist/order-notification-dispatcher-RU7BTGGX.cjs +0 -18
@@ -1,11 +1,14 @@
1
- import { resolveChatEmailToolSettings, CHAT_EMAIL_LOG, detectChatLeadIntent, parseIntentRecipientEmails, mergeEmailLayoutCompanyDetails, sendChatLeadEmail, buildTranscriptForLeadEmail, buildChatbotSystemPromptWithEmailTool, parseEmailRecipientsFromConfig, isWhatsAppPluginEnabled, mergeWhatsAppConfigLayers, whatsAppConfigured, WhatsAppService, registerWhatsAppQueueProcessor, emailPlugin, initWhatsappTriggerDispatcher } from './chunk-NNQBWDCI.js';
1
+ import { validateInventoryForConfirmedOrderLines, orderStatusHoldsStock, reconcileOrderInventoryBetweenSnapshots, orderInventorySnapshotFromRow, validateInventoryForOrderBecomingConfirmed } from './chunk-3K5AIEIZ.js';
2
+ import { retireSoftDeletedUniqueValue, activeUniqueValueExists } from './chunk-DBPSJYLZ.js';
3
+ import { isWhatsAppPluginEnabled, mergeWhatsAppConfigLayers, whatsAppConfigured, WhatsAppService, registerWhatsAppQueueProcessor, initWhatsappTriggerDispatcher } from './chunk-GUSHM5HN.js';
4
+ import { resolveChatEmailToolSettings, CHAT_EMAIL_LOG, detectChatLeadIntent, parseIntentRecipientEmails, sendChatLeadEmail, buildTranscriptForLeadEmail, buildChatbotSystemPromptWithEmailTool, parseEmailRecipientsFromConfig, emailPlugin } from './chunk-CRYKVJTO.js';
2
5
  import { inviteStatusForActivation } from './chunk-JXF23MPG.js';
3
- import { unwrapErpReadData, mapErpPayloadToFulfillment, extractChildOrderRefsFromSalePayload, mapErpPayloadToInvoiceNumber, streamOrderInvoicePdf } from './chunk-WEMDMVHQ.js';
4
- import { queueEmail, queueVendorOnboardEmails, registerEmailQueueProcessor } from './chunk-5ELSW2UP.js';
6
+ import { queueEmail, queueVendorOnboardEmails, registerEmailQueueProcessor } from './chunk-L3525VXI.js';
7
+ import { mergeEmailLayoutCompanyDetails, unwrapErpReadData, mapErpPayloadToFulfillment, extractChildOrderRefsFromSalePayload, mapErpPayloadToInvoiceNumber, streamOrderInvoicePdf } from './chunk-2KUCQVAQ.js';
5
8
  import { getSmsTemplateDefault, SMS_MESSAGE_TEMPLATE_DEFAULTS } from './chunk-MQBT33IV.js';
6
9
  import { EVENT_ORDER_MESSAGE_TEMPLATE_DEFAULTS, EVENT_ORDER_TEMPLATE_KEY, EVENT_ORDER_TEMPLATE_VARIABLES } from './chunk-KFFB6EMZ.js';
7
10
  import { JOB_RUNNER_QUEUE } from './chunk-GO7PPYNU.js';
8
- import { canManageRoles, sessionHasEntityAccess } from './chunk-BLR6H5GL.js';
11
+ import { canManageRoles, sessionHasEntityAccess } from './chunk-SF2XKMCI.js';
9
12
  import { permissionRowsToRecord, logRbac, isSuperAdmin, vendorPortalFlagsFromUser, VENDOR_SCOPED_STORE_ENTITIES, isVendorPortalUser, getPermissionableEntityKeys, VENDOR_STORE_RBAC_ENTITIES, isSuperAdminGroupName, VENDOR_RBAC_ENTITIES, VENDOR_VIEWER_ROLE_NAME, DEFAULT_VENDOR_ROLE_DEFS, VENDOR_OWNER_GROUP_NAME, resolveVendorScopeFromSessionUser, canManageVendorRoles, isPlatformAdministrator, canManageVendorTeam, resolveDisabledMultiVendorScope, logEntityAccessDecision, explainSessionEntityAccess } from './chunk-DN65KDIA.js';
10
13
  import { queueErp } from './chunk-SYBOCAWB.js';
11
14
  import { isErpIntegrationEnabled } from './chunk-JC6DLWTE.js';
@@ -646,6 +649,144 @@ function applyEventApprovalStatusSideEffects(updatePayload, opts) {
646
649
  }
647
650
  __name(applyEventApprovalStatusSideEffects, "applyEventApprovalStatusSideEffects");
648
651
 
652
+ // src/lib/discount-usage.ts
653
+ async function countDiscountOrdersForContact(dataSource, discountId, contactId) {
654
+ try {
655
+ const rows = await dataSource.query(`SELECT COUNT(DISTINCT od."orderId")::int AS count
656
+ FROM order_discounts od
657
+ WHERE od."discountId" = $1 AND od."userId" = $2`, [
658
+ discountId,
659
+ contactId
660
+ ]);
661
+ return Number(rows?.[0]?.count ?? 0);
662
+ } catch {
663
+ return 0;
664
+ }
665
+ }
666
+ __name(countDiscountOrdersForContact, "countDiscountOrdersForContact");
667
+ function isMaxTotalUsageReached(discount) {
668
+ if (discount.maxTotalUsage == null) return false;
669
+ return Number(discount.usedCount ?? 0) >= Number(discount.maxTotalUsage);
670
+ }
671
+ __name(isMaxTotalUsageReached, "isMaxTotalUsageReached");
672
+ async function isMaxPerUserUsageReached(dataSource, discount, contactId) {
673
+ if (discount.maxUsagePerUser == null || contactId == null) return false;
674
+ const count = await countDiscountOrdersForContact(dataSource, discount.id, contactId);
675
+ return count >= Number(discount.maxUsagePerUser);
676
+ }
677
+ __name(isMaxPerUserUsageReached, "isMaxPerUserUsageReached");
678
+ async function checkAndIncrementDiscountUsage(dataSource, entityMap, discountId, contactId) {
679
+ if (!entityMap.discounts) return null;
680
+ const discountRepo = dataSource.getRepository(entityMap.discounts);
681
+ const discount = await discountRepo.findOne({
682
+ where: {
683
+ id: discountId
684
+ }
685
+ });
686
+ if (!discount) return `Discount #${discountId} not found`;
687
+ const d = discount;
688
+ if (d.maxUsagePerUser != null) {
689
+ const usageCount = await countDiscountOrdersForContact(dataSource, discountId, contactId);
690
+ if (usageCount >= Number(d.maxUsagePerUser)) {
691
+ return "You have reached the maximum usage limit for this discount";
692
+ }
693
+ }
694
+ const updated = await dataSource.query(`UPDATE discounts
695
+ SET "usedCount" = "usedCount" + 1
696
+ WHERE id = $1
697
+ AND ("maxTotalUsage" IS NULL OR "usedCount" < "maxTotalUsage")
698
+ RETURNING id`, [
699
+ discountId
700
+ ]);
701
+ if (!Array.isArray(updated) || updated.length === 0) {
702
+ return "Discount usage limit has been reached";
703
+ }
704
+ return null;
705
+ }
706
+ __name(checkAndIncrementDiscountUsage, "checkAndIncrementDiscountUsage");
707
+ async function decrementDiscountUsage(dataSource, entityMap, discountId, orderId) {
708
+ if (!entityMap.discounts) return;
709
+ try {
710
+ await dataSource.query(`UPDATE discounts SET "usedCount" = GREATEST("usedCount" - 1, 0) WHERE id = $1`, [
711
+ discountId
712
+ ]);
713
+ if (entityMap.order_discounts) {
714
+ await dataSource.getRepository(entityMap.order_discounts).delete({
715
+ orderId,
716
+ discountId
717
+ });
718
+ }
719
+ } catch (err) {
720
+ console.error("[discountEngine] decrementDiscountUsage failed:", err);
721
+ }
722
+ }
723
+ __name(decrementDiscountUsage, "decrementDiscountUsage");
724
+ async function recordDiscountUsage(dataSource, entityMap, orderId, discountId, contactId, discountAmount) {
725
+ if (!entityMap.order_discounts) return;
726
+ try {
727
+ let discountCode = "";
728
+ if (entityMap.discounts) {
729
+ const discountRow = await dataSource.getRepository(entityMap.discounts).findOne({
730
+ where: {
731
+ id: discountId
732
+ }
733
+ });
734
+ discountCode = discountRow?.couponCode ?? "";
735
+ }
736
+ const odRepo = dataSource.getRepository(entityMap.order_discounts);
737
+ const orderItemRepo = dataSource.getRepository(entityMap.order_items);
738
+ const items = await orderItemRepo.find({
739
+ where: {
740
+ orderId
741
+ }
742
+ });
743
+ const discountedItems = items.filter((item) => Number(item.discount ?? 0) > 0);
744
+ if (discountedItems.length === 0) {
745
+ const firstItem = items[0];
746
+ if (!firstItem) return;
747
+ await odRepo.save(odRepo.create({
748
+ orderId,
749
+ orderItemId: firstItem.id,
750
+ discountId,
751
+ discountCode,
752
+ discountAmount,
753
+ userId: contactId
754
+ }));
755
+ return;
756
+ }
757
+ for (const item of discountedItems) {
758
+ const i = item;
759
+ await odRepo.save(odRepo.create({
760
+ orderId,
761
+ orderItemId: i.id,
762
+ discountId,
763
+ discountCode,
764
+ discountAmount: Number(i.discount ?? 0),
765
+ userId: contactId
766
+ }));
767
+ }
768
+ } catch (err) {
769
+ console.error("[discountEngine] recordDiscountUsage failed:", err);
770
+ }
771
+ }
772
+ __name(recordDiscountUsage, "recordDiscountUsage");
773
+ async function consumeAppliedDiscountUsages(dataSource, entityMap, orderId, contactId, applied) {
774
+ const unique = /* @__PURE__ */ new Map();
775
+ for (const row of applied) {
776
+ const id = Number(row.id);
777
+ if (!Number.isFinite(id) || id < 1) continue;
778
+ const amount = Math.max(0, Number(row.calculatedDiscount) || 0);
779
+ unique.set(id, (unique.get(id) ?? 0) + amount);
780
+ }
781
+ for (const [discountId, discountAmount] of unique) {
782
+ const usageErr = await checkAndIncrementDiscountUsage(dataSource, entityMap, discountId, contactId);
783
+ if (usageErr) return usageErr;
784
+ await recordDiscountUsage(dataSource, entityMap, orderId, discountId, contactId, discountAmount);
785
+ }
786
+ return null;
787
+ }
788
+ __name(consumeAppliedDiscountUsages, "consumeAppliedDiscountUsages");
789
+
649
790
  // src/lib/default-vendor-id.ts
650
791
  function getDefaultVendorId() {
651
792
  const raw = process.env.DEFAULT_VENDOR_ID;
@@ -1989,6 +2130,7 @@ async function ensureUniqueProductSlug(repo, baseSlug, vendorId, excludeId) {
1989
2130
  let slug = baseSlug;
1990
2131
  let suffix = 0;
1991
2132
  while (true) {
2133
+ await retireSoftDeletedUniqueValue(repo, "slug", slug);
1992
2134
  const where = excludeId != null ? {
1993
2135
  slug,
1994
2136
  vendorId,
@@ -2008,6 +2150,21 @@ async function ensureUniqueProductSlug(repo, baseSlug, vendorId, excludeId) {
2008
2150
  }
2009
2151
  }
2010
2152
  __name(ensureUniqueProductSlug, "ensureUniqueProductSlug");
2153
+ async function normalizeComboNameAndSlug(repo, body, vendorId, existing, excludeId) {
2154
+ const incomingName = typeof body.name === "string" ? body.name.trim() : "";
2155
+ const incomingSlugRaw = typeof body.slug === "string" ? body.slug.trim() : "";
2156
+ const existingName = typeof existing?.name === "string" ? existing.name.trim() : "";
2157
+ const existingSlug = typeof existing?.slug === "string" ? existing.slug.trim() : "";
2158
+ const name = incomingName || existingName;
2159
+ if (!name) return "Name is required";
2160
+ if (incomingName) body.name = name;
2161
+ const nameBasedSlug = slugifyProductLabel(name);
2162
+ const typedSlug = incomingSlugRaw ? slugifyProductLabel(incomingSlugRaw) : "";
2163
+ const slug = typedSlug || (excludeId != null ? existingSlug : "") || nameBasedSlug;
2164
+ body.slug = await ensureUniqueProductSlug(repo, slug, vendorId, excludeId);
2165
+ return null;
2166
+ }
2167
+ __name(normalizeComboNameAndSlug, "normalizeComboNameAndSlug");
2011
2168
  async function hydrateProductEventNames(dataSource, entityMap, products) {
2012
2169
  if (!products.length) return products;
2013
2170
  if (!entityMap.event_products || !entityMap.events) {
@@ -2254,6 +2411,20 @@ async function normalizeProductNameAndSlug(repo, body, vendorId, existing, exclu
2254
2411
  return null;
2255
2412
  }
2256
2413
  __name(normalizeProductNameAndSlug, "normalizeProductNameAndSlug");
2414
+ async function retireSoftDeletedUniquesFromBody(repo, body) {
2415
+ const cols = new Set(repo.metadata.columns.map((c) => c.propertyName));
2416
+ if (!cols.has("deleted")) return;
2417
+ for (const col of [
2418
+ "slug",
2419
+ "email"
2420
+ ]) {
2421
+ if (!cols.has(col)) continue;
2422
+ const raw = body[col];
2423
+ if (typeof raw !== "string" || !raw.trim()) continue;
2424
+ await retireSoftDeletedUniqueValue(repo, col, raw.trim());
2425
+ }
2426
+ }
2427
+ __name(retireSoftDeletedUniquesFromBody, "retireSoftDeletedUniquesFromBody");
2257
2428
  function buildSoftDeletePayload(meta, deletedBy) {
2258
2429
  const payload = {
2259
2430
  deleted: true
@@ -2490,6 +2661,7 @@ function discountEvaluateRule(rule, cartLines, cartTotal) {
2490
2661
  case "category":
2491
2662
  case "cart":
2492
2663
  case "paymentMethod":
2664
+ case "deviceType":
2493
2665
  case "order": {
2494
2666
  if (rule.subType === "nthOrder") {
2495
2667
  return true;
@@ -2673,87 +2845,22 @@ function applyDiscountToOrderLines(lines, evalResult, maxDiscountAmount) {
2673
2845
  return totalDiscountApplied;
2674
2846
  }
2675
2847
  __name(applyDiscountToOrderLines, "applyDiscountToOrderLines");
2676
- async function checkAndIncrementDiscountUsage(dataSource, entityMap, discountId, contactId) {
2677
- if (!entityMap["discounts"]) return null;
2678
- const discountRepo = dataSource.getRepository(entityMap["discounts"]);
2679
- const discount = await discountRepo.findOne({
2680
- where: {
2681
- id: discountId
2682
- }
2683
- });
2684
- if (!discount) return `Discount #${discountId} not found`;
2685
- await dataSource.query(`UPDATE discounts SET "usedCount" = "usedCount" + 1 WHERE id = $1`, [
2686
- discountId
2687
- ]);
2688
- return null;
2848
+ function asDiscountUsageEntityMap(entityMap) {
2849
+ return entityMap;
2689
2850
  }
2690
- __name(checkAndIncrementDiscountUsage, "checkAndIncrementDiscountUsage");
2691
- async function decrementDiscountUsage(dataSource, entityMap, discountId, orderId) {
2692
- if (!entityMap["discounts"]) return;
2693
- try {
2694
- await dataSource.query(`UPDATE discounts SET "usedCount" = GREATEST("usedCount" - 1, 0) WHERE id = $1`, [
2695
- discountId
2696
- ]);
2697
- if (entityMap["order_discounts"]) {
2698
- await dataSource.getRepository(entityMap["order_discounts"]).delete({
2699
- orderId,
2700
- discountId
2701
- });
2702
- }
2703
- } catch (err) {
2704
- console.error("[discountEngine] decrementDiscountUsage failed:", err);
2705
- }
2851
+ __name(asDiscountUsageEntityMap, "asDiscountUsageEntityMap");
2852
+ async function checkAndIncrementDiscountUsage2(dataSource, entityMap, discountId, contactId) {
2853
+ return checkAndIncrementDiscountUsage(dataSource, asDiscountUsageEntityMap(entityMap), discountId, contactId);
2706
2854
  }
2707
- __name(decrementDiscountUsage, "decrementDiscountUsage");
2708
- async function recordDiscountUsage(dataSource, entityMap, orderId, discountId, contactId, discountAmount) {
2709
- if (!entityMap["order_discounts"]) return;
2710
- try {
2711
- let discountCode = "";
2712
- if (entityMap["discounts"]) {
2713
- const discountRow = await dataSource.getRepository(entityMap["discounts"]).findOne({
2714
- where: {
2715
- id: discountId
2716
- }
2717
- });
2718
- discountCode = discountRow?.couponCode ?? "";
2719
- }
2720
- const odRepo = dataSource.getRepository(entityMap["order_discounts"]);
2721
- const orderItemRepo = dataSource.getRepository(entityMap.order_items);
2722
- const items = await orderItemRepo.find({
2723
- where: {
2724
- orderId
2725
- }
2726
- });
2727
- const discountedItems = items.filter((item) => Number(item.discount ?? 0) > 0);
2728
- if (discountedItems.length === 0) {
2729
- const firstItem = items[0];
2730
- if (!firstItem) return;
2731
- await odRepo.save(odRepo.create({
2732
- orderId,
2733
- orderItemId: firstItem.id,
2734
- discountId,
2735
- discountCode,
2736
- discountAmount,
2737
- userId: contactId
2738
- }));
2739
- return;
2740
- }
2741
- for (const item of discountedItems) {
2742
- const i = item;
2743
- await odRepo.save(odRepo.create({
2744
- orderId,
2745
- orderItemId: i.id,
2746
- discountId,
2747
- discountCode,
2748
- discountAmount: Number(i.discount ?? 0),
2749
- userId: contactId
2750
- }));
2751
- }
2752
- } catch (err) {
2753
- console.error("[discountEngine] recordDiscountUsage failed:", err);
2754
- }
2855
+ __name(checkAndIncrementDiscountUsage2, "checkAndIncrementDiscountUsage");
2856
+ async function decrementDiscountUsage2(dataSource, entityMap, discountId, orderId) {
2857
+ return decrementDiscountUsage(dataSource, asDiscountUsageEntityMap(entityMap), discountId, orderId);
2755
2858
  }
2756
- __name(recordDiscountUsage, "recordDiscountUsage");
2859
+ __name(decrementDiscountUsage2, "decrementDiscountUsage");
2860
+ async function recordDiscountUsage2(dataSource, entityMap, orderId, discountId, contactId, discountAmount) {
2861
+ return recordDiscountUsage(dataSource, asDiscountUsageEntityMap(entityMap), orderId, discountId, contactId, discountAmount);
2862
+ }
2863
+ __name(recordDiscountUsage2, "recordDiscountUsage");
2757
2864
  async function validateCoupon(dataSource, entityMap, couponCode, orderLines, currency = "INR", contactId, isAutomatic, discountId, vendorId) {
2758
2865
  if (!entityMap["discounts"]) return {
2759
2866
  valid: false,
@@ -2798,21 +2905,12 @@ async function validateCoupon(dataSource, entityMap, couponCode, orderLines, cur
2798
2905
  };
2799
2906
  }
2800
2907
  if (contactId != null && d.maxUsagePerUser != null) {
2801
- try {
2802
- const userUsage = await dataSource.query(`SELECT COUNT(DISTINCT od."orderId") as count FROM order_discounts od
2803
- WHERE od."discountId" = $1
2804
- AND od."userId" = $2`, [
2805
- d.id,
2806
- contactId
2807
- ]);
2808
- const usageCount = Number(userUsage?.[0]?.count ?? 0);
2809
- if (usageCount >= d.maxUsagePerUser) {
2810
- return {
2811
- valid: false,
2812
- error: "You have reached the maximum usage limit for this discount"
2813
- };
2814
- }
2815
- } catch {
2908
+ const usageCount = await countDiscountOrdersForContact(dataSource, d.id, contactId);
2909
+ if (usageCount >= d.maxUsagePerUser) {
2910
+ return {
2911
+ valid: false,
2912
+ error: "You have reached the maximum usage limit for this discount"
2913
+ };
2816
2914
  }
2817
2915
  }
2818
2916
  const flatRules = entityMap["discount_rules"] ? await dataSource.getRepository(entityMap["discount_rules"]).find({
@@ -2890,128 +2988,32 @@ function normalizeOrderLinesInput(raw) {
2890
2988
  if (!Number.isFinite(productId) || productId <= 0) continue;
2891
2989
  const qRaw = Number(r.quantity ?? 1);
2892
2990
  const q = Number.isFinite(qRaw) ? Math.min(99999, Math.max(1, Math.floor(qRaw))) : 1;
2893
- merged.set(productId, (merged.get(productId) ?? 0) + q);
2991
+ const vidRaw = r.variantId ?? r.variant_id;
2992
+ const vid = vidRaw != null && String(vidRaw).trim() !== "" ? Number(vidRaw) : NaN;
2993
+ const variantId = Number.isFinite(vid) && vid > 0 ? vid : null;
2994
+ const key = `${productId}:${variantId ?? 0}`;
2995
+ const prev = merged.get(key);
2996
+ if (prev) prev.quantity += q;
2997
+ else merged.set(key, {
2998
+ productId,
2999
+ quantity: q,
3000
+ variantId
3001
+ });
2894
3002
  }
2895
3003
  return [
2896
- ...merged.entries()
2897
- ].map(([productId, quantity]) => ({
2898
- productId,
2899
- quantity
2900
- }));
3004
+ ...merged.values()
3005
+ ];
2901
3006
  }
2902
3007
  __name(normalizeOrderLinesInput, "normalizeOrderLinesInput");
2903
3008
  var ORDER_STATUS_CONFIRMED = "confirmed";
2904
- function orderInventorySnapshotFromRow(order) {
2905
- return {
2906
- status: order.status ?? "pending",
2907
- lines: (order.items ?? []).map((i) => ({
2908
- productId: Number(i.productId),
2909
- quantity: Math.max(0, Math.floor(Number(i.quantity) || 0))
2910
- })).filter((l) => l.productId > 0 && l.quantity > 0)
2911
- };
2912
- }
2913
- __name(orderInventorySnapshotFromRow, "orderInventorySnapshotFromRow");
2914
- function mergeInventoryLinesByProduct(lines) {
2915
- const m = /* @__PURE__ */ new Map();
2916
- for (const { productId, quantity } of lines) {
2917
- if (!Number.isFinite(productId) || productId <= 0 || quantity <= 0) continue;
2918
- m.set(productId, (m.get(productId) ?? 0) + quantity);
2919
- }
2920
- return m;
2921
- }
2922
- __name(mergeInventoryLinesByProduct, "mergeInventoryLinesByProduct");
2923
- async function validateInventoryForConfirmedOrderLines(dataSource, entityMap, calcLines) {
2924
- const productRepo = dataSource.getRepository(entityMap.products);
2925
- const merged = mergeInventoryLinesByProduct(calcLines.filter((l) => l.found && l.productId != null).map((l) => ({
3009
+ async function validateInventoryForConfirmedOrderLines2(dataSource, entityMap, calcLines) {
3010
+ const lines = calcLines.filter((l) => l.found && l.productId != null).map((l) => ({
2926
3011
  productId: l.productId,
2927
3012
  quantity: l.quantity
2928
- })));
2929
- for (const [productId, qty] of merged) {
2930
- const row = await productRepo.findOne({
2931
- where: {
2932
- id: productId,
2933
- deleted: false
2934
- }
2935
- });
2936
- if (!row) return `Product #${productId} not found`;
2937
- if (row.type === "service") continue;
2938
- const cur = Number(row.quantity ?? 0);
2939
- if (cur < qty) {
2940
- const name = row.name ?? String(productId);
2941
- return `Insufficient stock for ${name}: available ${cur}, required ${qty}`;
2942
- }
2943
- }
2944
- return null;
2945
- }
2946
- __name(validateInventoryForConfirmedOrderLines, "validateInventoryForConfirmedOrderLines");
2947
- async function applyInventoryDeltaForMergedLines(dataSource, entityMap, merged, sign, validateDeduct) {
2948
- const productRepo = dataSource.getRepository(entityMap.products);
2949
- for (const [productId, qty] of merged) {
2950
- const row = await productRepo.findOne({
2951
- where: {
2952
- id: productId,
2953
- deleted: false
2954
- }
2955
- });
2956
- if (!row) return `Product #${productId} not found`;
2957
- if (row.type === "service") continue;
2958
- const cur = Number(row.quantity ?? 0);
2959
- if (sign === -1) {
2960
- if (validateDeduct && cur < qty) {
2961
- const name = row.name ?? String(productId);
2962
- return `Insufficient stock for ${name}: available ${cur}, required ${qty}`;
2963
- }
2964
- await productRepo.update(productId, {
2965
- quantity: Math.max(0, cur - qty)
2966
- });
2967
- } else {
2968
- await productRepo.update(productId, {
2969
- quantity: cur + qty
2970
- });
2971
- }
2972
- }
2973
- return null;
2974
- }
2975
- __name(applyInventoryDeltaForMergedLines, "applyInventoryDeltaForMergedLines");
2976
- async function validateInventoryForOrderBecomingConfirmed(dataSource, entityMap, prevSnap, nextLines) {
2977
- const mergedNext = mergeInventoryLinesByProduct(nextLines);
2978
- const productRepo = dataSource.getRepository(entityMap.products);
2979
- const prevByProduct = prevSnap?.status === ORDER_STATUS_CONFIRMED ? mergeInventoryLinesByProduct(prevSnap.lines) : /* @__PURE__ */ new Map();
2980
- for (const [productId, need] of mergedNext) {
2981
- const row = await productRepo.findOne({
2982
- where: {
2983
- id: productId,
2984
- deleted: false
2985
- }
2986
- });
2987
- if (!row) return `Product #${productId} not found`;
2988
- if (row.type === "service") continue;
2989
- let available = Number(row.quantity ?? 0);
2990
- available += prevByProduct.get(productId) ?? 0;
2991
- if (available < need) {
2992
- const name = row.name ?? String(productId);
2993
- return `Insufficient stock for ${name}: available ${available}, required ${need}`;
2994
- }
2995
- }
2996
- return null;
2997
- }
2998
- __name(validateInventoryForOrderBecomingConfirmed, "validateInventoryForOrderBecomingConfirmed");
2999
- async function reconcileOrderInventoryBetweenSnapshots(dataSource, entityMap, prev, next) {
3000
- const wasConfirmed = prev.status === ORDER_STATUS_CONFIRMED;
3001
- const isConfirmed = next.status === ORDER_STATUS_CONFIRMED;
3002
- if (wasConfirmed) {
3003
- const merged = mergeInventoryLinesByProduct(prev.lines);
3004
- const err = await applyInventoryDeltaForMergedLines(dataSource, entityMap, merged, 1, false);
3005
- if (err) return err;
3006
- }
3007
- if (isConfirmed) {
3008
- const merged = mergeInventoryLinesByProduct(next.lines);
3009
- const err = await applyInventoryDeltaForMergedLines(dataSource, entityMap, merged, -1, true);
3010
- if (err) return err;
3011
- }
3012
- return null;
3013
+ }));
3014
+ return validateInventoryForConfirmedOrderLines(dataSource, entityMap, lines);
3013
3015
  }
3014
- __name(reconcileOrderInventoryBetweenSnapshots, "reconcileOrderInventoryBetweenSnapshots");
3016
+ __name(validateInventoryForConfirmedOrderLines2, "validateInventoryForConfirmedOrderLines");
3015
3017
  async function resolveProductTaxRate(dataSource, entityMap, productId, productVendorId) {
3016
3018
  if (!entityMap.product_config || !entityMap.taxes) {
3017
3019
  return {
@@ -4997,6 +4999,22 @@ function createCrudHandler(dataSource, entityMap, options) {
4997
4999
  status: 400
4998
5000
  });
4999
5001
  }
5002
+ if (resource === "combos") {
5003
+ const vendorIdForSlug = scopeCreate.type === "vendor" ? scopeCreate.vendorId : Number(persistBody.vendorId);
5004
+ if (!Number.isFinite(vendorIdForSlug) || vendorIdForSlug <= 0) {
5005
+ return json({
5006
+ error: "vendorId is required"
5007
+ }, {
5008
+ status: 400
5009
+ });
5010
+ }
5011
+ const comboSlugErr = await normalizeComboNameAndSlug(repo, persistBody, vendorIdForSlug);
5012
+ if (comboSlugErr) return json({
5013
+ error: comboSlugErr
5014
+ }, {
5015
+ status: 400
5016
+ });
5017
+ }
5000
5018
  sanitizeBodyForEntity(repo, persistBody);
5001
5019
  let created;
5002
5020
  try {
@@ -5095,6 +5113,7 @@ function createCrudHandler(dataSource, entityMap, options) {
5095
5113
  persistBody.orderNumber = `ORD00${randomPart}`;
5096
5114
  persistBody.qrToken = crypto2.randomBytes(16).toString("hex");
5097
5115
  }
5116
+ await retireSoftDeletedUniquesFromBody(repo, persistBody);
5098
5117
  created = await repo.save(repo.create(persistBody));
5099
5118
  if (resource === "orders") {
5100
5119
  const orderItemRepo = dataSource.getRepository(entityMap.order_items);
@@ -5136,8 +5155,8 @@ function createCrudHandler(dataSource, entityMap, options) {
5136
5155
  }
5137
5156
  }
5138
5157
  const createdStatus = String(persistBody.status ?? "pending");
5139
- if (createdStatus === ORDER_STATUS_CONFIRMED) {
5140
- const stockErr = await validateInventoryForConfirmedOrderLines(dataSource, entityMap, lines);
5158
+ if (orderStatusHoldsStock(createdStatus)) {
5159
+ const stockErr = await validateInventoryForConfirmedOrderLines2(dataSource, entityMap, lines);
5141
5160
  if (stockErr) {
5142
5161
  await repo.delete(created.id);
5143
5162
  return json({
@@ -5159,6 +5178,7 @@ function createCrudHandler(dataSource, entityMap, options) {
5159
5178
  await orderItemRepo.save(orderItemRepo.create({
5160
5179
  orderId: created.id,
5161
5180
  productId: line.productId,
5181
+ variantId: null,
5162
5182
  quantity: line.quantity,
5163
5183
  unitPrice: line.unitPrice,
5164
5184
  subTotal: line.subtotal,
@@ -5252,7 +5272,7 @@ function createCrudHandler(dataSource, entityMap, options) {
5252
5272
  }
5253
5273
  if (orderDiscountId) {
5254
5274
  if (createdStatus === ORDER_STATUS_CONFIRMED) {
5255
- const usageErr = await checkAndIncrementDiscountUsage(dataSource, entityMap, orderDiscountId, persistBody.contactId);
5275
+ const usageErr = await checkAndIncrementDiscountUsage2(dataSource, entityMap, orderDiscountId, persistBody.contactId);
5256
5276
  if (usageErr) {
5257
5277
  await repo.delete(created.id);
5258
5278
  return json({
@@ -5262,7 +5282,7 @@ function createCrudHandler(dataSource, entityMap, options) {
5262
5282
  });
5263
5283
  }
5264
5284
  }
5265
- await recordDiscountUsage(dataSource, entityMap, created.id, orderDiscountId, persistBody.contactId, discount);
5285
+ await recordDiscountUsage2(dataSource, entityMap, created.id, orderDiscountId, persistBody.contactId, discount);
5266
5286
  }
5267
5287
  const orderIdForNotify = created.id;
5268
5288
  void import('./emit-order-notification-trigger-IVLWKKJL.js').then(({ fireOrderNotificationTrigger }) => {
@@ -5271,6 +5291,16 @@ function createCrudHandler(dataSource, entityMap, options) {
5271
5291
  entityMap
5272
5292
  });
5273
5293
  });
5294
+ if (getCms) {
5295
+ void import('./send-order-placed-emails-WPI37KRZ.js').then(({ sendOrderPlacedEmailsAfterConfirmation }) => {
5296
+ return sendOrderPlacedEmailsAfterConfirmation(orderIdForNotify, {
5297
+ getDataSource: /* @__PURE__ */ __name(async () => dataSource, "getDataSource"),
5298
+ entityMap,
5299
+ getCms: /* @__PURE__ */ __name(async () => await getCms(), "getCms")
5300
+ });
5301
+ }).catch(() => {
5302
+ });
5303
+ }
5274
5304
  }
5275
5305
  } catch (err) {
5276
5306
  const message = err instanceof Error ? err.message : String(err);
@@ -5916,6 +5946,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5916
5946
  }
5917
5947
  }
5918
5948
  sanitizeBodyForEntity(repo, updatePayload2);
5949
+ await retireSoftDeletedUniquesFromBody(repo, updatePayload2);
5919
5950
  await repo.update(numericId, updatePayload2);
5920
5951
  if (Array.isArray(rawBody.tags)) {
5921
5952
  const tagNames = rawBody.tags.map((t) => String(t).trim()).filter(Boolean);
@@ -6004,7 +6035,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6004
6035
  } : await calculateOrderTotalsFromLines(dataSource, entityMap, orderLinesNorm, effectiveCurrency, putDiscountId, Number.isFinite(putOrderVendorId) ? putOrderVendorId : null);
6005
6036
  const scalarUpdate = pickColumnUpdates(repo, rawBody);
6006
6037
  const nextStatus = typeof scalarUpdate.status === "string" && scalarUpdate.status ? String(scalarUpdate.status) : orderInvPrev?.status ?? "pending";
6007
- if (nextStatus === ORDER_STATUS_CONFIRMED && lines.length > 0) {
6038
+ if (orderStatusHoldsStock(nextStatus) && lines.length > 0) {
6008
6039
  const verr = await validateInventoryForOrderBecomingConfirmed(dataSource, entityMap, orderInvPrev, lines.filter((l) => l.found && l.productId != null).map((l) => ({
6009
6040
  productId: l.productId,
6010
6041
  quantity: l.quantity
@@ -6133,17 +6164,17 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6133
6164
  }) : null;
6134
6165
  const existingDiscountId = existingOd ? existingOd.discountId : null;
6135
6166
  if (!putDiscountId && existingDiscountId && wasConfirmed) {
6136
- await decrementDiscountUsage(dataSource, entityMap, existingDiscountId, numericId);
6167
+ await decrementDiscountUsage2(dataSource, entityMap, existingDiscountId, numericId);
6137
6168
  if (entityMap["order_discounts"] && existingOd) {
6138
6169
  await dataSource.getRepository(entityMap["order_discounts"]).delete(existingOd.id);
6139
6170
  }
6140
6171
  }
6141
6172
  if (putDiscountId && existingDiscountId && putDiscountId !== existingDiscountId) {
6142
6173
  if (wasConfirmed) {
6143
- await decrementDiscountUsage(dataSource, entityMap, existingDiscountId, numericId);
6174
+ await decrementDiscountUsage2(dataSource, entityMap, existingDiscountId, numericId);
6144
6175
  }
6145
6176
  if (isNowConfirmed) {
6146
- const usageErr = await checkAndIncrementDiscountUsage(dataSource, entityMap, putDiscountId);
6177
+ const usageErr = await checkAndIncrementDiscountUsage2(dataSource, entityMap, putDiscountId, cid);
6147
6178
  if (usageErr) return json({
6148
6179
  error: usageErr
6149
6180
  }, {
@@ -6156,12 +6187,12 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6156
6187
  discountAmount: discount
6157
6188
  });
6158
6189
  } else {
6159
- await recordDiscountUsage(dataSource, entityMap, numericId, putDiscountId, cid, discount);
6190
+ await recordDiscountUsage2(dataSource, entityMap, numericId, putDiscountId, cid, discount);
6160
6191
  }
6161
6192
  }
6162
6193
  if (putDiscountId && existingDiscountId && putDiscountId === existingDiscountId) {
6163
6194
  if (!wasConfirmed && isNowConfirmed) {
6164
- const usageErr = await checkAndIncrementDiscountUsage(dataSource, entityMap, putDiscountId);
6195
+ const usageErr = await checkAndIncrementDiscountUsage2(dataSource, entityMap, putDiscountId, cid);
6165
6196
  if (usageErr) return json({
6166
6197
  error: usageErr
6167
6198
  }, {
@@ -6169,7 +6200,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6169
6200
  });
6170
6201
  }
6171
6202
  if (wasConfirmed && !isNowConfirmed) {
6172
- await decrementDiscountUsage(dataSource, entityMap, putDiscountId, numericId);
6203
+ await decrementDiscountUsage2(dataSource, entityMap, putDiscountId, numericId);
6173
6204
  }
6174
6205
  if (existingOd && entityMap["order_discounts"]) {
6175
6206
  await dataSource.getRepository(entityMap["order_discounts"]).update(existingOd.id, {
@@ -6179,14 +6210,14 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6179
6210
  }
6180
6211
  if (putDiscountId && !existingDiscountId) {
6181
6212
  if (isNowConfirmed) {
6182
- const usageErr = await checkAndIncrementDiscountUsage(dataSource, entityMap, putDiscountId);
6213
+ const usageErr = await checkAndIncrementDiscountUsage2(dataSource, entityMap, putDiscountId, cid);
6183
6214
  if (usageErr) return json({
6184
6215
  error: usageErr
6185
6216
  }, {
6186
6217
  status: 400
6187
6218
  });
6188
6219
  }
6189
- await recordDiscountUsage(dataSource, entityMap, numericId, putDiscountId, cid, discount);
6220
+ await recordDiscountUsage2(dataSource, entityMap, numericId, putDiscountId, cid, discount);
6190
6221
  }
6191
6222
  }
6192
6223
  }
@@ -6657,7 +6688,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6657
6688
  }
6658
6689
  if (resource === "orders" && orderInvPrev) {
6659
6690
  const nextSt = typeof updatePayload.status === "string" && updatePayload.status ? String(updatePayload.status) : orderInvPrev.status;
6660
- if (nextSt === ORDER_STATUS_CONFIRMED && orderInvPrev.lines.length > 0) {
6691
+ if (orderStatusHoldsStock(nextSt) && orderInvPrev.lines.length > 0) {
6661
6692
  const verr = await validateInventoryForOrderBecomingConfirmed(dataSource, entityMap, orderInvPrev, orderInvPrev.lines);
6662
6693
  if (verr) return json({
6663
6694
  error: verr
@@ -6666,6 +6697,33 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6666
6697
  });
6667
6698
  }
6668
6699
  }
6700
+ if (resource === "combos") {
6701
+ const currentCombo = await repo.findOne({
6702
+ where: {
6703
+ id: numericId,
6704
+ deleted: false
6705
+ }
6706
+ });
6707
+ if (!currentCombo) return json({
6708
+ message: "Not found"
6709
+ }, {
6710
+ status: 404
6711
+ });
6712
+ const vendorIdForSlug = scopePut.type === "vendor" ? scopePut.vendorId : Number(currentCombo.vendorId);
6713
+ if (!Number.isFinite(vendorIdForSlug) || vendorIdForSlug <= 0) {
6714
+ return json({
6715
+ error: "vendorId is required"
6716
+ }, {
6717
+ status: 400
6718
+ });
6719
+ }
6720
+ const comboSlugErr = await normalizeComboNameAndSlug(repo, updatePayload, vendorIdForSlug, currentCombo, numericId);
6721
+ if (comboSlugErr) return json({
6722
+ error: comboSlugErr
6723
+ }, {
6724
+ status: 400
6725
+ });
6726
+ }
6669
6727
  if (Object.keys(updatePayload).length > 0) {
6670
6728
  if (resource === "payments" && updatePayload.status === "paid" && updatePayload.paidAt == null) {
6671
6729
  const existingPayment = await repo.findOne({
@@ -6679,6 +6737,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6679
6737
  }
6680
6738
  }
6681
6739
  sanitizeBodyForEntity(repo, updatePayload);
6740
+ await retireSoftDeletedUniquesFromBody(repo, updatePayload);
6682
6741
  await repo.update(numericId, updatePayload);
6683
6742
  }
6684
6743
  if (resource === "orders" && rawBody && entityMap["order_addresses"]) {
@@ -6854,7 +6913,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6854
6913
  });
6855
6914
  if (od) {
6856
6915
  const odRow = od;
6857
- await decrementDiscountUsage(dataSource, entityMap, odRow.discountId, numericId);
6916
+ await decrementDiscountUsage2(dataSource, entityMap, odRow.discountId, numericId);
6858
6917
  }
6859
6918
  }
6860
6919
  }
@@ -6867,11 +6926,19 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6867
6926
  }
6868
6927
  }
6869
6928
  await repo.update(numericId, buildSoftDeletePayload(repo.metadata, deletedBy));
6870
- if ((resource === "forms" || resource === "vendors") && existing && typeof existing.slug === "string") {
6871
- const slug = String(existing.slug).trim();
6872
- if (slug && !slug.includes("__deleted_")) {
6929
+ const softDeletedCols = repo.metadata.columns.map((c) => c.propertyName);
6930
+ const uniqueColsToRetire = [
6931
+ "slug",
6932
+ "email"
6933
+ ].filter((col) => softDeletedCols.includes(col));
6934
+ if (existing && uniqueColsToRetire.length > 0) {
6935
+ for (const col of uniqueColsToRetire) {
6936
+ const raw = existing[col];
6937
+ if (typeof raw !== "string") continue;
6938
+ const value = raw.trim();
6939
+ if (!value || value.includes("__deleted_")) continue;
6873
6940
  await repo.update(numericId, {
6874
- slug: `${slug}__deleted_${numericId}`
6941
+ [col]: `${value}__deleted_${numericId}`
6875
6942
  });
6876
6943
  }
6877
6944
  }
@@ -6919,6 +6986,49 @@ async function linkUnclaimedContactToUser(dataSource, contactsEntity, userId, em
6919
6986
  });
6920
6987
  }
6921
6988
  __name(linkUnclaimedContactToUser, "linkUnclaimedContactToUser");
6989
+ function buildUserInviteLink(baseUrl, token) {
6990
+ const base = baseUrl.replace(/\/+$/, "");
6991
+ return `${base}/admin/invite?token=${encodeURIComponent(token)}`;
6992
+ }
6993
+ __name(buildUserInviteLink, "buildUserInviteLink");
6994
+ function newUserInviteToken() {
6995
+ return randomBytes(32).toString("hex");
6996
+ }
6997
+ __name(newUserInviteToken, "newUserInviteToken");
6998
+ async function findUserByInviteToken(dataSource, userEntity, token) {
6999
+ const trimmed = token.trim();
7000
+ if (!trimmed) return null;
7001
+ const repo = dataSource.getRepository(userEntity);
7002
+ const row = await repo.findOne({
7003
+ where: {
7004
+ inviteToken: trimmed,
7005
+ deleted: false
7006
+ },
7007
+ select: [
7008
+ "id",
7009
+ "email",
7010
+ "blocked",
7011
+ "password",
7012
+ "inviteStatus",
7013
+ "inviteToken"
7014
+ ]
7015
+ });
7016
+ if (!row) return null;
7017
+ const u = row;
7018
+ return u;
7019
+ }
7020
+ __name(findUserByInviteToken, "findUserByInviteToken");
7021
+ async function completeUserInviteAccept(dataSource, userEntity, userId, passwordHash) {
7022
+ await dataSource.getRepository(userEntity).update(userId, {
7023
+ password: passwordHash,
7024
+ blocked: false,
7025
+ inviteStatus: "active",
7026
+ inviteToken: null
7027
+ });
7028
+ }
7029
+ __name(completeUserInviteAccept, "completeUserInviteAccept");
7030
+
7031
+ // src/lib/vendor-invite-token.ts
6922
7032
  var VENDOR_INVITE_METADATA_TOKEN = "inviteToken";
6923
7033
  var VENDOR_INVITE_METADATA_EXPIRES = "inviteExpiresAt";
6924
7034
  var VENDOR_INVITE_METADATA_SENT = "inviteSentAt";
@@ -6943,6 +7053,13 @@ function invalidatedInviteMessage() {
6943
7053
  return "This invitation is no longer valid. Ask an admin to resend the invite.";
6944
7054
  }
6945
7055
  __name(invalidatedInviteMessage, "invalidatedInviteMessage");
7056
+ function userAlreadyActive(user) {
7057
+ const hasPassword = !!(user.password && String(user.password).trim());
7058
+ if (user.inviteStatus === "active" && hasPassword) return true;
7059
+ if (user.inviteStatus === "pending") return false;
7060
+ return !user.blocked && hasPassword;
7061
+ }
7062
+ __name(userAlreadyActive, "userAlreadyActive");
6946
7063
  async function validateInviteTokenForActivation(dataSource, entityMap, token) {
6947
7064
  const trimmedToken = String(token ?? "").trim();
6948
7065
  if (!trimmedToken) {
@@ -6989,15 +7106,15 @@ async function validateInviteTokenForActivation(dataSource, entityMap, token) {
6989
7106
  "id",
6990
7107
  "email",
6991
7108
  "blocked",
6992
- "password"
7109
+ "password",
7110
+ "inviteStatus"
6993
7111
  ]
6994
7112
  });
6995
7113
  if (!user2) return {
6996
7114
  ok: false,
6997
7115
  error: "User not found"
6998
7116
  };
6999
- const hasPassword2 = !!user2.password;
7000
- if (!user2.blocked && hasPassword2) {
7117
+ if (userAlreadyActive(user2)) {
7001
7118
  return {
7002
7119
  ok: false,
7003
7120
  error: "User is already active"
@@ -7013,6 +7130,24 @@ async function validateInviteTokenForActivation(dataSource, entityMap, token) {
7013
7130
  };
7014
7131
  }
7015
7132
  }
7133
+ if (userRepo && entityMap.users) {
7134
+ const userMatch = await findUserByInviteToken(dataSource, entityMap.users, trimmedToken);
7135
+ if (userMatch) {
7136
+ if (userMatch.inviteStatus === "active" || userAlreadyActive(userMatch)) {
7137
+ return {
7138
+ ok: false,
7139
+ error: "This invitation has already been accepted"
7140
+ };
7141
+ }
7142
+ return {
7143
+ ok: true,
7144
+ target: {
7145
+ userId: userMatch.id,
7146
+ email: userMatch.email
7147
+ }
7148
+ };
7149
+ }
7150
+ }
7016
7151
  let email;
7017
7152
  try {
7018
7153
  email = Buffer.from(trimmedToken, "base64").toString("utf8").trim();
@@ -7042,22 +7177,30 @@ async function validateInviteTokenForActivation(dataSource, entityMap, token) {
7042
7177
  "id",
7043
7178
  "email",
7044
7179
  "blocked",
7045
- "password"
7180
+ "password",
7181
+ "inviteStatus",
7182
+ "inviteToken"
7046
7183
  ]
7047
7184
  });
7048
7185
  if (!user) return {
7049
7186
  ok: false,
7050
7187
  error: "User not found"
7051
7188
  };
7052
- const hasPassword = !!user.password;
7053
- if (!user.blocked && hasPassword) {
7189
+ const u = user;
7190
+ if (userAlreadyActive(u)) {
7054
7191
  return {
7055
7192
  ok: false,
7056
7193
  error: "User is already active"
7057
7194
  };
7058
7195
  }
7196
+ if (u.inviteToken && String(u.inviteToken).trim()) {
7197
+ return {
7198
+ ok: false,
7199
+ error: invalidatedInviteMessage()
7200
+ };
7201
+ }
7059
7202
  if (entityMap.vendors) {
7060
- const ownerVendor = await loadOwnerVendorForUser(dataSource, entityMap.vendors, user.id);
7203
+ const ownerVendor = await loadOwnerVendorForUser(dataSource, entityMap.vendors, u.id);
7061
7204
  if (ownerVendor && readVendorInviteToken(ownerVendor.metadata)) {
7062
7205
  return {
7063
7206
  ok: false,
@@ -7068,8 +7211,8 @@ async function validateInviteTokenForActivation(dataSource, entityMap, token) {
7068
7211
  return {
7069
7212
  ok: true,
7070
7213
  target: {
7071
- userId: user.id,
7072
- email: String(user.email)
7214
+ userId: u.id,
7215
+ email: String(u.email)
7073
7216
  }
7074
7217
  };
7075
7218
  }
@@ -7287,10 +7430,7 @@ function createInviteAcceptHandler(config) {
7287
7430
  }
7288
7431
  if (beforeActivate) await beforeActivate(email, userId);
7289
7432
  const hashedPassword = await hashPassword(password);
7290
- await dataSource.getRepository(entityMap.users).update(userId, {
7291
- password: hashedPassword,
7292
- blocked: false
7293
- });
7433
+ await completeUserInviteAccept(dataSource, entityMap.users, userId, hashedPassword);
7294
7434
  if (vendorId != null && entityMap.vendors) {
7295
7435
  await completeVendorInviteAccept(dataSource, entityMap.vendors, vendorId);
7296
7436
  }
@@ -7651,50 +7791,6 @@ async function findLlmAgentByScope(dataSource, entityMap, scope, options = {}) {
7651
7791
  }
7652
7792
  __name(findLlmAgentByScope, "findLlmAgentByScope");
7653
7793
 
7654
- // src/lib/retire-soft-deleted-unique.ts
7655
- async function retireSoftDeletedUniqueValue(repo, column, value) {
7656
- const trimmed = typeof value === "string" ? value.trim() : "";
7657
- if (!trimmed) return;
7658
- const cols = new Set(repo.metadata.columns.map((c) => c.propertyName));
7659
- if (!cols.has(column) || !cols.has("deleted")) return;
7660
- const rows = await repo.find({
7661
- where: {
7662
- [column]: trimmed,
7663
- deleted: true
7664
- }
7665
- });
7666
- for (const row of rows) {
7667
- const id = row.id;
7668
- if (!Number.isFinite(id)) continue;
7669
- const current = String(row[column] ?? "");
7670
- if (current.includes("__deleted_")) continue;
7671
- await repo.update(id, {
7672
- [column]: `${trimmed}__deleted_${id}`
7673
- });
7674
- }
7675
- }
7676
- __name(retireSoftDeletedUniqueValue, "retireSoftDeletedUniqueValue");
7677
- async function activeUniqueValueExists(repo, column, value, excludeId) {
7678
- const trimmed = typeof value === "string" ? value.trim() : "";
7679
- if (!trimmed) return false;
7680
- const cols = new Set(repo.metadata.columns.map((c) => c.propertyName));
7681
- if (!cols.has(column)) return false;
7682
- const qb = repo.createQueryBuilder("row").where(`row.${column} = :value`, {
7683
- value: trimmed
7684
- });
7685
- if (cols.has("deleted")) {
7686
- qb.andWhere("row.deleted = false");
7687
- }
7688
- if (excludeId != null && Number.isFinite(excludeId) && excludeId > 0) {
7689
- qb.andWhere("row.id != :excludeId", {
7690
- excludeId
7691
- });
7692
- }
7693
- const hit = await qb.getOne();
7694
- return !!hit;
7695
- }
7696
- __name(activeUniqueValueExists, "activeUniqueValueExists");
7697
-
7698
7794
  // src/lib/media-folder-path.ts
7699
7795
  function sanitizeMediaFolderPath(input) {
7700
7796
  if (input == null) return "";
@@ -8761,6 +8857,9 @@ function createUploadHandler(config) {
8761
8857
  "image/png",
8762
8858
  "image/gif",
8763
8859
  "image/webp",
8860
+ "video/mp4",
8861
+ "video/webm",
8862
+ "video/quicktime",
8764
8863
  "application/pdf",
8765
8864
  "text/plain",
8766
8865
  "application/zip",
@@ -8788,8 +8887,11 @@ function createUploadHandler(config) {
8788
8887
  });
8789
8888
  const defaultMax = 10 * 1024 * 1024;
8790
8889
  const maxZipBytes = 80 * 1024 * 1024;
8890
+ const maxVideoBytes = 100 * 1024 * 1024;
8791
8891
  const baseMax = maxSizeBytes ?? defaultMax;
8792
- const effectiveMax = file.type === "application/zip" || file.type === "application/x-zip-compressed" ? Math.max(baseMax, maxZipBytes) : baseMax;
8892
+ const isZip = file.type === "application/zip" || file.type === "application/x-zip-compressed";
8893
+ const isVideo = file.type === "video/mp4" || file.type === "video/webm" || file.type === "video/quicktime";
8894
+ const effectiveMax = isZip ? Math.max(baseMax, maxZipBytes) : isVideo ? Math.max(baseMax, maxVideoBytes) : baseMax;
8793
8895
  if (file.size > effectiveMax) return json({
8794
8896
  error: "File size exceeds limit"
8795
8897
  }, {
@@ -8862,12 +8964,20 @@ function createUploadHandler(config) {
8862
8964
  parentId
8863
8965
  });
8864
8966
  } catch (err) {
8967
+ const message = err instanceof Error ? err.message : String(err);
8865
8968
  console.error("[upload] File upload failed", {
8866
- error: err instanceof Error ? err.message : String(err),
8969
+ error: message,
8867
8970
  stack: err instanceof Error ? err.stack : void 0,
8868
8971
  name: err instanceof Error ? err.name : void 0,
8869
8972
  metadata: err && typeof err === "object" && "$metadata" in err ? err.$metadata : void 0
8870
8973
  });
8974
+ if (/Failed to parse body as FormData/i.test(message)) {
8975
+ return json({
8976
+ error: "File upload failed: request body could not be read as FormData. Exclude /api/upload from middleware matcher and raise proxyClientMaxBodySize."
8977
+ }, {
8978
+ status: 500
8979
+ });
8980
+ }
8871
8981
  return json({
8872
8982
  error: "File upload failed"
8873
8983
  }, {
@@ -9621,7 +9731,8 @@ function createUsersApiHandlers(config) {
9621
9731
  "name",
9622
9732
  "email",
9623
9733
  "id",
9624
- "blocked"
9734
+ "blocked",
9735
+ "inviteStatus"
9625
9736
  ]);
9626
9737
  const sortField = allowedSort.has(sortFieldRaw) ? sortFieldRaw : "createdAt";
9627
9738
  const sortOrder = url.searchParams.get("sortOrder") === "desc" ? "DESC" : "ASC";
@@ -9634,6 +9745,7 @@ function createUsersApiHandlers(config) {
9634
9745
  "u.name",
9635
9746
  "u.email",
9636
9747
  "u.blocked",
9748
+ "u.inviteStatus",
9637
9749
  "u.createdAt",
9638
9750
  "u.updatedAt",
9639
9751
  "u.groupId",
@@ -9729,6 +9841,7 @@ function createUsersApiHandlers(config) {
9729
9841
  const isCustomer = !!(customerG && gid === customerG.id);
9730
9842
  const adminAccess = isCustomer ? false : body.adminAccess === false ? false : true;
9731
9843
  const blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
9844
+ const inviteToken = newUserInviteToken();
9732
9845
  const newUser = existing?.deleted ? await (async () => {
9733
9846
  await userRepo().update(existing.id, {
9734
9847
  deleted: false,
@@ -9738,6 +9851,8 @@ function createUsersApiHandlers(config) {
9738
9851
  email,
9739
9852
  password: null,
9740
9853
  blocked,
9854
+ inviteStatus: "pending",
9855
+ inviteToken,
9741
9856
  groupId: gid,
9742
9857
  adminAccess,
9743
9858
  updatedAt: /* @__PURE__ */ new Date()
@@ -9754,6 +9869,8 @@ function createUsersApiHandlers(config) {
9754
9869
  email,
9755
9870
  password: null,
9756
9871
  blocked,
9872
+ inviteStatus: "pending",
9873
+ inviteToken,
9757
9874
  groupId: gid,
9758
9875
  adminAccess
9759
9876
  }));
@@ -9771,13 +9888,12 @@ function createUsersApiHandlers(config) {
9771
9888
  phone
9772
9889
  });
9773
9890
  }
9774
- const emailToken = Buffer.from(newUser.email).toString("base64");
9775
- const inviteLink = `${baseUrl}/admin/invite?token=${emailToken}`;
9776
- await trySendInviteEmail(newUser.email, inviteLink, newUser.name ?? "");
9891
+ const inviteLink = buildUserInviteLink(baseUrl, inviteToken);
9777
9892
  return json({
9778
- message: blocked ? "User created successfully (blocked until password is set)" : "User created successfully",
9893
+ message: "User created successfully. Invite is pending until they set a password.",
9779
9894
  user: newUser,
9780
- inviteLink
9895
+ inviteLink,
9896
+ emailSent: false
9781
9897
  }, {
9782
9898
  status: 201
9783
9899
  });
@@ -9811,6 +9927,7 @@ function createUsersApiHandlers(config) {
9811
9927
  "email",
9812
9928
  "phone",
9813
9929
  "blocked",
9930
+ "inviteStatus",
9814
9931
  "createdAt",
9815
9932
  "updatedAt",
9816
9933
  "groupId"
@@ -9923,6 +10040,7 @@ function createUsersApiHandlers(config) {
9923
10040
  "email",
9924
10041
  "phone",
9925
10042
  "blocked",
10043
+ "inviteStatus",
9926
10044
  "createdAt",
9927
10045
  "updatedAt",
9928
10046
  "groupId"
@@ -9990,13 +10108,21 @@ function createUsersApiHandlers(config) {
9990
10108
  });
9991
10109
  }
9992
10110
  },
9993
- async regenerateInvite(_req, id) {
9994
- const authErr = await requireAuth(new Request(_req.url));
10111
+ async regenerateInvite(req, id) {
10112
+ const authErr = await requireAuth(new Request(req.url));
9995
10113
  if (authErr) return authErr;
9996
10114
  if (requireEntityPermission) {
9997
- const pe = await requireEntityPermission(_req, "users", "update");
10115
+ const pe = await requireEntityPermission(req, "users", "update");
9998
10116
  if (pe) return pe;
9999
10117
  }
10118
+ let sendEmail = true;
10119
+ let rotate = true;
10120
+ try {
10121
+ const body = await req.json().catch(() => ({}));
10122
+ if (body.sendEmail === false) sendEmail = false;
10123
+ if (body.rotate === false) rotate = false;
10124
+ } catch {
10125
+ }
10000
10126
  try {
10001
10127
  const user = await userRepo().findOne({
10002
10128
  where: {
@@ -10006,7 +10132,11 @@ function createUsersApiHandlers(config) {
10006
10132
  select: [
10007
10133
  "id",
10008
10134
  "email",
10009
- "name"
10135
+ "name",
10136
+ "password",
10137
+ "inviteStatus",
10138
+ "inviteToken",
10139
+ "blocked"
10010
10140
  ]
10011
10141
  });
10012
10142
  if (!user) return json({
@@ -10014,34 +10144,75 @@ function createUsersApiHandlers(config) {
10014
10144
  }, {
10015
10145
  status: 404
10016
10146
  });
10017
- let inviteLink = `${baseUrl}/admin/invite?token=${Buffer.from(user.email).toString("base64")}`;
10147
+ const u = user;
10148
+ const hasPassword = !!(u.password && String(u.password).trim());
10149
+ if (u.inviteStatus === "active" && hasPassword) {
10150
+ return json({
10151
+ error: "User has already accepted the invite"
10152
+ }, {
10153
+ status: 400
10154
+ });
10155
+ }
10156
+ let inviteLink = "";
10018
10157
  if (entityMap.vendors) {
10019
10158
  const vendorRepo = dataSource.getRepository(entityMap.vendors);
10020
10159
  const vendor = await vendorRepo.findOne({
10021
10160
  where: {
10022
- userId: user.id,
10161
+ userId: u.id,
10023
10162
  deleted: false
10024
10163
  }
10025
10164
  });
10026
10165
  const v = vendor;
10027
10166
  if (v && (v.inviteStatus === "pending" || v.inviteStatus === "expired")) {
10028
- const rotatedMetadata = applyRotatingVendorInvite(v.metadata);
10029
- await vendorRepo.update(v.id, {
10030
- metadata: rotatedMetadata,
10031
- inviteStatus: "pending"
10032
- });
10033
- const token = readVendorInviteToken(rotatedMetadata);
10167
+ const nextMetadata = rotate ? applyRotatingVendorInvite(v.metadata) : {
10168
+ ...v.metadata ?? {}
10169
+ };
10170
+ if (rotate || v.inviteStatus !== "pending") {
10171
+ await vendorRepo.update(v.id, {
10172
+ metadata: nextMetadata,
10173
+ inviteStatus: "pending"
10174
+ });
10175
+ }
10176
+ const token = readVendorInviteToken(nextMetadata);
10034
10177
  if (token) {
10035
10178
  inviteLink = buildVendorInviteLink(baseUrl, token);
10036
10179
  }
10180
+ const { markVendorInvitePendingForUser } = await import('./vendor-invite-status-PWJ3DE76.js');
10181
+ await markVendorInvitePendingForUser(dataSource, entityMap.vendors, u.id);
10182
+ }
10183
+ }
10184
+ if (!inviteLink) {
10185
+ let token = u.inviteToken?.trim() || "";
10186
+ if (rotate || !token) {
10187
+ token = newUserInviteToken();
10188
+ await userRepo().update(u.id, {
10189
+ inviteToken: token,
10190
+ inviteStatus: "pending",
10191
+ password: null
10192
+ });
10193
+ } else if (u.inviteStatus !== "pending") {
10194
+ await userRepo().update(u.id, {
10195
+ inviteStatus: "pending"
10196
+ });
10037
10197
  }
10038
- const { markVendorInvitePendingForUser } = await import('./vendor-invite-status-PWJ3DE76.js');
10039
- await markVendorInvitePendingForUser(dataSource, entityMap.vendors, user.id);
10198
+ inviteLink = buildUserInviteLink(baseUrl, token);
10199
+ } else if (rotate) {
10200
+ await userRepo().update(u.id, {
10201
+ inviteStatus: "pending",
10202
+ password: null
10203
+ });
10040
10204
  }
10041
- await trySendInviteEmail(user.email, inviteLink, user.name ?? "");
10205
+ let emailSent = false;
10206
+ if (sendEmail && inviteLink) {
10207
+ await trySendInviteEmail(u.email, inviteLink, u.name ?? "");
10208
+ emailSent = true;
10209
+ }
10210
+ const rotatedNote = rotate ? " Previous invite link is no longer valid." : "";
10042
10211
  return json({
10043
- message: "New invite link generated successfully",
10044
- inviteLink
10212
+ message: emailSent ? `Invite email sent.${rotatedNote}` : sendEmail ? `Invite link ready (email may not have been sent \u2014 check email plugin).${rotatedNote}` : rotate ? "New invite link created. Previous invite link is no longer valid." : "Invite link ready.",
10213
+ emailSent,
10214
+ inviteLink,
10215
+ inviteStatus: "pending"
10045
10216
  });
10046
10217
  } catch {
10047
10218
  return json({
@@ -14746,6 +14917,13 @@ var User = class {
14746
14917
  phoneVerifiedAt;
14747
14918
  emailVerifiedAt;
14748
14919
  password;
14920
+ /**
14921
+ * `pending` = invite outstanding (cannot sign in until password is set).
14922
+ * `active` = invite accepted / account usable (subject to `blocked`).
14923
+ */
14924
+ inviteStatus;
14925
+ /** Rotating invite token; null when active or no invite outstanding. */
14926
+ inviteToken;
14749
14927
  blocked;
14750
14928
  adminAccess;
14751
14929
  groupId;
@@ -14798,6 +14976,24 @@ _ts_decorate4([
14798
14976
  }),
14799
14977
  _ts_metadata4("design:type", Object)
14800
14978
  ], User.prototype, "password", void 0);
14979
+ _ts_decorate4([
14980
+ Column({
14981
+ type: "enum",
14982
+ enum: [
14983
+ "pending",
14984
+ "active"
14985
+ ],
14986
+ enumName: "users_invitestatus_enum",
14987
+ default: "active"
14988
+ }),
14989
+ _ts_metadata4("design:type", typeof UserInviteStatus === "undefined" ? Object : UserInviteStatus)
14990
+ ], User.prototype, "inviteStatus", void 0);
14991
+ _ts_decorate4([
14992
+ Column("varchar", {
14993
+ nullable: true
14994
+ }),
14995
+ _ts_metadata4("design:type", Object)
14996
+ ], User.prototype, "inviteToken", void 0);
14801
14997
  _ts_decorate4([
14802
14998
  Column("boolean", {
14803
14999
  default: false
@@ -19531,6 +19727,8 @@ var OrderItem = class {
19531
19727
  id;
19532
19728
  orderId;
19533
19729
  productId;
19730
+ /** Selected product variant at order time (optional). */
19731
+ variantId;
19534
19732
  discountIds;
19535
19733
  vendorId;
19536
19734
  vendor;
@@ -19566,6 +19764,12 @@ _ts_decorate39([
19566
19764
  Column("int"),
19567
19765
  _ts_metadata39("design:type", Number)
19568
19766
  ], OrderItem.prototype, "productId", void 0);
19767
+ _ts_decorate39([
19768
+ Column("int", {
19769
+ nullable: true
19770
+ }),
19771
+ _ts_metadata39("design:type", Object)
19772
+ ], OrderItem.prototype, "variantId", void 0);
19569
19773
  _ts_decorate39([
19570
19774
  Column({
19571
19775
  type: "int",
@@ -20375,6 +20579,7 @@ var Type = /* @__PURE__ */ (function(Type2) {
20375
20579
  Type2["CATEGORY"] = "category";
20376
20580
  Type2["CART"] = "cart";
20377
20581
  Type2["PAYMENT_METHOD"] = "paymentMethod";
20582
+ Type2["DEVICE_TYPE"] = "deviceType";
20378
20583
  Type2["ORDER"] = "order";
20379
20584
  Type2["QUANTITY"] = "quantity";
20380
20585
  Type2["TIME"] = "time";
@@ -22397,6 +22602,7 @@ var Combo = class {
22397
22602
  vendorId;
22398
22603
  vendor;
22399
22604
  name;
22605
+ slug;
22400
22606
  desc;
22401
22607
  eventId;
22402
22608
  event;
@@ -22440,6 +22646,12 @@ _ts_decorate63([
22440
22646
  }),
22441
22647
  _ts_metadata63("design:type", String)
22442
22648
  ], Combo.prototype, "name", void 0);
22649
+ _ts_decorate63([
22650
+ Column({
22651
+ type: "varchar"
22652
+ }),
22653
+ _ts_metadata63("design:type", String)
22654
+ ], Combo.prototype, "slug", void 0);
22443
22655
  _ts_decorate63([
22444
22656
  Column({
22445
22657
  type: "text",
@@ -22577,7 +22789,11 @@ _ts_decorate63([
22577
22789
  _ts_metadata63("design:type", Number)
22578
22790
  ], Combo.prototype, "deletedBy", void 0);
22579
22791
  Combo = _ts_decorate63([
22580
- Entity("combos")
22792
+ Entity("combos"),
22793
+ Unique("UQ_combos_vendor_slug", [
22794
+ "vendorId",
22795
+ "slug"
22796
+ ])
22581
22797
  ], Combo);
22582
22798
  function _ts_decorate64(decorators, target, key, desc) {
22583
22799
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -26503,8 +26719,11 @@ function createMessageTemplateRowLoader(dataSource, entityMap) {
26503
26719
  };
26504
26720
  }
26505
26721
  __name(createMessageTemplateRowLoader, "createMessageTemplateRowLoader");
26506
- function registerMessagingQueueProcessors(cms) {
26507
- registerEmailQueueProcessor(cms);
26722
+ function registerMessagingQueueProcessors(cms, entityMap) {
26723
+ registerEmailQueueProcessor(cms, {
26724
+ getDataSource: /* @__PURE__ */ __name(async () => cms.dataSource, "getDataSource"),
26725
+ entityMap
26726
+ });
26508
26727
  registerWhatsAppQueueProcessor(cms);
26509
26728
  }
26510
26729
  __name(registerMessagingQueueProcessors, "registerMessagingQueueProcessors");
@@ -26556,7 +26775,7 @@ async function ensureMessagingPluginsOnCms(base, options) {
26556
26775
  }
26557
26776
  }
26558
26777
  const merged = Object.keys(overlay).length > 0 ? overlayCmsPlugins(cms, overlay) : cms;
26559
- registerMessagingQueueProcessors(merged);
26778
+ registerMessagingQueueProcessors(merged, entityMap);
26560
26779
  return merged;
26561
26780
  }
26562
26781
  __name(ensureMessagingPluginsOnCms, "ensureMessagingPluginsOnCms");
@@ -26605,7 +26824,7 @@ async function createCmsAppWithMessaging(options) {
26605
26824
  logger,
26606
26825
  plugins: mergedPlugins
26607
26826
  });
26608
- registerMessagingQueueProcessors(cms);
26827
+ registerMessagingQueueProcessors(cms, entityMap);
26609
26828
  return cms;
26610
26829
  }
26611
26830
  __name(createCmsAppWithMessaging, "createCmsAppWithMessaging");
@@ -26802,7 +27021,7 @@ function createCmsApiHandler(config) {
26802
27021
  companyDetails
26803
27022
  };
26804
27023
  if (queue) {
26805
- const { queueEmail: queueEmail2 } = await import('./email-queue-CGZBVVAS.js');
27024
+ const { queueEmail: queueEmail2 } = await import('./email-queue-OI2HSCGE.js');
26806
27025
  await queueEmail2(cms, {
26807
27026
  to: opts.to,
26808
27027
  templateName: "passwordReset",
@@ -28056,14 +28275,27 @@ function createCmsApiHandler(config) {
28056
28275
  const pe = await requireEntityPermissionEffective(req, "orders", "read");
28057
28276
  if (pe) return pe;
28058
28277
  const cms = await getCms();
28059
- const { streamOrderInvoicePdf: streamOrderInvoicePdf2 } = await import('./erp-order-invoice-3YISSOWW.js');
28278
+ const { streamOrderInvoicePdf: streamOrderInvoicePdf2 } = await import('./erp-order-invoice-3GXDE443.js');
28060
28279
  const oid = Number(path2[1]);
28061
28280
  if (!Number.isFinite(oid)) return config.json({
28062
28281
  error: "Invalid id"
28063
28282
  }, {
28064
28283
  status: 400
28065
28284
  });
28066
- return streamOrderInvoicePdf2(cms, dataSource, entityMap, oid, {});
28285
+ try {
28286
+ return await streamOrderInvoicePdf2(cms, dataSource, entityMap, oid, {});
28287
+ } catch (err) {
28288
+ const msg = err instanceof Error ? err.message : String(err);
28289
+ console.error("[invoice] route failed", {
28290
+ orderId: oid,
28291
+ error: msg
28292
+ });
28293
+ return config.json({
28294
+ error: `Invoice failed: ${msg}`
28295
+ }, {
28296
+ status: 500
28297
+ });
28298
+ }
28067
28299
  }
28068
28300
  if (path2[0] === "orders" && path2.length === 3 && path2[2] === "repost-erp" && getCms) {
28069
28301
  const a = await config.requireAuth(req);
@@ -28410,7 +28642,7 @@ function createCmsApiHandler(config) {
28410
28642
  status: 400
28411
28643
  });
28412
28644
  }
28413
- const { resendOrderNotification } = await import('./order-notification-dispatcher-XWIJYDGA.js');
28645
+ const { resendOrderNotification } = await import('./order-notification-dispatcher-ZJZB2HUN.js');
28414
28646
  const result = await resendOrderNotification(triggerKey, orderId, {
28415
28647
  dataSource,
28416
28648
  entityMap,
@@ -28482,6 +28714,30 @@ function createCmsApiHandler(config) {
28482
28714
  paymentId = Number(payment.id);
28483
28715
  }
28484
28716
  }
28717
+ const orderWithItems = await orderRepo.findOne({
28718
+ where: {
28719
+ id: orderId,
28720
+ deleted: false
28721
+ },
28722
+ relations: [
28723
+ "items"
28724
+ ]
28725
+ });
28726
+ if (orderWithItems) {
28727
+ const { reconcileOrderInventoryBetweenSnapshots: reconcileOrderInventoryBetweenSnapshots2, orderInventorySnapshotFromRow: orderInventorySnapshotFromRow2 } = await import('./order-inventory-76V4XIGW.js');
28728
+ const prevSnap = orderInventorySnapshotFromRow2(orderWithItems);
28729
+ const invErr = await reconcileOrderInventoryBetweenSnapshots2(dataSource, entityMap, prevSnap, {
28730
+ status: "cancelled",
28731
+ lines: prevSnap.lines
28732
+ });
28733
+ if (invErr) {
28734
+ return config.json({
28735
+ error: invErr
28736
+ }, {
28737
+ status: 400
28738
+ });
28739
+ }
28740
+ }
28485
28741
  await orderRepo.update(orderId, {
28486
28742
  status: "cancelled"
28487
28743
  });
@@ -29525,6 +29781,14 @@ function createStorefrontApiHandler(config) {
29525
29781
  entityMap
29526
29782
  });
29527
29783
  }).catch((err) => console.error("[notification-triggers] import failed", err));
29784
+ if (!getCms) return;
29785
+ void import('./send-order-placed-emails-WPI37KRZ.js').then(({ sendOrderPlacedEmailsAfterConfirmation }) => {
29786
+ return sendOrderPlacedEmailsAfterConfirmation(orderId, {
29787
+ getDataSource: /* @__PURE__ */ __name(async () => dataSource, "getDataSource"),
29788
+ entityMap,
29789
+ getCms
29790
+ });
29791
+ }).catch((err) => console.error("[order-emails] sendOrderPlacedEmailsAfterConfirmation failed", err));
29528
29792
  }
29529
29793
  __name(fireOrderPlacedNotification, "fireOrderPlacedNotification");
29530
29794
  const otpAllowPhoneLogin = config.otpAllowPhoneLogin !== false;
@@ -31293,9 +31557,12 @@ function createStorefrontApiHandler(config) {
31293
31557
  }, {
31294
31558
  status: 400
31295
31559
  });
31560
+ const { retireSoftDeletedUniqueValue: retireSoftDeletedUniqueValue2 } = await import('./retire-soft-deleted-unique-NXQIH4BC.js');
31561
+ await retireSoftDeletedUniqueValue2(userRepo(), "email", email);
31296
31562
  const existing = await userRepo().findOne({
31297
31563
  where: {
31298
- email
31564
+ email,
31565
+ deleted: false
31299
31566
  }
31300
31567
  });
31301
31568
  if (existing) return json({
@@ -32176,6 +32443,7 @@ function createStorefrontApiHandler(config) {
32176
32443
  unitPrice: line.unitPrice,
32177
32444
  tax: line.tax,
32178
32445
  total: line.total,
32446
+ metadata: line.metadata ?? null,
32179
32447
  product: p ? {
32180
32448
  name: p.name,
32181
32449
  slug: p.slug,
@@ -32243,4 +32511,4 @@ function createStorefrontApiHandler(config) {
32243
32511
  }
32244
32512
  __name(createStorefrontApiHandler, "createStorefrontApiHandler");
32245
32513
 
32246
- export { Address, Attendee, Attribute, BLOG_GENERATOR_AGENT_NAME, BLOG_GENERATOR_DEFAULT_SYSTEM_INSTRUCTION, BLOG_GENERATOR_DEFAULT_VALIDATION_RULES, BLOG_GENERATOR_LLM_AGENT_SLUG, BLOG_GENERATOR_MARKDOWN_ARTICLE_SEPARATOR, BLOG_METADATA_ENRICHER_AGENT_NAME, BLOG_METADATA_ENRICHER_DEFAULT_SYSTEM_INSTRUCTION, BLOG_METADATA_ENRICHER_DEFAULT_VALIDATION_RULES, BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG, Blog, BlogGeneratorService, Brand, CMS_ENTITY_MAP, Cart, CartItem, Category, ChatConversation, ChatMessage, Collection, Combo, ComboItem, Comment, Config, Contact, Currency, CurrencyExchange, Customer, Customer_Contacts, Discount, DiscountRules, Event, EventProduct, Form, FormField, FormSubmission, JobSchedule, JobScheduleRun, KnowledgeBaseChunk, KnowledgeBaseDocument, LlmAgent, Media, MessageTemplate, Order, OrderAddresses, OrderDiscounts, OrderItem, OrderNotificationBinding, OrderNotificationTrigger, OtpChallenge, Page, PasswordResetToken, Payment, Permission, Product, ProductAttribute, ProductCategory, ProductConfig, ProductVariant, RefundPolicy, RefundRequest, RssArticle, RssFeed, Seo, Tag, Tax, User, UserGroup, Vendor, VendorCustomer, VendorRole, VendorRolePermission, VendorUser, Wishlist, WishlistItem, ZIP_MIME_TYPES, applyApprovalStatusSideEffects, applyEventApprovalStatusSideEffects, applyRotatingVendorInvite, applyVendorCustomersContactFilter, applyVendorEventCreateApproval, applyVendorProductCreateApproval, assertCaptchaOk, assertContactAllowedForVendorOrder, assertEventApprovalUpdate, assertProductApprovalUpdate, buildBlogMetadataUserPrompt, buildCronFromSchedule, buildRssUserPromptFromFeeds, buildVendorInviteLink, calculateOrderRefundPreview, calculateRefundFromPolicy, checkEventsEnabled, checkMultiVendorEnabled, contactIsVendorCustomer, countRecentOtpSends, createAnalyticsHandlers, createBlogBySlugHandler, createChangePasswordHandler, createCmsApiHandler, createCmsApp, createCmsAppWithMessaging, createCrudByIdHandler, createCrudHandler, createDashboardStatsHandler, createEcommerceAnalyticsHandler, createEventOrderMessageTemplateHandlers, createForgotPasswordHandler, createFormBySlugHandler, createInviteAcceptHandler, createJobScheduleHandlers, createLlmAgentKnowledgeHandlers, createMediaZipExtractHandler, createMessageTemplateRowLoader, createOtpChallenge, createSetPasswordHandler, createSettingsApiHandlers, createSocialMediaHandlers, createStorefrontApiHandler, createUploadHandler, createUserAuthApiRouter, createUserAvatarHandler, createUserProfileHandler, createUsersApiHandlers, createVendorDashboardHandler, createVendorOnboardHandlers, customerPhoneForEmail, customerPhoneForUser, daysBeforeEventStart, describeEventTierPolicy, ensureCustomerForUser, ensureCustomerRecord, ensureMessagingPluginsOnCms, ensureScheduleQueueWorker, ensureVendorCustomerForOrderContact, ensureVendorCustomersForOrder, findActiveRefundPolicyForVendor, findVendorByInviteToken, formatTierRange, generateNumericOtp, getPublicSettingsGroup, getRequireEventApproval, getRequireProductApproval, getRssArticleSummaryFromItem, getVendorCatalogCreateFlags, hashOtpCode, hydrateVendorSessionUser, invalidateEventsCache, invalidateMultiVendorCache, invalidateRequireEventApprovalCache, invalidateRequireProductApprovalCache, invalidateVendorCatalogCreateFlagsCache, isCustomerTypeContact, isSyntheticCustomerPhone, isZipMedia, linkUnclaimedContactToUser, llmAgentToChatAgentOptions, loadSettingsGroupFromDb, loadUserVendorContext, mergeGuardrailsIntoSystemPrompt, messagingPlugins, metaFetchUserManagedPages, metaPostPageFeed, metaPostPagePhoto, metaResolvePageAccessToken, normalizeCustomerPhone, normalizePhoneE164, normalizeRefundTiers, overlayCmsPlugins, parseBlogGeneratorAgentContent, parseBlogGeneratorModelOutput, parseBlogMetadataEnrichmentJson, parseLlmAgentValidationRules, pgBossScheduleNameForId, queueErpCreateContactIfEnabled, queueJobScheduleNow, queuePlugin, queueSms, registerJobRunnerWorker, registerMessagingQueueProcessors, registerSmsQueueProcessor, relativePathFromMediaParentId, resolveBlogCategoryIdByName, resolveSettingsEncryptionKey, resolveVendorIdForContactCheck, sanitizeMediaFolderPath, sanitizeStorageSegment, sendVendorOnboardEmails, simpleDecrypt, simpleEncrypt, syncJobScheduleToPgBoss, validateRefundTiers, validateScheduleInput, validateUserMessageAgainstAgentRules, validateUserMessageAgainstStructuredRules, verifyAndConsumeOtpChallenge, verifyOtpCodeHash, whatsappPlugin, wrapGetCmsWithMessaging };
32514
+ export { Address, Attendee, Attribute, BLOG_GENERATOR_AGENT_NAME, BLOG_GENERATOR_DEFAULT_SYSTEM_INSTRUCTION, BLOG_GENERATOR_DEFAULT_VALIDATION_RULES, BLOG_GENERATOR_LLM_AGENT_SLUG, BLOG_GENERATOR_MARKDOWN_ARTICLE_SEPARATOR, BLOG_METADATA_ENRICHER_AGENT_NAME, BLOG_METADATA_ENRICHER_DEFAULT_SYSTEM_INSTRUCTION, BLOG_METADATA_ENRICHER_DEFAULT_VALIDATION_RULES, BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG, Blog, BlogGeneratorService, Brand, CMS_ENTITY_MAP, Cart, CartItem, Category, ChatConversation, ChatMessage, Collection, Combo, ComboItem, Comment, Config, Contact, Currency, CurrencyExchange, Customer, Customer_Contacts, Discount, DiscountRules, Event, EventProduct, Form, FormField, FormSubmission, JobSchedule, JobScheduleRun, KnowledgeBaseChunk, KnowledgeBaseDocument, LlmAgent, Media, MessageTemplate, Order, OrderAddresses, OrderDiscounts, OrderItem, OrderNotificationBinding, OrderNotificationTrigger, OtpChallenge, Page, PasswordResetToken, Payment, Permission, Product, ProductAttribute, ProductCategory, ProductConfig, ProductVariant, RefundPolicy, RefundRequest, RssArticle, RssFeed, Seo, Tag, Tax, User, UserGroup, Vendor, VendorCustomer, VendorRole, VendorRolePermission, VendorUser, Wishlist, WishlistItem, ZIP_MIME_TYPES, applyApprovalStatusSideEffects, applyEventApprovalStatusSideEffects, applyRotatingVendorInvite, applyVendorCustomersContactFilter, applyVendorEventCreateApproval, applyVendorProductCreateApproval, assertCaptchaOk, assertContactAllowedForVendorOrder, assertEventApprovalUpdate, assertProductApprovalUpdate, buildBlogMetadataUserPrompt, buildCronFromSchedule, buildRssUserPromptFromFeeds, buildUserInviteLink, buildVendorInviteLink, calculateOrderRefundPreview, calculateRefundFromPolicy, checkAndIncrementDiscountUsage, checkEventsEnabled, checkMultiVendorEnabled, completeUserInviteAccept, consumeAppliedDiscountUsages, contactIsVendorCustomer, countDiscountOrdersForContact, countRecentOtpSends, createAnalyticsHandlers, createBlogBySlugHandler, createChangePasswordHandler, createCmsApiHandler, createCmsApp, createCmsAppWithMessaging, createCrudByIdHandler, createCrudHandler, createDashboardStatsHandler, createEcommerceAnalyticsHandler, createEventOrderMessageTemplateHandlers, createForgotPasswordHandler, createFormBySlugHandler, createInviteAcceptHandler, createJobScheduleHandlers, createLlmAgentKnowledgeHandlers, createMediaZipExtractHandler, createMessageTemplateRowLoader, createOtpChallenge, createSetPasswordHandler, createSettingsApiHandlers, createSocialMediaHandlers, createStorefrontApiHandler, createUploadHandler, createUserAuthApiRouter, createUserAvatarHandler, createUserProfileHandler, createUsersApiHandlers, createVendorDashboardHandler, createVendorOnboardHandlers, customerPhoneForEmail, customerPhoneForUser, daysBeforeEventStart, decrementDiscountUsage, describeEventTierPolicy, ensureCustomerForUser, ensureCustomerRecord, ensureMessagingPluginsOnCms, ensureScheduleQueueWorker, ensureVendorCustomerForOrderContact, ensureVendorCustomersForOrder, findActiveRefundPolicyForVendor, findUserByInviteToken, findVendorByInviteToken, formatTierRange, generateNumericOtp, getPublicSettingsGroup, getRequireEventApproval, getRequireProductApproval, getRssArticleSummaryFromItem, getVendorCatalogCreateFlags, hashOtpCode, hydrateVendorSessionUser, invalidateEventsCache, invalidateMultiVendorCache, invalidateRequireEventApprovalCache, invalidateRequireProductApprovalCache, invalidateVendorCatalogCreateFlagsCache, isCustomerTypeContact, isMaxPerUserUsageReached, isMaxTotalUsageReached, isSyntheticCustomerPhone, isZipMedia, linkUnclaimedContactToUser, llmAgentToChatAgentOptions, loadSettingsGroupFromDb, loadUserVendorContext, mergeGuardrailsIntoSystemPrompt, messagingPlugins, metaFetchUserManagedPages, metaPostPageFeed, metaPostPagePhoto, metaResolvePageAccessToken, newUserInviteToken, normalizeCustomerPhone, normalizePhoneE164, normalizeRefundTiers, overlayCmsPlugins, parseBlogGeneratorAgentContent, parseBlogGeneratorModelOutput, parseBlogMetadataEnrichmentJson, parseLlmAgentValidationRules, pgBossScheduleNameForId, queueErpCreateContactIfEnabled, queueJobScheduleNow, queuePlugin, queueSms, recordDiscountUsage, registerJobRunnerWorker, registerMessagingQueueProcessors, registerSmsQueueProcessor, relativePathFromMediaParentId, resolveBlogCategoryIdByName, resolveSettingsEncryptionKey, resolveVendorIdForContactCheck, sanitizeMediaFolderPath, sanitizeStorageSegment, sendVendorOnboardEmails, simpleDecrypt, simpleEncrypt, syncJobScheduleToPgBoss, validateRefundTiers, validateScheduleInput, validateUserMessageAgainstAgentRules, validateUserMessageAgainstStructuredRules, verifyAndConsumeOtpChallenge, verifyOtpCodeHash, whatsappPlugin, wrapGetCmsWithMessaging };