@infuro/cms-core 1.0.44 → 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];
@@ -1100,11 +1130,118 @@ function pgErrorCode(err) {
1100
1130
  return driver?.code ?? err.code;
1101
1131
  }
1102
1132
  chunkUSNT2KNT_cjs.__name(pgErrorCode, "pgErrorCode");
1103
- function customerPhoneForUser(userId, phone) {
1133
+ function normalizeCustomerPhone(phone) {
1104
1134
  const p = typeof phone === "string" ? phone.trim() : "";
1105
- 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);
1106
1148
  }
1107
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");
1108
1245
  async function restoreCustomerRow(repo, row, user, name, email, phone) {
1109
1246
  const id = row.id;
1110
1247
  await repo.update(id, {
@@ -1126,7 +1263,7 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1126
1263
  const repo = dsOrEm.getRepository(customerEntity);
1127
1264
  const email = normalizeEmail(user.email);
1128
1265
  const name = String(user.name ?? "").trim() || email.split("@")[0] || "User";
1129
- const phone = customerPhoneForUser(user.id, overrides?.phone ?? user.phone);
1266
+ const phone = normalizeCustomerPhone(overrides?.phone ?? user.phone);
1130
1267
  let row = await repo.findOne({
1131
1268
  where: {
1132
1269
  userId: user.id,
@@ -1134,10 +1271,11 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1134
1271
  }
1135
1272
  });
1136
1273
  if (row) {
1274
+ const nextPhone = resolveNextPhone(phone, row.phone);
1137
1275
  await repo.update(row.id, {
1138
1276
  name,
1139
1277
  email,
1140
- phone,
1278
+ phone: nextPhone,
1141
1279
  updatedAt: /* @__PURE__ */ new Date()
1142
1280
  });
1143
1281
  return {
@@ -1155,10 +1293,11 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1155
1293
  if (existingUserId != null && existingUserId !== user.id) {
1156
1294
  return null;
1157
1295
  }
1296
+ const nextPhone = resolveNextPhone(phone, row.phone);
1158
1297
  await repo.update(row.id, {
1159
1298
  userId: user.id,
1160
1299
  name,
1161
- phone,
1300
+ phone: nextPhone,
1162
1301
  updatedAt: /* @__PURE__ */ new Date()
1163
1302
  });
1164
1303
  return {
@@ -1173,21 +1312,23 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1173
1312
  if (deletedByEmail) {
1174
1313
  const existingUserId = deletedByEmail.userId;
1175
1314
  if (existingUserId != null && existingUserId !== user.id) return null;
1176
- return restoreCustomerRow(repo, deletedByEmail, user, name, email, phone);
1315
+ return restoreCustomerRow(repo, deletedByEmail, user, name, email, resolveNextPhone(phone, deletedByEmail.phone));
1177
1316
  }
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
- });
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);
1189
1331
  }
1190
- return restoreCustomerRow(repo, deletedByPhone, user, name, email, phone);
1191
1332
  }
1192
1333
  try {
1193
1334
  const created = await repo.save(repo.create({
@@ -1208,34 +1349,22 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
1208
1349
  userId: user.id
1209
1350
  }
1210
1351
  });
1211
- 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
+ }
1212
1355
  row = await repo.findOne({
1213
1356
  where: {
1214
1357
  email
1215
1358
  }
1216
1359
  });
1217
1360
  if (row && (row.userId ?? user.id) === user.id) {
1218
- return restoreCustomerRow(repo, row, user, name, email, phone);
1361
+ return restoreCustomerRow(repo, row, user, name, email, resolveNextPhone(phone, row.phone));
1219
1362
  }
1220
1363
  if (code === "23505") return null;
1221
1364
  throw err;
1222
1365
  }
1223
1366
  }
1224
1367
  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
1368
 
1240
1369
  // src/lib/vendor-customer-contacts.ts
1241
1370
  function isCustomerTypeContact(type) {
@@ -1328,8 +1457,27 @@ function resolveVendorIdForContactCheck(scope, bodyOrRow) {
1328
1457
  return Number.isFinite(n) ? n : null;
1329
1458
  }
1330
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");
1331
1472
  async function ensureVendorCustomerForOrderContact(dataSource, entityMap, vendorId, contactId, details) {
1332
- 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;
1333
1481
  const vcEntity = entityMap.vendor_customers;
1334
1482
  const contactEntity = entityMap.contacts;
1335
1483
  if (!vcEntity || !contactEntity) return;
@@ -1355,96 +1503,61 @@ async function ensureVendorCustomerForOrderContact(dataSource, entityMap, vendor
1355
1503
  }
1356
1504
  let customerId = null;
1357
1505
  if (entityMap.customer) {
1358
- const customerRepo = dataSource.getRepository(entityMap.customer);
1359
- const existingCustomer = await customerRepo.findOne({
1360
- where: {
1361
- email,
1362
- deleted: false
1363
- }
1506
+ const ensured = await ensureCustomerRecord(dataSource, entityMap.customer, {
1507
+ name,
1508
+ email,
1509
+ phone,
1510
+ userId: null
1364
1511
  });
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({
1512
+ if (ensured) customerId = ensured.id;
1513
+ }
1514
+ for (const vendorId of vendors) {
1515
+ if (customerId != null) {
1516
+ const byCustomer = await vcRepo.findOne({
1370
1517
  where: {
1371
- email,
1372
- deleted: false
1518
+ vendorId,
1519
+ customerId
1373
1520
  }
1374
1521
  });
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
- }
1522
+ if (byCustomer) {
1523
+ const existingContactId = byCustomer.contactId;
1524
+ if (existingContactId == null) {
1525
+ await vcRepo.update(byCustomer.id, {
1526
+ contactId
1384
1527
  });
1385
- if (customerGroup) groupId = Number(customerGroup.id);
1386
1528
  }
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
- }));
1529
+ continue;
1396
1530
  }
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
1531
  }
1409
- }
1410
- if (customerId != null) {
1411
- const byCustomer = await vcRepo.findOne({
1532
+ const byContact = await vcRepo.findOne({
1412
1533
  where: {
1413
1534
  vendorId,
1414
- customerId
1535
+ contactId
1415
1536
  }
1416
1537
  });
1417
- if (byCustomer) {
1418
- const existingContactId = byCustomer.contactId;
1419
- if (existingContactId == null) {
1420
- await vcRepo.update(byCustomer.id, {
1421
- contactId
1538
+ if (byContact) {
1539
+ if (customerId != null && byContact.customerId == null) {
1540
+ await vcRepo.update(byContact.id, {
1541
+ customerId
1422
1542
  });
1423
1543
  }
1424
- return;
1544
+ continue;
1425
1545
  }
1426
- }
1427
- const byContact = await vcRepo.findOne({
1428
- where: {
1429
- vendorId,
1430
- 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
+ }));
1431
1557
  }
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
1558
  }
1442
- await vcRepo.save(vcRepo.create({
1443
- vendorId,
1444
- contactId
1445
- }));
1446
1559
  }
1447
- chunkUSNT2KNT_cjs.__name(ensureVendorCustomerForOrderContact, "ensureVendorCustomerForOrderContact");
1560
+ chunkUSNT2KNT_cjs.__name(ensureVendorCustomersForOrder, "ensureVendorCustomersForOrder");
1448
1561
 
1449
1562
  // src/lib/currency-prices.ts
1450
1563
  function normalizeCurrencyCode(code) {
@@ -3810,23 +3923,24 @@ function createCrudHandler(dataSource, entityMap, options) {
3810
3923
  if (resource === "vendor_customers" && entityMap["customer"]) {
3811
3924
  const scope = await resolveVendorCustomersScope(dataSource, entityMap, resolveScope);
3812
3925
  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);
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);
3814
3927
  applyVendorScopeToQueryBuilder(qb, "vc", scope);
3815
3928
  if (search && typeof search === "string" && search.trim()) {
3816
3929
  const term = `%${search.trim()}%`;
3817
- 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)", {
3818
3931
  term
3819
3932
  });
3820
3933
  }
3821
3934
  const [rows, total2] = await qb.getManyAndCount();
3822
3935
  const data2 = rows.map((row) => {
3823
3936
  const customer = row.customer;
3937
+ const contact = row.contact;
3824
3938
  return {
3825
3939
  ...row,
3826
- name: customer?.name ?? null,
3827
- email: customer?.email ?? null,
3828
- phone: customer?.phone ?? null,
3829
- 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
3830
3944
  };
3831
3945
  });
3832
3946
  return json({
@@ -4160,7 +4274,11 @@ function createCrudHandler(dataSource, entityMap, options) {
4160
4274
  } else if (resource === "collections") {
4161
4275
  if (scope.type === "vendor") {
4162
4276
  where = mergeVendorScopeIntoWhere(where, scope, resource, catalogFlags);
4163
- } 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") {
4164
4282
  where = mergeListWhereAnd(where, {
4165
4283
  isCatalog: false
4166
4284
  });
@@ -4357,41 +4475,17 @@ function createCrudHandler(dataSource, entityMap, options) {
4357
4475
  status: 503
4358
4476
  });
4359
4477
  }
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 {
4478
+ if (!entityMap["contacts"]) {
4385
4479
  return json({
4386
- error: "user_groups entity not configured"
4480
+ error: "Contacts entity not configured"
4387
4481
  }, {
4388
4482
  status: 503
4389
4483
  });
4390
4484
  }
4391
4485
  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();
4486
+ const email = String(body.email ?? "").trim().toLowerCase();
4487
+ const phoneRaw = String(body.phone ?? "").trim();
4488
+ const phone = phoneRaw || null;
4395
4489
  if (!name) return json({
4396
4490
  error: "name is required"
4397
4491
  }, {
@@ -4402,25 +4496,7 @@ function createCrudHandler(dataSource, entityMap, options) {
4402
4496
  }, {
4403
4497
  status: 400
4404
4498
  });
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
4499
  const customerRepo = dataSource.getRepository(entityMap["customer"]);
4423
- const userRepo = dataSource.getRepository(entityMap["users"]);
4424
4500
  const dupCustEmail = await customerRepo.findOne({
4425
4501
  where: {
4426
4502
  email,
@@ -4434,49 +4510,26 @@ function createCrudHandler(dataSource, entityMap, options) {
4434
4510
  status: 409
4435
4511
  });
4436
4512
  }
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
4513
+ if (phone) {
4514
+ const dupCustPhone = await customerRepo.findOne({
4515
+ where: {
4516
+ phone,
4517
+ deleted: false
4518
+ }
4448
4519
  });
4449
- }
4450
- let userId;
4451
- const dupUser = await userRepo.findOne({
4452
- where: {
4453
- email,
4454
- deleted: false
4520
+ if (dupCustPhone) {
4521
+ return json({
4522
+ error: "A customer with this phone number already exists"
4523
+ }, {
4524
+ status: 409
4525
+ });
4455
4526
  }
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
4527
  }
4473
- const savedCustomer = await ensureCustomerForUser(dataSource, entityMap["customer"], {
4474
- id: userId,
4528
+ const savedCustomer = await ensureCustomerRecord(dataSource, entityMap["customer"], {
4475
4529
  name,
4476
4530
  email,
4477
- phone
4478
- }, {
4479
- phone
4531
+ phone,
4532
+ userId: null
4480
4533
  });
4481
4534
  if (!savedCustomer) {
4482
4535
  return json({
@@ -4485,37 +4538,37 @@ function createCrudHandler(dataSource, entityMap, options) {
4485
4538
  status: 409
4486
4539
  });
4487
4540
  }
4488
- const customerRepo2 = dataSource.getRepository(entityMap["customer"]);
4489
- const customerRow = await customerRepo2.findOne({
4541
+ const contactRepo = dataSource.getRepository(entityMap["contacts"]);
4542
+ let contact = await contactRepo.findOne({
4490
4543
  where: {
4491
- id: savedCustomer.id,
4544
+ email,
4492
4545
  deleted: false
4493
4546
  }
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
- }
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
+ };
4518
4570
  }
4571
+ const contactId = Number(contact.id);
4519
4572
  const scope = await resolveVendorCustomersScope(dataSource, entityMap, resolveScope);
4520
4573
  let vendorId = null;
4521
4574
  if (scope.type === "vendor") {
@@ -4531,19 +4584,25 @@ function createCrudHandler(dataSource, entityMap, options) {
4531
4584
  status: 400
4532
4585
  });
4533
4586
  }
4534
- const vcRepo = dataSource.getRepository(entity);
4535
- 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({
4536
4595
  where: {
4537
- customerId: savedCustomer.id
4596
+ id: savedCustomer.id,
4597
+ deleted: false
4538
4598
  }
4539
4599
  });
4540
- if (!existingLink) {
4541
- await vcRepo.save(vcRepo.create({
4542
- customerId: savedCustomer.id,
4543
- vendorId
4544
- }));
4545
- }
4546
- return json(customerRow, {
4600
+ return json(customerRow ?? {
4601
+ id: savedCustomer.id,
4602
+ name,
4603
+ email,
4604
+ phone
4605
+ }, {
4547
4606
  status: 201
4548
4607
  });
4549
4608
  }
@@ -4991,6 +5050,7 @@ function createCrudHandler(dataSource, entityMap, options) {
4991
5050
  }
4992
5051
  const nameRaw = String(body["contact.name"] ?? contact.name ?? "").trim();
4993
5052
  const phoneRaw = body["contact.phone"] ?? contact.phone;
5053
+ const emailForVc = (emailRaw || String(contact.email ?? "")).trim().toLowerCase();
4994
5054
  const phoneToSave = phoneRaw == null || phoneRaw === "" ? null : String(phoneRaw).trim();
4995
5055
  if (phoneToSave && contact.phone !== phoneToSave) {
4996
5056
  await contactRepo.update(contact.id, {
@@ -4999,21 +5059,45 @@ function createCrudHandler(dataSource, entityMap, options) {
4999
5059
  contact.phone = phoneToSave;
5000
5060
  }
5001
5061
  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,
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,
5007
5083
  phone: phoneRaw == null || phoneRaw === "" ? null : String(phoneRaw)
5008
5084
  });
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
- });
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
+ }));
5017
5101
  }
5018
5102
  }
5019
5103
  const randomPart = Math.floor(1e4 + Math.random() * 9e4);
@@ -6793,6 +6877,14 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6793
6877
  }
6794
6878
  }
6795
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
+ }
6796
6888
  return json({
6797
6889
  message: "Deleted successfully"
6798
6890
  }, {
@@ -6823,6 +6915,20 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6823
6915
  };
6824
6916
  }
6825
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");
6826
6932
  var VENDOR_INVITE_METADATA_TOKEN = "inviteToken";
6827
6933
  var VENDOR_INVITE_METADATA_EXPIRES = "inviteExpiresAt";
6828
6934
  var VENDOR_INVITE_METADATA_SENT = "inviteSentAt";
@@ -7555,6 +7661,50 @@ async function findLlmAgentByScope(dataSource, entityMap, scope, options = {}) {
7555
7661
  }
7556
7662
  chunkUSNT2KNT_cjs.__name(findLlmAgentByScope, "findLlmAgentByScope");
7557
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
+
7558
7708
  // src/lib/media-folder-path.ts
7559
7709
  function sanitizeMediaFolderPath(input) {
7560
7710
  if (input == null) return "";
@@ -8948,6 +9098,17 @@ function createFormSaveHandlers(config) {
8948
9098
  });
8949
9099
  const fields = Array.isArray(body.fields) ? body.fields : [];
8950
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
+ }
8951
9112
  const form = await formRepo().save(formRepo().create(formRow));
8952
9113
  for (let i = 0; i < fields.length; i++) {
8953
9114
  const row = normalizeFieldRow(fields[i], form.id);
@@ -8971,6 +9132,14 @@ function createFormSaveHandlers(config) {
8971
9132
  status: 201
8972
9133
  });
8973
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
+ }
8974
9143
  return json({
8975
9144
  error: "Server Error"
8976
9145
  }, {
@@ -9019,6 +9188,20 @@ function createFormSaveHandlers(config) {
9019
9188
  ]) {
9020
9189
  if (body[key] !== void 0) formRow[key] = body[key];
9021
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
+ }
9022
9205
  if (Object.keys(formRow).length > 0) await formRepo().update(formId, formRow);
9023
9206
  await fieldRepo().delete({
9024
9207
  formId
@@ -9047,6 +9230,14 @@ function createFormSaveHandlers(config) {
9047
9230
  status: 404
9048
9231
  });
9049
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
+ }
9050
9241
  return json({
9051
9242
  error: "Server Error"
9052
9243
  }, {
@@ -9628,6 +9819,7 @@ function createUsersApiHandlers(config) {
9628
9819
  "id",
9629
9820
  "name",
9630
9821
  "email",
9822
+ "phone",
9631
9823
  "blocked",
9632
9824
  "createdAt",
9633
9825
  "updatedAt",
@@ -9657,6 +9849,11 @@ function createUsersApiHandlers(config) {
9657
9849
  }
9658
9850
  try {
9659
9851
  const uid = parseInt(id, 10);
9852
+ if (!Number.isFinite(uid)) return json({
9853
+ error: "Invalid id"
9854
+ }, {
9855
+ status: 400
9856
+ });
9660
9857
  const existing = await userRepo().findOne({
9661
9858
  where: {
9662
9859
  id: uid,
@@ -9669,8 +9866,59 @@ function createUsersApiHandlers(config) {
9669
9866
  status: 404
9670
9867
  });
9671
9868
  const body = await req.json();
9672
- const { password: _p, ...safe } = body;
9673
- 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);
9674
9922
  const updated = await userRepo().findOne({
9675
9923
  where: {
9676
9924
  id: uid,
@@ -9683,6 +9931,7 @@ function createUsersApiHandlers(config) {
9683
9931
  "id",
9684
9932
  "name",
9685
9933
  "email",
9934
+ "phone",
9686
9935
  "blocked",
9687
9936
  "createdAt",
9688
9937
  "updatedAt",
@@ -9694,7 +9943,8 @@ function createUsersApiHandlers(config) {
9694
9943
  }, {
9695
9944
  status: 404
9696
9945
  });
9697
- } catch {
9946
+ } catch (err) {
9947
+ console.error("[users.update]", err);
9698
9948
  return json({
9699
9949
  error: "Server Error"
9700
9950
  }, {
@@ -12742,6 +12992,39 @@ function slugify(input) {
12742
12992
  return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
12743
12993
  }
12744
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");
12745
13028
  function vendorOnboardErrorResponse(json, err) {
12746
13029
  const msg = err instanceof Error ? err.message : String(err);
12747
13030
  console.error("[vendor-onboard]", err);
@@ -12840,14 +13123,80 @@ function createVendorOnboardHandlers(config) {
12840
13123
  chunkUSNT2KNT_cjs.__name(gateAdmin, "gateAdmin");
12841
13124
  async function resolveActiveVendorId(u) {
12842
13125
  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;
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
+ }
12847
13168
  }
12848
13169
  return null;
12849
13170
  }
12850
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");
12851
13200
  async function gateVendorTeam() {
12852
13201
  const u = await getSessionUser();
12853
13202
  if (!u?.email) return json({
@@ -12901,6 +13250,40 @@ function createVendorOnboardHandlers(config) {
12901
13250
  }
12902
13251
  chunkUSNT2KNT_cjs.__name(trySendVendorOnboardEmails, "trySendVendorOnboardEmails");
12903
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
+ },
12904
13287
  /** POST /api/admin/vendors/onboard — create vendor + owner user + vendor_users + invite */
12905
13288
  async onboard(req) {
12906
13289
  const err = await gateAdmin();
@@ -12969,6 +13352,7 @@ function createVendorOnboardHandlers(config) {
12969
13352
  }
12970
13353
  });
12971
13354
  if (dupVendor) throw new Error("VENDOR_SLUG_EXISTS");
13355
+ await retireSoftDeletedUniqueValue(vendorRepo, "slug", slug);
12972
13356
  let ownerGroup = await groupRepo.findOne({
12973
13357
  where: {
12974
13358
  name: chunkX6UQFV4X_cjs.VENDOR_OWNER_GROUP_NAME,
@@ -12997,8 +13381,15 @@ function createVendorOnboardHandlers(config) {
12997
13381
  email: userEmail
12998
13382
  }
12999
13383
  });
13000
- if (existingUser && !existingUser.deleted) throw new Error("USER_EMAIL_EXISTS");
13001
- 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 () => {
13002
13393
  await userRepo.update(existingUser.id, {
13003
13394
  deleted: false,
13004
13395
  deletedAt: null,
@@ -16081,10 +16472,12 @@ exports.Customer = class Customer {
16081
16472
  chunkUSNT2KNT_cjs.__name(this, "Customer");
16082
16473
  }
16083
16474
  id;
16475
+ /** Set only when the customer can log in (linked `users` row). Admin/guest customers stay null. */
16084
16476
  userId;
16085
16477
  user;
16086
16478
  name;
16087
16479
  email;
16480
+ /** Optional; multiple customers may have null (PostgreSQL UNIQUE allows multiple NULLs). */
16088
16481
  phone;
16089
16482
  createdAt;
16090
16483
  updatedAt;
@@ -16099,8 +16492,10 @@ _ts_decorate18([
16099
16492
  _ts_metadata18("design:type", Number)
16100
16493
  ], exports.Customer.prototype, "id", void 0);
16101
16494
  _ts_decorate18([
16102
- typeorm.Column("int"),
16103
- _ts_metadata18("design:type", Number)
16495
+ typeorm.Column("int", {
16496
+ nullable: true
16497
+ }),
16498
+ _ts_metadata18("design:type", Object)
16104
16499
  ], exports.Customer.prototype, "userId", void 0);
16105
16500
  _ts_decorate18([
16106
16501
  typeorm.ManyToOne(() => exports.User, {
@@ -16123,9 +16518,10 @@ _ts_decorate18([
16123
16518
  ], exports.Customer.prototype, "email", void 0);
16124
16519
  _ts_decorate18([
16125
16520
  typeorm.Column("varchar", {
16126
- unique: true
16521
+ unique: true,
16522
+ nullable: true
16127
16523
  }),
16128
- _ts_metadata18("design:type", String)
16524
+ _ts_metadata18("design:type", Object)
16129
16525
  ], exports.Customer.prototype, "phone", void 0);
16130
16526
  _ts_decorate18([
16131
16527
  typeorm.Column({
@@ -26709,6 +27105,14 @@ function createCmsApiHandler(config) {
26709
27105
  });
26710
27106
  return vendorHandlers.switchVendor(req);
26711
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
+ }
26712
27116
  if (path2[0] === "admin" && path2[1] === "vendor" && path2[2] === "roles" && vendorRolesHandlers) {
26713
27117
  if (path2.length === 3 && m === "GET") return vendorRolesHandlers.list();
26714
27118
  if (path2.length === 3 && m === "POST") return vendorRolesHandlers.create(req);
@@ -29178,13 +29582,36 @@ function createStorefrontApiHandler(config) {
29178
29582
  status: 400
29179
29583
  };
29180
29584
  }
29585
+ const list = [
29586
+ ...vendorIds
29587
+ ];
29181
29588
  return {
29182
- vendorId: [
29183
- ...vendorIds
29184
- ][0]
29589
+ vendorId: list[0],
29590
+ vendorIds: list
29185
29591
  };
29186
29592
  }
29187
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");
29188
29615
  function roundMoney3(n) {
29189
29616
  return Math.round(n * 100) / 100;
29190
29617
  }
@@ -31506,6 +31933,7 @@ function createStorefrontApiHandler(config) {
31506
31933
  taxCode: line.taxCode
31507
31934
  }));
31508
31935
  }
31936
+ await linkOrderContactToVendors(contactId, vendorRes.vendorIds);
31509
31937
  fireOrderPlacedNotification(oid);
31510
31938
  return json({
31511
31939
  orderId: oid,
@@ -31578,6 +32006,7 @@ function createStorefrontApiHandler(config) {
31578
32006
  taxCode: line.taxCode
31579
32007
  }));
31580
32008
  }
32009
+ await linkOrderContactToVendors(contactId, vendorResChk.vendorIds);
31581
32010
  await cartItemRepo().delete({
31582
32011
  cartId: cart.id
31583
32012
  });
@@ -31886,13 +32315,16 @@ exports.createUserProfileHandler = createUserProfileHandler;
31886
32315
  exports.createUsersApiHandlers = createUsersApiHandlers;
31887
32316
  exports.createVendorDashboardHandler = createVendorDashboardHandler;
31888
32317
  exports.createVendorOnboardHandlers = createVendorOnboardHandlers;
32318
+ exports.customerPhoneForEmail = customerPhoneForEmail;
31889
32319
  exports.customerPhoneForUser = customerPhoneForUser;
31890
32320
  exports.daysBeforeEventStart = daysBeforeEventStart;
31891
32321
  exports.describeEventTierPolicy = describeEventTierPolicy;
31892
32322
  exports.ensureCustomerForUser = ensureCustomerForUser;
32323
+ exports.ensureCustomerRecord = ensureCustomerRecord;
31893
32324
  exports.ensureMessagingPluginsOnCms = ensureMessagingPluginsOnCms;
31894
32325
  exports.ensureScheduleQueueWorker = ensureScheduleQueueWorker;
31895
32326
  exports.ensureVendorCustomerForOrderContact = ensureVendorCustomerForOrderContact;
32327
+ exports.ensureVendorCustomersForOrder = ensureVendorCustomersForOrder;
31896
32328
  exports.findActiveRefundPolicyForVendor = findActiveRefundPolicyForVendor;
31897
32329
  exports.findVendorByInviteToken = findVendorByInviteToken;
31898
32330
  exports.formatTierRange = formatTierRange;
@@ -31910,6 +32342,7 @@ exports.invalidateRequireEventApprovalCache = invalidateRequireEventApprovalCach
31910
32342
  exports.invalidateRequireProductApprovalCache = invalidateRequireProductApprovalCache;
31911
32343
  exports.invalidateVendorCatalogCreateFlagsCache = invalidateVendorCatalogCreateFlagsCache;
31912
32344
  exports.isCustomerTypeContact = isCustomerTypeContact;
32345
+ exports.isSyntheticCustomerPhone = isSyntheticCustomerPhone;
31913
32346
  exports.isZipMedia = isZipMedia;
31914
32347
  exports.linkUnclaimedContactToUser = linkUnclaimedContactToUser;
31915
32348
  exports.llmAgentToChatAgentOptions = llmAgentToChatAgentOptions;
@@ -31921,6 +32354,7 @@ exports.metaFetchUserManagedPages = metaFetchUserManagedPages;
31921
32354
  exports.metaPostPageFeed = metaPostPageFeed;
31922
32355
  exports.metaPostPagePhoto = metaPostPagePhoto;
31923
32356
  exports.metaResolvePageAccessToken = metaResolvePageAccessToken;
32357
+ exports.normalizeCustomerPhone = normalizeCustomerPhone;
31924
32358
  exports.normalizePhoneE164 = normalizePhoneE164;
31925
32359
  exports.normalizeRefundTiers = normalizeRefundTiers;
31926
32360
  exports.overlayCmsPlugins = overlayCmsPlugins;