@infuro/cms-core 1.0.44 → 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 (66) hide show
  1. package/README.md +19 -2
  2. package/dist/admin.cjs +1413 -620
  3. package/dist/admin.d.cts +8 -2
  4. package/dist/admin.d.ts +8 -2
  5. package/dist/admin.js +1414 -621
  6. package/dist/api.cjs +40 -37
  7. package/dist/api.d.cts +3 -1
  8. package/dist/api.d.ts +3 -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-P2IWWBJV.cjs → chunk-UDVLFVEC.cjs} +1314 -601
  31. package/dist/{chunk-SQGBHYJF.js → chunk-UKC3HYXI.js} +1245 -548
  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 +426 -359
  41. package/dist/index.d.cts +308 -20
  42. package/dist/index.d.ts +308 -20
  43. package/dist/index.js +31 -110
  44. package/dist/migrations/1781800000000-CustomerPhoneNullable.ts +53 -0
  45. package/dist/migrations/1781810000000-OrderItemsVariantId.ts +44 -0
  46. package/dist/migrations/1781900000000-UserInviteStatusToken.ts +46 -0
  47. package/dist/migrations/1782000000000-ComboSlug.ts +58 -0
  48. package/dist/migrations/1782100000000-DiscountDeviceType.ts +20 -0
  49. package/dist/order-inventory-2MYRKEPC.cjs +39 -0
  50. package/dist/order-inventory-76V4XIGW.js +2 -0
  51. package/dist/order-notification-dispatcher-SBQAMM5V.cjs +21 -0
  52. package/dist/{order-notification-dispatcher-XWIJYDGA.js → order-notification-dispatcher-ZJZB2HUN.js} +5 -2
  53. package/dist/retire-soft-deleted-unique-5VXBA5XX.cjs +15 -0
  54. package/dist/retire-soft-deleted-unique-NXQIH4BC.js +2 -0
  55. package/dist/send-order-placed-emails-B5ZVJT7T.cjs +15 -0
  56. package/dist/send-order-placed-emails-WPI37KRZ.js +6 -0
  57. package/package.json +5 -4
  58. package/src/admin/admin.css +27 -24
  59. package/dist/chunk-5ELSW2UP.js +0 -150
  60. package/dist/chunk-PNOKNSG2.cjs +0 -222
  61. package/dist/chunk-WEMDMVHQ.js +0 -216
  62. package/dist/chunk-XW5ATPRW.cjs +0 -155
  63. package/dist/email-queue-CGZBVVAS.js +0 -2
  64. package/dist/erp-order-invoice-3YISSOWW.js +0 -3
  65. package/dist/erp-order-invoice-ZR5F2KTA.cjs +0 -12
  66. package/dist/order-notification-dispatcher-RU7BTGGX.cjs +0 -18
@@ -1,13 +1,16 @@
1
1
  'use strict';
2
2
 
3
- var chunk22FFHLTO_cjs = require('./chunk-22FFHLTO.cjs');
3
+ var chunkHY3AXLOI_cjs = require('./chunk-HY3AXLOI.cjs');
4
+ var chunkOY2YTBMP_cjs = require('./chunk-OY2YTBMP.cjs');
5
+ var chunk7PMHZRF3_cjs = require('./chunk-7PMHZRF3.cjs');
6
+ var chunkTCSGFAWB_cjs = require('./chunk-TCSGFAWB.cjs');
4
7
  var chunk7NMT3GBR_cjs = require('./chunk-7NMT3GBR.cjs');
5
- var chunkPNOKNSG2_cjs = require('./chunk-PNOKNSG2.cjs');
6
- var chunkXW5ATPRW_cjs = require('./chunk-XW5ATPRW.cjs');
8
+ var chunkV25RDUOU_cjs = require('./chunk-V25RDUOU.cjs');
9
+ var chunkBXYZDMTZ_cjs = require('./chunk-BXYZDMTZ.cjs');
7
10
  var chunk3DZRIZOD_cjs = require('./chunk-3DZRIZOD.cjs');
8
11
  var chunkSPQFNIYJ_cjs = require('./chunk-SPQFNIYJ.cjs');
9
12
  var chunkXQ3QUHWR_cjs = require('./chunk-XQ3QUHWR.cjs');
10
- var chunkZMRQ72F6_cjs = require('./chunk-ZMRQ72F6.cjs');
13
+ var chunkNBY4NVTY_cjs = require('./chunk-NBY4NVTY.cjs');
11
14
  var chunkX6UQFV4X_cjs = require('./chunk-X6UQFV4X.cjs');
12
15
  var chunkD5FBNKQM_cjs = require('./chunk-D5FBNKQM.cjs');
13
16
  var chunkUPLVMVRX_cjs = require('./chunk-UPLVMVRX.cjs');
@@ -46,6 +49,7 @@ async function queryVendorLinks(dataSource, userId) {
46
49
  vr."name" AS "vendorRoleName",
47
50
  vr."isOwnerRole" AS "isOwnerRole"
48
51
  FROM "vendor_users" vu
52
+ INNER JOIN "vendors" v ON v.id = vu."vendorId" AND v.deleted = false
49
53
  LEFT JOIN "vendor_roles" vr ON vr.id = vu."vendorRoleId" AND vr.deleted = false
50
54
  WHERE vu."userId" = $1
51
55
  ORDER BY vu.id ASC
@@ -55,10 +59,11 @@ async function queryVendorLinks(dataSource, userId) {
55
59
  } catch (err) {
56
60
  if (!isMissingVendorRoleSchemaError(err)) throw err;
57
61
  const legacy = await dataSource.query(`
58
- SELECT "vendorId", "role"
59
- FROM "vendor_users"
60
- WHERE "userId" = $1
61
- ORDER BY id ASC
62
+ SELECT vu."vendorId" AS "vendorId", vu."role" AS "role"
63
+ FROM "vendor_users" vu
64
+ INNER JOIN "vendors" v ON v.id = vu."vendorId" AND v.deleted = false
65
+ WHERE vu."userId" = $1
66
+ ORDER BY vu.id ASC
62
67
  `, [
63
68
  userId
64
69
  ]);
@@ -72,8 +77,36 @@ async function queryVendorLinks(dataSource, userId) {
72
77
  }
73
78
  }
74
79
  chunkUSNT2KNT_cjs.__name(queryVendorLinks, "queryVendorLinks");
80
+ async function queryOwnedVendorIds(dataSource, userId) {
81
+ try {
82
+ const rows = await dataSource.query(`
83
+ SELECT id
84
+ FROM "vendors"
85
+ WHERE "userId" = $1 AND deleted = false
86
+ ORDER BY id ASC
87
+ `, [
88
+ userId
89
+ ]);
90
+ return rows.map((r) => Number(r.id)).filter((id) => Number.isFinite(id) && id > 0);
91
+ } catch {
92
+ return [];
93
+ }
94
+ }
95
+ chunkUSNT2KNT_cjs.__name(queryOwnedVendorIds, "queryOwnedVendorIds");
75
96
  async function loadUserVendorContext(dataSource, userId, preferredVendorId) {
76
97
  const rows = await queryVendorLinks(dataSource, userId);
98
+ const linkIds = new Set(rows.map((r) => Number(r.vendorId)));
99
+ for (const ownedId of await queryOwnedVendorIds(dataSource, userId)) {
100
+ if (linkIds.has(ownedId)) continue;
101
+ rows.push({
102
+ vendorId: ownedId,
103
+ role: "owner",
104
+ vendorRoleId: null,
105
+ vendorRoleName: null,
106
+ isOwnerRole: true
107
+ });
108
+ linkIds.add(ownedId);
109
+ }
77
110
  const vendorIds = rows.map((r) => Number(r.vendorId));
78
111
  const activeRow = preferredVendorId != null ? rows.find((r) => Number(r.vendorId) === preferredVendorId) : void 0;
79
112
  const primary = activeRow ?? rows[0];
@@ -626,6 +659,144 @@ function applyEventApprovalStatusSideEffects(updatePayload, opts) {
626
659
  }
627
660
  chunkUSNT2KNT_cjs.__name(applyEventApprovalStatusSideEffects, "applyEventApprovalStatusSideEffects");
628
661
 
662
+ // src/lib/discount-usage.ts
663
+ async function countDiscountOrdersForContact(dataSource, discountId, contactId) {
664
+ try {
665
+ const rows = await dataSource.query(`SELECT COUNT(DISTINCT od."orderId")::int AS count
666
+ FROM order_discounts od
667
+ WHERE od."discountId" = $1 AND od."userId" = $2`, [
668
+ discountId,
669
+ contactId
670
+ ]);
671
+ return Number(rows?.[0]?.count ?? 0);
672
+ } catch {
673
+ return 0;
674
+ }
675
+ }
676
+ chunkUSNT2KNT_cjs.__name(countDiscountOrdersForContact, "countDiscountOrdersForContact");
677
+ function isMaxTotalUsageReached(discount) {
678
+ if (discount.maxTotalUsage == null) return false;
679
+ return Number(discount.usedCount ?? 0) >= Number(discount.maxTotalUsage);
680
+ }
681
+ chunkUSNT2KNT_cjs.__name(isMaxTotalUsageReached, "isMaxTotalUsageReached");
682
+ async function isMaxPerUserUsageReached(dataSource, discount, contactId) {
683
+ if (discount.maxUsagePerUser == null || contactId == null) return false;
684
+ const count = await countDiscountOrdersForContact(dataSource, discount.id, contactId);
685
+ return count >= Number(discount.maxUsagePerUser);
686
+ }
687
+ chunkUSNT2KNT_cjs.__name(isMaxPerUserUsageReached, "isMaxPerUserUsageReached");
688
+ async function checkAndIncrementDiscountUsage(dataSource, entityMap, discountId, contactId) {
689
+ if (!entityMap.discounts) return null;
690
+ const discountRepo = dataSource.getRepository(entityMap.discounts);
691
+ const discount = await discountRepo.findOne({
692
+ where: {
693
+ id: discountId
694
+ }
695
+ });
696
+ if (!discount) return `Discount #${discountId} not found`;
697
+ const d = discount;
698
+ if (d.maxUsagePerUser != null) {
699
+ const usageCount = await countDiscountOrdersForContact(dataSource, discountId, contactId);
700
+ if (usageCount >= Number(d.maxUsagePerUser)) {
701
+ return "You have reached the maximum usage limit for this discount";
702
+ }
703
+ }
704
+ const updated = await dataSource.query(`UPDATE discounts
705
+ SET "usedCount" = "usedCount" + 1
706
+ WHERE id = $1
707
+ AND ("maxTotalUsage" IS NULL OR "usedCount" < "maxTotalUsage")
708
+ RETURNING id`, [
709
+ discountId
710
+ ]);
711
+ if (!Array.isArray(updated) || updated.length === 0) {
712
+ return "Discount usage limit has been reached";
713
+ }
714
+ return null;
715
+ }
716
+ chunkUSNT2KNT_cjs.__name(checkAndIncrementDiscountUsage, "checkAndIncrementDiscountUsage");
717
+ async function decrementDiscountUsage(dataSource, entityMap, discountId, orderId) {
718
+ if (!entityMap.discounts) return;
719
+ try {
720
+ await dataSource.query(`UPDATE discounts SET "usedCount" = GREATEST("usedCount" - 1, 0) WHERE id = $1`, [
721
+ discountId
722
+ ]);
723
+ if (entityMap.order_discounts) {
724
+ await dataSource.getRepository(entityMap.order_discounts).delete({
725
+ orderId,
726
+ discountId
727
+ });
728
+ }
729
+ } catch (err) {
730
+ console.error("[discountEngine] decrementDiscountUsage failed:", err);
731
+ }
732
+ }
733
+ chunkUSNT2KNT_cjs.__name(decrementDiscountUsage, "decrementDiscountUsage");
734
+ async function recordDiscountUsage(dataSource, entityMap, orderId, discountId, contactId, discountAmount) {
735
+ if (!entityMap.order_discounts) return;
736
+ try {
737
+ let discountCode = "";
738
+ if (entityMap.discounts) {
739
+ const discountRow = await dataSource.getRepository(entityMap.discounts).findOne({
740
+ where: {
741
+ id: discountId
742
+ }
743
+ });
744
+ discountCode = discountRow?.couponCode ?? "";
745
+ }
746
+ const odRepo = dataSource.getRepository(entityMap.order_discounts);
747
+ const orderItemRepo = dataSource.getRepository(entityMap.order_items);
748
+ const items = await orderItemRepo.find({
749
+ where: {
750
+ orderId
751
+ }
752
+ });
753
+ const discountedItems = items.filter((item) => Number(item.discount ?? 0) > 0);
754
+ if (discountedItems.length === 0) {
755
+ const firstItem = items[0];
756
+ if (!firstItem) return;
757
+ await odRepo.save(odRepo.create({
758
+ orderId,
759
+ orderItemId: firstItem.id,
760
+ discountId,
761
+ discountCode,
762
+ discountAmount,
763
+ userId: contactId
764
+ }));
765
+ return;
766
+ }
767
+ for (const item of discountedItems) {
768
+ const i = item;
769
+ await odRepo.save(odRepo.create({
770
+ orderId,
771
+ orderItemId: i.id,
772
+ discountId,
773
+ discountCode,
774
+ discountAmount: Number(i.discount ?? 0),
775
+ userId: contactId
776
+ }));
777
+ }
778
+ } catch (err) {
779
+ console.error("[discountEngine] recordDiscountUsage failed:", err);
780
+ }
781
+ }
782
+ chunkUSNT2KNT_cjs.__name(recordDiscountUsage, "recordDiscountUsage");
783
+ async function consumeAppliedDiscountUsages(dataSource, entityMap, orderId, contactId, applied) {
784
+ const unique = /* @__PURE__ */ new Map();
785
+ for (const row of applied) {
786
+ const id = Number(row.id);
787
+ if (!Number.isFinite(id) || id < 1) continue;
788
+ const amount = Math.max(0, Number(row.calculatedDiscount) || 0);
789
+ unique.set(id, (unique.get(id) ?? 0) + amount);
790
+ }
791
+ for (const [discountId, discountAmount] of unique) {
792
+ const usageErr = await checkAndIncrementDiscountUsage(dataSource, entityMap, discountId, contactId);
793
+ if (usageErr) return usageErr;
794
+ await recordDiscountUsage(dataSource, entityMap, orderId, discountId, contactId, discountAmount);
795
+ }
796
+ return null;
797
+ }
798
+ chunkUSNT2KNT_cjs.__name(consumeAppliedDiscountUsages, "consumeAppliedDiscountUsages");
799
+
629
800
  // src/lib/default-vendor-id.ts
630
801
  function getDefaultVendorId() {
631
802
  const raw = process.env.DEFAULT_VENDOR_ID;
@@ -1100,11 +1271,118 @@ function pgErrorCode(err) {
1100
1271
  return driver?.code ?? err.code;
1101
1272
  }
1102
1273
  chunkUSNT2KNT_cjs.__name(pgErrorCode, "pgErrorCode");
1103
- function customerPhoneForUser(userId, phone) {
1274
+ function normalizeCustomerPhone(phone) {
1104
1275
  const p = typeof phone === "string" ? phone.trim() : "";
1105
- return p || `u-${userId}`;
1276
+ return p || null;
1277
+ }
1278
+ chunkUSNT2KNT_cjs.__name(normalizeCustomerPhone, "normalizeCustomerPhone");
1279
+ function isSyntheticCustomerPhone(phone) {
1280
+ const p = String(phone ?? "").trim();
1281
+ if (!p) return true;
1282
+ if (p.startsWith("e-") || p.startsWith("u-")) return true;
1283
+ if (p.includes("@")) return true;
1284
+ return false;
1285
+ }
1286
+ chunkUSNT2KNT_cjs.__name(isSyntheticCustomerPhone, "isSyntheticCustomerPhone");
1287
+ function customerPhoneForUser(_userId, phone) {
1288
+ return normalizeCustomerPhone(phone);
1106
1289
  }
1107
1290
  chunkUSNT2KNT_cjs.__name(customerPhoneForUser, "customerPhoneForUser");
1291
+ function customerPhoneForEmail(_email, phone) {
1292
+ return normalizeCustomerPhone(phone);
1293
+ }
1294
+ chunkUSNT2KNT_cjs.__name(customerPhoneForEmail, "customerPhoneForEmail");
1295
+ function resolveNextPhone(inputPhone, existingPhone) {
1296
+ if (inputPhone) return inputPhone;
1297
+ const existing = normalizeCustomerPhone(existingPhone);
1298
+ if (!existing || isSyntheticCustomerPhone(existing)) return null;
1299
+ return existing;
1300
+ }
1301
+ chunkUSNT2KNT_cjs.__name(resolveNextPhone, "resolveNextPhone");
1302
+ async function ensureCustomerRecord(dsOrEm, customerEntity, input) {
1303
+ const repo = dsOrEm.getRepository(customerEntity);
1304
+ const email = normalizeEmail(input.email);
1305
+ if (!email) return null;
1306
+ const name = String(input.name ?? "").trim() || email.split("@")[0] || "Customer";
1307
+ const userId = input.userId != null && Number.isFinite(Number(input.userId)) && Number(input.userId) > 0 ? Number(input.userId) : null;
1308
+ const phone = normalizeCustomerPhone(input.phone);
1309
+ let row = await repo.findOne({
1310
+ where: {
1311
+ email,
1312
+ deleted: false
1313
+ }
1314
+ });
1315
+ if (!row) {
1316
+ row = await repo.findOne({
1317
+ where: {
1318
+ email
1319
+ }
1320
+ });
1321
+ }
1322
+ if (row) {
1323
+ const existingUserId = row.userId;
1324
+ if (userId != null && existingUserId != null && existingUserId !== userId) {
1325
+ return null;
1326
+ }
1327
+ const nextPhone = resolveNextPhone(phone, row.phone);
1328
+ const patch = {
1329
+ name,
1330
+ phone: nextPhone,
1331
+ deleted: false,
1332
+ deletedAt: null,
1333
+ deletedBy: null,
1334
+ updatedAt: /* @__PURE__ */ new Date()
1335
+ };
1336
+ if (userId != null && existingUserId == null) {
1337
+ patch.userId = userId;
1338
+ }
1339
+ await repo.update(row.id, patch);
1340
+ return {
1341
+ id: row.id
1342
+ };
1343
+ }
1344
+ try {
1345
+ const created = await repo.save(repo.create({
1346
+ userId,
1347
+ name,
1348
+ email,
1349
+ phone,
1350
+ deleted: false
1351
+ }));
1352
+ return {
1353
+ id: created.id
1354
+ };
1355
+ } catch (err) {
1356
+ const code = pgErrorCode(err);
1357
+ if (code === "25P02") throw err;
1358
+ row = await repo.findOne({
1359
+ where: {
1360
+ email
1361
+ }
1362
+ });
1363
+ if (row) {
1364
+ const existingUserId = row.userId;
1365
+ if (userId != null && existingUserId != null && existingUserId !== userId) return null;
1366
+ await repo.update(row.id, {
1367
+ name,
1368
+ phone: resolveNextPhone(phone, row.phone),
1369
+ ...userId != null && existingUserId == null ? {
1370
+ userId
1371
+ } : {},
1372
+ deleted: false,
1373
+ deletedAt: null,
1374
+ deletedBy: null,
1375
+ updatedAt: /* @__PURE__ */ new Date()
1376
+ });
1377
+ return {
1378
+ id: row.id
1379
+ };
1380
+ }
1381
+ if (code === "23505") return null;
1382
+ throw err;
1383
+ }
1384
+ }
1385
+ chunkUSNT2KNT_cjs.__name(ensureCustomerRecord, "ensureCustomerRecord");
1108
1386
  async function restoreCustomerRow(repo, row, user, name, email, phone) {
1109
1387
  const id = row.id;
1110
1388
  await repo.update(id, {
@@ -1126,7 +1404,7 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1126
1404
  const repo = dsOrEm.getRepository(customerEntity);
1127
1405
  const email = normalizeEmail(user.email);
1128
1406
  const name = String(user.name ?? "").trim() || email.split("@")[0] || "User";
1129
- const phone = customerPhoneForUser(user.id, overrides?.phone ?? user.phone);
1407
+ const phone = normalizeCustomerPhone(overrides?.phone ?? user.phone);
1130
1408
  let row = await repo.findOne({
1131
1409
  where: {
1132
1410
  userId: user.id,
@@ -1134,10 +1412,11 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1134
1412
  }
1135
1413
  });
1136
1414
  if (row) {
1415
+ const nextPhone = resolveNextPhone(phone, row.phone);
1137
1416
  await repo.update(row.id, {
1138
1417
  name,
1139
1418
  email,
1140
- phone,
1419
+ phone: nextPhone,
1141
1420
  updatedAt: /* @__PURE__ */ new Date()
1142
1421
  });
1143
1422
  return {
@@ -1155,10 +1434,11 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1155
1434
  if (existingUserId != null && existingUserId !== user.id) {
1156
1435
  return null;
1157
1436
  }
1437
+ const nextPhone = resolveNextPhone(phone, row.phone);
1158
1438
  await repo.update(row.id, {
1159
1439
  userId: user.id,
1160
1440
  name,
1161
- phone,
1441
+ phone: nextPhone,
1162
1442
  updatedAt: /* @__PURE__ */ new Date()
1163
1443
  });
1164
1444
  return {
@@ -1173,21 +1453,23 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1173
1453
  if (deletedByEmail) {
1174
1454
  const existingUserId = deletedByEmail.userId;
1175
1455
  if (existingUserId != null && existingUserId !== user.id) return null;
1176
- return restoreCustomerRow(repo, deletedByEmail, user, name, email, phone);
1456
+ return restoreCustomerRow(repo, deletedByEmail, user, name, email, resolveNextPhone(phone, deletedByEmail.phone));
1177
1457
  }
1178
- const deletedByPhone = await repo.findOne({
1179
- where: {
1180
- phone
1181
- }
1182
- });
1183
- if (deletedByPhone) {
1184
- const existingUserId = deletedByPhone.userId;
1185
- if (existingUserId != null && existingUserId !== user.id) {
1186
- return ensureCustomerForUser(dsOrEm, customerEntity, user, {
1187
- phone: `u-${user.id}-${Date.now()}`
1188
- });
1458
+ if (phone) {
1459
+ const deletedByPhone = await repo.findOne({
1460
+ where: {
1461
+ phone
1462
+ }
1463
+ });
1464
+ if (deletedByPhone) {
1465
+ const existingUserId = deletedByPhone.userId;
1466
+ if (existingUserId != null && existingUserId !== user.id) {
1467
+ return ensureCustomerForUser(dsOrEm, customerEntity, user, {
1468
+ phone: null
1469
+ });
1470
+ }
1471
+ return restoreCustomerRow(repo, deletedByPhone, user, name, email, phone);
1189
1472
  }
1190
- return restoreCustomerRow(repo, deletedByPhone, user, name, email, phone);
1191
1473
  }
1192
1474
  try {
1193
1475
  const created = await repo.save(repo.create({
@@ -1208,34 +1490,22 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1208
1490
  userId: user.id
1209
1491
  }
1210
1492
  });
1211
- if (row) return restoreCustomerRow(repo, row, user, name, email, phone);
1493
+ if (row) {
1494
+ return restoreCustomerRow(repo, row, user, name, email, resolveNextPhone(phone, row.phone));
1495
+ }
1212
1496
  row = await repo.findOne({
1213
1497
  where: {
1214
1498
  email
1215
1499
  }
1216
1500
  });
1217
1501
  if (row && (row.userId ?? user.id) === user.id) {
1218
- return restoreCustomerRow(repo, row, user, name, email, phone);
1502
+ return restoreCustomerRow(repo, row, user, name, email, resolveNextPhone(phone, row.phone));
1219
1503
  }
1220
1504
  if (code === "23505") return null;
1221
1505
  throw err;
1222
1506
  }
1223
1507
  }
1224
1508
  chunkUSNT2KNT_cjs.__name(ensureCustomerForUser, "ensureCustomerForUser");
1225
- async function linkUnclaimedContactToUser(dataSource, contactsEntity, userId, email) {
1226
- const repo = dataSource.getRepository(contactsEntity);
1227
- const found = await repo.findOne({
1228
- where: {
1229
- email,
1230
- userId: typeorm.IsNull(),
1231
- deleted: false
1232
- }
1233
- });
1234
- if (found) await repo.update(found.id, {
1235
- userId
1236
- });
1237
- }
1238
- chunkUSNT2KNT_cjs.__name(linkUnclaimedContactToUser, "linkUnclaimedContactToUser");
1239
1509
 
1240
1510
  // src/lib/vendor-customer-contacts.ts
1241
1511
  function isCustomerTypeContact(type) {
@@ -1328,8 +1598,27 @@ function resolveVendorIdForContactCheck(scope, bodyOrRow) {
1328
1598
  return Number.isFinite(n) ? n : null;
1329
1599
  }
1330
1600
  chunkUSNT2KNT_cjs.__name(resolveVendorIdForContactCheck, "resolveVendorIdForContactCheck");
1601
+ function uniquePositiveIds(ids) {
1602
+ const out = [];
1603
+ const seen = /* @__PURE__ */ new Set();
1604
+ for (const raw of ids) {
1605
+ const n = Number(raw);
1606
+ if (!Number.isFinite(n) || n <= 0 || seen.has(n)) continue;
1607
+ seen.add(n);
1608
+ out.push(n);
1609
+ }
1610
+ return out;
1611
+ }
1612
+ chunkUSNT2KNT_cjs.__name(uniquePositiveIds, "uniquePositiveIds");
1331
1613
  async function ensureVendorCustomerForOrderContact(dataSource, entityMap, vendorId, contactId, details) {
1332
- if (!Number.isFinite(vendorId) || !Number.isFinite(contactId)) return;
1614
+ await ensureVendorCustomersForOrder(dataSource, entityMap, [
1615
+ vendorId
1616
+ ], contactId, details);
1617
+ }
1618
+ chunkUSNT2KNT_cjs.__name(ensureVendorCustomerForOrderContact, "ensureVendorCustomerForOrderContact");
1619
+ async function ensureVendorCustomersForOrder(dataSource, entityMap, vendorIds, contactId, details) {
1620
+ const vendors = uniquePositiveIds(vendorIds);
1621
+ if (vendors.length === 0 || !Number.isFinite(contactId) || contactId <= 0) return;
1333
1622
  const vcEntity = entityMap.vendor_customers;
1334
1623
  const contactEntity = entityMap.contacts;
1335
1624
  if (!vcEntity || !contactEntity) return;
@@ -1355,96 +1644,61 @@ async function ensureVendorCustomerForOrderContact(dataSource, entityMap, vendor
1355
1644
  }
1356
1645
  let customerId = null;
1357
1646
  if (entityMap.customer) {
1358
- const customerRepo = dataSource.getRepository(entityMap.customer);
1359
- const existingCustomer = await customerRepo.findOne({
1360
- where: {
1361
- email,
1362
- deleted: false
1363
- }
1647
+ const ensured = await ensureCustomerRecord(dataSource, entityMap.customer, {
1648
+ name,
1649
+ email,
1650
+ phone,
1651
+ userId: null
1364
1652
  });
1365
- if (existingCustomer) {
1366
- customerId = Number(existingCustomer.id);
1367
- } else if (entityMap.users) {
1368
- const userRepo = dataSource.getRepository(entityMap.users);
1369
- let user = await userRepo.findOne({
1653
+ if (ensured) customerId = ensured.id;
1654
+ }
1655
+ for (const vendorId of vendors) {
1656
+ if (customerId != null) {
1657
+ const byCustomer = await vcRepo.findOne({
1370
1658
  where: {
1371
- email,
1372
- deleted: false
1659
+ vendorId,
1660
+ customerId
1373
1661
  }
1374
1662
  });
1375
- if (!user) {
1376
- let groupId = null;
1377
- if (entityMap.user_groups) {
1378
- const userGroupRepo = dataSource.getRepository(entityMap.user_groups);
1379
- const customerGroup = await userGroupRepo.findOne({
1380
- where: {
1381
- name: "Customer",
1382
- deleted: false
1383
- }
1663
+ if (byCustomer) {
1664
+ const existingContactId = byCustomer.contactId;
1665
+ if (existingContactId == null) {
1666
+ await vcRepo.update(byCustomer.id, {
1667
+ contactId
1384
1668
  });
1385
- if (customerGroup) groupId = Number(customerGroup.id);
1386
1669
  }
1387
- user = await userRepo.save(userRepo.create({
1388
- name,
1389
- email,
1390
- phone,
1391
- password: null,
1392
- blocked: false,
1393
- groupId,
1394
- adminAccess: false
1395
- }));
1670
+ continue;
1396
1671
  }
1397
- const userId = Number(user.id);
1398
- await linkUnclaimedContactToUser(dataSource, contactEntity, userId, email);
1399
- const ensured = await ensureCustomerForUser(dataSource, entityMap.customer, {
1400
- id: userId,
1401
- name,
1402
- email,
1403
- phone
1404
- }, {
1405
- phone
1406
- });
1407
- if (ensured) customerId = ensured.id;
1408
1672
  }
1409
- }
1410
- if (customerId != null) {
1411
- const byCustomer = await vcRepo.findOne({
1673
+ const byContact = await vcRepo.findOne({
1412
1674
  where: {
1413
1675
  vendorId,
1414
- customerId
1676
+ contactId
1415
1677
  }
1416
1678
  });
1417
- if (byCustomer) {
1418
- const existingContactId = byCustomer.contactId;
1419
- if (existingContactId == null) {
1420
- await vcRepo.update(byCustomer.id, {
1421
- contactId
1679
+ if (byContact) {
1680
+ if (customerId != null && byContact.customerId == null) {
1681
+ await vcRepo.update(byContact.id, {
1682
+ customerId
1422
1683
  });
1423
1684
  }
1424
- return;
1685
+ continue;
1425
1686
  }
1426
- }
1427
- const byContact = await vcRepo.findOne({
1428
- where: {
1429
- vendorId,
1430
- contactId
1687
+ if (customerId != null) {
1688
+ await vcRepo.save(vcRepo.create({
1689
+ vendorId,
1690
+ customerId,
1691
+ contactId
1692
+ }));
1693
+ } else {
1694
+ await vcRepo.save(vcRepo.create({
1695
+ vendorId,
1696
+ contactId
1697
+ }));
1431
1698
  }
1432
- });
1433
- if (byContact) return;
1434
- if (customerId != null) {
1435
- await vcRepo.save(vcRepo.create({
1436
- vendorId,
1437
- customerId,
1438
- contactId
1439
- }));
1440
- return;
1441
1699
  }
1442
- await vcRepo.save(vcRepo.create({
1443
- vendorId,
1444
- contactId
1445
- }));
1446
1700
  }
1447
- chunkUSNT2KNT_cjs.__name(ensureVendorCustomerForOrderContact, "ensureVendorCustomerForOrderContact");
1701
+ chunkUSNT2KNT_cjs.__name(ensureVendorCustomersForOrder, "ensureVendorCustomersForOrder");
1448
1702
 
1449
1703
  // src/lib/currency-prices.ts
1450
1704
  function normalizeCurrencyCode(code) {
@@ -1886,6 +2140,7 @@ async function ensureUniqueProductSlug(repo, baseSlug, vendorId, excludeId) {
1886
2140
  let slug = baseSlug;
1887
2141
  let suffix = 0;
1888
2142
  while (true) {
2143
+ await chunkOY2YTBMP_cjs.retireSoftDeletedUniqueValue(repo, "slug", slug);
1889
2144
  const where = excludeId != null ? {
1890
2145
  slug,
1891
2146
  vendorId,
@@ -1905,6 +2160,21 @@ async function ensureUniqueProductSlug(repo, baseSlug, vendorId, excludeId) {
1905
2160
  }
1906
2161
  }
1907
2162
  chunkUSNT2KNT_cjs.__name(ensureUniqueProductSlug, "ensureUniqueProductSlug");
2163
+ async function normalizeComboNameAndSlug(repo, body, vendorId, existing, excludeId) {
2164
+ const incomingName = typeof body.name === "string" ? body.name.trim() : "";
2165
+ const incomingSlugRaw = typeof body.slug === "string" ? body.slug.trim() : "";
2166
+ const existingName = typeof existing?.name === "string" ? existing.name.trim() : "";
2167
+ const existingSlug = typeof existing?.slug === "string" ? existing.slug.trim() : "";
2168
+ const name = incomingName || existingName;
2169
+ if (!name) return "Name is required";
2170
+ if (incomingName) body.name = name;
2171
+ const nameBasedSlug = slugifyProductLabel(name);
2172
+ const typedSlug = incomingSlugRaw ? slugifyProductLabel(incomingSlugRaw) : "";
2173
+ const slug = typedSlug || (excludeId != null ? existingSlug : "") || nameBasedSlug;
2174
+ body.slug = await ensureUniqueProductSlug(repo, slug, vendorId, excludeId);
2175
+ return null;
2176
+ }
2177
+ chunkUSNT2KNT_cjs.__name(normalizeComboNameAndSlug, "normalizeComboNameAndSlug");
1908
2178
  async function hydrateProductEventNames(dataSource, entityMap, products) {
1909
2179
  if (!products.length) return products;
1910
2180
  if (!entityMap.event_products || !entityMap.events) {
@@ -2151,6 +2421,20 @@ async function normalizeProductNameAndSlug(repo, body, vendorId, existing, exclu
2151
2421
  return null;
2152
2422
  }
2153
2423
  chunkUSNT2KNT_cjs.__name(normalizeProductNameAndSlug, "normalizeProductNameAndSlug");
2424
+ async function retireSoftDeletedUniquesFromBody(repo, body) {
2425
+ const cols = new Set(repo.metadata.columns.map((c) => c.propertyName));
2426
+ if (!cols.has("deleted")) return;
2427
+ for (const col of [
2428
+ "slug",
2429
+ "email"
2430
+ ]) {
2431
+ if (!cols.has(col)) continue;
2432
+ const raw = body[col];
2433
+ if (typeof raw !== "string" || !raw.trim()) continue;
2434
+ await chunkOY2YTBMP_cjs.retireSoftDeletedUniqueValue(repo, col, raw.trim());
2435
+ }
2436
+ }
2437
+ chunkUSNT2KNT_cjs.__name(retireSoftDeletedUniquesFromBody, "retireSoftDeletedUniquesFromBody");
2154
2438
  function buildSoftDeletePayload(meta, deletedBy) {
2155
2439
  const payload = {
2156
2440
  deleted: true
@@ -2387,6 +2671,7 @@ function discountEvaluateRule(rule, cartLines, cartTotal) {
2387
2671
  case "category":
2388
2672
  case "cart":
2389
2673
  case "paymentMethod":
2674
+ case "deviceType":
2390
2675
  case "order": {
2391
2676
  if (rule.subType === "nthOrder") {
2392
2677
  return true;
@@ -2570,87 +2855,22 @@ function applyDiscountToOrderLines(lines, evalResult, maxDiscountAmount) {
2570
2855
  return totalDiscountApplied;
2571
2856
  }
2572
2857
  chunkUSNT2KNT_cjs.__name(applyDiscountToOrderLines, "applyDiscountToOrderLines");
2573
- async function checkAndIncrementDiscountUsage(dataSource, entityMap, discountId, contactId) {
2574
- if (!entityMap["discounts"]) return null;
2575
- const discountRepo = dataSource.getRepository(entityMap["discounts"]);
2576
- const discount = await discountRepo.findOne({
2577
- where: {
2578
- id: discountId
2579
- }
2580
- });
2581
- if (!discount) return `Discount #${discountId} not found`;
2582
- await dataSource.query(`UPDATE discounts SET "usedCount" = "usedCount" + 1 WHERE id = $1`, [
2583
- discountId
2584
- ]);
2585
- return null;
2858
+ function asDiscountUsageEntityMap(entityMap) {
2859
+ return entityMap;
2586
2860
  }
2587
- chunkUSNT2KNT_cjs.__name(checkAndIncrementDiscountUsage, "checkAndIncrementDiscountUsage");
2588
- async function decrementDiscountUsage(dataSource, entityMap, discountId, orderId) {
2589
- if (!entityMap["discounts"]) return;
2590
- try {
2591
- await dataSource.query(`UPDATE discounts SET "usedCount" = GREATEST("usedCount" - 1, 0) WHERE id = $1`, [
2592
- discountId
2593
- ]);
2594
- if (entityMap["order_discounts"]) {
2595
- await dataSource.getRepository(entityMap["order_discounts"]).delete({
2596
- orderId,
2597
- discountId
2598
- });
2599
- }
2600
- } catch (err) {
2601
- console.error("[discountEngine] decrementDiscountUsage failed:", err);
2602
- }
2861
+ chunkUSNT2KNT_cjs.__name(asDiscountUsageEntityMap, "asDiscountUsageEntityMap");
2862
+ async function checkAndIncrementDiscountUsage2(dataSource, entityMap, discountId, contactId) {
2863
+ return checkAndIncrementDiscountUsage(dataSource, asDiscountUsageEntityMap(entityMap), discountId, contactId);
2603
2864
  }
2604
- chunkUSNT2KNT_cjs.__name(decrementDiscountUsage, "decrementDiscountUsage");
2605
- async function recordDiscountUsage(dataSource, entityMap, orderId, discountId, contactId, discountAmount) {
2606
- if (!entityMap["order_discounts"]) return;
2607
- try {
2608
- let discountCode = "";
2609
- if (entityMap["discounts"]) {
2610
- const discountRow = await dataSource.getRepository(entityMap["discounts"]).findOne({
2611
- where: {
2612
- id: discountId
2613
- }
2614
- });
2615
- discountCode = discountRow?.couponCode ?? "";
2616
- }
2617
- const odRepo = dataSource.getRepository(entityMap["order_discounts"]);
2618
- const orderItemRepo = dataSource.getRepository(entityMap.order_items);
2619
- const items = await orderItemRepo.find({
2620
- where: {
2621
- orderId
2622
- }
2623
- });
2624
- const discountedItems = items.filter((item) => Number(item.discount ?? 0) > 0);
2625
- if (discountedItems.length === 0) {
2626
- const firstItem = items[0];
2627
- if (!firstItem) return;
2628
- await odRepo.save(odRepo.create({
2629
- orderId,
2630
- orderItemId: firstItem.id,
2631
- discountId,
2632
- discountCode,
2633
- discountAmount,
2634
- userId: contactId
2635
- }));
2636
- return;
2637
- }
2638
- for (const item of discountedItems) {
2639
- const i = item;
2640
- await odRepo.save(odRepo.create({
2641
- orderId,
2642
- orderItemId: i.id,
2643
- discountId,
2644
- discountCode,
2645
- discountAmount: Number(i.discount ?? 0),
2646
- userId: contactId
2647
- }));
2648
- }
2649
- } catch (err) {
2650
- console.error("[discountEngine] recordDiscountUsage failed:", err);
2651
- }
2865
+ chunkUSNT2KNT_cjs.__name(checkAndIncrementDiscountUsage2, "checkAndIncrementDiscountUsage");
2866
+ async function decrementDiscountUsage2(dataSource, entityMap, discountId, orderId) {
2867
+ return decrementDiscountUsage(dataSource, asDiscountUsageEntityMap(entityMap), discountId, orderId);
2652
2868
  }
2653
- chunkUSNT2KNT_cjs.__name(recordDiscountUsage, "recordDiscountUsage");
2869
+ chunkUSNT2KNT_cjs.__name(decrementDiscountUsage2, "decrementDiscountUsage");
2870
+ async function recordDiscountUsage2(dataSource, entityMap, orderId, discountId, contactId, discountAmount) {
2871
+ return recordDiscountUsage(dataSource, asDiscountUsageEntityMap(entityMap), orderId, discountId, contactId, discountAmount);
2872
+ }
2873
+ chunkUSNT2KNT_cjs.__name(recordDiscountUsage2, "recordDiscountUsage");
2654
2874
  async function validateCoupon(dataSource, entityMap, couponCode, orderLines, currency = "INR", contactId, isAutomatic, discountId, vendorId) {
2655
2875
  if (!entityMap["discounts"]) return {
2656
2876
  valid: false,
@@ -2695,21 +2915,12 @@ async function validateCoupon(dataSource, entityMap, couponCode, orderLines, cur
2695
2915
  };
2696
2916
  }
2697
2917
  if (contactId != null && d.maxUsagePerUser != null) {
2698
- try {
2699
- const userUsage = await dataSource.query(`SELECT COUNT(DISTINCT od."orderId") as count FROM order_discounts od
2700
- WHERE od."discountId" = $1
2701
- AND od."userId" = $2`, [
2702
- d.id,
2703
- contactId
2704
- ]);
2705
- const usageCount = Number(userUsage?.[0]?.count ?? 0);
2706
- if (usageCount >= d.maxUsagePerUser) {
2707
- return {
2708
- valid: false,
2709
- error: "You have reached the maximum usage limit for this discount"
2710
- };
2711
- }
2712
- } catch {
2918
+ const usageCount = await countDiscountOrdersForContact(dataSource, d.id, contactId);
2919
+ if (usageCount >= d.maxUsagePerUser) {
2920
+ return {
2921
+ valid: false,
2922
+ error: "You have reached the maximum usage limit for this discount"
2923
+ };
2713
2924
  }
2714
2925
  }
2715
2926
  const flatRules = entityMap["discount_rules"] ? await dataSource.getRepository(entityMap["discount_rules"]).find({
@@ -2787,128 +2998,32 @@ function normalizeOrderLinesInput(raw) {
2787
2998
  if (!Number.isFinite(productId) || productId <= 0) continue;
2788
2999
  const qRaw = Number(r.quantity ?? 1);
2789
3000
  const q = Number.isFinite(qRaw) ? Math.min(99999, Math.max(1, Math.floor(qRaw))) : 1;
2790
- merged.set(productId, (merged.get(productId) ?? 0) + q);
3001
+ const vidRaw = r.variantId ?? r.variant_id;
3002
+ const vid = vidRaw != null && String(vidRaw).trim() !== "" ? Number(vidRaw) : NaN;
3003
+ const variantId = Number.isFinite(vid) && vid > 0 ? vid : null;
3004
+ const key = `${productId}:${variantId ?? 0}`;
3005
+ const prev = merged.get(key);
3006
+ if (prev) prev.quantity += q;
3007
+ else merged.set(key, {
3008
+ productId,
3009
+ quantity: q,
3010
+ variantId
3011
+ });
2791
3012
  }
2792
3013
  return [
2793
- ...merged.entries()
2794
- ].map(([productId, quantity]) => ({
2795
- productId,
2796
- quantity
2797
- }));
3014
+ ...merged.values()
3015
+ ];
2798
3016
  }
2799
3017
  chunkUSNT2KNT_cjs.__name(normalizeOrderLinesInput, "normalizeOrderLinesInput");
2800
- var ORDER_STATUS_CONFIRMED = "confirmed";
2801
- function orderInventorySnapshotFromRow(order) {
2802
- return {
2803
- status: order.status ?? "pending",
2804
- lines: (order.items ?? []).map((i) => ({
2805
- productId: Number(i.productId),
2806
- quantity: Math.max(0, Math.floor(Number(i.quantity) || 0))
2807
- })).filter((l) => l.productId > 0 && l.quantity > 0)
2808
- };
2809
- }
2810
- chunkUSNT2KNT_cjs.__name(orderInventorySnapshotFromRow, "orderInventorySnapshotFromRow");
2811
- function mergeInventoryLinesByProduct(lines) {
2812
- const m = /* @__PURE__ */ new Map();
2813
- for (const { productId, quantity } of lines) {
2814
- if (!Number.isFinite(productId) || productId <= 0 || quantity <= 0) continue;
2815
- m.set(productId, (m.get(productId) ?? 0) + quantity);
2816
- }
2817
- return m;
2818
- }
2819
- chunkUSNT2KNT_cjs.__name(mergeInventoryLinesByProduct, "mergeInventoryLinesByProduct");
2820
- async function validateInventoryForConfirmedOrderLines(dataSource, entityMap, calcLines) {
2821
- const productRepo = dataSource.getRepository(entityMap.products);
2822
- const merged = mergeInventoryLinesByProduct(calcLines.filter((l) => l.found && l.productId != null).map((l) => ({
2823
- productId: l.productId,
2824
- quantity: l.quantity
2825
- })));
2826
- for (const [productId, qty] of merged) {
2827
- const row = await productRepo.findOne({
2828
- where: {
2829
- id: productId,
2830
- deleted: false
2831
- }
2832
- });
2833
- if (!row) return `Product #${productId} not found`;
2834
- if (row.type === "service") continue;
2835
- const cur = Number(row.quantity ?? 0);
2836
- if (cur < qty) {
2837
- const name = row.name ?? String(productId);
2838
- return `Insufficient stock for ${name}: available ${cur}, required ${qty}`;
2839
- }
2840
- }
2841
- return null;
2842
- }
2843
- chunkUSNT2KNT_cjs.__name(validateInventoryForConfirmedOrderLines, "validateInventoryForConfirmedOrderLines");
2844
- async function applyInventoryDeltaForMergedLines(dataSource, entityMap, merged, sign, validateDeduct) {
2845
- const productRepo = dataSource.getRepository(entityMap.products);
2846
- for (const [productId, qty] of merged) {
2847
- const row = await productRepo.findOne({
2848
- where: {
2849
- id: productId,
2850
- deleted: false
2851
- }
2852
- });
2853
- if (!row) return `Product #${productId} not found`;
2854
- if (row.type === "service") continue;
2855
- const cur = Number(row.quantity ?? 0);
2856
- if (sign === -1) {
2857
- if (validateDeduct && cur < qty) {
2858
- const name = row.name ?? String(productId);
2859
- return `Insufficient stock for ${name}: available ${cur}, required ${qty}`;
2860
- }
2861
- await productRepo.update(productId, {
2862
- quantity: Math.max(0, cur - qty)
2863
- });
2864
- } else {
2865
- await productRepo.update(productId, {
2866
- quantity: cur + qty
2867
- });
2868
- }
2869
- }
2870
- return null;
2871
- }
2872
- chunkUSNT2KNT_cjs.__name(applyInventoryDeltaForMergedLines, "applyInventoryDeltaForMergedLines");
2873
- async function validateInventoryForOrderBecomingConfirmed(dataSource, entityMap, prevSnap, nextLines) {
2874
- const mergedNext = mergeInventoryLinesByProduct(nextLines);
2875
- const productRepo = dataSource.getRepository(entityMap.products);
2876
- const prevByProduct = prevSnap?.status === ORDER_STATUS_CONFIRMED ? mergeInventoryLinesByProduct(prevSnap.lines) : /* @__PURE__ */ new Map();
2877
- for (const [productId, need] of mergedNext) {
2878
- const row = await productRepo.findOne({
2879
- where: {
2880
- id: productId,
2881
- deleted: false
2882
- }
2883
- });
2884
- if (!row) return `Product #${productId} not found`;
2885
- if (row.type === "service") continue;
2886
- let available = Number(row.quantity ?? 0);
2887
- available += prevByProduct.get(productId) ?? 0;
2888
- if (available < need) {
2889
- const name = row.name ?? String(productId);
2890
- return `Insufficient stock for ${name}: available ${available}, required ${need}`;
2891
- }
2892
- }
2893
- return null;
2894
- }
2895
- chunkUSNT2KNT_cjs.__name(validateInventoryForOrderBecomingConfirmed, "validateInventoryForOrderBecomingConfirmed");
2896
- async function reconcileOrderInventoryBetweenSnapshots(dataSource, entityMap, prev, next) {
2897
- const wasConfirmed = prev.status === ORDER_STATUS_CONFIRMED;
2898
- const isConfirmed = next.status === ORDER_STATUS_CONFIRMED;
2899
- if (wasConfirmed) {
2900
- const merged = mergeInventoryLinesByProduct(prev.lines);
2901
- const err = await applyInventoryDeltaForMergedLines(dataSource, entityMap, merged, 1, false);
2902
- if (err) return err;
2903
- }
2904
- if (isConfirmed) {
2905
- const merged = mergeInventoryLinesByProduct(next.lines);
2906
- const err = await applyInventoryDeltaForMergedLines(dataSource, entityMap, merged, -1, true);
2907
- if (err) return err;
2908
- }
2909
- return null;
3018
+ var ORDER_STATUS_CONFIRMED = "confirmed";
3019
+ async function validateInventoryForConfirmedOrderLines2(dataSource, entityMap, calcLines) {
3020
+ const lines = calcLines.filter((l) => l.found && l.productId != null).map((l) => ({
3021
+ productId: l.productId,
3022
+ quantity: l.quantity
3023
+ }));
3024
+ return chunkHY3AXLOI_cjs.validateInventoryForConfirmedOrderLines(dataSource, entityMap, lines);
2910
3025
  }
2911
- chunkUSNT2KNT_cjs.__name(reconcileOrderInventoryBetweenSnapshots, "reconcileOrderInventoryBetweenSnapshots");
3026
+ chunkUSNT2KNT_cjs.__name(validateInventoryForConfirmedOrderLines2, "validateInventoryForConfirmedOrderLines");
2912
3027
  async function resolveProductTaxRate(dataSource, entityMap, productId, productVendorId) {
2913
3028
  if (!entityMap.product_config || !entityMap.taxes) {
2914
3029
  return {
@@ -3810,23 +3925,24 @@ function createCrudHandler(dataSource, entityMap, options) {
3810
3925
  if (resource === "vendor_customers" && entityMap["customer"]) {
3811
3926
  const scope = await resolveVendorCustomersScope(dataSource, entityMap, resolveScope);
3812
3927
  const repo2 = dataSource.getRepository(entity);
3813
- const qb = repo2.createQueryBuilder("vc").leftJoinAndSelect("vc.customer", "customer").leftJoinAndSelect("customer.user", "user").orderBy("vc.createdAt", sortOrder === "DESC" ? "DESC" : "ASC").skip(skip).take(limit);
3928
+ const qb = repo2.createQueryBuilder("vc").leftJoinAndSelect("vc.customer", "customer").leftJoinAndSelect("customer.user", "user").leftJoinAndSelect("vc.contact", "contact").orderBy("vc.createdAt", sortOrder === "DESC" ? "DESC" : "ASC").skip(skip).take(limit);
3814
3929
  applyVendorScopeToQueryBuilder(qb, "vc", scope);
3815
3930
  if (search && typeof search === "string" && search.trim()) {
3816
3931
  const term = `%${search.trim()}%`;
3817
- qb.andWhere("(customer.name ILIKE :term OR customer.email ILIKE :term OR customer.phone ILIKE :term)", {
3932
+ qb.andWhere("(customer.name ILIKE :term OR customer.email ILIKE :term OR customer.phone ILIKE :term OR contact.name ILIKE :term OR contact.email ILIKE :term OR contact.phone ILIKE :term)", {
3818
3933
  term
3819
3934
  });
3820
3935
  }
3821
3936
  const [rows, total2] = await qb.getManyAndCount();
3822
3937
  const data2 = rows.map((row) => {
3823
3938
  const customer = row.customer;
3939
+ const contact = row.contact;
3824
3940
  return {
3825
3941
  ...row,
3826
- name: customer?.name ?? null,
3827
- email: customer?.email ?? null,
3828
- phone: customer?.phone ?? null,
3829
- company: customer?.company ?? null
3942
+ name: customer?.name ?? contact?.name ?? null,
3943
+ email: customer?.email ?? contact?.email ?? null,
3944
+ phone: customer?.phone ?? contact?.phone ?? null,
3945
+ company: customer?.company ?? contact?.company ?? null
3830
3946
  };
3831
3947
  });
3832
3948
  return json({
@@ -4160,7 +4276,11 @@ function createCrudHandler(dataSource, entityMap, options) {
4160
4276
  } else if (resource === "collections") {
4161
4277
  if (scope.type === "vendor") {
4162
4278
  where = mergeVendorScopeIntoWhere(where, scope, resource, catalogFlags);
4163
- } else if (searchParams.get("isCatalog") !== "true") {
4279
+ } else if (searchParams.get("isCatalog") === "true") {
4280
+ where = mergeListWhereAnd(where, {
4281
+ isCatalog: true
4282
+ });
4283
+ } else if (searchParams.get("isCatalog") === "false") {
4164
4284
  where = mergeListWhereAnd(where, {
4165
4285
  isCatalog: false
4166
4286
  });
@@ -4357,41 +4477,17 @@ function createCrudHandler(dataSource, entityMap, options) {
4357
4477
  status: 503
4358
4478
  });
4359
4479
  }
4360
- if (!entityMap["users"]) {
4361
- return json({
4362
- error: "Users entity not configured"
4363
- }, {
4364
- status: 503
4365
- });
4366
- }
4367
- let customerGroupId = null;
4368
- if (entityMap["user_groups"]) {
4369
- const userGroupRepo = dataSource.getRepository(entityMap["user_groups"]);
4370
- const customerGroup = await userGroupRepo.findOne({
4371
- where: {
4372
- name: "Customer",
4373
- deleted: false
4374
- }
4375
- });
4376
- if (!customerGroup) {
4377
- return json({
4378
- error: "User group 'customer' not found"
4379
- }, {
4380
- status: 500
4381
- });
4382
- }
4383
- customerGroupId = Number(customerGroup.id);
4384
- } else {
4480
+ if (!entityMap["contacts"]) {
4385
4481
  return json({
4386
- error: "user_groups entity not configured"
4482
+ error: "Contacts entity not configured"
4387
4483
  }, {
4388
4484
  status: 503
4389
4485
  });
4390
4486
  }
4391
4487
  const name = String(body.name ?? "").trim();
4392
- const email = String(body.email ?? "").trim();
4393
- const phone = String(body.phone ?? "").trim();
4394
- const rawPw = String(body._password ?? "").trim();
4488
+ const email = String(body.email ?? "").trim().toLowerCase();
4489
+ const phoneRaw = String(body.phone ?? "").trim();
4490
+ const phone = phoneRaw || null;
4395
4491
  if (!name) return json({
4396
4492
  error: "name is required"
4397
4493
  }, {
@@ -4402,25 +4498,7 @@ function createCrudHandler(dataSource, entityMap, options) {
4402
4498
  }, {
4403
4499
  status: 400
4404
4500
  });
4405
- if (!phone) return json({
4406
- error: "phone is required"
4407
- }, {
4408
- status: 400
4409
- });
4410
- if (!rawPw) return json({
4411
- error: "password is required"
4412
- }, {
4413
- status: 400
4414
- });
4415
- if (rawPw.length < 6) {
4416
- return json({
4417
- error: "Password must be at least 6 characters"
4418
- }, {
4419
- status: 400
4420
- });
4421
- }
4422
4501
  const customerRepo = dataSource.getRepository(entityMap["customer"]);
4423
- const userRepo = dataSource.getRepository(entityMap["users"]);
4424
4502
  const dupCustEmail = await customerRepo.findOne({
4425
4503
  where: {
4426
4504
  email,
@@ -4434,49 +4512,26 @@ function createCrudHandler(dataSource, entityMap, options) {
4434
4512
  status: 409
4435
4513
  });
4436
4514
  }
4437
- const dupCustPhone = await customerRepo.findOne({
4438
- where: {
4439
- phone,
4440
- deleted: false
4441
- }
4442
- });
4443
- if (dupCustPhone) {
4444
- return json({
4445
- error: "A customer with this phone number already exists"
4446
- }, {
4447
- status: 409
4515
+ if (phone) {
4516
+ const dupCustPhone = await customerRepo.findOne({
4517
+ where: {
4518
+ phone,
4519
+ deleted: false
4520
+ }
4448
4521
  });
4449
- }
4450
- let userId;
4451
- const dupUser = await userRepo.findOne({
4452
- where: {
4453
- email,
4454
- deleted: false
4522
+ if (dupCustPhone) {
4523
+ return json({
4524
+ error: "A customer with this phone number already exists"
4525
+ }, {
4526
+ status: 409
4527
+ });
4455
4528
  }
4456
- });
4457
- if (dupUser) {
4458
- userId = Number(dupUser.id);
4459
- } else {
4460
- const bcrypt = await import('bcryptjs');
4461
- const hashedPassword = await bcrypt.hash(rawPw, 10);
4462
- const savedUser = await userRepo.save(userRepo.create({
4463
- name,
4464
- email,
4465
- phone,
4466
- password: hashedPassword,
4467
- groupId: customerGroupId,
4468
- adminAccess: false,
4469
- blocked: false
4470
- }));
4471
- userId = Number(savedUser.id);
4472
4529
  }
4473
- const savedCustomer = await ensureCustomerForUser(dataSource, entityMap["customer"], {
4474
- id: userId,
4530
+ const savedCustomer = await ensureCustomerRecord(dataSource, entityMap["customer"], {
4475
4531
  name,
4476
4532
  email,
4477
- phone
4478
- }, {
4479
- phone
4533
+ phone,
4534
+ userId: null
4480
4535
  });
4481
4536
  if (!savedCustomer) {
4482
4537
  return json({
@@ -4485,37 +4540,37 @@ function createCrudHandler(dataSource, entityMap, options) {
4485
4540
  status: 409
4486
4541
  });
4487
4542
  }
4488
- const customerRepo2 = dataSource.getRepository(entityMap["customer"]);
4489
- const customerRow = await customerRepo2.findOne({
4543
+ const contactRepo = dataSource.getRepository(entityMap["contacts"]);
4544
+ let contact = await contactRepo.findOne({
4490
4545
  where: {
4491
- id: savedCustomer.id,
4546
+ email,
4492
4547
  deleted: false
4493
4548
  }
4494
- }) ?? savedCustomer;
4495
- if (entityMap["contacts"]) {
4496
- const contactRepo = dataSource.getRepository(entityMap["contacts"]);
4497
- const existingContact = await contactRepo.findOne({
4498
- where: {
4499
- email,
4500
- deleted: false
4501
- }
4502
- });
4503
- if (!existingContact) {
4504
- await contactRepo.save(contactRepo.create({
4505
- name,
4506
- email,
4507
- phone: phone || null,
4508
- type: "customer"
4509
- }));
4510
- } else {
4511
- const t = existingContact.type;
4512
- if (t == null || t === "" || String(t).toLowerCase() !== "customer") {
4513
- await contactRepo.update(existingContact.id, {
4514
- type: "customer"
4515
- });
4516
- }
4517
- }
4549
+ });
4550
+ if (!contact) {
4551
+ contact = await contactRepo.save(contactRepo.create({
4552
+ name,
4553
+ email,
4554
+ phone: phone || null,
4555
+ type: "customer"
4556
+ }));
4557
+ } else {
4558
+ const t = contact.type;
4559
+ const patch = {
4560
+ name,
4561
+ phone: phone || contact.phone || null
4562
+ };
4563
+ if (t == null || t === "" || String(t).toLowerCase() !== "customer") {
4564
+ patch.type = "customer";
4565
+ }
4566
+ await contactRepo.update(contact.id, patch);
4567
+ contact = {
4568
+ ...contact,
4569
+ ...patch,
4570
+ id: contact.id
4571
+ };
4518
4572
  }
4573
+ const contactId = Number(contact.id);
4519
4574
  const scope = await resolveVendorCustomersScope(dataSource, entityMap, resolveScope);
4520
4575
  let vendorId = null;
4521
4576
  if (scope.type === "vendor") {
@@ -4531,19 +4586,25 @@ function createCrudHandler(dataSource, entityMap, options) {
4531
4586
  status: 400
4532
4587
  });
4533
4588
  }
4534
- const vcRepo = dataSource.getRepository(entity);
4535
- const existingLink = await vcRepo.findOne({
4589
+ await ensureVendorCustomersForOrder(dataSource, entityMap, [
4590
+ vendorId
4591
+ ], contactId, {
4592
+ name,
4593
+ email,
4594
+ phone
4595
+ });
4596
+ const customerRow = await customerRepo.findOne({
4536
4597
  where: {
4537
- customerId: savedCustomer.id
4598
+ id: savedCustomer.id,
4599
+ deleted: false
4538
4600
  }
4539
4601
  });
4540
- if (!existingLink) {
4541
- await vcRepo.save(vcRepo.create({
4542
- customerId: savedCustomer.id,
4543
- vendorId
4544
- }));
4545
- }
4546
- return json(customerRow, {
4602
+ return json(customerRow ?? {
4603
+ id: savedCustomer.id,
4604
+ name,
4605
+ email,
4606
+ phone
4607
+ }, {
4547
4608
  status: 201
4548
4609
  });
4549
4610
  }
@@ -4948,6 +5009,22 @@ function createCrudHandler(dataSource, entityMap, options) {
4948
5009
  status: 400
4949
5010
  });
4950
5011
  }
5012
+ if (resource === "combos") {
5013
+ const vendorIdForSlug = scopeCreate.type === "vendor" ? scopeCreate.vendorId : Number(persistBody.vendorId);
5014
+ if (!Number.isFinite(vendorIdForSlug) || vendorIdForSlug <= 0) {
5015
+ return json({
5016
+ error: "vendorId is required"
5017
+ }, {
5018
+ status: 400
5019
+ });
5020
+ }
5021
+ const comboSlugErr = await normalizeComboNameAndSlug(repo, persistBody, vendorIdForSlug);
5022
+ if (comboSlugErr) return json({
5023
+ error: comboSlugErr
5024
+ }, {
5025
+ status: 400
5026
+ });
5027
+ }
4951
5028
  sanitizeBodyForEntity(repo, persistBody);
4952
5029
  let created;
4953
5030
  try {
@@ -4991,6 +5068,7 @@ function createCrudHandler(dataSource, entityMap, options) {
4991
5068
  }
4992
5069
  const nameRaw = String(body["contact.name"] ?? contact.name ?? "").trim();
4993
5070
  const phoneRaw = body["contact.phone"] ?? contact.phone;
5071
+ const emailForVc = (emailRaw || String(contact.email ?? "")).trim().toLowerCase();
4994
5072
  const phoneToSave = phoneRaw == null || phoneRaw === "" ? null : String(phoneRaw).trim();
4995
5073
  if (phoneToSave && contact.phone !== phoneToSave) {
4996
5074
  await contactRepo.update(contact.id, {
@@ -4999,21 +5077,45 @@ function createCrudHandler(dataSource, entityMap, options) {
4999
5077
  contact.phone = phoneToSave;
5000
5078
  }
5001
5079
  const vendorIdForCustomer = resolveVendorIdForContactCheck(scopeCreate, persistBody);
5002
- const selectedExistingCustomer = Number(body.customerId);
5003
- if (vendorIdForCustomer != null && (!Number.isFinite(selectedExistingCustomer) || selectedExistingCustomer <= 0)) {
5004
- await ensureVendorCustomerForOrderContact(dataSource, entityMap, vendorIdForCustomer, contact.id, {
5005
- name: nameRaw || emailRaw.split("@")[0] || "Customer",
5006
- email: emailRaw,
5080
+ const orderLinesForVendors = normalizeOrderLinesInput(body.orderLines);
5081
+ const vendorIdsForVc = [];
5082
+ if (vendorIdForCustomer != null) vendorIdsForVc.push(vendorIdForCustomer);
5083
+ if (orderLinesForVendors?.length && entityMap.products) {
5084
+ const productRepoForVc = dataSource.getRepository(entityMap.products);
5085
+ for (const line of orderLinesForVendors) {
5086
+ const pid = Number(line.productId);
5087
+ if (!Number.isFinite(pid)) continue;
5088
+ const product = await productRepoForVc.findOne({
5089
+ where: {
5090
+ id: pid
5091
+ }
5092
+ });
5093
+ const pVid = Number(product?.vendorId);
5094
+ if (Number.isFinite(pVid) && pVid > 0) vendorIdsForVc.push(pVid);
5095
+ }
5096
+ }
5097
+ if (vendorIdsForVc.length > 0 && emailForVc) {
5098
+ await ensureVendorCustomersForOrder(dataSource, entityMap, vendorIdsForVc, contact.id, {
5099
+ name: nameRaw || emailForVc.split("@")[0] || "Customer",
5100
+ email: emailForVc,
5007
5101
  phone: phoneRaw == null || phoneRaw === "" ? null : String(phoneRaw)
5008
5102
  });
5009
- } else if (vendorIdForCustomer != null) {
5010
- const contactErr = await assertContactAllowedForVendorOrder(dataSource, entityMap, vendorIdForCustomer, contact.id);
5011
- if (contactErr) {
5012
- return json({
5013
- error: contactErr
5014
- }, {
5015
- status: 400
5016
- });
5103
+ }
5104
+ const accountCustomerId = Number(body.accountCustomerId);
5105
+ if (entityMap.customer_contacts && Number.isFinite(accountCustomerId) && accountCustomerId > 0) {
5106
+ const ccRepo = dataSource.getRepository(entityMap.customer_contacts);
5107
+ const orderContactId = contact.id;
5108
+ const existingLink = await ccRepo.findOne({
5109
+ where: {
5110
+ customerId: accountCustomerId,
5111
+ contactId: orderContactId
5112
+ }
5113
+ });
5114
+ if (!existingLink) {
5115
+ await ccRepo.save(ccRepo.create({
5116
+ customerId: accountCustomerId,
5117
+ contactId: orderContactId
5118
+ }));
5017
5119
  }
5018
5120
  }
5019
5121
  const randomPart = Math.floor(1e4 + Math.random() * 9e4);
@@ -5021,6 +5123,7 @@ function createCrudHandler(dataSource, entityMap, options) {
5021
5123
  persistBody.orderNumber = `ORD00${randomPart}`;
5022
5124
  persistBody.qrToken = crypto2__default.default.randomBytes(16).toString("hex");
5023
5125
  }
5126
+ await retireSoftDeletedUniquesFromBody(repo, persistBody);
5024
5127
  created = await repo.save(repo.create(persistBody));
5025
5128
  if (resource === "orders") {
5026
5129
  const orderItemRepo = dataSource.getRepository(entityMap.order_items);
@@ -5062,8 +5165,8 @@ function createCrudHandler(dataSource, entityMap, options) {
5062
5165
  }
5063
5166
  }
5064
5167
  const createdStatus = String(persistBody.status ?? "pending");
5065
- if (createdStatus === ORDER_STATUS_CONFIRMED) {
5066
- const stockErr = await validateInventoryForConfirmedOrderLines(dataSource, entityMap, lines);
5168
+ if (chunkHY3AXLOI_cjs.orderStatusHoldsStock(createdStatus)) {
5169
+ const stockErr = await validateInventoryForConfirmedOrderLines2(dataSource, entityMap, lines);
5067
5170
  if (stockErr) {
5068
5171
  await repo.delete(created.id);
5069
5172
  return json({
@@ -5085,6 +5188,7 @@ function createCrudHandler(dataSource, entityMap, options) {
5085
5188
  await orderItemRepo.save(orderItemRepo.create({
5086
5189
  orderId: created.id,
5087
5190
  productId: line.productId,
5191
+ variantId: null,
5088
5192
  quantity: line.quantity,
5089
5193
  unitPrice: line.unitPrice,
5090
5194
  subTotal: line.subtotal,
@@ -5160,10 +5264,10 @@ function createCrudHandler(dataSource, entityMap, options) {
5160
5264
  ]
5161
5265
  });
5162
5266
  if (reloadedOrder) {
5163
- const invErr = await reconcileOrderInventoryBetweenSnapshots(dataSource, entityMap, {
5267
+ const invErr = await chunkHY3AXLOI_cjs.reconcileOrderInventoryBetweenSnapshots(dataSource, entityMap, {
5164
5268
  status: "pending",
5165
5269
  lines: []
5166
- }, orderInventorySnapshotFromRow(reloadedOrder));
5270
+ }, chunkHY3AXLOI_cjs.orderInventorySnapshotFromRow(reloadedOrder));
5167
5271
  if (invErr) {
5168
5272
  logCrudServerError("POST order inventory reconcile failed", {
5169
5273
  message: invErr,
@@ -5178,7 +5282,7 @@ function createCrudHandler(dataSource, entityMap, options) {
5178
5282
  }
5179
5283
  if (orderDiscountId) {
5180
5284
  if (createdStatus === ORDER_STATUS_CONFIRMED) {
5181
- const usageErr = await checkAndIncrementDiscountUsage(dataSource, entityMap, orderDiscountId, persistBody.contactId);
5285
+ const usageErr = await checkAndIncrementDiscountUsage2(dataSource, entityMap, orderDiscountId, persistBody.contactId);
5182
5286
  if (usageErr) {
5183
5287
  await repo.delete(created.id);
5184
5288
  return json({
@@ -5188,7 +5292,7 @@ function createCrudHandler(dataSource, entityMap, options) {
5188
5292
  });
5189
5293
  }
5190
5294
  }
5191
- await recordDiscountUsage(dataSource, entityMap, created.id, orderDiscountId, persistBody.contactId, discount);
5295
+ await recordDiscountUsage2(dataSource, entityMap, created.id, orderDiscountId, persistBody.contactId, discount);
5192
5296
  }
5193
5297
  const orderIdForNotify = created.id;
5194
5298
  void import('./emit-order-notification-trigger-D2N3KFHL.cjs').then(({ fireOrderNotificationTrigger }) => {
@@ -5197,6 +5301,16 @@ function createCrudHandler(dataSource, entityMap, options) {
5197
5301
  entityMap
5198
5302
  });
5199
5303
  });
5304
+ if (getCms) {
5305
+ void import('./send-order-placed-emails-B5ZVJT7T.cjs').then(({ sendOrderPlacedEmailsAfterConfirmation }) => {
5306
+ return sendOrderPlacedEmailsAfterConfirmation(orderIdForNotify, {
5307
+ getDataSource: /* @__PURE__ */ chunkUSNT2KNT_cjs.__name(async () => dataSource, "getDataSource"),
5308
+ entityMap,
5309
+ getCms: /* @__PURE__ */ chunkUSNT2KNT_cjs.__name(async () => await getCms(), "getCms")
5310
+ });
5311
+ }).catch(() => {
5312
+ });
5313
+ }
5200
5314
  }
5201
5315
  } catch (err) {
5202
5316
  const message = err instanceof Error ? err.message : String(err);
@@ -5842,6 +5956,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5842
5956
  }
5843
5957
  }
5844
5958
  sanitizeBodyForEntity(repo, updatePayload2);
5959
+ await retireSoftDeletedUniquesFromBody(repo, updatePayload2);
5845
5960
  await repo.update(numericId, updatePayload2);
5846
5961
  if (Array.isArray(rawBody.tags)) {
5847
5962
  const tagNames = rawBody.tags.map((t) => String(t).trim()).filter(Boolean);
@@ -5903,7 +6018,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5903
6018
  status: 404
5904
6019
  });
5905
6020
  if (resource === "orders") {
5906
- orderInvPrev = orderInventorySnapshotFromRow(cur);
6021
+ orderInvPrev = chunkHY3AXLOI_cjs.orderInventorySnapshotFromRow(cur);
5907
6022
  }
5908
6023
  }
5909
6024
  if (resource === "orders" && rawBody && typeof rawBody === "object" && Array.isArray(rawBody.orderLines)) {
@@ -5930,8 +6045,8 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5930
6045
  } : await calculateOrderTotalsFromLines(dataSource, entityMap, orderLinesNorm, effectiveCurrency, putDiscountId, Number.isFinite(putOrderVendorId) ? putOrderVendorId : null);
5931
6046
  const scalarUpdate = pickColumnUpdates(repo, rawBody);
5932
6047
  const nextStatus = typeof scalarUpdate.status === "string" && scalarUpdate.status ? String(scalarUpdate.status) : orderInvPrev?.status ?? "pending";
5933
- if (nextStatus === ORDER_STATUS_CONFIRMED && lines.length > 0) {
5934
- const verr = await validateInventoryForOrderBecomingConfirmed(dataSource, entityMap, orderInvPrev, lines.filter((l) => l.found && l.productId != null).map((l) => ({
6048
+ if (chunkHY3AXLOI_cjs.orderStatusHoldsStock(nextStatus) && lines.length > 0) {
6049
+ const verr = await chunkHY3AXLOI_cjs.validateInventoryForOrderBecomingConfirmed(dataSource, entityMap, orderInvPrev, lines.filter((l) => l.found && l.productId != null).map((l) => ({
5935
6050
  productId: l.productId,
5936
6051
  quantity: l.quantity
5937
6052
  })));
@@ -6035,7 +6150,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6035
6150
  status: 404
6036
6151
  });
6037
6152
  if (orderInvPrev) {
6038
- const invErr = await reconcileOrderInventoryBetweenSnapshots(dataSource, entityMap, orderInvPrev, orderInventorySnapshotFromRow(updatedOrder));
6153
+ const invErr = await chunkHY3AXLOI_cjs.reconcileOrderInventoryBetweenSnapshots(dataSource, entityMap, orderInvPrev, chunkHY3AXLOI_cjs.orderInventorySnapshotFromRow(updatedOrder));
6039
6154
  if (invErr) return json({
6040
6155
  error: invErr
6041
6156
  }, {
@@ -6059,17 +6174,17 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6059
6174
  }) : null;
6060
6175
  const existingDiscountId = existingOd ? existingOd.discountId : null;
6061
6176
  if (!putDiscountId && existingDiscountId && wasConfirmed) {
6062
- await decrementDiscountUsage(dataSource, entityMap, existingDiscountId, numericId);
6177
+ await decrementDiscountUsage2(dataSource, entityMap, existingDiscountId, numericId);
6063
6178
  if (entityMap["order_discounts"] && existingOd) {
6064
6179
  await dataSource.getRepository(entityMap["order_discounts"]).delete(existingOd.id);
6065
6180
  }
6066
6181
  }
6067
6182
  if (putDiscountId && existingDiscountId && putDiscountId !== existingDiscountId) {
6068
6183
  if (wasConfirmed) {
6069
- await decrementDiscountUsage(dataSource, entityMap, existingDiscountId, numericId);
6184
+ await decrementDiscountUsage2(dataSource, entityMap, existingDiscountId, numericId);
6070
6185
  }
6071
6186
  if (isNowConfirmed) {
6072
- const usageErr = await checkAndIncrementDiscountUsage(dataSource, entityMap, putDiscountId);
6187
+ const usageErr = await checkAndIncrementDiscountUsage2(dataSource, entityMap, putDiscountId, cid);
6073
6188
  if (usageErr) return json({
6074
6189
  error: usageErr
6075
6190
  }, {
@@ -6082,12 +6197,12 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6082
6197
  discountAmount: discount
6083
6198
  });
6084
6199
  } else {
6085
- await recordDiscountUsage(dataSource, entityMap, numericId, putDiscountId, cid, discount);
6200
+ await recordDiscountUsage2(dataSource, entityMap, numericId, putDiscountId, cid, discount);
6086
6201
  }
6087
6202
  }
6088
6203
  if (putDiscountId && existingDiscountId && putDiscountId === existingDiscountId) {
6089
6204
  if (!wasConfirmed && isNowConfirmed) {
6090
- const usageErr = await checkAndIncrementDiscountUsage(dataSource, entityMap, putDiscountId);
6205
+ const usageErr = await checkAndIncrementDiscountUsage2(dataSource, entityMap, putDiscountId, cid);
6091
6206
  if (usageErr) return json({
6092
6207
  error: usageErr
6093
6208
  }, {
@@ -6095,7 +6210,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6095
6210
  });
6096
6211
  }
6097
6212
  if (wasConfirmed && !isNowConfirmed) {
6098
- await decrementDiscountUsage(dataSource, entityMap, putDiscountId, numericId);
6213
+ await decrementDiscountUsage2(dataSource, entityMap, putDiscountId, numericId);
6099
6214
  }
6100
6215
  if (existingOd && entityMap["order_discounts"]) {
6101
6216
  await dataSource.getRepository(entityMap["order_discounts"]).update(existingOd.id, {
@@ -6105,14 +6220,14 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6105
6220
  }
6106
6221
  if (putDiscountId && !existingDiscountId) {
6107
6222
  if (isNowConfirmed) {
6108
- const usageErr = await checkAndIncrementDiscountUsage(dataSource, entityMap, putDiscountId);
6223
+ const usageErr = await checkAndIncrementDiscountUsage2(dataSource, entityMap, putDiscountId, cid);
6109
6224
  if (usageErr) return json({
6110
6225
  error: usageErr
6111
6226
  }, {
6112
6227
  status: 400
6113
6228
  });
6114
6229
  }
6115
- await recordDiscountUsage(dataSource, entityMap, numericId, putDiscountId, cid, discount);
6230
+ await recordDiscountUsage2(dataSource, entityMap, numericId, putDiscountId, cid, discount);
6116
6231
  }
6117
6232
  }
6118
6233
  }
@@ -6583,8 +6698,8 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6583
6698
  }
6584
6699
  if (resource === "orders" && orderInvPrev) {
6585
6700
  const nextSt = typeof updatePayload.status === "string" && updatePayload.status ? String(updatePayload.status) : orderInvPrev.status;
6586
- if (nextSt === ORDER_STATUS_CONFIRMED && orderInvPrev.lines.length > 0) {
6587
- const verr = await validateInventoryForOrderBecomingConfirmed(dataSource, entityMap, orderInvPrev, orderInvPrev.lines);
6701
+ if (chunkHY3AXLOI_cjs.orderStatusHoldsStock(nextSt) && orderInvPrev.lines.length > 0) {
6702
+ const verr = await chunkHY3AXLOI_cjs.validateInventoryForOrderBecomingConfirmed(dataSource, entityMap, orderInvPrev, orderInvPrev.lines);
6588
6703
  if (verr) return json({
6589
6704
  error: verr
6590
6705
  }, {
@@ -6592,6 +6707,33 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6592
6707
  });
6593
6708
  }
6594
6709
  }
6710
+ if (resource === "combos") {
6711
+ const currentCombo = await repo.findOne({
6712
+ where: {
6713
+ id: numericId,
6714
+ deleted: false
6715
+ }
6716
+ });
6717
+ if (!currentCombo) return json({
6718
+ message: "Not found"
6719
+ }, {
6720
+ status: 404
6721
+ });
6722
+ const vendorIdForSlug = scopePut.type === "vendor" ? scopePut.vendorId : Number(currentCombo.vendorId);
6723
+ if (!Number.isFinite(vendorIdForSlug) || vendorIdForSlug <= 0) {
6724
+ return json({
6725
+ error: "vendorId is required"
6726
+ }, {
6727
+ status: 400
6728
+ });
6729
+ }
6730
+ const comboSlugErr = await normalizeComboNameAndSlug(repo, updatePayload, vendorIdForSlug, currentCombo, numericId);
6731
+ if (comboSlugErr) return json({
6732
+ error: comboSlugErr
6733
+ }, {
6734
+ status: 400
6735
+ });
6736
+ }
6595
6737
  if (Object.keys(updatePayload).length > 0) {
6596
6738
  if (resource === "payments" && updatePayload.status === "paid" && updatePayload.paidAt == null) {
6597
6739
  const existingPayment = await repo.findOne({
@@ -6605,6 +6747,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6605
6747
  }
6606
6748
  }
6607
6749
  sanitizeBodyForEntity(repo, updatePayload);
6750
+ await retireSoftDeletedUniquesFromBody(repo, updatePayload);
6608
6751
  await repo.update(numericId, updatePayload);
6609
6752
  }
6610
6753
  if (resource === "orders" && rawBody && entityMap["order_addresses"]) {
@@ -6657,7 +6800,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6657
6800
  }
6658
6801
  });
6659
6802
  if (resource === "orders" && orderInvPrev && updated) {
6660
- const invErr = await reconcileOrderInventoryBetweenSnapshots(dataSource, entityMap, orderInvPrev, orderInventorySnapshotFromRow(updated));
6803
+ const invErr = await chunkHY3AXLOI_cjs.reconcileOrderInventoryBetweenSnapshots(dataSource, entityMap, orderInvPrev, chunkHY3AXLOI_cjs.orderInventorySnapshotFromRow(updated));
6661
6804
  if (invErr) return json({
6662
6805
  error: invErr
6663
6806
  }, {
@@ -6780,7 +6923,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6780
6923
  });
6781
6924
  if (od) {
6782
6925
  const odRow = od;
6783
- await decrementDiscountUsage(dataSource, entityMap, odRow.discountId, numericId);
6926
+ await decrementDiscountUsage2(dataSource, entityMap, odRow.discountId, numericId);
6784
6927
  }
6785
6928
  }
6786
6929
  }
@@ -6793,6 +6936,22 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6793
6936
  }
6794
6937
  }
6795
6938
  await repo.update(numericId, buildSoftDeletePayload(repo.metadata, deletedBy));
6939
+ const softDeletedCols = repo.metadata.columns.map((c) => c.propertyName);
6940
+ const uniqueColsToRetire = [
6941
+ "slug",
6942
+ "email"
6943
+ ].filter((col) => softDeletedCols.includes(col));
6944
+ if (existing && uniqueColsToRetire.length > 0) {
6945
+ for (const col of uniqueColsToRetire) {
6946
+ const raw = existing[col];
6947
+ if (typeof raw !== "string") continue;
6948
+ const value = raw.trim();
6949
+ if (!value || value.includes("__deleted_")) continue;
6950
+ await repo.update(numericId, {
6951
+ [col]: `${value}__deleted_${numericId}`
6952
+ });
6953
+ }
6954
+ }
6796
6955
  return json({
6797
6956
  message: "Deleted successfully"
6798
6957
  }, {
@@ -6823,6 +6982,63 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6823
6982
  };
6824
6983
  }
6825
6984
  chunkUSNT2KNT_cjs.__name(createCrudByIdHandler, "createCrudByIdHandler");
6985
+ async function linkUnclaimedContactToUser(dataSource, contactsEntity, userId, email) {
6986
+ const repo = dataSource.getRepository(contactsEntity);
6987
+ const found = await repo.findOne({
6988
+ where: {
6989
+ email,
6990
+ userId: typeorm.IsNull(),
6991
+ deleted: false
6992
+ }
6993
+ });
6994
+ if (found) await repo.update(found.id, {
6995
+ userId
6996
+ });
6997
+ }
6998
+ chunkUSNT2KNT_cjs.__name(linkUnclaimedContactToUser, "linkUnclaimedContactToUser");
6999
+ function buildUserInviteLink(baseUrl, token) {
7000
+ const base = baseUrl.replace(/\/+$/, "");
7001
+ return `${base}/admin/invite?token=${encodeURIComponent(token)}`;
7002
+ }
7003
+ chunkUSNT2KNT_cjs.__name(buildUserInviteLink, "buildUserInviteLink");
7004
+ function newUserInviteToken() {
7005
+ return crypto2.randomBytes(32).toString("hex");
7006
+ }
7007
+ chunkUSNT2KNT_cjs.__name(newUserInviteToken, "newUserInviteToken");
7008
+ async function findUserByInviteToken(dataSource, userEntity, token) {
7009
+ const trimmed = token.trim();
7010
+ if (!trimmed) return null;
7011
+ const repo = dataSource.getRepository(userEntity);
7012
+ const row = await repo.findOne({
7013
+ where: {
7014
+ inviteToken: trimmed,
7015
+ deleted: false
7016
+ },
7017
+ select: [
7018
+ "id",
7019
+ "email",
7020
+ "blocked",
7021
+ "password",
7022
+ "inviteStatus",
7023
+ "inviteToken"
7024
+ ]
7025
+ });
7026
+ if (!row) return null;
7027
+ const u = row;
7028
+ return u;
7029
+ }
7030
+ chunkUSNT2KNT_cjs.__name(findUserByInviteToken, "findUserByInviteToken");
7031
+ async function completeUserInviteAccept(dataSource, userEntity, userId, passwordHash) {
7032
+ await dataSource.getRepository(userEntity).update(userId, {
7033
+ password: passwordHash,
7034
+ blocked: false,
7035
+ inviteStatus: "active",
7036
+ inviteToken: null
7037
+ });
7038
+ }
7039
+ chunkUSNT2KNT_cjs.__name(completeUserInviteAccept, "completeUserInviteAccept");
7040
+
7041
+ // src/lib/vendor-invite-token.ts
6826
7042
  var VENDOR_INVITE_METADATA_TOKEN = "inviteToken";
6827
7043
  var VENDOR_INVITE_METADATA_EXPIRES = "inviteExpiresAt";
6828
7044
  var VENDOR_INVITE_METADATA_SENT = "inviteSentAt";
@@ -6847,6 +7063,13 @@ function invalidatedInviteMessage() {
6847
7063
  return "This invitation is no longer valid. Ask an admin to resend the invite.";
6848
7064
  }
6849
7065
  chunkUSNT2KNT_cjs.__name(invalidatedInviteMessage, "invalidatedInviteMessage");
7066
+ function userAlreadyActive(user) {
7067
+ const hasPassword = !!(user.password && String(user.password).trim());
7068
+ if (user.inviteStatus === "active" && hasPassword) return true;
7069
+ if (user.inviteStatus === "pending") return false;
7070
+ return !user.blocked && hasPassword;
7071
+ }
7072
+ chunkUSNT2KNT_cjs.__name(userAlreadyActive, "userAlreadyActive");
6850
7073
  async function validateInviteTokenForActivation(dataSource, entityMap, token) {
6851
7074
  const trimmedToken = String(token ?? "").trim();
6852
7075
  if (!trimmedToken) {
@@ -6893,15 +7116,15 @@ async function validateInviteTokenForActivation(dataSource, entityMap, token) {
6893
7116
  "id",
6894
7117
  "email",
6895
7118
  "blocked",
6896
- "password"
7119
+ "password",
7120
+ "inviteStatus"
6897
7121
  ]
6898
7122
  });
6899
7123
  if (!user2) return {
6900
7124
  ok: false,
6901
7125
  error: "User not found"
6902
7126
  };
6903
- const hasPassword2 = !!user2.password;
6904
- if (!user2.blocked && hasPassword2) {
7127
+ if (userAlreadyActive(user2)) {
6905
7128
  return {
6906
7129
  ok: false,
6907
7130
  error: "User is already active"
@@ -6917,6 +7140,24 @@ async function validateInviteTokenForActivation(dataSource, entityMap, token) {
6917
7140
  };
6918
7141
  }
6919
7142
  }
7143
+ if (userRepo && entityMap.users) {
7144
+ const userMatch = await findUserByInviteToken(dataSource, entityMap.users, trimmedToken);
7145
+ if (userMatch) {
7146
+ if (userMatch.inviteStatus === "active" || userAlreadyActive(userMatch)) {
7147
+ return {
7148
+ ok: false,
7149
+ error: "This invitation has already been accepted"
7150
+ };
7151
+ }
7152
+ return {
7153
+ ok: true,
7154
+ target: {
7155
+ userId: userMatch.id,
7156
+ email: userMatch.email
7157
+ }
7158
+ };
7159
+ }
7160
+ }
6920
7161
  let email;
6921
7162
  try {
6922
7163
  email = Buffer.from(trimmedToken, "base64").toString("utf8").trim();
@@ -6946,22 +7187,30 @@ async function validateInviteTokenForActivation(dataSource, entityMap, token) {
6946
7187
  "id",
6947
7188
  "email",
6948
7189
  "blocked",
6949
- "password"
7190
+ "password",
7191
+ "inviteStatus",
7192
+ "inviteToken"
6950
7193
  ]
6951
7194
  });
6952
7195
  if (!user) return {
6953
7196
  ok: false,
6954
7197
  error: "User not found"
6955
7198
  };
6956
- const hasPassword = !!user.password;
6957
- if (!user.blocked && hasPassword) {
7199
+ const u = user;
7200
+ if (userAlreadyActive(u)) {
6958
7201
  return {
6959
7202
  ok: false,
6960
7203
  error: "User is already active"
6961
7204
  };
6962
7205
  }
7206
+ if (u.inviteToken && String(u.inviteToken).trim()) {
7207
+ return {
7208
+ ok: false,
7209
+ error: invalidatedInviteMessage()
7210
+ };
7211
+ }
6963
7212
  if (entityMap.vendors) {
6964
- const ownerVendor = await loadOwnerVendorForUser(dataSource, entityMap.vendors, user.id);
7213
+ const ownerVendor = await loadOwnerVendorForUser(dataSource, entityMap.vendors, u.id);
6965
7214
  if (ownerVendor && readVendorInviteToken(ownerVendor.metadata)) {
6966
7215
  return {
6967
7216
  ok: false,
@@ -6972,8 +7221,8 @@ async function validateInviteTokenForActivation(dataSource, entityMap, token) {
6972
7221
  return {
6973
7222
  ok: true,
6974
7223
  target: {
6975
- userId: user.id,
6976
- email: String(user.email)
7224
+ userId: u.id,
7225
+ email: String(u.email)
6977
7226
  }
6978
7227
  };
6979
7228
  }
@@ -7191,10 +7440,7 @@ function createInviteAcceptHandler(config) {
7191
7440
  }
7192
7441
  if (beforeActivate) await beforeActivate(email, userId);
7193
7442
  const hashedPassword = await hashPassword(password);
7194
- await dataSource.getRepository(entityMap.users).update(userId, {
7195
- password: hashedPassword,
7196
- blocked: false
7197
- });
7443
+ await completeUserInviteAccept(dataSource, entityMap.users, userId, hashedPassword);
7198
7444
  if (vendorId != null && entityMap.vendors) {
7199
7445
  await completeVendorInviteAccept(dataSource, entityMap.vendors, vendorId);
7200
7446
  }
@@ -8621,6 +8867,9 @@ function createUploadHandler(config) {
8621
8867
  "image/png",
8622
8868
  "image/gif",
8623
8869
  "image/webp",
8870
+ "video/mp4",
8871
+ "video/webm",
8872
+ "video/quicktime",
8624
8873
  "application/pdf",
8625
8874
  "text/plain",
8626
8875
  "application/zip",
@@ -8648,8 +8897,11 @@ function createUploadHandler(config) {
8648
8897
  });
8649
8898
  const defaultMax = 10 * 1024 * 1024;
8650
8899
  const maxZipBytes = 80 * 1024 * 1024;
8900
+ const maxVideoBytes = 100 * 1024 * 1024;
8651
8901
  const baseMax = maxSizeBytes ?? defaultMax;
8652
- const effectiveMax = file.type === "application/zip" || file.type === "application/x-zip-compressed" ? Math.max(baseMax, maxZipBytes) : baseMax;
8902
+ const isZip = file.type === "application/zip" || file.type === "application/x-zip-compressed";
8903
+ const isVideo = file.type === "video/mp4" || file.type === "video/webm" || file.type === "video/quicktime";
8904
+ const effectiveMax = isZip ? Math.max(baseMax, maxZipBytes) : isVideo ? Math.max(baseMax, maxVideoBytes) : baseMax;
8653
8905
  if (file.size > effectiveMax) return json({
8654
8906
  error: "File size exceeds limit"
8655
8907
  }, {
@@ -8722,12 +8974,20 @@ function createUploadHandler(config) {
8722
8974
  parentId
8723
8975
  });
8724
8976
  } catch (err) {
8977
+ const message = err instanceof Error ? err.message : String(err);
8725
8978
  console.error("[upload] File upload failed", {
8726
- error: err instanceof Error ? err.message : String(err),
8979
+ error: message,
8727
8980
  stack: err instanceof Error ? err.stack : void 0,
8728
8981
  name: err instanceof Error ? err.name : void 0,
8729
8982
  metadata: err && typeof err === "object" && "$metadata" in err ? err.$metadata : void 0
8730
8983
  });
8984
+ if (/Failed to parse body as FormData/i.test(message)) {
8985
+ return json({
8986
+ error: "File upload failed: request body could not be read as FormData. Exclude /api/upload from middleware matcher and raise proxyClientMaxBodySize."
8987
+ }, {
8988
+ status: 500
8989
+ });
8990
+ }
8731
8991
  return json({
8732
8992
  error: "File upload failed"
8733
8993
  }, {
@@ -8948,6 +9208,17 @@ function createFormSaveHandlers(config) {
8948
9208
  });
8949
9209
  const fields = Array.isArray(body.fields) ? body.fields : [];
8950
9210
  const { fields: _f, ...formRow } = body;
9211
+ const slug = typeof formRow.slug === "string" ? formRow.slug.trim() : "";
9212
+ if (slug) {
9213
+ if (await chunkOY2YTBMP_cjs.activeUniqueValueExists(formRepo(), "slug", slug)) {
9214
+ return json({
9215
+ error: "A form with this slug already exists"
9216
+ }, {
9217
+ status: 400
9218
+ });
9219
+ }
9220
+ await chunkOY2YTBMP_cjs.retireSoftDeletedUniqueValue(formRepo(), "slug", slug);
9221
+ }
8951
9222
  const form = await formRepo().save(formRepo().create(formRow));
8952
9223
  for (let i = 0; i < fields.length; i++) {
8953
9224
  const row = normalizeFieldRow(fields[i], form.id);
@@ -8971,6 +9242,14 @@ function createFormSaveHandlers(config) {
8971
9242
  status: 201
8972
9243
  });
8973
9244
  } catch (e) {
9245
+ const msg = e instanceof Error ? e.message : String(e);
9246
+ if (/duplicate key|unique constraint/i.test(msg) && /slug/i.test(msg)) {
9247
+ return json({
9248
+ error: "A form with this slug already exists"
9249
+ }, {
9250
+ status: 400
9251
+ });
9252
+ }
8974
9253
  return json({
8975
9254
  error: "Server Error"
8976
9255
  }, {
@@ -9019,6 +9298,20 @@ function createFormSaveHandlers(config) {
9019
9298
  ]) {
9020
9299
  if (body[key] !== void 0) formRow[key] = body[key];
9021
9300
  }
9301
+ if (typeof formRow.slug === "string") {
9302
+ const slug = formRow.slug.trim();
9303
+ formRow.slug = slug;
9304
+ if (slug) {
9305
+ if (await chunkOY2YTBMP_cjs.activeUniqueValueExists(formRepo(), "slug", slug, formId)) {
9306
+ return json({
9307
+ error: "A form with this slug already exists"
9308
+ }, {
9309
+ status: 400
9310
+ });
9311
+ }
9312
+ await chunkOY2YTBMP_cjs.retireSoftDeletedUniqueValue(formRepo(), "slug", slug);
9313
+ }
9314
+ }
9022
9315
  if (Object.keys(formRow).length > 0) await formRepo().update(formId, formRow);
9023
9316
  await fieldRepo().delete({
9024
9317
  formId
@@ -9047,6 +9340,14 @@ function createFormSaveHandlers(config) {
9047
9340
  status: 404
9048
9341
  });
9049
9342
  } catch (e) {
9343
+ const msg = e instanceof Error ? e.message : String(e);
9344
+ if (/duplicate key|unique constraint/i.test(msg) && /slug/i.test(msg)) {
9345
+ return json({
9346
+ error: "A form with this slug already exists"
9347
+ }, {
9348
+ status: 400
9349
+ });
9350
+ }
9050
9351
  return json({
9051
9352
  error: "Server Error"
9052
9353
  }, {
@@ -9351,7 +9652,7 @@ function createFormSubmissionHandler(config) {
9351
9652
  label: f.label && String(f.label).trim() || `Field ${f.id}`,
9352
9653
  value: formatSubmissionFieldValue(data[String(f.id)])
9353
9654
  }));
9354
- await chunkXW5ATPRW_cjs.queueEmail(cms, {
9655
+ await chunkV25RDUOU_cjs.queueEmail(cms, {
9355
9656
  to,
9356
9657
  templateName: "formSubmission",
9357
9658
  ctx: {
@@ -9405,7 +9706,7 @@ function createUsersApiHandlers(config) {
9405
9706
  try {
9406
9707
  const cms = await getCms();
9407
9708
  const companyDetails = getCompanyDetails ? await getCompanyDetails() : {};
9408
- await chunkXW5ATPRW_cjs.queueEmail(cms, {
9709
+ await chunkV25RDUOU_cjs.queueEmail(cms, {
9409
9710
  to: toEmail,
9410
9711
  templateName: "invite",
9411
9712
  ctx: {
@@ -9440,7 +9741,8 @@ function createUsersApiHandlers(config) {
9440
9741
  "name",
9441
9742
  "email",
9442
9743
  "id",
9443
- "blocked"
9744
+ "blocked",
9745
+ "inviteStatus"
9444
9746
  ]);
9445
9747
  const sortField = allowedSort.has(sortFieldRaw) ? sortFieldRaw : "createdAt";
9446
9748
  const sortOrder = url.searchParams.get("sortOrder") === "desc" ? "DESC" : "ASC";
@@ -9453,6 +9755,7 @@ function createUsersApiHandlers(config) {
9453
9755
  "u.name",
9454
9756
  "u.email",
9455
9757
  "u.blocked",
9758
+ "u.inviteStatus",
9456
9759
  "u.createdAt",
9457
9760
  "u.updatedAt",
9458
9761
  "u.groupId",
@@ -9548,6 +9851,7 @@ function createUsersApiHandlers(config) {
9548
9851
  const isCustomer = !!(customerG && gid === customerG.id);
9549
9852
  const adminAccess = isCustomer ? false : body.adminAccess === false ? false : true;
9550
9853
  const blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
9854
+ const inviteToken = newUserInviteToken();
9551
9855
  const newUser = existing?.deleted ? await (async () => {
9552
9856
  await userRepo().update(existing.id, {
9553
9857
  deleted: false,
@@ -9557,6 +9861,8 @@ function createUsersApiHandlers(config) {
9557
9861
  email,
9558
9862
  password: null,
9559
9863
  blocked,
9864
+ inviteStatus: "pending",
9865
+ inviteToken,
9560
9866
  groupId: gid,
9561
9867
  adminAccess,
9562
9868
  updatedAt: /* @__PURE__ */ new Date()
@@ -9573,6 +9879,8 @@ function createUsersApiHandlers(config) {
9573
9879
  email,
9574
9880
  password: null,
9575
9881
  blocked,
9882
+ inviteStatus: "pending",
9883
+ inviteToken,
9576
9884
  groupId: gid,
9577
9885
  adminAccess
9578
9886
  }));
@@ -9590,13 +9898,12 @@ function createUsersApiHandlers(config) {
9590
9898
  phone
9591
9899
  });
9592
9900
  }
9593
- const emailToken = Buffer.from(newUser.email).toString("base64");
9594
- const inviteLink = `${baseUrl}/admin/invite?token=${emailToken}`;
9595
- await trySendInviteEmail(newUser.email, inviteLink, newUser.name ?? "");
9901
+ const inviteLink = buildUserInviteLink(baseUrl, inviteToken);
9596
9902
  return json({
9597
- message: blocked ? "User created successfully (blocked until password is set)" : "User created successfully",
9903
+ message: "User created successfully. Invite is pending until they set a password.",
9598
9904
  user: newUser,
9599
- inviteLink
9905
+ inviteLink,
9906
+ emailSent: false
9600
9907
  }, {
9601
9908
  status: 201
9602
9909
  });
@@ -9628,7 +9935,9 @@ function createUsersApiHandlers(config) {
9628
9935
  "id",
9629
9936
  "name",
9630
9937
  "email",
9938
+ "phone",
9631
9939
  "blocked",
9940
+ "inviteStatus",
9632
9941
  "createdAt",
9633
9942
  "updatedAt",
9634
9943
  "groupId"
@@ -9657,6 +9966,11 @@ function createUsersApiHandlers(config) {
9657
9966
  }
9658
9967
  try {
9659
9968
  const uid = parseInt(id, 10);
9969
+ if (!Number.isFinite(uid)) return json({
9970
+ error: "Invalid id"
9971
+ }, {
9972
+ status: 400
9973
+ });
9660
9974
  const existing = await userRepo().findOne({
9661
9975
  where: {
9662
9976
  id: uid,
@@ -9669,8 +9983,59 @@ function createUsersApiHandlers(config) {
9669
9983
  status: 404
9670
9984
  });
9671
9985
  const body = await req.json();
9672
- const { password: _p, ...safe } = body;
9673
- await userRepo().update(uid, safe);
9986
+ const patch = {
9987
+ updatedAt: /* @__PURE__ */ new Date()
9988
+ };
9989
+ if (typeof body.name === "string") patch.name = body.name.trim();
9990
+ if (typeof body.email === "string") patch.email = body.email.trim().toLowerCase();
9991
+ if (body.blocked !== void 0) {
9992
+ patch.blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
9993
+ }
9994
+ if (body.adminAccess !== void 0) {
9995
+ patch.adminAccess = body.adminAccess === true || body.adminAccess === "true" || body.adminAccess === 1 || body.adminAccess === "1";
9996
+ }
9997
+ if (body.groupId !== void 0) {
9998
+ if (body.groupId === null || body.groupId === "") {
9999
+ patch.groupId = null;
10000
+ } else {
10001
+ const gid = Number(body.groupId);
10002
+ if (!Number.isFinite(gid)) {
10003
+ return json({
10004
+ error: "Invalid groupId"
10005
+ }, {
10006
+ status: 400
10007
+ });
10008
+ }
10009
+ patch.groupId = gid;
10010
+ }
10011
+ }
10012
+ if (body.phone !== void 0) {
10013
+ const phone = body.phone == null ? null : String(body.phone).trim();
10014
+ patch.phone = phone || null;
10015
+ }
10016
+ if (Object.keys(patch).length <= 1) {
10017
+ return json({
10018
+ error: "No valid fields to update"
10019
+ }, {
10020
+ status: 400
10021
+ });
10022
+ }
10023
+ if (typeof patch.email === "string" && patch.email !== existing.email) {
10024
+ const emailTaken = await userRepo().findOne({
10025
+ where: {
10026
+ email: patch.email,
10027
+ deleted: false
10028
+ }
10029
+ });
10030
+ if (emailTaken && Number(emailTaken.id) !== uid) {
10031
+ return json({
10032
+ error: "Email already in use"
10033
+ }, {
10034
+ status: 400
10035
+ });
10036
+ }
10037
+ }
10038
+ await userRepo().update(uid, patch);
9674
10039
  const updated = await userRepo().findOne({
9675
10040
  where: {
9676
10041
  id: uid,
@@ -9683,7 +10048,9 @@ function createUsersApiHandlers(config) {
9683
10048
  "id",
9684
10049
  "name",
9685
10050
  "email",
10051
+ "phone",
9686
10052
  "blocked",
10053
+ "inviteStatus",
9687
10054
  "createdAt",
9688
10055
  "updatedAt",
9689
10056
  "groupId"
@@ -9694,7 +10061,8 @@ function createUsersApiHandlers(config) {
9694
10061
  }, {
9695
10062
  status: 404
9696
10063
  });
9697
- } catch {
10064
+ } catch (err) {
10065
+ console.error("[users.update]", err);
9698
10066
  return json({
9699
10067
  error: "Server Error"
9700
10068
  }, {
@@ -9750,13 +10118,21 @@ function createUsersApiHandlers(config) {
9750
10118
  });
9751
10119
  }
9752
10120
  },
9753
- async regenerateInvite(_req, id) {
9754
- const authErr = await requireAuth(new Request(_req.url));
10121
+ async regenerateInvite(req, id) {
10122
+ const authErr = await requireAuth(new Request(req.url));
9755
10123
  if (authErr) return authErr;
9756
10124
  if (requireEntityPermission) {
9757
- const pe = await requireEntityPermission(_req, "users", "update");
10125
+ const pe = await requireEntityPermission(req, "users", "update");
9758
10126
  if (pe) return pe;
9759
10127
  }
10128
+ let sendEmail = true;
10129
+ let rotate = true;
10130
+ try {
10131
+ const body = await req.json().catch(() => ({}));
10132
+ if (body.sendEmail === false) sendEmail = false;
10133
+ if (body.rotate === false) rotate = false;
10134
+ } catch {
10135
+ }
9760
10136
  try {
9761
10137
  const user = await userRepo().findOne({
9762
10138
  where: {
@@ -9766,7 +10142,11 @@ function createUsersApiHandlers(config) {
9766
10142
  select: [
9767
10143
  "id",
9768
10144
  "email",
9769
- "name"
10145
+ "name",
10146
+ "password",
10147
+ "inviteStatus",
10148
+ "inviteToken",
10149
+ "blocked"
9770
10150
  ]
9771
10151
  });
9772
10152
  if (!user) return json({
@@ -9774,34 +10154,75 @@ function createUsersApiHandlers(config) {
9774
10154
  }, {
9775
10155
  status: 404
9776
10156
  });
9777
- let inviteLink = `${baseUrl}/admin/invite?token=${Buffer.from(user.email).toString("base64")}`;
10157
+ const u = user;
10158
+ const hasPassword = !!(u.password && String(u.password).trim());
10159
+ if (u.inviteStatus === "active" && hasPassword) {
10160
+ return json({
10161
+ error: "User has already accepted the invite"
10162
+ }, {
10163
+ status: 400
10164
+ });
10165
+ }
10166
+ let inviteLink = "";
9778
10167
  if (entityMap.vendors) {
9779
10168
  const vendorRepo = dataSource.getRepository(entityMap.vendors);
9780
10169
  const vendor = await vendorRepo.findOne({
9781
10170
  where: {
9782
- userId: user.id,
10171
+ userId: u.id,
9783
10172
  deleted: false
9784
10173
  }
9785
10174
  });
9786
10175
  const v = vendor;
9787
10176
  if (v && (v.inviteStatus === "pending" || v.inviteStatus === "expired")) {
9788
- const rotatedMetadata = applyRotatingVendorInvite(v.metadata);
9789
- await vendorRepo.update(v.id, {
9790
- metadata: rotatedMetadata,
9791
- inviteStatus: "pending"
9792
- });
9793
- const token = readVendorInviteToken(rotatedMetadata);
10177
+ const nextMetadata = rotate ? applyRotatingVendorInvite(v.metadata) : {
10178
+ ...v.metadata ?? {}
10179
+ };
10180
+ if (rotate || v.inviteStatus !== "pending") {
10181
+ await vendorRepo.update(v.id, {
10182
+ metadata: nextMetadata,
10183
+ inviteStatus: "pending"
10184
+ });
10185
+ }
10186
+ const token = readVendorInviteToken(nextMetadata);
9794
10187
  if (token) {
9795
10188
  inviteLink = buildVendorInviteLink(baseUrl, token);
9796
10189
  }
10190
+ const { markVendorInvitePendingForUser } = await import('./vendor-invite-status-77O7ZQ3U.cjs');
10191
+ await markVendorInvitePendingForUser(dataSource, entityMap.vendors, u.id);
10192
+ }
10193
+ }
10194
+ if (!inviteLink) {
10195
+ let token = u.inviteToken?.trim() || "";
10196
+ if (rotate || !token) {
10197
+ token = newUserInviteToken();
10198
+ await userRepo().update(u.id, {
10199
+ inviteToken: token,
10200
+ inviteStatus: "pending",
10201
+ password: null
10202
+ });
10203
+ } else if (u.inviteStatus !== "pending") {
10204
+ await userRepo().update(u.id, {
10205
+ inviteStatus: "pending"
10206
+ });
9797
10207
  }
9798
- const { markVendorInvitePendingForUser } = await import('./vendor-invite-status-77O7ZQ3U.cjs');
9799
- await markVendorInvitePendingForUser(dataSource, entityMap.vendors, user.id);
10208
+ inviteLink = buildUserInviteLink(baseUrl, token);
10209
+ } else if (rotate) {
10210
+ await userRepo().update(u.id, {
10211
+ inviteStatus: "pending",
10212
+ password: null
10213
+ });
9800
10214
  }
9801
- await trySendInviteEmail(user.email, inviteLink, user.name ?? "");
10215
+ let emailSent = false;
10216
+ if (sendEmail && inviteLink) {
10217
+ await trySendInviteEmail(u.email, inviteLink, u.name ?? "");
10218
+ emailSent = true;
10219
+ }
10220
+ const rotatedNote = rotate ? " Previous invite link is no longer valid." : "";
9802
10221
  return json({
9803
- message: "New invite link generated successfully",
9804
- inviteLink
10222
+ 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.",
10223
+ emailSent,
10224
+ inviteLink,
10225
+ inviteStatus: "pending"
9805
10226
  });
9806
10227
  } catch {
9807
10228
  return json({
@@ -10558,7 +10979,7 @@ function createChatHandlers(config) {
10558
10979
  const notifyEmailAgent = await findLlmAgentByScope(dataSource, entityMap, LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT, {
10559
10980
  enabledOnly: false
10560
10981
  });
10561
- const emailTool = chunk22FFHLTO_cjs.resolveChatEmailToolSettings(llmSettings, {
10982
+ const emailTool = chunkTCSGFAWB_cjs.resolveChatEmailToolSettings(llmSettings, {
10562
10983
  chatbotValidationRules: agentRow?.validationRules ?? null,
10563
10984
  notifyAgent: notifyEmailAgent ? {
10564
10985
  systemInstruction: notifyEmailAgent.systemInstruction,
@@ -10568,7 +10989,7 @@ function createChatHandlers(config) {
10568
10989
  const emailPlugin2 = cms.getPlugin("email");
10569
10990
  const convLeadSent = conv.leadEmailSentAt;
10570
10991
  const chatAgentFn = llm.chatAgent?.bind(llm);
10571
- console.info(chunk22FFHLTO_cjs.CHAT_EMAIL_LOG, "pipeline check", {
10992
+ console.info(chunkTCSGFAWB_cjs.CHAT_EMAIL_LOG, "pipeline check", {
10572
10993
  conversationId,
10573
10994
  enabled: emailTool.enabled,
10574
10995
  intentCount: emailTool.intents.length,
@@ -10582,27 +11003,27 @@ function createChatHandlers(config) {
10582
11003
  mergedPromptIncludesNotify: Boolean(notifyEmailAgent?.systemInstruction?.trim())
10583
11004
  });
10584
11005
  if (!emailTool.enabled) {
10585
- console.info(chunk22FFHLTO_cjs.CHAT_EMAIL_LOG, "pipeline skipped", {
11006
+ console.info(chunkTCSGFAWB_cjs.CHAT_EMAIL_LOG, "pipeline skipped", {
10586
11007
  conversationId,
10587
11008
  reason: "email_tool_disabled"
10588
11009
  });
10589
11010
  } else if (emailTool.intents.length === 0) {
10590
- console.info(chunk22FFHLTO_cjs.CHAT_EMAIL_LOG, "pipeline skipped", {
11011
+ console.info(chunkTCSGFAWB_cjs.CHAT_EMAIL_LOG, "pipeline skipped", {
10591
11012
  conversationId,
10592
11013
  reason: "no_intents_configured"
10593
11014
  });
10594
11015
  } else if (!emailPlugin2) {
10595
- console.info(chunk22FFHLTO_cjs.CHAT_EMAIL_LOG, "pipeline skipped", {
11016
+ console.info(chunkTCSGFAWB_cjs.CHAT_EMAIL_LOG, "pipeline skipped", {
10596
11017
  conversationId,
10597
11018
  reason: "email_plugin_missing"
10598
11019
  });
10599
11020
  } else if (!chatAgentFn) {
10600
- console.info(chunk22FFHLTO_cjs.CHAT_EMAIL_LOG, "pipeline skipped", {
11021
+ console.info(chunkTCSGFAWB_cjs.CHAT_EMAIL_LOG, "pipeline skipped", {
10601
11022
  conversationId,
10602
11023
  reason: "llm_chat_agent_unavailable"
10603
11024
  });
10604
11025
  } else if (convLeadSent) {
10605
- console.info(chunk22FFHLTO_cjs.CHAT_EMAIL_LOG, "pipeline skipped", {
11026
+ console.info(chunkTCSGFAWB_cjs.CHAT_EMAIL_LOG, "pipeline skipped", {
10606
11027
  conversationId,
10607
11028
  reason: "lead_email_already_sent",
10608
11029
  leadEmailSentAt: convLeadSent
@@ -10615,7 +11036,7 @@ function createChatHandlers(config) {
10615
11036
  }
10616
11037
  });
10617
11038
  if (!contactRow) {
10618
- console.warn(chunk22FFHLTO_cjs.CHAT_EMAIL_LOG, "pipeline skipped", {
11039
+ console.warn(chunkTCSGFAWB_cjs.CHAT_EMAIL_LOG, "pipeline skipped", {
10619
11040
  conversationId,
10620
11041
  reason: "contact_not_found",
10621
11042
  contactId
@@ -10623,7 +11044,7 @@ function createChatHandlers(config) {
10623
11044
  } else {
10624
11045
  const c = contactRow;
10625
11046
  try {
10626
- const intent = await chunk22FFHLTO_cjs.detectChatLeadIntent({
11047
+ const intent = await chunkTCSGFAWB_cjs.detectChatLeadIntent({
10627
11048
  chatAgent: chatAgentFn
10628
11049
  }, {
10629
11050
  settings: emailTool,
@@ -10637,15 +11058,15 @@ function createChatHandlers(config) {
10637
11058
  model: agentRow?.model?.trim() || notifyEmailAgent?.model?.trim() || void 0
10638
11059
  });
10639
11060
  if (!intent.intent || !intent.emailTo) {
10640
- console.info(chunk22FFHLTO_cjs.CHAT_EMAIL_LOG, "no email sent", {
11061
+ console.info(chunkTCSGFAWB_cjs.CHAT_EMAIL_LOG, "no email sent", {
10641
11062
  conversationId,
10642
11063
  reason: intent.intent ? "missing_email_to" : "no_intent_match",
10643
11064
  intentLabel: intent.intentLabel,
10644
11065
  classifierReason: intent.reason
10645
11066
  });
10646
11067
  } else {
10647
- const intentRecipients = chunk22FFHLTO_cjs.parseIntentRecipientEmails(intent.emailTo);
10648
- console.info(chunk22FFHLTO_cjs.CHAT_EMAIL_LOG, "intent matched \u2014 sending", {
11068
+ const intentRecipients = chunkTCSGFAWB_cjs.parseIntentRecipientEmails(intent.emailTo);
11069
+ console.info(chunkTCSGFAWB_cjs.CHAT_EMAIL_LOG, "intent matched \u2014 sending", {
10649
11070
  conversationId,
10650
11071
  intent: intent.intent,
10651
11072
  emailTo: intent.emailTo,
@@ -10654,8 +11075,8 @@ function createChatHandlers(config) {
10654
11075
  });
10655
11076
  const emailSettings = await loadSettingsGroupMap(dataSource, entityMap, "email");
10656
11077
  const brandingSettings = await loadSettingsGroupMap(dataSource, entityMap, "branding");
10657
- const companyDetails = chunk22FFHLTO_cjs.mergeEmailLayoutCompanyDetails(brandingSettings, emailSettings);
10658
- const leadResult = await chunk22FFHLTO_cjs.sendChatLeadEmail(cms, emailSettings, brandingSettings, {
11078
+ const companyDetails = chunkBXYZDMTZ_cjs.mergeEmailLayoutCompanyDetails(brandingSettings, emailSettings);
11079
+ const leadResult = await chunkTCSGFAWB_cjs.sendChatLeadEmail(cms, emailSettings, brandingSettings, {
10659
11080
  contactName: String(c.name ?? "").trim() || "Visitor",
10660
11081
  contactEmail: String(c.email ?? "").trim(),
10661
11082
  contactPhone: c.phone ?? null,
@@ -10663,7 +11084,7 @@ function createChatHandlers(config) {
10663
11084
  latestMessage: message,
10664
11085
  intentCode: intent.intent,
10665
11086
  intentReason: intent.intentLabel || intent.reason,
10666
- transcript: chunk22FFHLTO_cjs.buildTranscriptForLeadEmail(history, message),
11087
+ transcript: chunkTCSGFAWB_cjs.buildTranscriptForLeadEmail(history, message),
10667
11088
  companyDetails,
10668
11089
  recipients: intentRecipients.length > 0 ? intentRecipients : void 0
10669
11090
  });
@@ -10671,13 +11092,13 @@ function createChatHandlers(config) {
10671
11092
  await convRepo().update(conversationId, {
10672
11093
  leadEmailSentAt: /* @__PURE__ */ new Date()
10673
11094
  });
10674
- console.info(chunk22FFHLTO_cjs.CHAT_EMAIL_LOG, "lead email recorded", {
11095
+ console.info(chunkTCSGFAWB_cjs.CHAT_EMAIL_LOG, "lead email recorded", {
10675
11096
  conversationId,
10676
11097
  sent: true,
10677
11098
  recipients: leadResult.recipients
10678
11099
  });
10679
11100
  } else {
10680
- console.warn(chunk22FFHLTO_cjs.CHAT_EMAIL_LOG, "lead email not sent", {
11101
+ console.warn(chunkTCSGFAWB_cjs.CHAT_EMAIL_LOG, "lead email not sent", {
10681
11102
  conversationId,
10682
11103
  sent: false,
10683
11104
  error: leadResult.error ?? "unknown",
@@ -10686,7 +11107,7 @@ function createChatHandlers(config) {
10686
11107
  }
10687
11108
  }
10688
11109
  } catch (intentErr) {
10689
- console.warn(chunk22FFHLTO_cjs.CHAT_EMAIL_LOG, "pipeline error", {
11110
+ console.warn(chunkTCSGFAWB_cjs.CHAT_EMAIL_LOG, "pipeline error", {
10690
11111
  conversationId,
10691
11112
  error: intentErr instanceof Error ? intentErr.message : String(intentErr)
10692
11113
  });
@@ -10703,7 +11124,7 @@ function createChatHandlers(config) {
10703
11124
  });
10704
11125
  if (agentRow && llm.chatAgent) {
10705
11126
  const fromAgent = llmAgentToChatAgentOptions(agentRow);
10706
- const systemPrompt = chunk22FFHLTO_cjs.buildChatbotSystemPromptWithEmailTool(fromAgent.systemPrompt, parsedValidation.guardrailsForPrompt, emailTool);
11127
+ const systemPrompt = chunkTCSGFAWB_cjs.buildChatbotSystemPromptWithEmailTool(fromAgent.systemPrompt, parsedValidation.guardrailsForPrompt, emailTool);
10707
11128
  const res = await llm.chatAgent({
10708
11129
  ...fromAgent,
10709
11130
  systemPrompt: systemPrompt || void 0,
@@ -10724,7 +11145,7 @@ ${contextParts.join("\n\n")}` : "";
10724
11145
  agentRow.systemInstruction?.trim(),
10725
11146
  ragSystem
10726
11147
  ].filter(Boolean).join("\n\n");
10727
- systemContent = chunk22FFHLTO_cjs.buildChatbotSystemPromptWithEmailTool(mergedBase || defaultSystem, parsedValidation.guardrailsForPrompt, emailTool) || defaultSystem;
11148
+ systemContent = chunkTCSGFAWB_cjs.buildChatbotSystemPromptWithEmailTool(mergedBase || defaultSystem, parsedValidation.guardrailsForPrompt, emailTool) || defaultSystem;
10728
11149
  } else {
10729
11150
  systemContent = ragSystem || defaultSystem;
10730
11151
  }
@@ -12150,7 +12571,7 @@ function createAdminRolesHandlers(config) {
12150
12571
  }, {
12151
12572
  status: 401
12152
12573
  });
12153
- if (!chunkZMRQ72F6_cjs.canManageRoles(u) && !chunkX6UQFV4X_cjs.canManageVendorRoles(u)) {
12574
+ if (!chunkNBY4NVTY_cjs.canManageRoles(u) && !chunkX6UQFV4X_cjs.canManageVendorRoles(u)) {
12154
12575
  return json({
12155
12576
  error: "Forbidden"
12156
12577
  }, {
@@ -12161,7 +12582,7 @@ function createAdminRolesHandlers(config) {
12161
12582
  }
12162
12583
  chunkUSNT2KNT_cjs.__name(gate, "gate");
12163
12584
  function isVendorRolesOnly(u) {
12164
- return chunkX6UQFV4X_cjs.canManageVendorRoles(u) && !chunkZMRQ72F6_cjs.canManageRoles(u);
12585
+ return chunkX6UQFV4X_cjs.canManageVendorRoles(u) && !chunkNBY4NVTY_cjs.canManageRoles(u);
12165
12586
  }
12166
12587
  chunkUSNT2KNT_cjs.__name(isVendorRolesOnly, "isVendorRolesOnly");
12167
12588
  const vendorRoleEntities = [
@@ -12518,12 +12939,12 @@ async function sendVendorOnboardEmails(input, deps) {
12518
12939
  getSettingsGroup(deps, "branding"),
12519
12940
  getSettingsGroup(deps, "email")
12520
12941
  ]);
12521
- const companyDetails = chunk22FFHLTO_cjs.mergeEmailLayoutCompanyDetails(branding, emailSettings);
12522
- const notifyEmails = chunk22FFHLTO_cjs.parseEmailRecipientsFromConfig(emailSettings.salesTeamEmails ?? emailSettings.salesTeamEmail);
12942
+ const companyDetails = chunkBXYZDMTZ_cjs.mergeEmailLayoutCompanyDetails(branding, emailSettings);
12943
+ const notifyEmails = chunkTCSGFAWB_cjs.parseEmailRecipientsFromConfig(emailSettings.salesTeamEmails ?? emailSettings.salesTeamEmail);
12523
12944
  const sendToOwner = input.sendToOwner !== false;
12524
12945
  const ownerEmail = input.ownerEmail?.trim() || "";
12525
12946
  const cms = await deps.getCms();
12526
- await chunkXW5ATPRW_cjs.queueVendorOnboardEmails(cms, {
12947
+ await chunkV25RDUOU_cjs.queueVendorOnboardEmails(cms, {
12527
12948
  vendorName: input.vendorName,
12528
12949
  vendorSlug: input.vendorSlug,
12529
12950
  ownerName: input.ownerName,
@@ -12742,6 +13163,39 @@ function slugify(input) {
12742
13163
  return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
12743
13164
  }
12744
13165
  chunkUSNT2KNT_cjs.__name(slugify, "slugify");
13166
+ async function userHasActiveVendor(em, entityMap, userId) {
13167
+ const vendorRepo = em.getRepository(entityMap.vendors);
13168
+ const owned = await vendorRepo.findOne({
13169
+ where: {
13170
+ userId,
13171
+ deleted: false
13172
+ }
13173
+ });
13174
+ if (owned) return true;
13175
+ if (!entityMap.vendor_users) return false;
13176
+ const link = await em.getRepository(entityMap.vendor_users).createQueryBuilder("vu").innerJoin("vendors", "v", 'v.id = vu."vendorId" AND v.deleted = false').where('vu."userId" = :userId', {
13177
+ userId
13178
+ }).getOne();
13179
+ return !!link;
13180
+ }
13181
+ chunkUSNT2KNT_cjs.__name(userHasActiveVendor, "userHasActiveVendor");
13182
+ async function userHadAnyVendor(em, entityMap, userId) {
13183
+ const vendorRepo = em.getRepository(entityMap.vendors);
13184
+ const owned = await vendorRepo.findOne({
13185
+ where: {
13186
+ userId
13187
+ }
13188
+ });
13189
+ if (owned) return true;
13190
+ if (!entityMap.vendor_users) return false;
13191
+ const link = await em.getRepository(entityMap.vendor_users).findOne({
13192
+ where: {
13193
+ userId
13194
+ }
13195
+ });
13196
+ return !!link;
13197
+ }
13198
+ chunkUSNT2KNT_cjs.__name(userHadAnyVendor, "userHadAnyVendor");
12745
13199
  function vendorOnboardErrorResponse(json, err) {
12746
13200
  const msg = err instanceof Error ? err.message : String(err);
12747
13201
  console.error("[vendor-onboard]", err);
@@ -12840,14 +13294,80 @@ function createVendorOnboardHandlers(config) {
12840
13294
  chunkUSNT2KNT_cjs.__name(gateAdmin, "gateAdmin");
12841
13295
  async function resolveActiveVendorId(u) {
12842
13296
  const scope = chunkX6UQFV4X_cjs.resolveVendorScopeFromSessionUser(u);
12843
- if (scope.type === "vendor") return scope.vendorId;
12844
- if (scope.type === "all") {
12845
- const id = u.activeVendorId ?? u.vendorIds?.[0];
12846
- return id != null && Number.isFinite(id) ? id : null;
13297
+ const candidates = [];
13298
+ if (scope.type === "vendor") candidates.push(scope.vendorId);
13299
+ if (scope.type === "all" || scope.type === "vendor") {
13300
+ const preferred = u.activeVendorId ?? u.vendorIds?.[0];
13301
+ if (preferred != null && Number.isFinite(preferred)) candidates.push(Number(preferred));
13302
+ for (const id of u.vendorIds ?? []) {
13303
+ if (Number.isFinite(id)) candidates.push(Number(id));
13304
+ }
13305
+ }
13306
+ if (entityMap.vendors && candidates.length > 0) {
13307
+ const unique = [
13308
+ ...new Set(candidates.filter((id) => id > 0))
13309
+ ];
13310
+ for (const id of unique) {
13311
+ try {
13312
+ const row = await dataSource.getRepository(entityMap.vendors).findOne({
13313
+ where: {
13314
+ id,
13315
+ deleted: false
13316
+ }
13317
+ });
13318
+ if (row) return id;
13319
+ } catch {
13320
+ }
13321
+ }
13322
+ }
13323
+ const uid = u.id != null ? Number(u.id) : NaN;
13324
+ if (Number.isFinite(uid) && entityMap.vendors) {
13325
+ try {
13326
+ const owned = await dataSource.getRepository(entityMap.vendors).findOne({
13327
+ where: {
13328
+ userId: uid,
13329
+ deleted: false
13330
+ },
13331
+ order: {
13332
+ id: "ASC"
13333
+ }
13334
+ });
13335
+ const vid = owned ? Number(owned.id) : NaN;
13336
+ if (Number.isFinite(vid) && vid > 0) return vid;
13337
+ } catch {
13338
+ }
12847
13339
  }
12848
13340
  return null;
12849
13341
  }
12850
13342
  chunkUSNT2KNT_cjs.__name(resolveActiveVendorId, "resolveActiveVendorId");
13343
+ async function gateVendorPortal() {
13344
+ const u = await getSessionUser();
13345
+ if (!u?.email) return json({
13346
+ error: "Unauthorized"
13347
+ }, {
13348
+ status: 401
13349
+ });
13350
+ if (!chunkX6UQFV4X_cjs.isVendorPortalUser(u) && !chunkX6UQFV4X_cjs.isPlatformAdministrator(u)) {
13351
+ return json({
13352
+ error: "Forbidden"
13353
+ }, {
13354
+ status: 403
13355
+ });
13356
+ }
13357
+ const vendorId = await resolveActiveVendorId(u);
13358
+ if (vendorId == null) {
13359
+ return json({
13360
+ error: "No vendor is linked to your account."
13361
+ }, {
13362
+ status: 400
13363
+ });
13364
+ }
13365
+ return {
13366
+ user: u,
13367
+ vendorId
13368
+ };
13369
+ }
13370
+ chunkUSNT2KNT_cjs.__name(gateVendorPortal, "gateVendorPortal");
12851
13371
  async function gateVendorTeam() {
12852
13372
  const u = await getSessionUser();
12853
13373
  if (!u?.email) return json({
@@ -12901,6 +13421,40 @@ function createVendorOnboardHandlers(config) {
12901
13421
  }
12902
13422
  chunkUSNT2KNT_cjs.__name(trySendVendorOnboardEmails, "trySendVendorOnboardEmails");
12903
13423
  return {
13424
+ /** GET /api/admin/vendor/profile — current user's store (server-resolved vendor id). */
13425
+ async getProfile() {
13426
+ const gated = await gateVendorPortal();
13427
+ if (gated instanceof Response) return gated;
13428
+ const { vendorId } = gated;
13429
+ if (!entityMap.vendors) {
13430
+ return json({
13431
+ error: "Vendors not configured"
13432
+ }, {
13433
+ status: 500
13434
+ });
13435
+ }
13436
+ try {
13437
+ const row = await dataSource.getRepository(entityMap.vendors).findOne({
13438
+ where: {
13439
+ id: vendorId,
13440
+ deleted: false
13441
+ }
13442
+ });
13443
+ if (!row) return json({
13444
+ error: "Vendor not found"
13445
+ }, {
13446
+ status: 404
13447
+ });
13448
+ return json(row);
13449
+ } catch (e) {
13450
+ console.error("[vendor.profile]", e);
13451
+ return json({
13452
+ error: "Failed to load vendor profile"
13453
+ }, {
13454
+ status: 500
13455
+ });
13456
+ }
13457
+ },
12904
13458
  /** POST /api/admin/vendors/onboard — create vendor + owner user + vendor_users + invite */
12905
13459
  async onboard(req) {
12906
13460
  const err = await gateAdmin();
@@ -12969,6 +13523,7 @@ function createVendorOnboardHandlers(config) {
12969
13523
  }
12970
13524
  });
12971
13525
  if (dupVendor) throw new Error("VENDOR_SLUG_EXISTS");
13526
+ await chunkOY2YTBMP_cjs.retireSoftDeletedUniqueValue(vendorRepo, "slug", slug);
12972
13527
  let ownerGroup = await groupRepo.findOne({
12973
13528
  where: {
12974
13529
  name: chunkX6UQFV4X_cjs.VENDOR_OWNER_GROUP_NAME,
@@ -12997,8 +13552,15 @@ function createVendorOnboardHandlers(config) {
12997
13552
  email: userEmail
12998
13553
  }
12999
13554
  });
13000
- if (existingUser && !existingUser.deleted) throw new Error("USER_EMAIL_EXISTS");
13001
- const newUser = existingUser?.deleted ? await (async () => {
13555
+ if (existingUser && !existingUser.deleted) {
13556
+ if (await userHasActiveVendor(em, entityMap, existingUser.id)) {
13557
+ throw new Error("USER_EMAIL_EXISTS");
13558
+ }
13559
+ if (!await userHadAnyVendor(em, entityMap, existingUser.id)) {
13560
+ throw new Error("USER_EMAIL_EXISTS");
13561
+ }
13562
+ }
13563
+ const newUser = existingUser ? await (async () => {
13002
13564
  await userRepo.update(existingUser.id, {
13003
13565
  deleted: false,
13004
13566
  deletedAt: null,
@@ -14365,6 +14927,13 @@ exports.User = class User {
14365
14927
  phoneVerifiedAt;
14366
14928
  emailVerifiedAt;
14367
14929
  password;
14930
+ /**
14931
+ * `pending` = invite outstanding (cannot sign in until password is set).
14932
+ * `active` = invite accepted / account usable (subject to `blocked`).
14933
+ */
14934
+ inviteStatus;
14935
+ /** Rotating invite token; null when active or no invite outstanding. */
14936
+ inviteToken;
14368
14937
  blocked;
14369
14938
  adminAccess;
14370
14939
  groupId;
@@ -14417,6 +14986,24 @@ _ts_decorate4([
14417
14986
  }),
14418
14987
  _ts_metadata4("design:type", Object)
14419
14988
  ], exports.User.prototype, "password", void 0);
14989
+ _ts_decorate4([
14990
+ typeorm.Column({
14991
+ type: "enum",
14992
+ enum: [
14993
+ "pending",
14994
+ "active"
14995
+ ],
14996
+ enumName: "users_invitestatus_enum",
14997
+ default: "active"
14998
+ }),
14999
+ _ts_metadata4("design:type", typeof UserInviteStatus === "undefined" ? Object : UserInviteStatus)
15000
+ ], exports.User.prototype, "inviteStatus", void 0);
15001
+ _ts_decorate4([
15002
+ typeorm.Column("varchar", {
15003
+ nullable: true
15004
+ }),
15005
+ _ts_metadata4("design:type", Object)
15006
+ ], exports.User.prototype, "inviteToken", void 0);
14420
15007
  _ts_decorate4([
14421
15008
  typeorm.Column("boolean", {
14422
15009
  default: false
@@ -16081,10 +16668,12 @@ exports.Customer = class Customer {
16081
16668
  chunkUSNT2KNT_cjs.__name(this, "Customer");
16082
16669
  }
16083
16670
  id;
16671
+ /** Set only when the customer can log in (linked `users` row). Admin/guest customers stay null. */
16084
16672
  userId;
16085
16673
  user;
16086
16674
  name;
16087
16675
  email;
16676
+ /** Optional; multiple customers may have null (PostgreSQL UNIQUE allows multiple NULLs). */
16088
16677
  phone;
16089
16678
  createdAt;
16090
16679
  updatedAt;
@@ -16099,8 +16688,10 @@ _ts_decorate18([
16099
16688
  _ts_metadata18("design:type", Number)
16100
16689
  ], exports.Customer.prototype, "id", void 0);
16101
16690
  _ts_decorate18([
16102
- typeorm.Column("int"),
16103
- _ts_metadata18("design:type", Number)
16691
+ typeorm.Column("int", {
16692
+ nullable: true
16693
+ }),
16694
+ _ts_metadata18("design:type", Object)
16104
16695
  ], exports.Customer.prototype, "userId", void 0);
16105
16696
  _ts_decorate18([
16106
16697
  typeorm.ManyToOne(() => exports.User, {
@@ -16123,9 +16714,10 @@ _ts_decorate18([
16123
16714
  ], exports.Customer.prototype, "email", void 0);
16124
16715
  _ts_decorate18([
16125
16716
  typeorm.Column("varchar", {
16126
- unique: true
16717
+ unique: true,
16718
+ nullable: true
16127
16719
  }),
16128
- _ts_metadata18("design:type", String)
16720
+ _ts_metadata18("design:type", Object)
16129
16721
  ], exports.Customer.prototype, "phone", void 0);
16130
16722
  _ts_decorate18([
16131
16723
  typeorm.Column({
@@ -19145,6 +19737,8 @@ exports.OrderItem = class OrderItem {
19145
19737
  id;
19146
19738
  orderId;
19147
19739
  productId;
19740
+ /** Selected product variant at order time (optional). */
19741
+ variantId;
19148
19742
  discountIds;
19149
19743
  vendorId;
19150
19744
  vendor;
@@ -19180,6 +19774,12 @@ _ts_decorate39([
19180
19774
  typeorm.Column("int"),
19181
19775
  _ts_metadata39("design:type", Number)
19182
19776
  ], exports.OrderItem.prototype, "productId", void 0);
19777
+ _ts_decorate39([
19778
+ typeorm.Column("int", {
19779
+ nullable: true
19780
+ }),
19781
+ _ts_metadata39("design:type", Object)
19782
+ ], exports.OrderItem.prototype, "variantId", void 0);
19183
19783
  _ts_decorate39([
19184
19784
  typeorm.Column({
19185
19785
  type: "int",
@@ -19989,6 +20589,7 @@ var Type = /* @__PURE__ */ (function(Type2) {
19989
20589
  Type2["CATEGORY"] = "category";
19990
20590
  Type2["CART"] = "cart";
19991
20591
  Type2["PAYMENT_METHOD"] = "paymentMethod";
20592
+ Type2["DEVICE_TYPE"] = "deviceType";
19992
20593
  Type2["ORDER"] = "order";
19993
20594
  Type2["QUANTITY"] = "quantity";
19994
20595
  Type2["TIME"] = "time";
@@ -22011,6 +22612,7 @@ exports.Combo = class Combo {
22011
22612
  vendorId;
22012
22613
  vendor;
22013
22614
  name;
22615
+ slug;
22014
22616
  desc;
22015
22617
  eventId;
22016
22618
  event;
@@ -22054,6 +22656,12 @@ _ts_decorate63([
22054
22656
  }),
22055
22657
  _ts_metadata63("design:type", String)
22056
22658
  ], exports.Combo.prototype, "name", void 0);
22659
+ _ts_decorate63([
22660
+ typeorm.Column({
22661
+ type: "varchar"
22662
+ }),
22663
+ _ts_metadata63("design:type", String)
22664
+ ], exports.Combo.prototype, "slug", void 0);
22057
22665
  _ts_decorate63([
22058
22666
  typeorm.Column({
22059
22667
  type: "text",
@@ -22191,7 +22799,11 @@ _ts_decorate63([
22191
22799
  _ts_metadata63("design:type", Number)
22192
22800
  ], exports.Combo.prototype, "deletedBy", void 0);
22193
22801
  exports.Combo = _ts_decorate63([
22194
- typeorm.Entity("combos")
22802
+ typeorm.Entity("combos"),
22803
+ typeorm.Unique("UQ_combos_vendor_slug", [
22804
+ "vendorId",
22805
+ "slug"
22806
+ ])
22195
22807
  ], exports.Combo);
22196
22808
  function _ts_decorate64(decorators, target, key, desc) {
22197
22809
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -26036,16 +26648,16 @@ function whatsappPlugin(config = {}) {
26036
26648
  db = await getWhatsAppSettings?.() ?? {};
26037
26649
  } catch {
26038
26650
  }
26039
- if (!chunk22FFHLTO_cjs.isWhatsAppPluginEnabled(db)) {
26651
+ if (!chunk7PMHZRF3_cjs.isWhatsAppPluginEnabled(db)) {
26040
26652
  context.logger.warn("WhatsApp plugin skipped: disabled in Plugins \u2192 WhatsApp");
26041
26653
  return null;
26042
26654
  }
26043
- const merged = chunk22FFHLTO_cjs.mergeWhatsAppConfigLayers(env, db, staticRest);
26044
- if (!getWhatsAppSettings && !chunk22FFHLTO_cjs.whatsAppConfigured(merged)) {
26655
+ const merged = chunk7PMHZRF3_cjs.mergeWhatsAppConfigLayers(env, db, staticRest);
26656
+ if (!getWhatsAppSettings && !chunk7PMHZRF3_cjs.whatsAppConfigured(merged)) {
26045
26657
  context.logger.warn("WhatsApp plugin skipped: set WHATSAPP_ACCESS_TOKEN and WHATSAPP_PHONE_NUMBER_ID, or pass getWhatsAppSettings");
26046
26658
  return null;
26047
26659
  }
26048
- const svc = new chunk22FFHLTO_cjs.WhatsAppService(env, staticRest, getWhatsAppSettings, getMessageTemplateRow);
26660
+ const svc = new chunk7PMHZRF3_cjs.WhatsAppService(env, staticRest, getWhatsAppSettings, getMessageTemplateRow);
26049
26661
  return {
26050
26662
  send: /* @__PURE__ */ chunkUSNT2KNT_cjs.__name((opts) => svc.send(opts), "send")
26051
26663
  };
@@ -26117,9 +26729,12 @@ function createMessageTemplateRowLoader(dataSource, entityMap) {
26117
26729
  };
26118
26730
  }
26119
26731
  chunkUSNT2KNT_cjs.__name(createMessageTemplateRowLoader, "createMessageTemplateRowLoader");
26120
- function registerMessagingQueueProcessors(cms) {
26121
- chunkXW5ATPRW_cjs.registerEmailQueueProcessor(cms);
26122
- chunk22FFHLTO_cjs.registerWhatsAppQueueProcessor(cms);
26732
+ function registerMessagingQueueProcessors(cms, entityMap) {
26733
+ chunkV25RDUOU_cjs.registerEmailQueueProcessor(cms, {
26734
+ getDataSource: /* @__PURE__ */ chunkUSNT2KNT_cjs.__name(async () => cms.dataSource, "getDataSource"),
26735
+ entityMap
26736
+ });
26737
+ chunk7PMHZRF3_cjs.registerWhatsAppQueueProcessor(cms);
26123
26738
  }
26124
26739
  chunkUSNT2KNT_cjs.__name(registerMessagingQueueProcessors, "registerMessagingQueueProcessors");
26125
26740
  async function ensureMessagingPluginsOnCms(base, options) {
@@ -26148,7 +26763,7 @@ async function ensureMessagingPluginsOnCms(base, options) {
26148
26763
  }
26149
26764
  if (!cms.getPlugin("email")) {
26150
26765
  try {
26151
- const instance = await chunk22FFHLTO_cjs.emailPlugin({
26766
+ const instance = await chunkTCSGFAWB_cjs.emailPlugin({
26152
26767
  type: "SMTP",
26153
26768
  from: config.SMTP_FROM ?? "no-reply@localhost",
26154
26769
  to: config.SMTP_TO ?? ""
@@ -26170,7 +26785,7 @@ async function ensureMessagingPluginsOnCms(base, options) {
26170
26785
  }
26171
26786
  }
26172
26787
  const merged = Object.keys(overlay).length > 0 ? overlayCmsPlugins(cms, overlay) : cms;
26173
- registerMessagingQueueProcessors(merged);
26788
+ registerMessagingQueueProcessors(merged, entityMap);
26174
26789
  return merged;
26175
26790
  }
26176
26791
  chunkUSNT2KNT_cjs.__name(ensureMessagingPluginsOnCms, "ensureMessagingPluginsOnCms");
@@ -26189,7 +26804,7 @@ function messagingPlugins(options) {
26189
26804
  const config = options.config ?? (typeof process !== "undefined" ? process.env : {});
26190
26805
  return [
26191
26806
  queuePlugin(),
26192
- chunk22FFHLTO_cjs.emailPlugin({
26807
+ chunkTCSGFAWB_cjs.emailPlugin({
26193
26808
  type: "SMTP",
26194
26809
  from: config.SMTP_FROM ?? "no-reply@localhost",
26195
26810
  to: config.SMTP_TO ?? ""
@@ -26219,7 +26834,7 @@ async function createCmsAppWithMessaging(options) {
26219
26834
  logger,
26220
26835
  plugins: mergedPlugins
26221
26836
  });
26222
- registerMessagingQueueProcessors(cms);
26837
+ registerMessagingQueueProcessors(cms, entityMap);
26223
26838
  return cms;
26224
26839
  }
26225
26840
  chunkUSNT2KNT_cjs.__name(createCmsAppWithMessaging, "createCmsAppWithMessaging");
@@ -26323,7 +26938,7 @@ function createCmsApiHandler(config) {
26323
26938
  } : void 0;
26324
26939
  const entityMap = withLlmKnowledgeEntityFallbacks(rawEntityMap);
26325
26940
  if (getCms) {
26326
- chunk22FFHLTO_cjs.initWhatsappTriggerDispatcher({
26941
+ chunk7PMHZRF3_cjs.initWhatsappTriggerDispatcher({
26327
26942
  dataSource,
26328
26943
  entityMap,
26329
26944
  getCms
@@ -26356,7 +26971,7 @@ function createCmsApiHandler(config) {
26356
26971
  status: 401
26357
26972
  });
26358
26973
  }
26359
- const allowed = chunkZMRQ72F6_cjs.sessionHasEntityAccess(u, entity, action);
26974
+ const allowed = chunkNBY4NVTY_cjs.sessionHasEntityAccess(u, entity, action);
26360
26975
  await chunkX6UQFV4X_cjs.logEntityAccessDecision(dataSource, "cmsApi.requireEntityPermission", u, entity, action, {
26361
26976
  allowed,
26362
26977
  resolvePath: "hydrated_session"
@@ -26416,7 +27031,7 @@ function createCmsApiHandler(config) {
26416
27031
  companyDetails
26417
27032
  };
26418
27033
  if (queue) {
26419
- const { queueEmail: queueEmail2 } = await import('./email-queue-TPZWLTIV.cjs');
27034
+ const { queueEmail: queueEmail2 } = await import('./email-queue-6NJY6HNM.cjs');
26420
27035
  await queueEmail2(cms, {
26421
27036
  to: opts.to,
26422
27037
  templateName: "passwordReset",
@@ -26709,6 +27324,14 @@ function createCmsApiHandler(config) {
26709
27324
  });
26710
27325
  return vendorHandlers.switchVendor(req);
26711
27326
  }
27327
+ if (path2[0] === "admin" && path2[1] === "vendor" && path2[2] === "profile" && path2.length === 3 && m === "GET") {
27328
+ if (!vendorHandlers) return config.json({
27329
+ error: "Not found"
27330
+ }, {
27331
+ status: 404
27332
+ });
27333
+ return vendorHandlers.getProfile();
27334
+ }
26712
27335
  if (path2[0] === "admin" && path2[1] === "vendor" && path2[2] === "roles" && vendorRolesHandlers) {
26713
27336
  if (path2.length === 3 && m === "GET") return vendorRolesHandlers.list();
26714
27337
  if (path2.length === 3 && m === "POST") return vendorRolesHandlers.create(req);
@@ -27662,14 +28285,27 @@ function createCmsApiHandler(config) {
27662
28285
  const pe = await requireEntityPermissionEffective(req, "orders", "read");
27663
28286
  if (pe) return pe;
27664
28287
  const cms = await getCms();
27665
- const { streamOrderInvoicePdf: streamOrderInvoicePdf2 } = await import('./erp-order-invoice-ZR5F2KTA.cjs');
28288
+ const { streamOrderInvoicePdf: streamOrderInvoicePdf2 } = await import('./erp-order-invoice-EYYNR526.cjs');
27666
28289
  const oid = Number(path2[1]);
27667
28290
  if (!Number.isFinite(oid)) return config.json({
27668
28291
  error: "Invalid id"
27669
28292
  }, {
27670
28293
  status: 400
27671
28294
  });
27672
- return streamOrderInvoicePdf2(cms, dataSource, entityMap, oid, {});
28295
+ try {
28296
+ return await streamOrderInvoicePdf2(cms, dataSource, entityMap, oid, {});
28297
+ } catch (err) {
28298
+ const msg = err instanceof Error ? err.message : String(err);
28299
+ console.error("[invoice] route failed", {
28300
+ orderId: oid,
28301
+ error: msg
28302
+ });
28303
+ return config.json({
28304
+ error: `Invoice failed: ${msg}`
28305
+ }, {
28306
+ status: 500
28307
+ });
28308
+ }
27673
28309
  }
27674
28310
  if (path2[0] === "orders" && path2.length === 3 && path2[2] === "repost-erp" && getCms) {
27675
28311
  const a = await config.requireAuth(req);
@@ -28016,7 +28652,7 @@ function createCmsApiHandler(config) {
28016
28652
  status: 400
28017
28653
  });
28018
28654
  }
28019
- const { resendOrderNotification } = await import('./order-notification-dispatcher-RU7BTGGX.cjs');
28655
+ const { resendOrderNotification } = await import('./order-notification-dispatcher-SBQAMM5V.cjs');
28020
28656
  const result = await resendOrderNotification(triggerKey, orderId, {
28021
28657
  dataSource,
28022
28658
  entityMap,
@@ -28088,6 +28724,30 @@ function createCmsApiHandler(config) {
28088
28724
  paymentId = Number(payment.id);
28089
28725
  }
28090
28726
  }
28727
+ const orderWithItems = await orderRepo.findOne({
28728
+ where: {
28729
+ id: orderId,
28730
+ deleted: false
28731
+ },
28732
+ relations: [
28733
+ "items"
28734
+ ]
28735
+ });
28736
+ if (orderWithItems) {
28737
+ const { reconcileOrderInventoryBetweenSnapshots: reconcileOrderInventoryBetweenSnapshots2, orderInventorySnapshotFromRow: orderInventorySnapshotFromRow2 } = await import('./order-inventory-2MYRKEPC.cjs');
28738
+ const prevSnap = orderInventorySnapshotFromRow2(orderWithItems);
28739
+ const invErr = await reconcileOrderInventoryBetweenSnapshots2(dataSource, entityMap, prevSnap, {
28740
+ status: "cancelled",
28741
+ lines: prevSnap.lines
28742
+ });
28743
+ if (invErr) {
28744
+ return config.json({
28745
+ error: invErr
28746
+ }, {
28747
+ status: 400
28748
+ });
28749
+ }
28750
+ }
28091
28751
  await orderRepo.update(orderId, {
28092
28752
  status: "cancelled"
28093
28753
  });
@@ -28747,12 +29407,12 @@ async function refreshOrderFromErp(cms, dataSource, entityMap, submission, order
28747
29407
  const r1 = await submission.postErpReadAction("get-order-status", {
28748
29408
  platformOrderId: refId
28749
29409
  });
28750
- const d1 = r1.ok ? chunkPNOKNSG2_cjs.unwrapErpReadData(r1.json) : null;
29410
+ const d1 = r1.ok ? chunkBXYZDMTZ_cjs.unwrapErpReadData(r1.json) : null;
28751
29411
  if (d1) {
28752
29412
  const mapped = mapErpSaleStatusToOrderStatus(typeof d1.status === "string" ? d1.status : typeof d1.orderStatus === "string" ? d1.orderStatus : typeof d1.state === "string" ? d1.state : void 0);
28753
29413
  if (mapped) newStatus = mapped;
28754
- fulfillment = chunkPNOKNSG2_cjs.mapErpPayloadToFulfillment(d1);
28755
- const refs = chunkPNOKNSG2_cjs.extractChildOrderRefsFromSalePayload(d1);
29414
+ fulfillment = chunkBXYZDMTZ_cjs.mapErpPayloadToFulfillment(d1);
29415
+ const refs = chunkBXYZDMTZ_cjs.extractChildOrderRefsFromSalePayload(d1);
28756
29416
  if (refs.length) {
28757
29417
  await ensureChildOrdersFromRefs(orderRepo, order, refs, order.contactId, String(order.currency || "INR"));
28758
29418
  }
@@ -28760,16 +29420,16 @@ async function refreshOrderFromErp(cms, dataSource, entityMap, submission, order
28760
29420
  const r2 = await submission.postErpReadAction("get-fulfillment-status", {
28761
29421
  platformOrderId: refId
28762
29422
  });
28763
- const d2 = r2.ok ? chunkPNOKNSG2_cjs.unwrapErpReadData(r2.json) : null;
29423
+ const d2 = r2.ok ? chunkBXYZDMTZ_cjs.unwrapErpReadData(r2.json) : null;
28764
29424
  if (d2) {
28765
- fulfillment = deepMergeFulfillment(fulfillment, chunkPNOKNSG2_cjs.mapErpPayloadToFulfillment(d2));
29425
+ fulfillment = deepMergeFulfillment(fulfillment, chunkBXYZDMTZ_cjs.mapErpPayloadToFulfillment(d2));
28766
29426
  }
28767
29427
  const r3 = await submission.postErpReadAction("get-invoice", {
28768
29428
  platformOrderId: refId
28769
29429
  });
28770
- const d3 = r3.ok ? chunkPNOKNSG2_cjs.unwrapErpReadData(r3.json) : null;
29430
+ const d3 = r3.ok ? chunkBXYZDMTZ_cjs.unwrapErpReadData(r3.json) : null;
28771
29431
  if (d3) {
28772
- invoiceNumber = chunkPNOKNSG2_cjs.mapErpPayloadToInvoiceNumber(d3);
29432
+ invoiceNumber = chunkBXYZDMTZ_cjs.mapErpPayloadToInvoiceNumber(d3);
28773
29433
  invoiceId = pickInvoiceId(d3);
28774
29434
  }
28775
29435
  const oid = order.id;
@@ -28805,10 +29465,10 @@ async function refreshOrderFromErp(cms, dataSource, entityMap, submission, order
28805
29465
  const r = await submission.postErpReadAction("get-return-status", {
28806
29466
  platformReturnId
28807
29467
  });
28808
- const d = r.ok ? chunkPNOKNSG2_cjs.unwrapErpReadData(r.json) : null;
29468
+ const d = r.ok ? chunkBXYZDMTZ_cjs.unwrapErpReadData(r.json) : null;
28809
29469
  if (!d) return;
28810
29470
  const mapped = mapErpSaleStatusToOrderStatus(typeof d.status === "string" ? d.status : typeof d.returnStatus === "string" ? d.returnStatus : void 0);
28811
- const fulfillment = chunkPNOKNSG2_cjs.mapErpPayloadToFulfillment(d);
29471
+ const fulfillment = chunkBXYZDMTZ_cjs.mapErpPayloadToFulfillment(d);
28812
29472
  const patch = {};
28813
29473
  if (fulfillment !== void 0) patch.fulfillment = fulfillment;
28814
29474
  const nextMeta = Object.keys(patch).length ? mergeOrderMetadataPatch(meta, patch) : meta;
@@ -29131,6 +29791,14 @@ function createStorefrontApiHandler(config) {
29131
29791
  entityMap
29132
29792
  });
29133
29793
  }).catch((err) => console.error("[notification-triggers] import failed", err));
29794
+ if (!getCms) return;
29795
+ void import('./send-order-placed-emails-B5ZVJT7T.cjs').then(({ sendOrderPlacedEmailsAfterConfirmation }) => {
29796
+ return sendOrderPlacedEmailsAfterConfirmation(orderId, {
29797
+ getDataSource: /* @__PURE__ */ chunkUSNT2KNT_cjs.__name(async () => dataSource, "getDataSource"),
29798
+ entityMap,
29799
+ getCms
29800
+ });
29801
+ }).catch((err) => console.error("[order-emails] sendOrderPlacedEmailsAfterConfirmation failed", err));
29134
29802
  }
29135
29803
  chunkUSNT2KNT_cjs.__name(fireOrderPlacedNotification, "fireOrderPlacedNotification");
29136
29804
  const otpAllowPhoneLogin = config.otpAllowPhoneLogin !== false;
@@ -29178,13 +29846,36 @@ function createStorefrontApiHandler(config) {
29178
29846
  status: 400
29179
29847
  };
29180
29848
  }
29849
+ const list = [
29850
+ ...vendorIds
29851
+ ];
29181
29852
  return {
29182
- vendorId: [
29183
- ...vendorIds
29184
- ][0]
29853
+ vendorId: list[0],
29854
+ vendorIds: list
29185
29855
  };
29186
29856
  }
29187
29857
  chunkUSNT2KNT_cjs.__name(resolveSingleVendorIdFromCart, "resolveSingleVendorIdFromCart");
29858
+ async function linkOrderContactToVendors(contactId, vendorIds) {
29859
+ try {
29860
+ const contact = await contactRepo().findOne({
29861
+ where: {
29862
+ id: contactId,
29863
+ deleted: false
29864
+ }
29865
+ });
29866
+ if (!contact) return;
29867
+ const email = String(contact.email ?? "").trim().toLowerCase();
29868
+ if (!email) return;
29869
+ await ensureVendorCustomersForOrder(dataSource, entityMap, vendorIds, contactId, {
29870
+ name: String(contact.name ?? "").trim() || email.split("@")[0] || "Customer",
29871
+ email,
29872
+ phone: contact.phone ?? null
29873
+ });
29874
+ } catch (err) {
29875
+ console.error("[storefront] vendor_customers link failed", err);
29876
+ }
29877
+ }
29878
+ chunkUSNT2KNT_cjs.__name(linkOrderContactToVendors, "linkOrderContactToVendors");
29188
29879
  function roundMoney3(n) {
29189
29880
  return Math.round(n * 100) / 100;
29190
29881
  }
@@ -30709,7 +31400,7 @@ function createStorefrontApiHandler(config) {
30709
31400
  status: 503
30710
31401
  });
30711
31402
  const companyDetails = getCompanyDetails ? await getCompanyDetails() : {};
30712
- await chunkXW5ATPRW_cjs.queueEmail(cms, {
31403
+ await chunkV25RDUOU_cjs.queueEmail(cms, {
30713
31404
  to: identifier,
30714
31405
  templateName: "otp",
30715
31406
  ctx: {
@@ -30876,9 +31567,12 @@ function createStorefrontApiHandler(config) {
30876
31567
  }, {
30877
31568
  status: 400
30878
31569
  });
31570
+ const { retireSoftDeletedUniqueValue: retireSoftDeletedUniqueValue2 } = await import('./retire-soft-deleted-unique-5VXBA5XX.cjs');
31571
+ await retireSoftDeletedUniqueValue2(userRepo(), "email", email);
30879
31572
  const existing = await userRepo().findOne({
30880
31573
  where: {
30881
- email
31574
+ email,
31575
+ deleted: false
30882
31576
  }
30883
31577
  });
30884
31578
  if (existing) return json({
@@ -30931,7 +31625,7 @@ function createStorefrontApiHandler(config) {
30931
31625
  const companyDetails = getCompanyDetails ? await getCompanyDetails() : {};
30932
31626
  const base = (publicSiteUrl || "").replace(/\/$/, "").trim() || "http://localhost:3000";
30933
31627
  const verifyEmailUrl = `${base}/verify-email?token=${encodeURIComponent(rawToken)}`;
30934
- await chunkXW5ATPRW_cjs.queueEmail(cms, {
31628
+ await chunkV25RDUOU_cjs.queueEmail(cms, {
30935
31629
  to: email,
30936
31630
  templateName: "signup",
30937
31631
  ctx: {
@@ -31506,6 +32200,7 @@ function createStorefrontApiHandler(config) {
31506
32200
  taxCode: line.taxCode
31507
32201
  }));
31508
32202
  }
32203
+ await linkOrderContactToVendors(contactId, vendorRes.vendorIds);
31509
32204
  fireOrderPlacedNotification(oid);
31510
32205
  return json({
31511
32206
  orderId: oid,
@@ -31578,6 +32273,7 @@ function createStorefrontApiHandler(config) {
31578
32273
  taxCode: line.taxCode
31579
32274
  }));
31580
32275
  }
32276
+ await linkOrderContactToVendors(contactId, vendorResChk.vendorIds);
31581
32277
  await cartItemRepo().delete({
31582
32278
  cartId: cart.id
31583
32279
  });
@@ -31687,7 +32383,7 @@ function createStorefrontApiHandler(config) {
31687
32383
  status: 400
31688
32384
  });
31689
32385
  const cms = await getCms();
31690
- return chunkPNOKNSG2_cjs.streamOrderInvoicePdf(cms, dataSource, entityMap, orderId, {
32386
+ return chunkBXYZDMTZ_cjs.streamOrderInvoicePdf(cms, dataSource, entityMap, orderId, {
31691
32387
  ownerContactId: contact.id
31692
32388
  });
31693
32389
  }
@@ -31757,6 +32453,7 @@ function createStorefrontApiHandler(config) {
31757
32453
  unitPrice: line.unitPrice,
31758
32454
  tax: line.tax,
31759
32455
  total: line.total,
32456
+ metadata: line.metadata ?? null,
31760
32457
  product: p ? {
31761
32458
  name: p.name,
31762
32459
  slug: p.slug,
@@ -31849,12 +32546,17 @@ exports.assertProductApprovalUpdate = assertProductApprovalUpdate;
31849
32546
  exports.buildBlogMetadataUserPrompt = buildBlogMetadataUserPrompt;
31850
32547
  exports.buildCronFromSchedule = buildCronFromSchedule;
31851
32548
  exports.buildRssUserPromptFromFeeds = buildRssUserPromptFromFeeds;
32549
+ exports.buildUserInviteLink = buildUserInviteLink;
31852
32550
  exports.buildVendorInviteLink = buildVendorInviteLink;
31853
32551
  exports.calculateOrderRefundPreview = calculateOrderRefundPreview;
31854
32552
  exports.calculateRefundFromPolicy = calculateRefundFromPolicy;
32553
+ exports.checkAndIncrementDiscountUsage = checkAndIncrementDiscountUsage;
31855
32554
  exports.checkEventsEnabled = checkEventsEnabled;
31856
32555
  exports.checkMultiVendorEnabled = checkMultiVendorEnabled;
32556
+ exports.completeUserInviteAccept = completeUserInviteAccept;
32557
+ exports.consumeAppliedDiscountUsages = consumeAppliedDiscountUsages;
31857
32558
  exports.contactIsVendorCustomer = contactIsVendorCustomer;
32559
+ exports.countDiscountOrdersForContact = countDiscountOrdersForContact;
31858
32560
  exports.countRecentOtpSends = countRecentOtpSends;
31859
32561
  exports.createAnalyticsHandlers = createAnalyticsHandlers;
31860
32562
  exports.createBlogBySlugHandler = createBlogBySlugHandler;
@@ -31886,14 +32588,19 @@ exports.createUserProfileHandler = createUserProfileHandler;
31886
32588
  exports.createUsersApiHandlers = createUsersApiHandlers;
31887
32589
  exports.createVendorDashboardHandler = createVendorDashboardHandler;
31888
32590
  exports.createVendorOnboardHandlers = createVendorOnboardHandlers;
32591
+ exports.customerPhoneForEmail = customerPhoneForEmail;
31889
32592
  exports.customerPhoneForUser = customerPhoneForUser;
31890
32593
  exports.daysBeforeEventStart = daysBeforeEventStart;
32594
+ exports.decrementDiscountUsage = decrementDiscountUsage;
31891
32595
  exports.describeEventTierPolicy = describeEventTierPolicy;
31892
32596
  exports.ensureCustomerForUser = ensureCustomerForUser;
32597
+ exports.ensureCustomerRecord = ensureCustomerRecord;
31893
32598
  exports.ensureMessagingPluginsOnCms = ensureMessagingPluginsOnCms;
31894
32599
  exports.ensureScheduleQueueWorker = ensureScheduleQueueWorker;
31895
32600
  exports.ensureVendorCustomerForOrderContact = ensureVendorCustomerForOrderContact;
32601
+ exports.ensureVendorCustomersForOrder = ensureVendorCustomersForOrder;
31896
32602
  exports.findActiveRefundPolicyForVendor = findActiveRefundPolicyForVendor;
32603
+ exports.findUserByInviteToken = findUserByInviteToken;
31897
32604
  exports.findVendorByInviteToken = findVendorByInviteToken;
31898
32605
  exports.formatTierRange = formatTierRange;
31899
32606
  exports.generateNumericOtp = generateNumericOtp;
@@ -31910,6 +32617,9 @@ exports.invalidateRequireEventApprovalCache = invalidateRequireEventApprovalCach
31910
32617
  exports.invalidateRequireProductApprovalCache = invalidateRequireProductApprovalCache;
31911
32618
  exports.invalidateVendorCatalogCreateFlagsCache = invalidateVendorCatalogCreateFlagsCache;
31912
32619
  exports.isCustomerTypeContact = isCustomerTypeContact;
32620
+ exports.isMaxPerUserUsageReached = isMaxPerUserUsageReached;
32621
+ exports.isMaxTotalUsageReached = isMaxTotalUsageReached;
32622
+ exports.isSyntheticCustomerPhone = isSyntheticCustomerPhone;
31913
32623
  exports.isZipMedia = isZipMedia;
31914
32624
  exports.linkUnclaimedContactToUser = linkUnclaimedContactToUser;
31915
32625
  exports.llmAgentToChatAgentOptions = llmAgentToChatAgentOptions;
@@ -31921,6 +32631,8 @@ exports.metaFetchUserManagedPages = metaFetchUserManagedPages;
31921
32631
  exports.metaPostPageFeed = metaPostPageFeed;
31922
32632
  exports.metaPostPagePhoto = metaPostPagePhoto;
31923
32633
  exports.metaResolvePageAccessToken = metaResolvePageAccessToken;
32634
+ exports.newUserInviteToken = newUserInviteToken;
32635
+ exports.normalizeCustomerPhone = normalizeCustomerPhone;
31924
32636
  exports.normalizePhoneE164 = normalizePhoneE164;
31925
32637
  exports.normalizeRefundTiers = normalizeRefundTiers;
31926
32638
  exports.overlayCmsPlugins = overlayCmsPlugins;
@@ -31933,6 +32645,7 @@ exports.queueErpCreateContactIfEnabled = queueErpCreateContactIfEnabled;
31933
32645
  exports.queueJobScheduleNow = queueJobScheduleNow;
31934
32646
  exports.queuePlugin = queuePlugin;
31935
32647
  exports.queueSms = queueSms;
32648
+ exports.recordDiscountUsage = recordDiscountUsage;
31936
32649
  exports.registerJobRunnerWorker = registerJobRunnerWorker;
31937
32650
  exports.registerMessagingQueueProcessors = registerMessagingQueueProcessors;
31938
32651
  exports.registerSmsQueueProcessor = registerSmsQueueProcessor;