@infuro/cms-core 1.0.43 → 1.0.45

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.
@@ -46,6 +46,7 @@ async function queryVendorLinks(dataSource, userId) {
46
46
  vr."name" AS "vendorRoleName",
47
47
  vr."isOwnerRole" AS "isOwnerRole"
48
48
  FROM "vendor_users" vu
49
+ INNER JOIN "vendors" v ON v.id = vu."vendorId" AND v.deleted = false
49
50
  LEFT JOIN "vendor_roles" vr ON vr.id = vu."vendorRoleId" AND vr.deleted = false
50
51
  WHERE vu."userId" = $1
51
52
  ORDER BY vu.id ASC
@@ -55,10 +56,11 @@ async function queryVendorLinks(dataSource, userId) {
55
56
  } catch (err) {
56
57
  if (!isMissingVendorRoleSchemaError(err)) throw err;
57
58
  const legacy = await dataSource.query(`
58
- SELECT "vendorId", "role"
59
- FROM "vendor_users"
60
- WHERE "userId" = $1
61
- ORDER BY id ASC
59
+ SELECT vu."vendorId" AS "vendorId", vu."role" AS "role"
60
+ FROM "vendor_users" vu
61
+ INNER JOIN "vendors" v ON v.id = vu."vendorId" AND v.deleted = false
62
+ WHERE vu."userId" = $1
63
+ ORDER BY vu.id ASC
62
64
  `, [
63
65
  userId
64
66
  ]);
@@ -72,8 +74,36 @@ async function queryVendorLinks(dataSource, userId) {
72
74
  }
73
75
  }
74
76
  chunkUSNT2KNT_cjs.__name(queryVendorLinks, "queryVendorLinks");
77
+ async function queryOwnedVendorIds(dataSource, userId) {
78
+ try {
79
+ const rows = await dataSource.query(`
80
+ SELECT id
81
+ FROM "vendors"
82
+ WHERE "userId" = $1 AND deleted = false
83
+ ORDER BY id ASC
84
+ `, [
85
+ userId
86
+ ]);
87
+ return rows.map((r) => Number(r.id)).filter((id) => Number.isFinite(id) && id > 0);
88
+ } catch {
89
+ return [];
90
+ }
91
+ }
92
+ chunkUSNT2KNT_cjs.__name(queryOwnedVendorIds, "queryOwnedVendorIds");
75
93
  async function loadUserVendorContext(dataSource, userId, preferredVendorId) {
76
94
  const rows = await queryVendorLinks(dataSource, userId);
95
+ const linkIds = new Set(rows.map((r) => Number(r.vendorId)));
96
+ for (const ownedId of await queryOwnedVendorIds(dataSource, userId)) {
97
+ if (linkIds.has(ownedId)) continue;
98
+ rows.push({
99
+ vendorId: ownedId,
100
+ role: "owner",
101
+ vendorRoleId: null,
102
+ vendorRoleName: null,
103
+ isOwnerRole: true
104
+ });
105
+ linkIds.add(ownedId);
106
+ }
77
107
  const vendorIds = rows.map((r) => Number(r.vendorId));
78
108
  const activeRow = preferredVendorId != null ? rows.find((r) => Number(r.vendorId) === preferredVendorId) : void 0;
79
109
  const primary = activeRow ?? rows[0];
@@ -462,6 +492,41 @@ function applyApprovalStatusSideEffects(updatePayload, opts) {
462
492
  }
463
493
  }
464
494
  chunkUSNT2KNT_cjs.__name(applyApprovalStatusSideEffects, "applyApprovalStatusSideEffects");
495
+ async function syncProductVariantsStatusWithProduct(dataSource, entityMap, productId, productStatus) {
496
+ if (!entityMap.product_variants || !Number.isFinite(productId) || productId < 1) return;
497
+ const status = String(productStatus || "draft");
498
+ const repo = dataSource.getRepository(entityMap.product_variants);
499
+ if (status === "available") {
500
+ await repo.createQueryBuilder().update().set({
501
+ status: "available"
502
+ }).where('"productId" = :productId', {
503
+ productId
504
+ }).andWhere("status = :from", {
505
+ from: "draft"
506
+ }).execute();
507
+ return;
508
+ }
509
+ if (status === "draft") {
510
+ await repo.createQueryBuilder().update().set({
511
+ status: "draft"
512
+ }).where('"productId" = :productId', {
513
+ productId
514
+ }).andWhere("status = :from", {
515
+ from: "available"
516
+ }).execute();
517
+ }
518
+ }
519
+ chunkUSNT2KNT_cjs.__name(syncProductVariantsStatusWithProduct, "syncProductVariantsStatusWithProduct");
520
+ function coerceVariantStatusForProduct(variantStatus, productStatus, opts) {
521
+ const next = String(variantStatus ?? "draft").trim() || "draft";
522
+ const productLive = String(productStatus ?? "draft") === "available";
523
+ const approvalOk = !opts?.requireApproval || String(opts?.approvalStatus ?? "") === "approved";
524
+ if (!productLive || !approvalOk) {
525
+ return "draft";
526
+ }
527
+ return next;
528
+ }
529
+ chunkUSNT2KNT_cjs.__name(coerceVariantStatusForProduct, "coerceVariantStatusForProduct");
465
530
 
466
531
  // src/lib/event-approval.ts
467
532
  var EVENT_APPROVAL_STATUSES = [
@@ -1065,11 +1130,118 @@ function pgErrorCode(err) {
1065
1130
  return driver?.code ?? err.code;
1066
1131
  }
1067
1132
  chunkUSNT2KNT_cjs.__name(pgErrorCode, "pgErrorCode");
1068
- function customerPhoneForUser(userId, phone) {
1133
+ function normalizeCustomerPhone(phone) {
1069
1134
  const p = typeof phone === "string" ? phone.trim() : "";
1070
- return p || `u-${userId}`;
1135
+ return p || null;
1136
+ }
1137
+ chunkUSNT2KNT_cjs.__name(normalizeCustomerPhone, "normalizeCustomerPhone");
1138
+ function isSyntheticCustomerPhone(phone) {
1139
+ const p = String(phone ?? "").trim();
1140
+ if (!p) return true;
1141
+ if (p.startsWith("e-") || p.startsWith("u-")) return true;
1142
+ if (p.includes("@")) return true;
1143
+ return false;
1144
+ }
1145
+ chunkUSNT2KNT_cjs.__name(isSyntheticCustomerPhone, "isSyntheticCustomerPhone");
1146
+ function customerPhoneForUser(_userId, phone) {
1147
+ return normalizeCustomerPhone(phone);
1071
1148
  }
1072
1149
  chunkUSNT2KNT_cjs.__name(customerPhoneForUser, "customerPhoneForUser");
1150
+ function customerPhoneForEmail(_email, phone) {
1151
+ return normalizeCustomerPhone(phone);
1152
+ }
1153
+ chunkUSNT2KNT_cjs.__name(customerPhoneForEmail, "customerPhoneForEmail");
1154
+ function resolveNextPhone(inputPhone, existingPhone) {
1155
+ if (inputPhone) return inputPhone;
1156
+ const existing = normalizeCustomerPhone(existingPhone);
1157
+ if (!existing || isSyntheticCustomerPhone(existing)) return null;
1158
+ return existing;
1159
+ }
1160
+ chunkUSNT2KNT_cjs.__name(resolveNextPhone, "resolveNextPhone");
1161
+ async function ensureCustomerRecord(dsOrEm, customerEntity, input) {
1162
+ const repo = dsOrEm.getRepository(customerEntity);
1163
+ const email = normalizeEmail(input.email);
1164
+ if (!email) return null;
1165
+ const name = String(input.name ?? "").trim() || email.split("@")[0] || "Customer";
1166
+ const userId = input.userId != null && Number.isFinite(Number(input.userId)) && Number(input.userId) > 0 ? Number(input.userId) : null;
1167
+ const phone = normalizeCustomerPhone(input.phone);
1168
+ let row = await repo.findOne({
1169
+ where: {
1170
+ email,
1171
+ deleted: false
1172
+ }
1173
+ });
1174
+ if (!row) {
1175
+ row = await repo.findOne({
1176
+ where: {
1177
+ email
1178
+ }
1179
+ });
1180
+ }
1181
+ if (row) {
1182
+ const existingUserId = row.userId;
1183
+ if (userId != null && existingUserId != null && existingUserId !== userId) {
1184
+ return null;
1185
+ }
1186
+ const nextPhone = resolveNextPhone(phone, row.phone);
1187
+ const patch = {
1188
+ name,
1189
+ phone: nextPhone,
1190
+ deleted: false,
1191
+ deletedAt: null,
1192
+ deletedBy: null,
1193
+ updatedAt: /* @__PURE__ */ new Date()
1194
+ };
1195
+ if (userId != null && existingUserId == null) {
1196
+ patch.userId = userId;
1197
+ }
1198
+ await repo.update(row.id, patch);
1199
+ return {
1200
+ id: row.id
1201
+ };
1202
+ }
1203
+ try {
1204
+ const created = await repo.save(repo.create({
1205
+ userId,
1206
+ name,
1207
+ email,
1208
+ phone,
1209
+ deleted: false
1210
+ }));
1211
+ return {
1212
+ id: created.id
1213
+ };
1214
+ } catch (err) {
1215
+ const code = pgErrorCode(err);
1216
+ if (code === "25P02") throw err;
1217
+ row = await repo.findOne({
1218
+ where: {
1219
+ email
1220
+ }
1221
+ });
1222
+ if (row) {
1223
+ const existingUserId = row.userId;
1224
+ if (userId != null && existingUserId != null && existingUserId !== userId) return null;
1225
+ await repo.update(row.id, {
1226
+ name,
1227
+ phone: resolveNextPhone(phone, row.phone),
1228
+ ...userId != null && existingUserId == null ? {
1229
+ userId
1230
+ } : {},
1231
+ deleted: false,
1232
+ deletedAt: null,
1233
+ deletedBy: null,
1234
+ updatedAt: /* @__PURE__ */ new Date()
1235
+ });
1236
+ return {
1237
+ id: row.id
1238
+ };
1239
+ }
1240
+ if (code === "23505") return null;
1241
+ throw err;
1242
+ }
1243
+ }
1244
+ chunkUSNT2KNT_cjs.__name(ensureCustomerRecord, "ensureCustomerRecord");
1073
1245
  async function restoreCustomerRow(repo, row, user, name, email, phone) {
1074
1246
  const id = row.id;
1075
1247
  await repo.update(id, {
@@ -1091,7 +1263,7 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1091
1263
  const repo = dsOrEm.getRepository(customerEntity);
1092
1264
  const email = normalizeEmail(user.email);
1093
1265
  const name = String(user.name ?? "").trim() || email.split("@")[0] || "User";
1094
- const phone = customerPhoneForUser(user.id, overrides?.phone ?? user.phone);
1266
+ const phone = normalizeCustomerPhone(overrides?.phone ?? user.phone);
1095
1267
  let row = await repo.findOne({
1096
1268
  where: {
1097
1269
  userId: user.id,
@@ -1099,10 +1271,11 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1099
1271
  }
1100
1272
  });
1101
1273
  if (row) {
1274
+ const nextPhone = resolveNextPhone(phone, row.phone);
1102
1275
  await repo.update(row.id, {
1103
1276
  name,
1104
1277
  email,
1105
- phone,
1278
+ phone: nextPhone,
1106
1279
  updatedAt: /* @__PURE__ */ new Date()
1107
1280
  });
1108
1281
  return {
@@ -1120,10 +1293,11 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1120
1293
  if (existingUserId != null && existingUserId !== user.id) {
1121
1294
  return null;
1122
1295
  }
1296
+ const nextPhone = resolveNextPhone(phone, row.phone);
1123
1297
  await repo.update(row.id, {
1124
1298
  userId: user.id,
1125
1299
  name,
1126
- phone,
1300
+ phone: nextPhone,
1127
1301
  updatedAt: /* @__PURE__ */ new Date()
1128
1302
  });
1129
1303
  return {
@@ -1138,21 +1312,23 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1138
1312
  if (deletedByEmail) {
1139
1313
  const existingUserId = deletedByEmail.userId;
1140
1314
  if (existingUserId != null && existingUserId !== user.id) return null;
1141
- return restoreCustomerRow(repo, deletedByEmail, user, name, email, phone);
1315
+ return restoreCustomerRow(repo, deletedByEmail, user, name, email, resolveNextPhone(phone, deletedByEmail.phone));
1142
1316
  }
1143
- const deletedByPhone = await repo.findOne({
1144
- where: {
1145
- phone
1146
- }
1147
- });
1148
- if (deletedByPhone) {
1149
- const existingUserId = deletedByPhone.userId;
1150
- if (existingUserId != null && existingUserId !== user.id) {
1151
- return ensureCustomerForUser(dsOrEm, customerEntity, user, {
1152
- phone: `u-${user.id}-${Date.now()}`
1153
- });
1317
+ if (phone) {
1318
+ const deletedByPhone = await repo.findOne({
1319
+ where: {
1320
+ phone
1321
+ }
1322
+ });
1323
+ if (deletedByPhone) {
1324
+ const existingUserId = deletedByPhone.userId;
1325
+ if (existingUserId != null && existingUserId !== user.id) {
1326
+ return ensureCustomerForUser(dsOrEm, customerEntity, user, {
1327
+ phone: null
1328
+ });
1329
+ }
1330
+ return restoreCustomerRow(repo, deletedByPhone, user, name, email, phone);
1154
1331
  }
1155
- return restoreCustomerRow(repo, deletedByPhone, user, name, email, phone);
1156
1332
  }
1157
1333
  try {
1158
1334
  const created = await repo.save(repo.create({
@@ -1173,34 +1349,22 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1173
1349
  userId: user.id
1174
1350
  }
1175
1351
  });
1176
- if (row) return restoreCustomerRow(repo, row, user, name, email, phone);
1352
+ if (row) {
1353
+ return restoreCustomerRow(repo, row, user, name, email, resolveNextPhone(phone, row.phone));
1354
+ }
1177
1355
  row = await repo.findOne({
1178
1356
  where: {
1179
1357
  email
1180
1358
  }
1181
1359
  });
1182
1360
  if (row && (row.userId ?? user.id) === user.id) {
1183
- return restoreCustomerRow(repo, row, user, name, email, phone);
1361
+ return restoreCustomerRow(repo, row, user, name, email, resolveNextPhone(phone, row.phone));
1184
1362
  }
1185
1363
  if (code === "23505") return null;
1186
1364
  throw err;
1187
1365
  }
1188
1366
  }
1189
1367
  chunkUSNT2KNT_cjs.__name(ensureCustomerForUser, "ensureCustomerForUser");
1190
- async function linkUnclaimedContactToUser(dataSource, contactsEntity, userId, email) {
1191
- const repo = dataSource.getRepository(contactsEntity);
1192
- const found = await repo.findOne({
1193
- where: {
1194
- email,
1195
- userId: typeorm.IsNull(),
1196
- deleted: false
1197
- }
1198
- });
1199
- if (found) await repo.update(found.id, {
1200
- userId
1201
- });
1202
- }
1203
- chunkUSNT2KNT_cjs.__name(linkUnclaimedContactToUser, "linkUnclaimedContactToUser");
1204
1368
 
1205
1369
  // src/lib/vendor-customer-contacts.ts
1206
1370
  function isCustomerTypeContact(type) {
@@ -1293,8 +1457,27 @@ function resolveVendorIdForContactCheck(scope, bodyOrRow) {
1293
1457
  return Number.isFinite(n) ? n : null;
1294
1458
  }
1295
1459
  chunkUSNT2KNT_cjs.__name(resolveVendorIdForContactCheck, "resolveVendorIdForContactCheck");
1460
+ function uniquePositiveIds(ids) {
1461
+ const out = [];
1462
+ const seen = /* @__PURE__ */ new Set();
1463
+ for (const raw of ids) {
1464
+ const n = Number(raw);
1465
+ if (!Number.isFinite(n) || n <= 0 || seen.has(n)) continue;
1466
+ seen.add(n);
1467
+ out.push(n);
1468
+ }
1469
+ return out;
1470
+ }
1471
+ chunkUSNT2KNT_cjs.__name(uniquePositiveIds, "uniquePositiveIds");
1296
1472
  async function ensureVendorCustomerForOrderContact(dataSource, entityMap, vendorId, contactId, details) {
1297
- if (!Number.isFinite(vendorId) || !Number.isFinite(contactId)) return;
1473
+ await ensureVendorCustomersForOrder(dataSource, entityMap, [
1474
+ vendorId
1475
+ ], contactId, details);
1476
+ }
1477
+ chunkUSNT2KNT_cjs.__name(ensureVendorCustomerForOrderContact, "ensureVendorCustomerForOrderContact");
1478
+ async function ensureVendorCustomersForOrder(dataSource, entityMap, vendorIds, contactId, details) {
1479
+ const vendors = uniquePositiveIds(vendorIds);
1480
+ if (vendors.length === 0 || !Number.isFinite(contactId) || contactId <= 0) return;
1298
1481
  const vcEntity = entityMap.vendor_customers;
1299
1482
  const contactEntity = entityMap.contacts;
1300
1483
  if (!vcEntity || !contactEntity) return;
@@ -1320,96 +1503,61 @@ async function ensureVendorCustomerForOrderContact(dataSource, entityMap, vendor
1320
1503
  }
1321
1504
  let customerId = null;
1322
1505
  if (entityMap.customer) {
1323
- const customerRepo = dataSource.getRepository(entityMap.customer);
1324
- const existingCustomer = await customerRepo.findOne({
1325
- where: {
1326
- email,
1327
- deleted: false
1328
- }
1506
+ const ensured = await ensureCustomerRecord(dataSource, entityMap.customer, {
1507
+ name,
1508
+ email,
1509
+ phone,
1510
+ userId: null
1329
1511
  });
1330
- if (existingCustomer) {
1331
- customerId = Number(existingCustomer.id);
1332
- } else if (entityMap.users) {
1333
- const userRepo = dataSource.getRepository(entityMap.users);
1334
- let user = await userRepo.findOne({
1512
+ if (ensured) customerId = ensured.id;
1513
+ }
1514
+ for (const vendorId of vendors) {
1515
+ if (customerId != null) {
1516
+ const byCustomer = await vcRepo.findOne({
1335
1517
  where: {
1336
- email,
1337
- deleted: false
1518
+ vendorId,
1519
+ customerId
1338
1520
  }
1339
1521
  });
1340
- if (!user) {
1341
- let groupId = null;
1342
- if (entityMap.user_groups) {
1343
- const userGroupRepo = dataSource.getRepository(entityMap.user_groups);
1344
- const customerGroup = await userGroupRepo.findOne({
1345
- where: {
1346
- name: "Customer",
1347
- deleted: false
1348
- }
1522
+ if (byCustomer) {
1523
+ const existingContactId = byCustomer.contactId;
1524
+ if (existingContactId == null) {
1525
+ await vcRepo.update(byCustomer.id, {
1526
+ contactId
1349
1527
  });
1350
- if (customerGroup) groupId = Number(customerGroup.id);
1351
1528
  }
1352
- user = await userRepo.save(userRepo.create({
1353
- name,
1354
- email,
1355
- phone,
1356
- password: null,
1357
- blocked: false,
1358
- groupId,
1359
- adminAccess: false
1360
- }));
1529
+ continue;
1361
1530
  }
1362
- const userId = Number(user.id);
1363
- await linkUnclaimedContactToUser(dataSource, contactEntity, userId, email);
1364
- const ensured = await ensureCustomerForUser(dataSource, entityMap.customer, {
1365
- id: userId,
1366
- name,
1367
- email,
1368
- phone
1369
- }, {
1370
- phone
1371
- });
1372
- if (ensured) customerId = ensured.id;
1373
1531
  }
1374
- }
1375
- if (customerId != null) {
1376
- const byCustomer = await vcRepo.findOne({
1532
+ const byContact = await vcRepo.findOne({
1377
1533
  where: {
1378
1534
  vendorId,
1379
- customerId
1535
+ contactId
1380
1536
  }
1381
1537
  });
1382
- if (byCustomer) {
1383
- const existingContactId = byCustomer.contactId;
1384
- if (existingContactId == null) {
1385
- await vcRepo.update(byCustomer.id, {
1386
- contactId
1538
+ if (byContact) {
1539
+ if (customerId != null && byContact.customerId == null) {
1540
+ await vcRepo.update(byContact.id, {
1541
+ customerId
1387
1542
  });
1388
1543
  }
1389
- return;
1544
+ continue;
1390
1545
  }
1391
- }
1392
- const byContact = await vcRepo.findOne({
1393
- where: {
1394
- vendorId,
1395
- contactId
1546
+ if (customerId != null) {
1547
+ await vcRepo.save(vcRepo.create({
1548
+ vendorId,
1549
+ customerId,
1550
+ contactId
1551
+ }));
1552
+ } else {
1553
+ await vcRepo.save(vcRepo.create({
1554
+ vendorId,
1555
+ contactId
1556
+ }));
1396
1557
  }
1397
- });
1398
- if (byContact) return;
1399
- if (customerId != null) {
1400
- await vcRepo.save(vcRepo.create({
1401
- vendorId,
1402
- customerId,
1403
- contactId
1404
- }));
1405
- return;
1406
1558
  }
1407
- await vcRepo.save(vcRepo.create({
1408
- vendorId,
1409
- contactId
1410
- }));
1411
1559
  }
1412
- chunkUSNT2KNT_cjs.__name(ensureVendorCustomerForOrderContact, "ensureVendorCustomerForOrderContact");
1560
+ chunkUSNT2KNT_cjs.__name(ensureVendorCustomersForOrder, "ensureVendorCustomersForOrder");
1413
1561
 
1414
1562
  // src/lib/currency-prices.ts
1415
1563
  function normalizeCurrencyCode(code) {
@@ -3775,23 +3923,24 @@ function createCrudHandler(dataSource, entityMap, options) {
3775
3923
  if (resource === "vendor_customers" && entityMap["customer"]) {
3776
3924
  const scope = await resolveVendorCustomersScope(dataSource, entityMap, resolveScope);
3777
3925
  const repo2 = dataSource.getRepository(entity);
3778
- const qb = repo2.createQueryBuilder("vc").leftJoinAndSelect("vc.customer", "customer").leftJoinAndSelect("customer.user", "user").orderBy("vc.createdAt", sortOrder === "DESC" ? "DESC" : "ASC").skip(skip).take(limit);
3926
+ 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);
3779
3927
  applyVendorScopeToQueryBuilder(qb, "vc", scope);
3780
3928
  if (search && typeof search === "string" && search.trim()) {
3781
3929
  const term = `%${search.trim()}%`;
3782
- qb.andWhere("(customer.name ILIKE :term OR customer.email ILIKE :term OR customer.phone ILIKE :term)", {
3930
+ 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)", {
3783
3931
  term
3784
3932
  });
3785
3933
  }
3786
3934
  const [rows, total2] = await qb.getManyAndCount();
3787
3935
  const data2 = rows.map((row) => {
3788
3936
  const customer = row.customer;
3937
+ const contact = row.contact;
3789
3938
  return {
3790
3939
  ...row,
3791
- name: customer?.name ?? null,
3792
- email: customer?.email ?? null,
3793
- phone: customer?.phone ?? null,
3794
- company: customer?.company ?? null
3940
+ name: customer?.name ?? contact?.name ?? null,
3941
+ email: customer?.email ?? contact?.email ?? null,
3942
+ phone: customer?.phone ?? contact?.phone ?? null,
3943
+ company: customer?.company ?? contact?.company ?? null
3795
3944
  };
3796
3945
  });
3797
3946
  return json({
@@ -4125,7 +4274,11 @@ function createCrudHandler(dataSource, entityMap, options) {
4125
4274
  } else if (resource === "collections") {
4126
4275
  if (scope.type === "vendor") {
4127
4276
  where = mergeVendorScopeIntoWhere(where, scope, resource, catalogFlags);
4128
- } else if (searchParams.get("isCatalog") !== "true") {
4277
+ } else if (searchParams.get("isCatalog") === "true") {
4278
+ where = mergeListWhereAnd(where, {
4279
+ isCatalog: true
4280
+ });
4281
+ } else if (searchParams.get("isCatalog") === "false") {
4129
4282
  where = mergeListWhereAnd(where, {
4130
4283
  isCatalog: false
4131
4284
  });
@@ -4322,41 +4475,17 @@ function createCrudHandler(dataSource, entityMap, options) {
4322
4475
  status: 503
4323
4476
  });
4324
4477
  }
4325
- if (!entityMap["users"]) {
4326
- return json({
4327
- error: "Users entity not configured"
4328
- }, {
4329
- status: 503
4330
- });
4331
- }
4332
- let customerGroupId = null;
4333
- if (entityMap["user_groups"]) {
4334
- const userGroupRepo = dataSource.getRepository(entityMap["user_groups"]);
4335
- const customerGroup = await userGroupRepo.findOne({
4336
- where: {
4337
- name: "Customer",
4338
- deleted: false
4339
- }
4340
- });
4341
- if (!customerGroup) {
4342
- return json({
4343
- error: "User group 'customer' not found"
4344
- }, {
4345
- status: 500
4346
- });
4347
- }
4348
- customerGroupId = Number(customerGroup.id);
4349
- } else {
4478
+ if (!entityMap["contacts"]) {
4350
4479
  return json({
4351
- error: "user_groups entity not configured"
4480
+ error: "Contacts entity not configured"
4352
4481
  }, {
4353
4482
  status: 503
4354
4483
  });
4355
4484
  }
4356
4485
  const name = String(body.name ?? "").trim();
4357
- const email = String(body.email ?? "").trim();
4358
- const phone = String(body.phone ?? "").trim();
4359
- const rawPw = String(body._password ?? "").trim();
4486
+ const email = String(body.email ?? "").trim().toLowerCase();
4487
+ const phoneRaw = String(body.phone ?? "").trim();
4488
+ const phone = phoneRaw || null;
4360
4489
  if (!name) return json({
4361
4490
  error: "name is required"
4362
4491
  }, {
@@ -4367,25 +4496,7 @@ function createCrudHandler(dataSource, entityMap, options) {
4367
4496
  }, {
4368
4497
  status: 400
4369
4498
  });
4370
- if (!phone) return json({
4371
- error: "phone is required"
4372
- }, {
4373
- status: 400
4374
- });
4375
- if (!rawPw) return json({
4376
- error: "password is required"
4377
- }, {
4378
- status: 400
4379
- });
4380
- if (rawPw.length < 6) {
4381
- return json({
4382
- error: "Password must be at least 6 characters"
4383
- }, {
4384
- status: 400
4385
- });
4386
- }
4387
4499
  const customerRepo = dataSource.getRepository(entityMap["customer"]);
4388
- const userRepo = dataSource.getRepository(entityMap["users"]);
4389
4500
  const dupCustEmail = await customerRepo.findOne({
4390
4501
  where: {
4391
4502
  email,
@@ -4399,49 +4510,26 @@ function createCrudHandler(dataSource, entityMap, options) {
4399
4510
  status: 409
4400
4511
  });
4401
4512
  }
4402
- const dupCustPhone = await customerRepo.findOne({
4403
- where: {
4404
- phone,
4405
- deleted: false
4406
- }
4407
- });
4408
- if (dupCustPhone) {
4409
- return json({
4410
- error: "A customer with this phone number already exists"
4411
- }, {
4412
- status: 409
4513
+ if (phone) {
4514
+ const dupCustPhone = await customerRepo.findOne({
4515
+ where: {
4516
+ phone,
4517
+ deleted: false
4518
+ }
4413
4519
  });
4414
- }
4415
- let userId;
4416
- const dupUser = await userRepo.findOne({
4417
- where: {
4418
- email,
4419
- deleted: false
4520
+ if (dupCustPhone) {
4521
+ return json({
4522
+ error: "A customer with this phone number already exists"
4523
+ }, {
4524
+ status: 409
4525
+ });
4420
4526
  }
4421
- });
4422
- if (dupUser) {
4423
- userId = Number(dupUser.id);
4424
- } else {
4425
- const bcrypt = await import('bcryptjs');
4426
- const hashedPassword = await bcrypt.hash(rawPw, 10);
4427
- const savedUser = await userRepo.save(userRepo.create({
4428
- name,
4429
- email,
4430
- phone,
4431
- password: hashedPassword,
4432
- groupId: customerGroupId,
4433
- adminAccess: false,
4434
- blocked: false
4435
- }));
4436
- userId = Number(savedUser.id);
4437
4527
  }
4438
- const savedCustomer = await ensureCustomerForUser(dataSource, entityMap["customer"], {
4439
- id: userId,
4528
+ const savedCustomer = await ensureCustomerRecord(dataSource, entityMap["customer"], {
4440
4529
  name,
4441
4530
  email,
4442
- phone
4443
- }, {
4444
- phone
4531
+ phone,
4532
+ userId: null
4445
4533
  });
4446
4534
  if (!savedCustomer) {
4447
4535
  return json({
@@ -4450,37 +4538,37 @@ function createCrudHandler(dataSource, entityMap, options) {
4450
4538
  status: 409
4451
4539
  });
4452
4540
  }
4453
- const customerRepo2 = dataSource.getRepository(entityMap["customer"]);
4454
- const customerRow = await customerRepo2.findOne({
4541
+ const contactRepo = dataSource.getRepository(entityMap["contacts"]);
4542
+ let contact = await contactRepo.findOne({
4455
4543
  where: {
4456
- id: savedCustomer.id,
4544
+ email,
4457
4545
  deleted: false
4458
4546
  }
4459
- }) ?? savedCustomer;
4460
- if (entityMap["contacts"]) {
4461
- const contactRepo = dataSource.getRepository(entityMap["contacts"]);
4462
- const existingContact = await contactRepo.findOne({
4463
- where: {
4464
- email,
4465
- deleted: false
4466
- }
4467
- });
4468
- if (!existingContact) {
4469
- await contactRepo.save(contactRepo.create({
4470
- name,
4471
- email,
4472
- phone: phone || null,
4473
- type: "customer"
4474
- }));
4475
- } else {
4476
- const t = existingContact.type;
4477
- if (t == null || t === "" || String(t).toLowerCase() !== "customer") {
4478
- await contactRepo.update(existingContact.id, {
4479
- type: "customer"
4480
- });
4481
- }
4482
- }
4547
+ });
4548
+ if (!contact) {
4549
+ contact = await contactRepo.save(contactRepo.create({
4550
+ name,
4551
+ email,
4552
+ phone: phone || null,
4553
+ type: "customer"
4554
+ }));
4555
+ } else {
4556
+ const t = contact.type;
4557
+ const patch = {
4558
+ name,
4559
+ phone: phone || contact.phone || null
4560
+ };
4561
+ if (t == null || t === "" || String(t).toLowerCase() !== "customer") {
4562
+ patch.type = "customer";
4563
+ }
4564
+ await contactRepo.update(contact.id, patch);
4565
+ contact = {
4566
+ ...contact,
4567
+ ...patch,
4568
+ id: contact.id
4569
+ };
4483
4570
  }
4571
+ const contactId = Number(contact.id);
4484
4572
  const scope = await resolveVendorCustomersScope(dataSource, entityMap, resolveScope);
4485
4573
  let vendorId = null;
4486
4574
  if (scope.type === "vendor") {
@@ -4496,19 +4584,25 @@ function createCrudHandler(dataSource, entityMap, options) {
4496
4584
  status: 400
4497
4585
  });
4498
4586
  }
4499
- const vcRepo = dataSource.getRepository(entity);
4500
- const existingLink = await vcRepo.findOne({
4587
+ await ensureVendorCustomersForOrder(dataSource, entityMap, [
4588
+ vendorId
4589
+ ], contactId, {
4590
+ name,
4591
+ email,
4592
+ phone
4593
+ });
4594
+ const customerRow = await customerRepo.findOne({
4501
4595
  where: {
4502
- customerId: savedCustomer.id
4596
+ id: savedCustomer.id,
4597
+ deleted: false
4503
4598
  }
4504
4599
  });
4505
- if (!existingLink) {
4506
- await vcRepo.save(vcRepo.create({
4507
- customerId: savedCustomer.id,
4508
- vendorId
4509
- }));
4510
- }
4511
- return json(customerRow, {
4600
+ return json(customerRow ?? {
4601
+ id: savedCustomer.id,
4602
+ name,
4603
+ email,
4604
+ phone
4605
+ }, {
4512
4606
  status: 201
4513
4607
  });
4514
4608
  }
@@ -4604,6 +4698,26 @@ function createCrudHandler(dataSource, entityMap, options) {
4604
4698
  }
4605
4699
  }
4606
4700
  }
4701
+ if (resource === "product_variants" && entityMap.products) {
4702
+ const productIdRaw = persistBody.productId;
4703
+ const productId = typeof productIdRaw === "number" ? productIdRaw : typeof productIdRaw === "string" && /^\d+$/.test(productIdRaw) ? parseInt(productIdRaw, 10) : NaN;
4704
+ if (Number.isFinite(productId)) {
4705
+ const parent = await dataSource.getRepository(entityMap.products).findOne({
4706
+ where: {
4707
+ id: productId
4708
+ }
4709
+ });
4710
+ const parentStatus = parent?.status;
4711
+ const parentApproval = parent?.approvalStatus;
4712
+ const requireApproval = await getRequireProductApproval(dataSource);
4713
+ persistBody.status = coerceVariantStatusForProduct(persistBody.status, parentStatus, {
4714
+ approvalStatus: parentApproval,
4715
+ requireApproval
4716
+ });
4717
+ } else if (!("status" in persistBody)) {
4718
+ persistBody.status = "draft";
4719
+ }
4720
+ }
4607
4721
  if (resource === "products") {
4608
4722
  const scopeForProduct = await resolveScope();
4609
4723
  const productFlags = await getVendorCatalogCreateFlags(dataSource);
@@ -4936,6 +5050,7 @@ function createCrudHandler(dataSource, entityMap, options) {
4936
5050
  }
4937
5051
  const nameRaw = String(body["contact.name"] ?? contact.name ?? "").trim();
4938
5052
  const phoneRaw = body["contact.phone"] ?? contact.phone;
5053
+ const emailForVc = (emailRaw || String(contact.email ?? "")).trim().toLowerCase();
4939
5054
  const phoneToSave = phoneRaw == null || phoneRaw === "" ? null : String(phoneRaw).trim();
4940
5055
  if (phoneToSave && contact.phone !== phoneToSave) {
4941
5056
  await contactRepo.update(contact.id, {
@@ -4944,21 +5059,45 @@ function createCrudHandler(dataSource, entityMap, options) {
4944
5059
  contact.phone = phoneToSave;
4945
5060
  }
4946
5061
  const vendorIdForCustomer = resolveVendorIdForContactCheck(scopeCreate, persistBody);
4947
- const selectedExistingCustomer = Number(body.customerId);
4948
- if (vendorIdForCustomer != null && (!Number.isFinite(selectedExistingCustomer) || selectedExistingCustomer <= 0)) {
4949
- await ensureVendorCustomerForOrderContact(dataSource, entityMap, vendorIdForCustomer, contact.id, {
4950
- name: nameRaw || emailRaw.split("@")[0] || "Customer",
4951
- email: emailRaw,
5062
+ const orderLinesForVendors = normalizeOrderLinesInput(body.orderLines);
5063
+ const vendorIdsForVc = [];
5064
+ if (vendorIdForCustomer != null) vendorIdsForVc.push(vendorIdForCustomer);
5065
+ if (orderLinesForVendors?.length && entityMap.products) {
5066
+ const productRepoForVc = dataSource.getRepository(entityMap.products);
5067
+ for (const line of orderLinesForVendors) {
5068
+ const pid = Number(line.productId);
5069
+ if (!Number.isFinite(pid)) continue;
5070
+ const product = await productRepoForVc.findOne({
5071
+ where: {
5072
+ id: pid
5073
+ }
5074
+ });
5075
+ const pVid = Number(product?.vendorId);
5076
+ if (Number.isFinite(pVid) && pVid > 0) vendorIdsForVc.push(pVid);
5077
+ }
5078
+ }
5079
+ if (vendorIdsForVc.length > 0 && emailForVc) {
5080
+ await ensureVendorCustomersForOrder(dataSource, entityMap, vendorIdsForVc, contact.id, {
5081
+ name: nameRaw || emailForVc.split("@")[0] || "Customer",
5082
+ email: emailForVc,
4952
5083
  phone: phoneRaw == null || phoneRaw === "" ? null : String(phoneRaw)
4953
5084
  });
4954
- } else if (vendorIdForCustomer != null) {
4955
- const contactErr = await assertContactAllowedForVendorOrder(dataSource, entityMap, vendorIdForCustomer, contact.id);
4956
- if (contactErr) {
4957
- return json({
4958
- error: contactErr
4959
- }, {
4960
- status: 400
4961
- });
5085
+ }
5086
+ const accountCustomerId = Number(body.accountCustomerId);
5087
+ if (entityMap.customer_contacts && Number.isFinite(accountCustomerId) && accountCustomerId > 0) {
5088
+ const ccRepo = dataSource.getRepository(entityMap.customer_contacts);
5089
+ const orderContactId = contact.id;
5090
+ const existingLink = await ccRepo.findOne({
5091
+ where: {
5092
+ customerId: accountCustomerId,
5093
+ contactId: orderContactId
5094
+ }
5095
+ });
5096
+ if (!existingLink) {
5097
+ await ccRepo.save(ccRepo.create({
5098
+ customerId: accountCustomerId,
5099
+ contactId: orderContactId
5100
+ }));
4962
5101
  }
4963
5102
  }
4964
5103
  const randomPart = Math.floor(1e4 + Math.random() * 9e4);
@@ -6243,6 +6382,34 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6243
6382
  delete u.parentId;
6244
6383
  }
6245
6384
  }
6385
+ if (resource === "product_variants" && entityMap.products) {
6386
+ const currentVariant = await repo.findOne({
6387
+ where: {
6388
+ id: numericId
6389
+ }
6390
+ });
6391
+ if (!currentVariant) return json({
6392
+ message: "Not found"
6393
+ }, {
6394
+ status: 404
6395
+ });
6396
+ const productId = "productId" in updatePayload && updatePayload.productId != null ? Number(updatePayload.productId) : Number(currentVariant.productId);
6397
+ if (Number.isFinite(productId)) {
6398
+ const parent = await dataSource.getRepository(entityMap.products).findOne({
6399
+ where: {
6400
+ id: productId
6401
+ }
6402
+ });
6403
+ const parentStatus = parent?.status;
6404
+ const parentApproval = parent?.approvalStatus;
6405
+ const requireApproval = await getRequireProductApproval(dataSource);
6406
+ const nextStatus = "status" in updatePayload ? updatePayload.status : currentVariant.status;
6407
+ updatePayload.status = coerceVariantStatusForProduct(nextStatus, parentStatus, {
6408
+ approvalStatus: parentApproval,
6409
+ requireApproval
6410
+ });
6411
+ }
6412
+ }
6246
6413
  if (resource === "products") {
6247
6414
  const currentRow = await repo.findOne({
6248
6415
  where: {
@@ -6598,6 +6765,10 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6598
6765
  if (reloaded) updated = reloaded;
6599
6766
  }
6600
6767
  updated = hydrateProductDisplayName(updated);
6768
+ const productStatus = String(updated.status ?? "draft");
6769
+ if (Number.isFinite(updatedId) && updatedId > 0) {
6770
+ await syncProductVariantsStatusWithProduct(dataSource, entityMap, updatedId, productStatus);
6771
+ }
6601
6772
  if (getCms) {
6602
6773
  const cms = await getCms();
6603
6774
  await queueErpProductUpsertIfEnabled(cms, dataSource, entityMap, updated);
@@ -6706,6 +6877,14 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6706
6877
  }
6707
6878
  }
6708
6879
  await repo.update(numericId, buildSoftDeletePayload(repo.metadata, deletedBy));
6880
+ if ((resource === "forms" || resource === "vendors") && existing && typeof existing.slug === "string") {
6881
+ const slug = String(existing.slug).trim();
6882
+ if (slug && !slug.includes("__deleted_")) {
6883
+ await repo.update(numericId, {
6884
+ slug: `${slug}__deleted_${numericId}`
6885
+ });
6886
+ }
6887
+ }
6709
6888
  return json({
6710
6889
  message: "Deleted successfully"
6711
6890
  }, {
@@ -6735,7 +6914,21 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6735
6914
  }
6736
6915
  };
6737
6916
  }
6738
- chunkUSNT2KNT_cjs.__name(createCrudByIdHandler, "createCrudByIdHandler");
6917
+ chunkUSNT2KNT_cjs.__name(createCrudByIdHandler, "createCrudByIdHandler");
6918
+ async function linkUnclaimedContactToUser(dataSource, contactsEntity, userId, email) {
6919
+ const repo = dataSource.getRepository(contactsEntity);
6920
+ const found = await repo.findOne({
6921
+ where: {
6922
+ email,
6923
+ userId: typeorm.IsNull(),
6924
+ deleted: false
6925
+ }
6926
+ });
6927
+ if (found) await repo.update(found.id, {
6928
+ userId
6929
+ });
6930
+ }
6931
+ chunkUSNT2KNT_cjs.__name(linkUnclaimedContactToUser, "linkUnclaimedContactToUser");
6739
6932
  var VENDOR_INVITE_METADATA_TOKEN = "inviteToken";
6740
6933
  var VENDOR_INVITE_METADATA_EXPIRES = "inviteExpiresAt";
6741
6934
  var VENDOR_INVITE_METADATA_SENT = "inviteSentAt";
@@ -7468,6 +7661,50 @@ async function findLlmAgentByScope(dataSource, entityMap, scope, options = {}) {
7468
7661
  }
7469
7662
  chunkUSNT2KNT_cjs.__name(findLlmAgentByScope, "findLlmAgentByScope");
7470
7663
 
7664
+ // src/lib/retire-soft-deleted-unique.ts
7665
+ async function retireSoftDeletedUniqueValue(repo, column, value) {
7666
+ const trimmed = typeof value === "string" ? value.trim() : "";
7667
+ if (!trimmed) return;
7668
+ const cols = new Set(repo.metadata.columns.map((c) => c.propertyName));
7669
+ if (!cols.has(column) || !cols.has("deleted")) return;
7670
+ const rows = await repo.find({
7671
+ where: {
7672
+ [column]: trimmed,
7673
+ deleted: true
7674
+ }
7675
+ });
7676
+ for (const row of rows) {
7677
+ const id = row.id;
7678
+ if (!Number.isFinite(id)) continue;
7679
+ const current = String(row[column] ?? "");
7680
+ if (current.includes("__deleted_")) continue;
7681
+ await repo.update(id, {
7682
+ [column]: `${trimmed}__deleted_${id}`
7683
+ });
7684
+ }
7685
+ }
7686
+ chunkUSNT2KNT_cjs.__name(retireSoftDeletedUniqueValue, "retireSoftDeletedUniqueValue");
7687
+ async function activeUniqueValueExists(repo, column, value, excludeId) {
7688
+ const trimmed = typeof value === "string" ? value.trim() : "";
7689
+ if (!trimmed) return false;
7690
+ const cols = new Set(repo.metadata.columns.map((c) => c.propertyName));
7691
+ if (!cols.has(column)) return false;
7692
+ const qb = repo.createQueryBuilder("row").where(`row.${column} = :value`, {
7693
+ value: trimmed
7694
+ });
7695
+ if (cols.has("deleted")) {
7696
+ qb.andWhere("row.deleted = false");
7697
+ }
7698
+ if (excludeId != null && Number.isFinite(excludeId) && excludeId > 0) {
7699
+ qb.andWhere("row.id != :excludeId", {
7700
+ excludeId
7701
+ });
7702
+ }
7703
+ const hit = await qb.getOne();
7704
+ return !!hit;
7705
+ }
7706
+ chunkUSNT2KNT_cjs.__name(activeUniqueValueExists, "activeUniqueValueExists");
7707
+
7471
7708
  // src/lib/media-folder-path.ts
7472
7709
  function sanitizeMediaFolderPath(input) {
7473
7710
  if (input == null) return "";
@@ -8861,6 +9098,17 @@ function createFormSaveHandlers(config) {
8861
9098
  });
8862
9099
  const fields = Array.isArray(body.fields) ? body.fields : [];
8863
9100
  const { fields: _f, ...formRow } = body;
9101
+ const slug = typeof formRow.slug === "string" ? formRow.slug.trim() : "";
9102
+ if (slug) {
9103
+ if (await activeUniqueValueExists(formRepo(), "slug", slug)) {
9104
+ return json({
9105
+ error: "A form with this slug already exists"
9106
+ }, {
9107
+ status: 400
9108
+ });
9109
+ }
9110
+ await retireSoftDeletedUniqueValue(formRepo(), "slug", slug);
9111
+ }
8864
9112
  const form = await formRepo().save(formRepo().create(formRow));
8865
9113
  for (let i = 0; i < fields.length; i++) {
8866
9114
  const row = normalizeFieldRow(fields[i], form.id);
@@ -8884,6 +9132,14 @@ function createFormSaveHandlers(config) {
8884
9132
  status: 201
8885
9133
  });
8886
9134
  } catch (e) {
9135
+ const msg = e instanceof Error ? e.message : String(e);
9136
+ if (/duplicate key|unique constraint/i.test(msg) && /slug/i.test(msg)) {
9137
+ return json({
9138
+ error: "A form with this slug already exists"
9139
+ }, {
9140
+ status: 400
9141
+ });
9142
+ }
8887
9143
  return json({
8888
9144
  error: "Server Error"
8889
9145
  }, {
@@ -8932,6 +9188,20 @@ function createFormSaveHandlers(config) {
8932
9188
  ]) {
8933
9189
  if (body[key] !== void 0) formRow[key] = body[key];
8934
9190
  }
9191
+ if (typeof formRow.slug === "string") {
9192
+ const slug = formRow.slug.trim();
9193
+ formRow.slug = slug;
9194
+ if (slug) {
9195
+ if (await activeUniqueValueExists(formRepo(), "slug", slug, formId)) {
9196
+ return json({
9197
+ error: "A form with this slug already exists"
9198
+ }, {
9199
+ status: 400
9200
+ });
9201
+ }
9202
+ await retireSoftDeletedUniqueValue(formRepo(), "slug", slug);
9203
+ }
9204
+ }
8935
9205
  if (Object.keys(formRow).length > 0) await formRepo().update(formId, formRow);
8936
9206
  await fieldRepo().delete({
8937
9207
  formId
@@ -8960,6 +9230,14 @@ function createFormSaveHandlers(config) {
8960
9230
  status: 404
8961
9231
  });
8962
9232
  } catch (e) {
9233
+ const msg = e instanceof Error ? e.message : String(e);
9234
+ if (/duplicate key|unique constraint/i.test(msg) && /slug/i.test(msg)) {
9235
+ return json({
9236
+ error: "A form with this slug already exists"
9237
+ }, {
9238
+ status: 400
9239
+ });
9240
+ }
8963
9241
  return json({
8964
9242
  error: "Server Error"
8965
9243
  }, {
@@ -9541,6 +9819,7 @@ function createUsersApiHandlers(config) {
9541
9819
  "id",
9542
9820
  "name",
9543
9821
  "email",
9822
+ "phone",
9544
9823
  "blocked",
9545
9824
  "createdAt",
9546
9825
  "updatedAt",
@@ -9570,6 +9849,11 @@ function createUsersApiHandlers(config) {
9570
9849
  }
9571
9850
  try {
9572
9851
  const uid = parseInt(id, 10);
9852
+ if (!Number.isFinite(uid)) return json({
9853
+ error: "Invalid id"
9854
+ }, {
9855
+ status: 400
9856
+ });
9573
9857
  const existing = await userRepo().findOne({
9574
9858
  where: {
9575
9859
  id: uid,
@@ -9582,8 +9866,59 @@ function createUsersApiHandlers(config) {
9582
9866
  status: 404
9583
9867
  });
9584
9868
  const body = await req.json();
9585
- const { password: _p, ...safe } = body;
9586
- await userRepo().update(uid, safe);
9869
+ const patch = {
9870
+ updatedAt: /* @__PURE__ */ new Date()
9871
+ };
9872
+ if (typeof body.name === "string") patch.name = body.name.trim();
9873
+ if (typeof body.email === "string") patch.email = body.email.trim().toLowerCase();
9874
+ if (body.blocked !== void 0) {
9875
+ patch.blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
9876
+ }
9877
+ if (body.adminAccess !== void 0) {
9878
+ patch.adminAccess = body.adminAccess === true || body.adminAccess === "true" || body.adminAccess === 1 || body.adminAccess === "1";
9879
+ }
9880
+ if (body.groupId !== void 0) {
9881
+ if (body.groupId === null || body.groupId === "") {
9882
+ patch.groupId = null;
9883
+ } else {
9884
+ const gid = Number(body.groupId);
9885
+ if (!Number.isFinite(gid)) {
9886
+ return json({
9887
+ error: "Invalid groupId"
9888
+ }, {
9889
+ status: 400
9890
+ });
9891
+ }
9892
+ patch.groupId = gid;
9893
+ }
9894
+ }
9895
+ if (body.phone !== void 0) {
9896
+ const phone = body.phone == null ? null : String(body.phone).trim();
9897
+ patch.phone = phone || null;
9898
+ }
9899
+ if (Object.keys(patch).length <= 1) {
9900
+ return json({
9901
+ error: "No valid fields to update"
9902
+ }, {
9903
+ status: 400
9904
+ });
9905
+ }
9906
+ if (typeof patch.email === "string" && patch.email !== existing.email) {
9907
+ const emailTaken = await userRepo().findOne({
9908
+ where: {
9909
+ email: patch.email,
9910
+ deleted: false
9911
+ }
9912
+ });
9913
+ if (emailTaken && Number(emailTaken.id) !== uid) {
9914
+ return json({
9915
+ error: "Email already in use"
9916
+ }, {
9917
+ status: 400
9918
+ });
9919
+ }
9920
+ }
9921
+ await userRepo().update(uid, patch);
9587
9922
  const updated = await userRepo().findOne({
9588
9923
  where: {
9589
9924
  id: uid,
@@ -9596,6 +9931,7 @@ function createUsersApiHandlers(config) {
9596
9931
  "id",
9597
9932
  "name",
9598
9933
  "email",
9934
+ "phone",
9599
9935
  "blocked",
9600
9936
  "createdAt",
9601
9937
  "updatedAt",
@@ -9607,7 +9943,8 @@ function createUsersApiHandlers(config) {
9607
9943
  }, {
9608
9944
  status: 404
9609
9945
  });
9610
- } catch {
9946
+ } catch (err) {
9947
+ console.error("[users.update]", err);
9611
9948
  return json({
9612
9949
  error: "Server Error"
9613
9950
  }, {
@@ -12655,6 +12992,39 @@ function slugify(input) {
12655
12992
  return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
12656
12993
  }
12657
12994
  chunkUSNT2KNT_cjs.__name(slugify, "slugify");
12995
+ async function userHasActiveVendor(em, entityMap, userId) {
12996
+ const vendorRepo = em.getRepository(entityMap.vendors);
12997
+ const owned = await vendorRepo.findOne({
12998
+ where: {
12999
+ userId,
13000
+ deleted: false
13001
+ }
13002
+ });
13003
+ if (owned) return true;
13004
+ if (!entityMap.vendor_users) return false;
13005
+ 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', {
13006
+ userId
13007
+ }).getOne();
13008
+ return !!link;
13009
+ }
13010
+ chunkUSNT2KNT_cjs.__name(userHasActiveVendor, "userHasActiveVendor");
13011
+ async function userHadAnyVendor(em, entityMap, userId) {
13012
+ const vendorRepo = em.getRepository(entityMap.vendors);
13013
+ const owned = await vendorRepo.findOne({
13014
+ where: {
13015
+ userId
13016
+ }
13017
+ });
13018
+ if (owned) return true;
13019
+ if (!entityMap.vendor_users) return false;
13020
+ const link = await em.getRepository(entityMap.vendor_users).findOne({
13021
+ where: {
13022
+ userId
13023
+ }
13024
+ });
13025
+ return !!link;
13026
+ }
13027
+ chunkUSNT2KNT_cjs.__name(userHadAnyVendor, "userHadAnyVendor");
12658
13028
  function vendorOnboardErrorResponse(json, err) {
12659
13029
  const msg = err instanceof Error ? err.message : String(err);
12660
13030
  console.error("[vendor-onboard]", err);
@@ -12753,14 +13123,80 @@ function createVendorOnboardHandlers(config) {
12753
13123
  chunkUSNT2KNT_cjs.__name(gateAdmin, "gateAdmin");
12754
13124
  async function resolveActiveVendorId(u) {
12755
13125
  const scope = chunkX6UQFV4X_cjs.resolveVendorScopeFromSessionUser(u);
12756
- if (scope.type === "vendor") return scope.vendorId;
12757
- if (scope.type === "all") {
12758
- const id = u.activeVendorId ?? u.vendorIds?.[0];
12759
- return id != null && Number.isFinite(id) ? id : null;
13126
+ const candidates = [];
13127
+ if (scope.type === "vendor") candidates.push(scope.vendorId);
13128
+ if (scope.type === "all" || scope.type === "vendor") {
13129
+ const preferred = u.activeVendorId ?? u.vendorIds?.[0];
13130
+ if (preferred != null && Number.isFinite(preferred)) candidates.push(Number(preferred));
13131
+ for (const id of u.vendorIds ?? []) {
13132
+ if (Number.isFinite(id)) candidates.push(Number(id));
13133
+ }
13134
+ }
13135
+ if (entityMap.vendors && candidates.length > 0) {
13136
+ const unique = [
13137
+ ...new Set(candidates.filter((id) => id > 0))
13138
+ ];
13139
+ for (const id of unique) {
13140
+ try {
13141
+ const row = await dataSource.getRepository(entityMap.vendors).findOne({
13142
+ where: {
13143
+ id,
13144
+ deleted: false
13145
+ }
13146
+ });
13147
+ if (row) return id;
13148
+ } catch {
13149
+ }
13150
+ }
13151
+ }
13152
+ const uid = u.id != null ? Number(u.id) : NaN;
13153
+ if (Number.isFinite(uid) && entityMap.vendors) {
13154
+ try {
13155
+ const owned = await dataSource.getRepository(entityMap.vendors).findOne({
13156
+ where: {
13157
+ userId: uid,
13158
+ deleted: false
13159
+ },
13160
+ order: {
13161
+ id: "ASC"
13162
+ }
13163
+ });
13164
+ const vid = owned ? Number(owned.id) : NaN;
13165
+ if (Number.isFinite(vid) && vid > 0) return vid;
13166
+ } catch {
13167
+ }
12760
13168
  }
12761
13169
  return null;
12762
13170
  }
12763
13171
  chunkUSNT2KNT_cjs.__name(resolveActiveVendorId, "resolveActiveVendorId");
13172
+ async function gateVendorPortal() {
13173
+ const u = await getSessionUser();
13174
+ if (!u?.email) return json({
13175
+ error: "Unauthorized"
13176
+ }, {
13177
+ status: 401
13178
+ });
13179
+ if (!chunkX6UQFV4X_cjs.isVendorPortalUser(u) && !chunkX6UQFV4X_cjs.isPlatformAdministrator(u)) {
13180
+ return json({
13181
+ error: "Forbidden"
13182
+ }, {
13183
+ status: 403
13184
+ });
13185
+ }
13186
+ const vendorId = await resolveActiveVendorId(u);
13187
+ if (vendorId == null) {
13188
+ return json({
13189
+ error: "No vendor is linked to your account."
13190
+ }, {
13191
+ status: 400
13192
+ });
13193
+ }
13194
+ return {
13195
+ user: u,
13196
+ vendorId
13197
+ };
13198
+ }
13199
+ chunkUSNT2KNT_cjs.__name(gateVendorPortal, "gateVendorPortal");
12764
13200
  async function gateVendorTeam() {
12765
13201
  const u = await getSessionUser();
12766
13202
  if (!u?.email) return json({
@@ -12814,6 +13250,40 @@ function createVendorOnboardHandlers(config) {
12814
13250
  }
12815
13251
  chunkUSNT2KNT_cjs.__name(trySendVendorOnboardEmails, "trySendVendorOnboardEmails");
12816
13252
  return {
13253
+ /** GET /api/admin/vendor/profile — current user's store (server-resolved vendor id). */
13254
+ async getProfile() {
13255
+ const gated = await gateVendorPortal();
13256
+ if (gated instanceof Response) return gated;
13257
+ const { vendorId } = gated;
13258
+ if (!entityMap.vendors) {
13259
+ return json({
13260
+ error: "Vendors not configured"
13261
+ }, {
13262
+ status: 500
13263
+ });
13264
+ }
13265
+ try {
13266
+ const row = await dataSource.getRepository(entityMap.vendors).findOne({
13267
+ where: {
13268
+ id: vendorId,
13269
+ deleted: false
13270
+ }
13271
+ });
13272
+ if (!row) return json({
13273
+ error: "Vendor not found"
13274
+ }, {
13275
+ status: 404
13276
+ });
13277
+ return json(row);
13278
+ } catch (e) {
13279
+ console.error("[vendor.profile]", e);
13280
+ return json({
13281
+ error: "Failed to load vendor profile"
13282
+ }, {
13283
+ status: 500
13284
+ });
13285
+ }
13286
+ },
12817
13287
  /** POST /api/admin/vendors/onboard — create vendor + owner user + vendor_users + invite */
12818
13288
  async onboard(req) {
12819
13289
  const err = await gateAdmin();
@@ -12882,6 +13352,7 @@ function createVendorOnboardHandlers(config) {
12882
13352
  }
12883
13353
  });
12884
13354
  if (dupVendor) throw new Error("VENDOR_SLUG_EXISTS");
13355
+ await retireSoftDeletedUniqueValue(vendorRepo, "slug", slug);
12885
13356
  let ownerGroup = await groupRepo.findOne({
12886
13357
  where: {
12887
13358
  name: chunkX6UQFV4X_cjs.VENDOR_OWNER_GROUP_NAME,
@@ -12910,8 +13381,15 @@ function createVendorOnboardHandlers(config) {
12910
13381
  email: userEmail
12911
13382
  }
12912
13383
  });
12913
- if (existingUser && !existingUser.deleted) throw new Error("USER_EMAIL_EXISTS");
12914
- const newUser = existingUser?.deleted ? await (async () => {
13384
+ if (existingUser && !existingUser.deleted) {
13385
+ if (await userHasActiveVendor(em, entityMap, existingUser.id)) {
13386
+ throw new Error("USER_EMAIL_EXISTS");
13387
+ }
13388
+ if (!await userHadAnyVendor(em, entityMap, existingUser.id)) {
13389
+ throw new Error("USER_EMAIL_EXISTS");
13390
+ }
13391
+ }
13392
+ const newUser = existingUser ? await (async () => {
12915
13393
  await userRepo.update(existingUser.id, {
12916
13394
  deleted: false,
12917
13395
  deletedAt: null,
@@ -15994,10 +16472,12 @@ exports.Customer = class Customer {
15994
16472
  chunkUSNT2KNT_cjs.__name(this, "Customer");
15995
16473
  }
15996
16474
  id;
16475
+ /** Set only when the customer can log in (linked `users` row). Admin/guest customers stay null. */
15997
16476
  userId;
15998
16477
  user;
15999
16478
  name;
16000
16479
  email;
16480
+ /** Optional; multiple customers may have null (PostgreSQL UNIQUE allows multiple NULLs). */
16001
16481
  phone;
16002
16482
  createdAt;
16003
16483
  updatedAt;
@@ -16012,8 +16492,10 @@ _ts_decorate18([
16012
16492
  _ts_metadata18("design:type", Number)
16013
16493
  ], exports.Customer.prototype, "id", void 0);
16014
16494
  _ts_decorate18([
16015
- typeorm.Column("int"),
16016
- _ts_metadata18("design:type", Number)
16495
+ typeorm.Column("int", {
16496
+ nullable: true
16497
+ }),
16498
+ _ts_metadata18("design:type", Object)
16017
16499
  ], exports.Customer.prototype, "userId", void 0);
16018
16500
  _ts_decorate18([
16019
16501
  typeorm.ManyToOne(() => exports.User, {
@@ -16036,9 +16518,10 @@ _ts_decorate18([
16036
16518
  ], exports.Customer.prototype, "email", void 0);
16037
16519
  _ts_decorate18([
16038
16520
  typeorm.Column("varchar", {
16039
- unique: true
16521
+ unique: true,
16522
+ nullable: true
16040
16523
  }),
16041
- _ts_metadata18("design:type", String)
16524
+ _ts_metadata18("design:type", Object)
16042
16525
  ], exports.Customer.prototype, "phone", void 0);
16043
16526
  _ts_decorate18([
16044
16527
  typeorm.Column({
@@ -26622,6 +27105,14 @@ function createCmsApiHandler(config) {
26622
27105
  });
26623
27106
  return vendorHandlers.switchVendor(req);
26624
27107
  }
27108
+ if (path2[0] === "admin" && path2[1] === "vendor" && path2[2] === "profile" && path2.length === 3 && m === "GET") {
27109
+ if (!vendorHandlers) return config.json({
27110
+ error: "Not found"
27111
+ }, {
27112
+ status: 404
27113
+ });
27114
+ return vendorHandlers.getProfile();
27115
+ }
26625
27116
  if (path2[0] === "admin" && path2[1] === "vendor" && path2[2] === "roles" && vendorRolesHandlers) {
26626
27117
  if (path2.length === 3 && m === "GET") return vendorRolesHandlers.list();
26627
27118
  if (path2.length === 3 && m === "POST") return vendorRolesHandlers.create(req);
@@ -29091,13 +29582,36 @@ function createStorefrontApiHandler(config) {
29091
29582
  status: 400
29092
29583
  };
29093
29584
  }
29585
+ const list = [
29586
+ ...vendorIds
29587
+ ];
29094
29588
  return {
29095
- vendorId: [
29096
- ...vendorIds
29097
- ][0]
29589
+ vendorId: list[0],
29590
+ vendorIds: list
29098
29591
  };
29099
29592
  }
29100
29593
  chunkUSNT2KNT_cjs.__name(resolveSingleVendorIdFromCart, "resolveSingleVendorIdFromCart");
29594
+ async function linkOrderContactToVendors(contactId, vendorIds) {
29595
+ try {
29596
+ const contact = await contactRepo().findOne({
29597
+ where: {
29598
+ id: contactId,
29599
+ deleted: false
29600
+ }
29601
+ });
29602
+ if (!contact) return;
29603
+ const email = String(contact.email ?? "").trim().toLowerCase();
29604
+ if (!email) return;
29605
+ await ensureVendorCustomersForOrder(dataSource, entityMap, vendorIds, contactId, {
29606
+ name: String(contact.name ?? "").trim() || email.split("@")[0] || "Customer",
29607
+ email,
29608
+ phone: contact.phone ?? null
29609
+ });
29610
+ } catch (err) {
29611
+ console.error("[storefront] vendor_customers link failed", err);
29612
+ }
29613
+ }
29614
+ chunkUSNT2KNT_cjs.__name(linkOrderContactToVendors, "linkOrderContactToVendors");
29101
29615
  function roundMoney3(n) {
29102
29616
  return Math.round(n * 100) / 100;
29103
29617
  }
@@ -29815,7 +30329,8 @@ function createStorefrontApiHandler(config) {
29815
30329
  const repo = dataSource.getRepository(entityMap.product_variants);
29816
30330
  const rows = await repo.find({
29817
30331
  where: {
29818
- productId
30332
+ productId,
30333
+ status: "available"
29819
30334
  },
29820
30335
  order: {
29821
30336
  id: "ASC"
@@ -29848,6 +30363,52 @@ function createStorefrontApiHandler(config) {
29848
30363
  return result;
29849
30364
  }
29850
30365
  chunkUSNT2KNT_cjs.__name(loadProductVariantsWithPricing, "loadProductVariantsWithPricing");
30366
+ async function loadApplicableVendorPolicies(productId, vendorId) {
30367
+ if (!entityMap.refund_policies) return [];
30368
+ const policyRepo = dataSource.getRepository(entityMap.refund_policies);
30369
+ const format = /* @__PURE__ */ chunkUSNT2KNT_cjs.__name((row) => ({
30370
+ id: Number(row.id),
30371
+ name: String(row.name ?? "").trim(),
30372
+ desc: row.desc != null ? String(row.desc) : null,
30373
+ refundWindowDays: Number(row.refundWindowDays) || 0,
30374
+ type: String(row.type ?? "percentage"),
30375
+ value: Number(row.value) || 0
30376
+ }), "format");
30377
+ const linkedIds = /* @__PURE__ */ new Set();
30378
+ if (entityMap.product_config) {
30379
+ const configs = await dataSource.getRepository(entityMap.product_config).find({
30380
+ where: {
30381
+ productId
30382
+ }
30383
+ });
30384
+ for (const c of configs) {
30385
+ const id = Number(c.refundPolicyId);
30386
+ if (Number.isFinite(id) && id > 0) linkedIds.add(id);
30387
+ }
30388
+ }
30389
+ if (linkedIds.size > 0) {
30390
+ const rows = await policyRepo.find({
30391
+ where: {
30392
+ id: typeorm.In([
30393
+ ...linkedIds
30394
+ ])
30395
+ }
30396
+ });
30397
+ return rows.map((r) => format(r)).filter((p) => p.name).sort((a, b) => a.id - b.id);
30398
+ }
30399
+ if (vendorId == null || !Number.isFinite(Number(vendorId))) return [];
30400
+ const vendorRows = await policyRepo.find({
30401
+ where: {
30402
+ vendorId: Number(vendorId),
30403
+ status: "active"
30404
+ },
30405
+ order: {
30406
+ id: "ASC"
30407
+ }
30408
+ });
30409
+ return vendorRows.map((r) => format(r)).filter((p) => p.name);
30410
+ }
30411
+ chunkUSNT2KNT_cjs.__name(loadApplicableVendorPolicies, "loadApplicableVendorPolicies");
29851
30412
  return {
29852
30413
  async handle(method, path2, req) {
29853
30414
  try {
@@ -29915,13 +30476,11 @@ function createStorefrontApiHandler(config) {
29915
30476
  const url = new URL(req.url || "", "http://localhost");
29916
30477
  const collectionSlug = url.searchParams.get("collection")?.trim();
29917
30478
  const collectionId = url.searchParams.get("collectionId");
30479
+ const q = url.searchParams.get("q")?.trim() ?? "";
29918
30480
  const limit = Math.min(100, Math.max(1, parseInt(url.searchParams.get("limit") || "20", 10)));
29919
30481
  const offset = Math.max(0, parseInt(url.searchParams.get("offset") || "0", 10));
29920
- const where = {
29921
- status: "available",
29922
- deleted: false
29923
- };
29924
30482
  let collectionFilter = null;
30483
+ let collectionIdFilter = null;
29925
30484
  if (collectionSlug) {
29926
30485
  let col = null;
29927
30486
  if (/^\d+$/.test(collectionSlug)) {
@@ -29948,30 +30507,59 @@ function createStorefrontApiHandler(config) {
29948
30507
  collection: null
29949
30508
  });
29950
30509
  }
29951
- where.collectionId = col.id;
30510
+ collectionIdFilter = Number(col.id);
29952
30511
  collectionFilter = {
29953
30512
  name: col.name,
29954
30513
  slug: col.slug
29955
30514
  };
29956
30515
  } else if (collectionId) {
29957
30516
  const cid = parseInt(collectionId, 10);
29958
- if (Number.isFinite(cid)) where.collectionId = cid;
30517
+ if (Number.isFinite(cid)) collectionIdFilter = cid;
30518
+ }
30519
+ let items = [];
30520
+ let total = 0;
30521
+ if (q) {
30522
+ const like = `%${q.replace(/[%_]/g, "\\$&")}%`;
30523
+ const qb = productRepo().createQueryBuilder("p").where("p.status = :status", {
30524
+ status: "available"
30525
+ }).andWhere("p.deleted = :del", {
30526
+ del: false
30527
+ }).andWhere(`(p.name ILIKE :like OR p.slug ILIKE :like OR COALESCE(p.sku, '') ILIKE :like OR COALESCE(p.title, '') ILIKE :like OR COALESCE(p.metadata->>'description', '') ILIKE :like)`, {
30528
+ like
30529
+ }).orderBy("p.id", "ASC").take(limit).skip(offset);
30530
+ if (collectionIdFilter != null) {
30531
+ qb.andWhere("p.collectionId = :cid", {
30532
+ cid: collectionIdFilter
30533
+ });
30534
+ }
30535
+ [items, total] = await qb.getManyAndCount();
30536
+ } else {
30537
+ const where = {
30538
+ status: "available",
30539
+ deleted: false
30540
+ };
30541
+ if (collectionIdFilter != null) where.collectionId = collectionIdFilter;
30542
+ const result = await productRepo().findAndCount({
30543
+ where,
30544
+ order: {
30545
+ id: "ASC"
30546
+ },
30547
+ take: limit,
30548
+ skip: offset
30549
+ });
30550
+ items = result[0];
30551
+ total = result[1];
29959
30552
  }
29960
- const [items, total] = await productRepo().findAndCount({
29961
- where,
29962
- order: {
29963
- id: "ASC"
29964
- },
29965
- take: limit,
29966
- skip: offset
29967
- });
29968
30553
  const products = await Promise.all(items.map((item) => enrichProductPricing(item)));
29969
30554
  return json({
29970
30555
  products,
29971
30556
  total,
29972
30557
  ...collectionFilter && {
29973
30558
  collection: collectionFilter
29974
- }
30559
+ },
30560
+ ...q ? {
30561
+ q
30562
+ } : {}
29975
30563
  });
29976
30564
  }
29977
30565
  if (path2[0] === "products" && path2.length === 2 && method === "GET") {
@@ -30007,12 +30595,14 @@ function createStorefrontApiHandler(config) {
30007
30595
  const pricing = await resolveProductEventPricing(Number(p.id));
30008
30596
  const enriched = await enrichProductPricing(p, pricing);
30009
30597
  const variants = await loadProductVariantsWithPricing(Number(p.id), pricing);
30598
+ const policies = await loadApplicableVendorPolicies(Number(p.id), p.vendorId != null ? Number(p.vendorId) : null);
30010
30599
  return json({
30011
30600
  ...enriched,
30012
30601
  attributes: attributeTags,
30013
30602
  ...variants.length ? {
30014
30603
  variants
30015
- } : {}
30604
+ } : {},
30605
+ policies
30016
30606
  });
30017
30607
  }
30018
30608
  if (path2[0] === "collections" && path2.length === 1 && method === "GET") {
@@ -30824,24 +31414,61 @@ function createStorefrontApiHandler(config) {
30824
31414
  }, {
30825
31415
  status: 404
30826
31416
  });
31417
+ const rawVariantId = body.variantId ?? body.variant_id;
31418
+ const variantIdNum = rawVariantId != null && String(rawVariantId).trim() !== "" ? Number(rawVariantId) : NaN;
31419
+ const hasVariantId = Number.isFinite(variantIdNum) && variantIdNum > 0;
31420
+ const bodyMeta = body.metadata && typeof body.metadata === "object" && !Array.isArray(body.metadata) ? body.metadata : {};
31421
+ const optionsRaw = body.options && typeof body.options === "object" && !Array.isArray(body.options) ? body.options : bodyMeta.options && typeof bodyMeta.options === "object" && !Array.isArray(bodyMeta.options) ? bodyMeta.options : null;
31422
+ const options = optionsRaw && Object.fromEntries(Object.entries(optionsRaw).map(([k, v]) => [
31423
+ String(k),
31424
+ String(v ?? "").trim()
31425
+ ]).filter(([, v]) => v.length > 0));
31426
+ const lineMetadata = {
31427
+ ...bodyMeta,
31428
+ ...hasVariantId ? {
31429
+ variantId: variantIdNum
31430
+ } : {},
31431
+ ...options && Object.keys(options).length ? {
31432
+ options
31433
+ } : {}
31434
+ };
31435
+ if (typeof bodyMeta.imageUrl === "string" && bodyMeta.imageUrl.trim()) {
31436
+ lineMetadata.imageUrl = bodyMeta.imageUrl.trim();
31437
+ }
31438
+ if (typeof bodyMeta.title === "string" && bodyMeta.title.trim()) {
31439
+ lineMetadata.title = bodyMeta.title.trim();
31440
+ }
31441
+ const metadataPayload = Object.keys(lineMetadata).length ? lineMetadata : null;
30827
31442
  const { cart, setCookie, err } = await getOrCreateCart(req);
30828
31443
  if (err) return err;
30829
31444
  const cartId = cart.id;
30830
- const existing = await cartItemRepo().findOne({
31445
+ const sameProductLines = await cartItemRepo().find({
30831
31446
  where: {
30832
31447
  cartId,
30833
31448
  productId
30834
31449
  }
30835
31450
  });
31451
+ const existing = sameProductLines.find((row) => {
31452
+ const m = row.metadata;
31453
+ const existingVid = m?.variantId ?? m?.variant_id;
31454
+ if (hasVariantId) {
31455
+ return Number(existingVid) === variantIdNum;
31456
+ }
31457
+ return existingVid == null || existingVid === "";
31458
+ });
30836
31459
  if (existing) {
30837
31460
  await cartItemRepo().update(existing.id, {
30838
- quantity: existing.quantity + quantity
31461
+ quantity: existing.quantity + quantity,
31462
+ ...metadataPayload ? {
31463
+ metadata: metadataPayload
31464
+ } : {}
30839
31465
  });
30840
31466
  } else {
30841
31467
  await cartItemRepo().save(cartItemRepo().create({
30842
31468
  cartId,
30843
31469
  productId,
30844
- quantity
31470
+ quantity,
31471
+ metadata: metadataPayload
30845
31472
  }));
30846
31473
  }
30847
31474
  await cartRepo().update(cartId, {
@@ -31306,6 +31933,7 @@ function createStorefrontApiHandler(config) {
31306
31933
  taxCode: line.taxCode
31307
31934
  }));
31308
31935
  }
31936
+ await linkOrderContactToVendors(contactId, vendorRes.vendorIds);
31309
31937
  fireOrderPlacedNotification(oid);
31310
31938
  return json({
31311
31939
  orderId: oid,
@@ -31378,6 +32006,7 @@ function createStorefrontApiHandler(config) {
31378
32006
  taxCode: line.taxCode
31379
32007
  }));
31380
32008
  }
32009
+ await linkOrderContactToVendors(contactId, vendorResChk.vendorIds);
31381
32010
  await cartItemRepo().delete({
31382
32011
  cartId: cart.id
31383
32012
  });
@@ -31686,13 +32315,16 @@ exports.createUserProfileHandler = createUserProfileHandler;
31686
32315
  exports.createUsersApiHandlers = createUsersApiHandlers;
31687
32316
  exports.createVendorDashboardHandler = createVendorDashboardHandler;
31688
32317
  exports.createVendorOnboardHandlers = createVendorOnboardHandlers;
32318
+ exports.customerPhoneForEmail = customerPhoneForEmail;
31689
32319
  exports.customerPhoneForUser = customerPhoneForUser;
31690
32320
  exports.daysBeforeEventStart = daysBeforeEventStart;
31691
32321
  exports.describeEventTierPolicy = describeEventTierPolicy;
31692
32322
  exports.ensureCustomerForUser = ensureCustomerForUser;
32323
+ exports.ensureCustomerRecord = ensureCustomerRecord;
31693
32324
  exports.ensureMessagingPluginsOnCms = ensureMessagingPluginsOnCms;
31694
32325
  exports.ensureScheduleQueueWorker = ensureScheduleQueueWorker;
31695
32326
  exports.ensureVendorCustomerForOrderContact = ensureVendorCustomerForOrderContact;
32327
+ exports.ensureVendorCustomersForOrder = ensureVendorCustomersForOrder;
31696
32328
  exports.findActiveRefundPolicyForVendor = findActiveRefundPolicyForVendor;
31697
32329
  exports.findVendorByInviteToken = findVendorByInviteToken;
31698
32330
  exports.formatTierRange = formatTierRange;
@@ -31710,6 +32342,7 @@ exports.invalidateRequireEventApprovalCache = invalidateRequireEventApprovalCach
31710
32342
  exports.invalidateRequireProductApprovalCache = invalidateRequireProductApprovalCache;
31711
32343
  exports.invalidateVendorCatalogCreateFlagsCache = invalidateVendorCatalogCreateFlagsCache;
31712
32344
  exports.isCustomerTypeContact = isCustomerTypeContact;
32345
+ exports.isSyntheticCustomerPhone = isSyntheticCustomerPhone;
31713
32346
  exports.isZipMedia = isZipMedia;
31714
32347
  exports.linkUnclaimedContactToUser = linkUnclaimedContactToUser;
31715
32348
  exports.llmAgentToChatAgentOptions = llmAgentToChatAgentOptions;
@@ -31721,6 +32354,7 @@ exports.metaFetchUserManagedPages = metaFetchUserManagedPages;
31721
32354
  exports.metaPostPageFeed = metaPostPageFeed;
31722
32355
  exports.metaPostPagePhoto = metaPostPagePhoto;
31723
32356
  exports.metaResolvePageAccessToken = metaResolvePageAccessToken;
32357
+ exports.normalizeCustomerPhone = normalizeCustomerPhone;
31724
32358
  exports.normalizePhoneE164 = normalizePhoneE164;
31725
32359
  exports.normalizeRefundTiers = normalizeRefundTiers;
31726
32360
  exports.overlayCmsPlugins = overlayCmsPlugins;