@infuro/cms-core 1.0.44 → 1.0.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/dist/admin.cjs +1413 -620
- package/dist/admin.d.cts +8 -2
- package/dist/admin.d.ts +8 -2
- package/dist/admin.js +1414 -621
- package/dist/api.cjs +40 -37
- package/dist/api.d.cts +3 -1
- package/dist/api.d.ts +3 -1
- package/dist/api.js +8 -5
- package/dist/auth.cjs +23 -23
- package/dist/auth.d.cts +1 -0
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +2 -2
- package/dist/chunk-2KUCQVAQ.js +936 -0
- package/dist/chunk-3K5AIEIZ.js +256 -0
- package/dist/{chunk-I6NH72MO.js → chunk-57O3HZPG.js} +9 -1
- package/dist/chunk-7PMHZRF3.cjs +872 -0
- package/dist/chunk-BXYZDMTZ.cjs +953 -0
- package/dist/chunk-CQHXW4TR.js +107 -0
- package/dist/{chunk-NNQBWDCI.js → chunk-CRYKVJTO.js} +55 -959
- package/dist/chunk-DBPSJYLZ.js +47 -0
- package/dist/{chunk-TAHX46EI.cjs → chunk-FBKDMRQL.cjs} +9 -1
- package/dist/chunk-GUSHM5HN.js +862 -0
- package/dist/chunk-HY3AXLOI.cjs +265 -0
- package/dist/chunk-L3525VXI.js +243 -0
- package/dist/{chunk-ZMRQ72F6.cjs → chunk-NBY4NVTY.cjs} +3 -3
- package/dist/chunk-OY2YTBMP.cjs +50 -0
- package/dist/{chunk-BLR6H5GL.js → chunk-SF2XKMCI.js} +3 -3
- package/dist/{chunk-22FFHLTO.cjs → chunk-TCSGFAWB.cjs} +56 -970
- package/dist/{chunk-P2IWWBJV.cjs → chunk-UDVLFVEC.cjs} +1314 -601
- package/dist/{chunk-SQGBHYJF.js → chunk-UKC3HYXI.js} +1245 -548
- package/dist/chunk-V25RDUOU.cjs +248 -0
- package/dist/chunk-XMRMZ6NQ.cjs +109 -0
- package/dist/cli.cjs +9 -4
- package/dist/cli.js +9 -4
- package/dist/{email-queue-TPZWLTIV.cjs → email-queue-6NJY6HNM.cjs} +7 -5
- package/dist/email-queue-OI2HSCGE.js +4 -0
- package/dist/erp-order-invoice-3GXDE443.js +3 -0
- package/dist/erp-order-invoice-EYYNR526.cjs +24 -0
- package/dist/index.cjs +426 -359
- package/dist/index.d.cts +308 -20
- package/dist/index.d.ts +308 -20
- package/dist/index.js +31 -110
- package/dist/migrations/1781800000000-CustomerPhoneNullable.ts +53 -0
- package/dist/migrations/1781810000000-OrderItemsVariantId.ts +44 -0
- package/dist/migrations/1781900000000-UserInviteStatusToken.ts +46 -0
- package/dist/migrations/1782000000000-ComboSlug.ts +58 -0
- package/dist/migrations/1782100000000-DiscountDeviceType.ts +20 -0
- package/dist/order-inventory-2MYRKEPC.cjs +39 -0
- package/dist/order-inventory-76V4XIGW.js +2 -0
- package/dist/order-notification-dispatcher-SBQAMM5V.cjs +21 -0
- package/dist/{order-notification-dispatcher-XWIJYDGA.js → order-notification-dispatcher-ZJZB2HUN.js} +5 -2
- package/dist/retire-soft-deleted-unique-5VXBA5XX.cjs +15 -0
- package/dist/retire-soft-deleted-unique-NXQIH4BC.js +2 -0
- package/dist/send-order-placed-emails-B5ZVJT7T.cjs +15 -0
- package/dist/send-order-placed-emails-WPI37KRZ.js +6 -0
- package/package.json +5 -4
- package/src/admin/admin.css +27 -24
- package/dist/chunk-5ELSW2UP.js +0 -150
- package/dist/chunk-PNOKNSG2.cjs +0 -222
- package/dist/chunk-WEMDMVHQ.js +0 -216
- package/dist/chunk-XW5ATPRW.cjs +0 -155
- package/dist/email-queue-CGZBVVAS.js +0 -2
- package/dist/erp-order-invoice-3YISSOWW.js +0 -3
- package/dist/erp-order-invoice-ZR5F2KTA.cjs +0 -12
- package/dist/order-notification-dispatcher-RU7BTGGX.cjs +0 -18
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { validateInventoryForConfirmedOrderLines, orderStatusHoldsStock, reconcileOrderInventoryBetweenSnapshots, orderInventorySnapshotFromRow, validateInventoryForOrderBecomingConfirmed } from './chunk-3K5AIEIZ.js';
|
|
2
|
+
import { retireSoftDeletedUniqueValue, activeUniqueValueExists } from './chunk-DBPSJYLZ.js';
|
|
3
|
+
import { isWhatsAppPluginEnabled, mergeWhatsAppConfigLayers, whatsAppConfigured, WhatsAppService, registerWhatsAppQueueProcessor, initWhatsappTriggerDispatcher } from './chunk-GUSHM5HN.js';
|
|
4
|
+
import { resolveChatEmailToolSettings, CHAT_EMAIL_LOG, detectChatLeadIntent, parseIntentRecipientEmails, sendChatLeadEmail, buildTranscriptForLeadEmail, buildChatbotSystemPromptWithEmailTool, parseEmailRecipientsFromConfig, emailPlugin } from './chunk-CRYKVJTO.js';
|
|
2
5
|
import { inviteStatusForActivation } from './chunk-JXF23MPG.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
6
|
+
import { queueEmail, queueVendorOnboardEmails, registerEmailQueueProcessor } from './chunk-L3525VXI.js';
|
|
7
|
+
import { mergeEmailLayoutCompanyDetails, unwrapErpReadData, mapErpPayloadToFulfillment, extractChildOrderRefsFromSalePayload, mapErpPayloadToInvoiceNumber, streamOrderInvoicePdf } from './chunk-2KUCQVAQ.js';
|
|
5
8
|
import { getSmsTemplateDefault, SMS_MESSAGE_TEMPLATE_DEFAULTS } from './chunk-MQBT33IV.js';
|
|
6
9
|
import { EVENT_ORDER_MESSAGE_TEMPLATE_DEFAULTS, EVENT_ORDER_TEMPLATE_KEY, EVENT_ORDER_TEMPLATE_VARIABLES } from './chunk-KFFB6EMZ.js';
|
|
7
10
|
import { JOB_RUNNER_QUEUE } from './chunk-GO7PPYNU.js';
|
|
8
|
-
import { canManageRoles, sessionHasEntityAccess } from './chunk-
|
|
11
|
+
import { canManageRoles, sessionHasEntityAccess } from './chunk-SF2XKMCI.js';
|
|
9
12
|
import { permissionRowsToRecord, logRbac, isSuperAdmin, vendorPortalFlagsFromUser, VENDOR_SCOPED_STORE_ENTITIES, isVendorPortalUser, getPermissionableEntityKeys, VENDOR_STORE_RBAC_ENTITIES, isSuperAdminGroupName, VENDOR_RBAC_ENTITIES, VENDOR_VIEWER_ROLE_NAME, DEFAULT_VENDOR_ROLE_DEFS, VENDOR_OWNER_GROUP_NAME, resolveVendorScopeFromSessionUser, canManageVendorRoles, isPlatformAdministrator, canManageVendorTeam, resolveDisabledMultiVendorScope, logEntityAccessDecision, explainSessionEntityAccess } from './chunk-DN65KDIA.js';
|
|
10
13
|
import { queueErp } from './chunk-SYBOCAWB.js';
|
|
11
14
|
import { isErpIntegrationEnabled } from './chunk-JC6DLWTE.js';
|
|
12
15
|
import { __name } from './chunk-SHUYVCID.js';
|
|
13
|
-
import { PrimaryGeneratedColumn, Column, Entity, ManyToOne, JoinColumn, OneToMany, Index, ManyToMany, JoinTable, Unique, CreateDateColumn, UpdateDateColumn, In,
|
|
16
|
+
import { PrimaryGeneratedColumn, Column, Entity, ManyToOne, JoinColumn, OneToMany, Index, ManyToMany, JoinTable, Unique, CreateDateColumn, UpdateDateColumn, In, ILike, Between, MoreThanOrEqual, LessThanOrEqual, Not, MoreThan, IsNull } from 'typeorm';
|
|
14
17
|
import { Country, State, City } from 'country-state-city';
|
|
15
18
|
import crypto2, { randomBytes, randomUUID, createHmac, timingSafeEqual, randomInt } from 'crypto';
|
|
16
19
|
import Parser from 'rss-parser';
|
|
@@ -36,6 +39,7 @@ async function queryVendorLinks(dataSource, userId) {
|
|
|
36
39
|
vr."name" AS "vendorRoleName",
|
|
37
40
|
vr."isOwnerRole" AS "isOwnerRole"
|
|
38
41
|
FROM "vendor_users" vu
|
|
42
|
+
INNER JOIN "vendors" v ON v.id = vu."vendorId" AND v.deleted = false
|
|
39
43
|
LEFT JOIN "vendor_roles" vr ON vr.id = vu."vendorRoleId" AND vr.deleted = false
|
|
40
44
|
WHERE vu."userId" = $1
|
|
41
45
|
ORDER BY vu.id ASC
|
|
@@ -45,10 +49,11 @@ async function queryVendorLinks(dataSource, userId) {
|
|
|
45
49
|
} catch (err) {
|
|
46
50
|
if (!isMissingVendorRoleSchemaError(err)) throw err;
|
|
47
51
|
const legacy = await dataSource.query(`
|
|
48
|
-
SELECT "vendorId", "role"
|
|
49
|
-
FROM "vendor_users"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
SELECT vu."vendorId" AS "vendorId", vu."role" AS "role"
|
|
53
|
+
FROM "vendor_users" vu
|
|
54
|
+
INNER JOIN "vendors" v ON v.id = vu."vendorId" AND v.deleted = false
|
|
55
|
+
WHERE vu."userId" = $1
|
|
56
|
+
ORDER BY vu.id ASC
|
|
52
57
|
`, [
|
|
53
58
|
userId
|
|
54
59
|
]);
|
|
@@ -62,8 +67,36 @@ async function queryVendorLinks(dataSource, userId) {
|
|
|
62
67
|
}
|
|
63
68
|
}
|
|
64
69
|
__name(queryVendorLinks, "queryVendorLinks");
|
|
70
|
+
async function queryOwnedVendorIds(dataSource, userId) {
|
|
71
|
+
try {
|
|
72
|
+
const rows = await dataSource.query(`
|
|
73
|
+
SELECT id
|
|
74
|
+
FROM "vendors"
|
|
75
|
+
WHERE "userId" = $1 AND deleted = false
|
|
76
|
+
ORDER BY id ASC
|
|
77
|
+
`, [
|
|
78
|
+
userId
|
|
79
|
+
]);
|
|
80
|
+
return rows.map((r) => Number(r.id)).filter((id) => Number.isFinite(id) && id > 0);
|
|
81
|
+
} catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
__name(queryOwnedVendorIds, "queryOwnedVendorIds");
|
|
65
86
|
async function loadUserVendorContext(dataSource, userId, preferredVendorId) {
|
|
66
87
|
const rows = await queryVendorLinks(dataSource, userId);
|
|
88
|
+
const linkIds = new Set(rows.map((r) => Number(r.vendorId)));
|
|
89
|
+
for (const ownedId of await queryOwnedVendorIds(dataSource, userId)) {
|
|
90
|
+
if (linkIds.has(ownedId)) continue;
|
|
91
|
+
rows.push({
|
|
92
|
+
vendorId: ownedId,
|
|
93
|
+
role: "owner",
|
|
94
|
+
vendorRoleId: null,
|
|
95
|
+
vendorRoleName: null,
|
|
96
|
+
isOwnerRole: true
|
|
97
|
+
});
|
|
98
|
+
linkIds.add(ownedId);
|
|
99
|
+
}
|
|
67
100
|
const vendorIds = rows.map((r) => Number(r.vendorId));
|
|
68
101
|
const activeRow = preferredVendorId != null ? rows.find((r) => Number(r.vendorId) === preferredVendorId) : void 0;
|
|
69
102
|
const primary = activeRow ?? rows[0];
|
|
@@ -616,6 +649,144 @@ function applyEventApprovalStatusSideEffects(updatePayload, opts) {
|
|
|
616
649
|
}
|
|
617
650
|
__name(applyEventApprovalStatusSideEffects, "applyEventApprovalStatusSideEffects");
|
|
618
651
|
|
|
652
|
+
// src/lib/discount-usage.ts
|
|
653
|
+
async function countDiscountOrdersForContact(dataSource, discountId, contactId) {
|
|
654
|
+
try {
|
|
655
|
+
const rows = await dataSource.query(`SELECT COUNT(DISTINCT od."orderId")::int AS count
|
|
656
|
+
FROM order_discounts od
|
|
657
|
+
WHERE od."discountId" = $1 AND od."userId" = $2`, [
|
|
658
|
+
discountId,
|
|
659
|
+
contactId
|
|
660
|
+
]);
|
|
661
|
+
return Number(rows?.[0]?.count ?? 0);
|
|
662
|
+
} catch {
|
|
663
|
+
return 0;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
__name(countDiscountOrdersForContact, "countDiscountOrdersForContact");
|
|
667
|
+
function isMaxTotalUsageReached(discount) {
|
|
668
|
+
if (discount.maxTotalUsage == null) return false;
|
|
669
|
+
return Number(discount.usedCount ?? 0) >= Number(discount.maxTotalUsage);
|
|
670
|
+
}
|
|
671
|
+
__name(isMaxTotalUsageReached, "isMaxTotalUsageReached");
|
|
672
|
+
async function isMaxPerUserUsageReached(dataSource, discount, contactId) {
|
|
673
|
+
if (discount.maxUsagePerUser == null || contactId == null) return false;
|
|
674
|
+
const count = await countDiscountOrdersForContact(dataSource, discount.id, contactId);
|
|
675
|
+
return count >= Number(discount.maxUsagePerUser);
|
|
676
|
+
}
|
|
677
|
+
__name(isMaxPerUserUsageReached, "isMaxPerUserUsageReached");
|
|
678
|
+
async function checkAndIncrementDiscountUsage(dataSource, entityMap, discountId, contactId) {
|
|
679
|
+
if (!entityMap.discounts) return null;
|
|
680
|
+
const discountRepo = dataSource.getRepository(entityMap.discounts);
|
|
681
|
+
const discount = await discountRepo.findOne({
|
|
682
|
+
where: {
|
|
683
|
+
id: discountId
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
if (!discount) return `Discount #${discountId} not found`;
|
|
687
|
+
const d = discount;
|
|
688
|
+
if (d.maxUsagePerUser != null) {
|
|
689
|
+
const usageCount = await countDiscountOrdersForContact(dataSource, discountId, contactId);
|
|
690
|
+
if (usageCount >= Number(d.maxUsagePerUser)) {
|
|
691
|
+
return "You have reached the maximum usage limit for this discount";
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const updated = await dataSource.query(`UPDATE discounts
|
|
695
|
+
SET "usedCount" = "usedCount" + 1
|
|
696
|
+
WHERE id = $1
|
|
697
|
+
AND ("maxTotalUsage" IS NULL OR "usedCount" < "maxTotalUsage")
|
|
698
|
+
RETURNING id`, [
|
|
699
|
+
discountId
|
|
700
|
+
]);
|
|
701
|
+
if (!Array.isArray(updated) || updated.length === 0) {
|
|
702
|
+
return "Discount usage limit has been reached";
|
|
703
|
+
}
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
__name(checkAndIncrementDiscountUsage, "checkAndIncrementDiscountUsage");
|
|
707
|
+
async function decrementDiscountUsage(dataSource, entityMap, discountId, orderId) {
|
|
708
|
+
if (!entityMap.discounts) return;
|
|
709
|
+
try {
|
|
710
|
+
await dataSource.query(`UPDATE discounts SET "usedCount" = GREATEST("usedCount" - 1, 0) WHERE id = $1`, [
|
|
711
|
+
discountId
|
|
712
|
+
]);
|
|
713
|
+
if (entityMap.order_discounts) {
|
|
714
|
+
await dataSource.getRepository(entityMap.order_discounts).delete({
|
|
715
|
+
orderId,
|
|
716
|
+
discountId
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
} catch (err) {
|
|
720
|
+
console.error("[discountEngine] decrementDiscountUsage failed:", err);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
__name(decrementDiscountUsage, "decrementDiscountUsage");
|
|
724
|
+
async function recordDiscountUsage(dataSource, entityMap, orderId, discountId, contactId, discountAmount) {
|
|
725
|
+
if (!entityMap.order_discounts) return;
|
|
726
|
+
try {
|
|
727
|
+
let discountCode = "";
|
|
728
|
+
if (entityMap.discounts) {
|
|
729
|
+
const discountRow = await dataSource.getRepository(entityMap.discounts).findOne({
|
|
730
|
+
where: {
|
|
731
|
+
id: discountId
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
discountCode = discountRow?.couponCode ?? "";
|
|
735
|
+
}
|
|
736
|
+
const odRepo = dataSource.getRepository(entityMap.order_discounts);
|
|
737
|
+
const orderItemRepo = dataSource.getRepository(entityMap.order_items);
|
|
738
|
+
const items = await orderItemRepo.find({
|
|
739
|
+
where: {
|
|
740
|
+
orderId
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
const discountedItems = items.filter((item) => Number(item.discount ?? 0) > 0);
|
|
744
|
+
if (discountedItems.length === 0) {
|
|
745
|
+
const firstItem = items[0];
|
|
746
|
+
if (!firstItem) return;
|
|
747
|
+
await odRepo.save(odRepo.create({
|
|
748
|
+
orderId,
|
|
749
|
+
orderItemId: firstItem.id,
|
|
750
|
+
discountId,
|
|
751
|
+
discountCode,
|
|
752
|
+
discountAmount,
|
|
753
|
+
userId: contactId
|
|
754
|
+
}));
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
for (const item of discountedItems) {
|
|
758
|
+
const i = item;
|
|
759
|
+
await odRepo.save(odRepo.create({
|
|
760
|
+
orderId,
|
|
761
|
+
orderItemId: i.id,
|
|
762
|
+
discountId,
|
|
763
|
+
discountCode,
|
|
764
|
+
discountAmount: Number(i.discount ?? 0),
|
|
765
|
+
userId: contactId
|
|
766
|
+
}));
|
|
767
|
+
}
|
|
768
|
+
} catch (err) {
|
|
769
|
+
console.error("[discountEngine] recordDiscountUsage failed:", err);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
__name(recordDiscountUsage, "recordDiscountUsage");
|
|
773
|
+
async function consumeAppliedDiscountUsages(dataSource, entityMap, orderId, contactId, applied) {
|
|
774
|
+
const unique = /* @__PURE__ */ new Map();
|
|
775
|
+
for (const row of applied) {
|
|
776
|
+
const id = Number(row.id);
|
|
777
|
+
if (!Number.isFinite(id) || id < 1) continue;
|
|
778
|
+
const amount = Math.max(0, Number(row.calculatedDiscount) || 0);
|
|
779
|
+
unique.set(id, (unique.get(id) ?? 0) + amount);
|
|
780
|
+
}
|
|
781
|
+
for (const [discountId, discountAmount] of unique) {
|
|
782
|
+
const usageErr = await checkAndIncrementDiscountUsage(dataSource, entityMap, discountId, contactId);
|
|
783
|
+
if (usageErr) return usageErr;
|
|
784
|
+
await recordDiscountUsage(dataSource, entityMap, orderId, discountId, contactId, discountAmount);
|
|
785
|
+
}
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
__name(consumeAppliedDiscountUsages, "consumeAppliedDiscountUsages");
|
|
789
|
+
|
|
619
790
|
// src/lib/default-vendor-id.ts
|
|
620
791
|
function getDefaultVendorId() {
|
|
621
792
|
const raw = process.env.DEFAULT_VENDOR_ID;
|
|
@@ -1090,11 +1261,118 @@ function pgErrorCode(err) {
|
|
|
1090
1261
|
return driver?.code ?? err.code;
|
|
1091
1262
|
}
|
|
1092
1263
|
__name(pgErrorCode, "pgErrorCode");
|
|
1093
|
-
function
|
|
1264
|
+
function normalizeCustomerPhone(phone) {
|
|
1094
1265
|
const p = typeof phone === "string" ? phone.trim() : "";
|
|
1095
|
-
return p ||
|
|
1266
|
+
return p || null;
|
|
1267
|
+
}
|
|
1268
|
+
__name(normalizeCustomerPhone, "normalizeCustomerPhone");
|
|
1269
|
+
function isSyntheticCustomerPhone(phone) {
|
|
1270
|
+
const p = String(phone ?? "").trim();
|
|
1271
|
+
if (!p) return true;
|
|
1272
|
+
if (p.startsWith("e-") || p.startsWith("u-")) return true;
|
|
1273
|
+
if (p.includes("@")) return true;
|
|
1274
|
+
return false;
|
|
1275
|
+
}
|
|
1276
|
+
__name(isSyntheticCustomerPhone, "isSyntheticCustomerPhone");
|
|
1277
|
+
function customerPhoneForUser(_userId, phone) {
|
|
1278
|
+
return normalizeCustomerPhone(phone);
|
|
1096
1279
|
}
|
|
1097
1280
|
__name(customerPhoneForUser, "customerPhoneForUser");
|
|
1281
|
+
function customerPhoneForEmail(_email, phone) {
|
|
1282
|
+
return normalizeCustomerPhone(phone);
|
|
1283
|
+
}
|
|
1284
|
+
__name(customerPhoneForEmail, "customerPhoneForEmail");
|
|
1285
|
+
function resolveNextPhone(inputPhone, existingPhone) {
|
|
1286
|
+
if (inputPhone) return inputPhone;
|
|
1287
|
+
const existing = normalizeCustomerPhone(existingPhone);
|
|
1288
|
+
if (!existing || isSyntheticCustomerPhone(existing)) return null;
|
|
1289
|
+
return existing;
|
|
1290
|
+
}
|
|
1291
|
+
__name(resolveNextPhone, "resolveNextPhone");
|
|
1292
|
+
async function ensureCustomerRecord(dsOrEm, customerEntity, input) {
|
|
1293
|
+
const repo = dsOrEm.getRepository(customerEntity);
|
|
1294
|
+
const email = normalizeEmail(input.email);
|
|
1295
|
+
if (!email) return null;
|
|
1296
|
+
const name = String(input.name ?? "").trim() || email.split("@")[0] || "Customer";
|
|
1297
|
+
const userId = input.userId != null && Number.isFinite(Number(input.userId)) && Number(input.userId) > 0 ? Number(input.userId) : null;
|
|
1298
|
+
const phone = normalizeCustomerPhone(input.phone);
|
|
1299
|
+
let row = await repo.findOne({
|
|
1300
|
+
where: {
|
|
1301
|
+
email,
|
|
1302
|
+
deleted: false
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
if (!row) {
|
|
1306
|
+
row = await repo.findOne({
|
|
1307
|
+
where: {
|
|
1308
|
+
email
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
if (row) {
|
|
1313
|
+
const existingUserId = row.userId;
|
|
1314
|
+
if (userId != null && existingUserId != null && existingUserId !== userId) {
|
|
1315
|
+
return null;
|
|
1316
|
+
}
|
|
1317
|
+
const nextPhone = resolveNextPhone(phone, row.phone);
|
|
1318
|
+
const patch = {
|
|
1319
|
+
name,
|
|
1320
|
+
phone: nextPhone,
|
|
1321
|
+
deleted: false,
|
|
1322
|
+
deletedAt: null,
|
|
1323
|
+
deletedBy: null,
|
|
1324
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1325
|
+
};
|
|
1326
|
+
if (userId != null && existingUserId == null) {
|
|
1327
|
+
patch.userId = userId;
|
|
1328
|
+
}
|
|
1329
|
+
await repo.update(row.id, patch);
|
|
1330
|
+
return {
|
|
1331
|
+
id: row.id
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
try {
|
|
1335
|
+
const created = await repo.save(repo.create({
|
|
1336
|
+
userId,
|
|
1337
|
+
name,
|
|
1338
|
+
email,
|
|
1339
|
+
phone,
|
|
1340
|
+
deleted: false
|
|
1341
|
+
}));
|
|
1342
|
+
return {
|
|
1343
|
+
id: created.id
|
|
1344
|
+
};
|
|
1345
|
+
} catch (err) {
|
|
1346
|
+
const code = pgErrorCode(err);
|
|
1347
|
+
if (code === "25P02") throw err;
|
|
1348
|
+
row = await repo.findOne({
|
|
1349
|
+
where: {
|
|
1350
|
+
email
|
|
1351
|
+
}
|
|
1352
|
+
});
|
|
1353
|
+
if (row) {
|
|
1354
|
+
const existingUserId = row.userId;
|
|
1355
|
+
if (userId != null && existingUserId != null && existingUserId !== userId) return null;
|
|
1356
|
+
await repo.update(row.id, {
|
|
1357
|
+
name,
|
|
1358
|
+
phone: resolveNextPhone(phone, row.phone),
|
|
1359
|
+
...userId != null && existingUserId == null ? {
|
|
1360
|
+
userId
|
|
1361
|
+
} : {},
|
|
1362
|
+
deleted: false,
|
|
1363
|
+
deletedAt: null,
|
|
1364
|
+
deletedBy: null,
|
|
1365
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1366
|
+
});
|
|
1367
|
+
return {
|
|
1368
|
+
id: row.id
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
if (code === "23505") return null;
|
|
1372
|
+
throw err;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
__name(ensureCustomerRecord, "ensureCustomerRecord");
|
|
1098
1376
|
async function restoreCustomerRow(repo, row, user, name, email, phone) {
|
|
1099
1377
|
const id = row.id;
|
|
1100
1378
|
await repo.update(id, {
|
|
@@ -1116,7 +1394,7 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
|
|
|
1116
1394
|
const repo = dsOrEm.getRepository(customerEntity);
|
|
1117
1395
|
const email = normalizeEmail(user.email);
|
|
1118
1396
|
const name = String(user.name ?? "").trim() || email.split("@")[0] || "User";
|
|
1119
|
-
const phone =
|
|
1397
|
+
const phone = normalizeCustomerPhone(overrides?.phone ?? user.phone);
|
|
1120
1398
|
let row = await repo.findOne({
|
|
1121
1399
|
where: {
|
|
1122
1400
|
userId: user.id,
|
|
@@ -1124,10 +1402,11 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
|
|
|
1124
1402
|
}
|
|
1125
1403
|
});
|
|
1126
1404
|
if (row) {
|
|
1405
|
+
const nextPhone = resolveNextPhone(phone, row.phone);
|
|
1127
1406
|
await repo.update(row.id, {
|
|
1128
1407
|
name,
|
|
1129
1408
|
email,
|
|
1130
|
-
phone,
|
|
1409
|
+
phone: nextPhone,
|
|
1131
1410
|
updatedAt: /* @__PURE__ */ new Date()
|
|
1132
1411
|
});
|
|
1133
1412
|
return {
|
|
@@ -1145,10 +1424,11 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
|
|
|
1145
1424
|
if (existingUserId != null && existingUserId !== user.id) {
|
|
1146
1425
|
return null;
|
|
1147
1426
|
}
|
|
1427
|
+
const nextPhone = resolveNextPhone(phone, row.phone);
|
|
1148
1428
|
await repo.update(row.id, {
|
|
1149
1429
|
userId: user.id,
|
|
1150
1430
|
name,
|
|
1151
|
-
phone,
|
|
1431
|
+
phone: nextPhone,
|
|
1152
1432
|
updatedAt: /* @__PURE__ */ new Date()
|
|
1153
1433
|
});
|
|
1154
1434
|
return {
|
|
@@ -1163,21 +1443,23 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
|
|
|
1163
1443
|
if (deletedByEmail) {
|
|
1164
1444
|
const existingUserId = deletedByEmail.userId;
|
|
1165
1445
|
if (existingUserId != null && existingUserId !== user.id) return null;
|
|
1166
|
-
return restoreCustomerRow(repo, deletedByEmail, user, name, email, phone);
|
|
1446
|
+
return restoreCustomerRow(repo, deletedByEmail, user, name, email, resolveNextPhone(phone, deletedByEmail.phone));
|
|
1167
1447
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1448
|
+
if (phone) {
|
|
1449
|
+
const deletedByPhone = await repo.findOne({
|
|
1450
|
+
where: {
|
|
1451
|
+
phone
|
|
1452
|
+
}
|
|
1453
|
+
});
|
|
1454
|
+
if (deletedByPhone) {
|
|
1455
|
+
const existingUserId = deletedByPhone.userId;
|
|
1456
|
+
if (existingUserId != null && existingUserId !== user.id) {
|
|
1457
|
+
return ensureCustomerForUser(dsOrEm, customerEntity, user, {
|
|
1458
|
+
phone: null
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
return restoreCustomerRow(repo, deletedByPhone, user, name, email, phone);
|
|
1179
1462
|
}
|
|
1180
|
-
return restoreCustomerRow(repo, deletedByPhone, user, name, email, phone);
|
|
1181
1463
|
}
|
|
1182
1464
|
try {
|
|
1183
1465
|
const created = await repo.save(repo.create({
|
|
@@ -1198,34 +1480,22 @@ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
|
|
|
1198
1480
|
userId: user.id
|
|
1199
1481
|
}
|
|
1200
1482
|
});
|
|
1201
|
-
if (row)
|
|
1483
|
+
if (row) {
|
|
1484
|
+
return restoreCustomerRow(repo, row, user, name, email, resolveNextPhone(phone, row.phone));
|
|
1485
|
+
}
|
|
1202
1486
|
row = await repo.findOne({
|
|
1203
1487
|
where: {
|
|
1204
1488
|
email
|
|
1205
1489
|
}
|
|
1206
1490
|
});
|
|
1207
1491
|
if (row && (row.userId ?? user.id) === user.id) {
|
|
1208
|
-
return restoreCustomerRow(repo, row, user, name, email, phone);
|
|
1492
|
+
return restoreCustomerRow(repo, row, user, name, email, resolveNextPhone(phone, row.phone));
|
|
1209
1493
|
}
|
|
1210
1494
|
if (code === "23505") return null;
|
|
1211
1495
|
throw err;
|
|
1212
1496
|
}
|
|
1213
1497
|
}
|
|
1214
1498
|
__name(ensureCustomerForUser, "ensureCustomerForUser");
|
|
1215
|
-
async function linkUnclaimedContactToUser(dataSource, contactsEntity, userId, email) {
|
|
1216
|
-
const repo = dataSource.getRepository(contactsEntity);
|
|
1217
|
-
const found = await repo.findOne({
|
|
1218
|
-
where: {
|
|
1219
|
-
email,
|
|
1220
|
-
userId: IsNull(),
|
|
1221
|
-
deleted: false
|
|
1222
|
-
}
|
|
1223
|
-
});
|
|
1224
|
-
if (found) await repo.update(found.id, {
|
|
1225
|
-
userId
|
|
1226
|
-
});
|
|
1227
|
-
}
|
|
1228
|
-
__name(linkUnclaimedContactToUser, "linkUnclaimedContactToUser");
|
|
1229
1499
|
|
|
1230
1500
|
// src/lib/vendor-customer-contacts.ts
|
|
1231
1501
|
function isCustomerTypeContact(type) {
|
|
@@ -1318,8 +1588,27 @@ function resolveVendorIdForContactCheck(scope, bodyOrRow) {
|
|
|
1318
1588
|
return Number.isFinite(n) ? n : null;
|
|
1319
1589
|
}
|
|
1320
1590
|
__name(resolveVendorIdForContactCheck, "resolveVendorIdForContactCheck");
|
|
1591
|
+
function uniquePositiveIds(ids) {
|
|
1592
|
+
const out = [];
|
|
1593
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1594
|
+
for (const raw of ids) {
|
|
1595
|
+
const n = Number(raw);
|
|
1596
|
+
if (!Number.isFinite(n) || n <= 0 || seen.has(n)) continue;
|
|
1597
|
+
seen.add(n);
|
|
1598
|
+
out.push(n);
|
|
1599
|
+
}
|
|
1600
|
+
return out;
|
|
1601
|
+
}
|
|
1602
|
+
__name(uniquePositiveIds, "uniquePositiveIds");
|
|
1321
1603
|
async function ensureVendorCustomerForOrderContact(dataSource, entityMap, vendorId, contactId, details) {
|
|
1322
|
-
|
|
1604
|
+
await ensureVendorCustomersForOrder(dataSource, entityMap, [
|
|
1605
|
+
vendorId
|
|
1606
|
+
], contactId, details);
|
|
1607
|
+
}
|
|
1608
|
+
__name(ensureVendorCustomerForOrderContact, "ensureVendorCustomerForOrderContact");
|
|
1609
|
+
async function ensureVendorCustomersForOrder(dataSource, entityMap, vendorIds, contactId, details) {
|
|
1610
|
+
const vendors = uniquePositiveIds(vendorIds);
|
|
1611
|
+
if (vendors.length === 0 || !Number.isFinite(contactId) || contactId <= 0) return;
|
|
1323
1612
|
const vcEntity = entityMap.vendor_customers;
|
|
1324
1613
|
const contactEntity = entityMap.contacts;
|
|
1325
1614
|
if (!vcEntity || !contactEntity) return;
|
|
@@ -1345,96 +1634,61 @@ async function ensureVendorCustomerForOrderContact(dataSource, entityMap, vendor
|
|
|
1345
1634
|
}
|
|
1346
1635
|
let customerId = null;
|
|
1347
1636
|
if (entityMap.customer) {
|
|
1348
|
-
const
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
}
|
|
1637
|
+
const ensured = await ensureCustomerRecord(dataSource, entityMap.customer, {
|
|
1638
|
+
name,
|
|
1639
|
+
email,
|
|
1640
|
+
phone,
|
|
1641
|
+
userId: null
|
|
1354
1642
|
});
|
|
1355
|
-
if (
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1643
|
+
if (ensured) customerId = ensured.id;
|
|
1644
|
+
}
|
|
1645
|
+
for (const vendorId of vendors) {
|
|
1646
|
+
if (customerId != null) {
|
|
1647
|
+
const byCustomer = await vcRepo.findOne({
|
|
1360
1648
|
where: {
|
|
1361
|
-
|
|
1362
|
-
|
|
1649
|
+
vendorId,
|
|
1650
|
+
customerId
|
|
1363
1651
|
}
|
|
1364
1652
|
});
|
|
1365
|
-
if (
|
|
1366
|
-
|
|
1367
|
-
if (
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
where: {
|
|
1371
|
-
name: "Customer",
|
|
1372
|
-
deleted: false
|
|
1373
|
-
}
|
|
1653
|
+
if (byCustomer) {
|
|
1654
|
+
const existingContactId = byCustomer.contactId;
|
|
1655
|
+
if (existingContactId == null) {
|
|
1656
|
+
await vcRepo.update(byCustomer.id, {
|
|
1657
|
+
contactId
|
|
1374
1658
|
});
|
|
1375
|
-
if (customerGroup) groupId = Number(customerGroup.id);
|
|
1376
1659
|
}
|
|
1377
|
-
|
|
1378
|
-
name,
|
|
1379
|
-
email,
|
|
1380
|
-
phone,
|
|
1381
|
-
password: null,
|
|
1382
|
-
blocked: false,
|
|
1383
|
-
groupId,
|
|
1384
|
-
adminAccess: false
|
|
1385
|
-
}));
|
|
1660
|
+
continue;
|
|
1386
1661
|
}
|
|
1387
|
-
const userId = Number(user.id);
|
|
1388
|
-
await linkUnclaimedContactToUser(dataSource, contactEntity, userId, email);
|
|
1389
|
-
const ensured = await ensureCustomerForUser(dataSource, entityMap.customer, {
|
|
1390
|
-
id: userId,
|
|
1391
|
-
name,
|
|
1392
|
-
email,
|
|
1393
|
-
phone
|
|
1394
|
-
}, {
|
|
1395
|
-
phone
|
|
1396
|
-
});
|
|
1397
|
-
if (ensured) customerId = ensured.id;
|
|
1398
1662
|
}
|
|
1399
|
-
|
|
1400
|
-
if (customerId != null) {
|
|
1401
|
-
const byCustomer = await vcRepo.findOne({
|
|
1663
|
+
const byContact = await vcRepo.findOne({
|
|
1402
1664
|
where: {
|
|
1403
1665
|
vendorId,
|
|
1404
|
-
|
|
1666
|
+
contactId
|
|
1405
1667
|
}
|
|
1406
1668
|
});
|
|
1407
|
-
if (
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
contactId
|
|
1669
|
+
if (byContact) {
|
|
1670
|
+
if (customerId != null && byContact.customerId == null) {
|
|
1671
|
+
await vcRepo.update(byContact.id, {
|
|
1672
|
+
customerId
|
|
1412
1673
|
});
|
|
1413
1674
|
}
|
|
1414
|
-
|
|
1675
|
+
continue;
|
|
1415
1676
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1677
|
+
if (customerId != null) {
|
|
1678
|
+
await vcRepo.save(vcRepo.create({
|
|
1679
|
+
vendorId,
|
|
1680
|
+
customerId,
|
|
1681
|
+
contactId
|
|
1682
|
+
}));
|
|
1683
|
+
} else {
|
|
1684
|
+
await vcRepo.save(vcRepo.create({
|
|
1685
|
+
vendorId,
|
|
1686
|
+
contactId
|
|
1687
|
+
}));
|
|
1421
1688
|
}
|
|
1422
|
-
});
|
|
1423
|
-
if (byContact) return;
|
|
1424
|
-
if (customerId != null) {
|
|
1425
|
-
await vcRepo.save(vcRepo.create({
|
|
1426
|
-
vendorId,
|
|
1427
|
-
customerId,
|
|
1428
|
-
contactId
|
|
1429
|
-
}));
|
|
1430
|
-
return;
|
|
1431
1689
|
}
|
|
1432
|
-
await vcRepo.save(vcRepo.create({
|
|
1433
|
-
vendorId,
|
|
1434
|
-
contactId
|
|
1435
|
-
}));
|
|
1436
1690
|
}
|
|
1437
|
-
__name(
|
|
1691
|
+
__name(ensureVendorCustomersForOrder, "ensureVendorCustomersForOrder");
|
|
1438
1692
|
|
|
1439
1693
|
// src/lib/currency-prices.ts
|
|
1440
1694
|
function normalizeCurrencyCode(code) {
|
|
@@ -1876,6 +2130,7 @@ async function ensureUniqueProductSlug(repo, baseSlug, vendorId, excludeId) {
|
|
|
1876
2130
|
let slug = baseSlug;
|
|
1877
2131
|
let suffix = 0;
|
|
1878
2132
|
while (true) {
|
|
2133
|
+
await retireSoftDeletedUniqueValue(repo, "slug", slug);
|
|
1879
2134
|
const where = excludeId != null ? {
|
|
1880
2135
|
slug,
|
|
1881
2136
|
vendorId,
|
|
@@ -1895,6 +2150,21 @@ async function ensureUniqueProductSlug(repo, baseSlug, vendorId, excludeId) {
|
|
|
1895
2150
|
}
|
|
1896
2151
|
}
|
|
1897
2152
|
__name(ensureUniqueProductSlug, "ensureUniqueProductSlug");
|
|
2153
|
+
async function normalizeComboNameAndSlug(repo, body, vendorId, existing, excludeId) {
|
|
2154
|
+
const incomingName = typeof body.name === "string" ? body.name.trim() : "";
|
|
2155
|
+
const incomingSlugRaw = typeof body.slug === "string" ? body.slug.trim() : "";
|
|
2156
|
+
const existingName = typeof existing?.name === "string" ? existing.name.trim() : "";
|
|
2157
|
+
const existingSlug = typeof existing?.slug === "string" ? existing.slug.trim() : "";
|
|
2158
|
+
const name = incomingName || existingName;
|
|
2159
|
+
if (!name) return "Name is required";
|
|
2160
|
+
if (incomingName) body.name = name;
|
|
2161
|
+
const nameBasedSlug = slugifyProductLabel(name);
|
|
2162
|
+
const typedSlug = incomingSlugRaw ? slugifyProductLabel(incomingSlugRaw) : "";
|
|
2163
|
+
const slug = typedSlug || (excludeId != null ? existingSlug : "") || nameBasedSlug;
|
|
2164
|
+
body.slug = await ensureUniqueProductSlug(repo, slug, vendorId, excludeId);
|
|
2165
|
+
return null;
|
|
2166
|
+
}
|
|
2167
|
+
__name(normalizeComboNameAndSlug, "normalizeComboNameAndSlug");
|
|
1898
2168
|
async function hydrateProductEventNames(dataSource, entityMap, products) {
|
|
1899
2169
|
if (!products.length) return products;
|
|
1900
2170
|
if (!entityMap.event_products || !entityMap.events) {
|
|
@@ -2141,6 +2411,20 @@ async function normalizeProductNameAndSlug(repo, body, vendorId, existing, exclu
|
|
|
2141
2411
|
return null;
|
|
2142
2412
|
}
|
|
2143
2413
|
__name(normalizeProductNameAndSlug, "normalizeProductNameAndSlug");
|
|
2414
|
+
async function retireSoftDeletedUniquesFromBody(repo, body) {
|
|
2415
|
+
const cols = new Set(repo.metadata.columns.map((c) => c.propertyName));
|
|
2416
|
+
if (!cols.has("deleted")) return;
|
|
2417
|
+
for (const col of [
|
|
2418
|
+
"slug",
|
|
2419
|
+
"email"
|
|
2420
|
+
]) {
|
|
2421
|
+
if (!cols.has(col)) continue;
|
|
2422
|
+
const raw = body[col];
|
|
2423
|
+
if (typeof raw !== "string" || !raw.trim()) continue;
|
|
2424
|
+
await retireSoftDeletedUniqueValue(repo, col, raw.trim());
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
__name(retireSoftDeletedUniquesFromBody, "retireSoftDeletedUniquesFromBody");
|
|
2144
2428
|
function buildSoftDeletePayload(meta, deletedBy) {
|
|
2145
2429
|
const payload = {
|
|
2146
2430
|
deleted: true
|
|
@@ -2377,6 +2661,7 @@ function discountEvaluateRule(rule, cartLines, cartTotal) {
|
|
|
2377
2661
|
case "category":
|
|
2378
2662
|
case "cart":
|
|
2379
2663
|
case "paymentMethod":
|
|
2664
|
+
case "deviceType":
|
|
2380
2665
|
case "order": {
|
|
2381
2666
|
if (rule.subType === "nthOrder") {
|
|
2382
2667
|
return true;
|
|
@@ -2560,87 +2845,22 @@ function applyDiscountToOrderLines(lines, evalResult, maxDiscountAmount) {
|
|
|
2560
2845
|
return totalDiscountApplied;
|
|
2561
2846
|
}
|
|
2562
2847
|
__name(applyDiscountToOrderLines, "applyDiscountToOrderLines");
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
const discountRepo = dataSource.getRepository(entityMap["discounts"]);
|
|
2566
|
-
const discount = await discountRepo.findOne({
|
|
2567
|
-
where: {
|
|
2568
|
-
id: discountId
|
|
2569
|
-
}
|
|
2570
|
-
});
|
|
2571
|
-
if (!discount) return `Discount #${discountId} not found`;
|
|
2572
|
-
await dataSource.query(`UPDATE discounts SET "usedCount" = "usedCount" + 1 WHERE id = $1`, [
|
|
2573
|
-
discountId
|
|
2574
|
-
]);
|
|
2575
|
-
return null;
|
|
2848
|
+
function asDiscountUsageEntityMap(entityMap) {
|
|
2849
|
+
return entityMap;
|
|
2576
2850
|
}
|
|
2577
|
-
__name(
|
|
2578
|
-
async function
|
|
2579
|
-
|
|
2580
|
-
try {
|
|
2581
|
-
await dataSource.query(`UPDATE discounts SET "usedCount" = GREATEST("usedCount" - 1, 0) WHERE id = $1`, [
|
|
2582
|
-
discountId
|
|
2583
|
-
]);
|
|
2584
|
-
if (entityMap["order_discounts"]) {
|
|
2585
|
-
await dataSource.getRepository(entityMap["order_discounts"]).delete({
|
|
2586
|
-
orderId,
|
|
2587
|
-
discountId
|
|
2588
|
-
});
|
|
2589
|
-
}
|
|
2590
|
-
} catch (err) {
|
|
2591
|
-
console.error("[discountEngine] decrementDiscountUsage failed:", err);
|
|
2592
|
-
}
|
|
2851
|
+
__name(asDiscountUsageEntityMap, "asDiscountUsageEntityMap");
|
|
2852
|
+
async function checkAndIncrementDiscountUsage2(dataSource, entityMap, discountId, contactId) {
|
|
2853
|
+
return checkAndIncrementDiscountUsage(dataSource, asDiscountUsageEntityMap(entityMap), discountId, contactId);
|
|
2593
2854
|
}
|
|
2594
|
-
__name(
|
|
2595
|
-
async function
|
|
2596
|
-
|
|
2597
|
-
try {
|
|
2598
|
-
let discountCode = "";
|
|
2599
|
-
if (entityMap["discounts"]) {
|
|
2600
|
-
const discountRow = await dataSource.getRepository(entityMap["discounts"]).findOne({
|
|
2601
|
-
where: {
|
|
2602
|
-
id: discountId
|
|
2603
|
-
}
|
|
2604
|
-
});
|
|
2605
|
-
discountCode = discountRow?.couponCode ?? "";
|
|
2606
|
-
}
|
|
2607
|
-
const odRepo = dataSource.getRepository(entityMap["order_discounts"]);
|
|
2608
|
-
const orderItemRepo = dataSource.getRepository(entityMap.order_items);
|
|
2609
|
-
const items = await orderItemRepo.find({
|
|
2610
|
-
where: {
|
|
2611
|
-
orderId
|
|
2612
|
-
}
|
|
2613
|
-
});
|
|
2614
|
-
const discountedItems = items.filter((item) => Number(item.discount ?? 0) > 0);
|
|
2615
|
-
if (discountedItems.length === 0) {
|
|
2616
|
-
const firstItem = items[0];
|
|
2617
|
-
if (!firstItem) return;
|
|
2618
|
-
await odRepo.save(odRepo.create({
|
|
2619
|
-
orderId,
|
|
2620
|
-
orderItemId: firstItem.id,
|
|
2621
|
-
discountId,
|
|
2622
|
-
discountCode,
|
|
2623
|
-
discountAmount,
|
|
2624
|
-
userId: contactId
|
|
2625
|
-
}));
|
|
2626
|
-
return;
|
|
2627
|
-
}
|
|
2628
|
-
for (const item of discountedItems) {
|
|
2629
|
-
const i = item;
|
|
2630
|
-
await odRepo.save(odRepo.create({
|
|
2631
|
-
orderId,
|
|
2632
|
-
orderItemId: i.id,
|
|
2633
|
-
discountId,
|
|
2634
|
-
discountCode,
|
|
2635
|
-
discountAmount: Number(i.discount ?? 0),
|
|
2636
|
-
userId: contactId
|
|
2637
|
-
}));
|
|
2638
|
-
}
|
|
2639
|
-
} catch (err) {
|
|
2640
|
-
console.error("[discountEngine] recordDiscountUsage failed:", err);
|
|
2641
|
-
}
|
|
2855
|
+
__name(checkAndIncrementDiscountUsage2, "checkAndIncrementDiscountUsage");
|
|
2856
|
+
async function decrementDiscountUsage2(dataSource, entityMap, discountId, orderId) {
|
|
2857
|
+
return decrementDiscountUsage(dataSource, asDiscountUsageEntityMap(entityMap), discountId, orderId);
|
|
2642
2858
|
}
|
|
2643
|
-
__name(
|
|
2859
|
+
__name(decrementDiscountUsage2, "decrementDiscountUsage");
|
|
2860
|
+
async function recordDiscountUsage2(dataSource, entityMap, orderId, discountId, contactId, discountAmount) {
|
|
2861
|
+
return recordDiscountUsage(dataSource, asDiscountUsageEntityMap(entityMap), orderId, discountId, contactId, discountAmount);
|
|
2862
|
+
}
|
|
2863
|
+
__name(recordDiscountUsage2, "recordDiscountUsage");
|
|
2644
2864
|
async function validateCoupon(dataSource, entityMap, couponCode, orderLines, currency = "INR", contactId, isAutomatic, discountId, vendorId) {
|
|
2645
2865
|
if (!entityMap["discounts"]) return {
|
|
2646
2866
|
valid: false,
|
|
@@ -2685,21 +2905,12 @@ async function validateCoupon(dataSource, entityMap, couponCode, orderLines, cur
|
|
|
2685
2905
|
};
|
|
2686
2906
|
}
|
|
2687
2907
|
if (contactId != null && d.maxUsagePerUser != null) {
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
]);
|
|
2695
|
-
const usageCount = Number(userUsage?.[0]?.count ?? 0);
|
|
2696
|
-
if (usageCount >= d.maxUsagePerUser) {
|
|
2697
|
-
return {
|
|
2698
|
-
valid: false,
|
|
2699
|
-
error: "You have reached the maximum usage limit for this discount"
|
|
2700
|
-
};
|
|
2701
|
-
}
|
|
2702
|
-
} catch {
|
|
2908
|
+
const usageCount = await countDiscountOrdersForContact(dataSource, d.id, contactId);
|
|
2909
|
+
if (usageCount >= d.maxUsagePerUser) {
|
|
2910
|
+
return {
|
|
2911
|
+
valid: false,
|
|
2912
|
+
error: "You have reached the maximum usage limit for this discount"
|
|
2913
|
+
};
|
|
2703
2914
|
}
|
|
2704
2915
|
}
|
|
2705
2916
|
const flatRules = entityMap["discount_rules"] ? await dataSource.getRepository(entityMap["discount_rules"]).find({
|
|
@@ -2777,128 +2988,32 @@ function normalizeOrderLinesInput(raw) {
|
|
|
2777
2988
|
if (!Number.isFinite(productId) || productId <= 0) continue;
|
|
2778
2989
|
const qRaw = Number(r.quantity ?? 1);
|
|
2779
2990
|
const q = Number.isFinite(qRaw) ? Math.min(99999, Math.max(1, Math.floor(qRaw))) : 1;
|
|
2780
|
-
|
|
2991
|
+
const vidRaw = r.variantId ?? r.variant_id;
|
|
2992
|
+
const vid = vidRaw != null && String(vidRaw).trim() !== "" ? Number(vidRaw) : NaN;
|
|
2993
|
+
const variantId = Number.isFinite(vid) && vid > 0 ? vid : null;
|
|
2994
|
+
const key = `${productId}:${variantId ?? 0}`;
|
|
2995
|
+
const prev = merged.get(key);
|
|
2996
|
+
if (prev) prev.quantity += q;
|
|
2997
|
+
else merged.set(key, {
|
|
2998
|
+
productId,
|
|
2999
|
+
quantity: q,
|
|
3000
|
+
variantId
|
|
3001
|
+
});
|
|
2781
3002
|
}
|
|
2782
3003
|
return [
|
|
2783
|
-
...merged.
|
|
2784
|
-
]
|
|
2785
|
-
productId,
|
|
2786
|
-
quantity
|
|
2787
|
-
}));
|
|
3004
|
+
...merged.values()
|
|
3005
|
+
];
|
|
2788
3006
|
}
|
|
2789
3007
|
__name(normalizeOrderLinesInput, "normalizeOrderLinesInput");
|
|
2790
|
-
var ORDER_STATUS_CONFIRMED = "confirmed";
|
|
2791
|
-
function
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
})).filter((l) => l.productId > 0 && l.quantity > 0)
|
|
2798
|
-
};
|
|
2799
|
-
}
|
|
2800
|
-
__name(orderInventorySnapshotFromRow, "orderInventorySnapshotFromRow");
|
|
2801
|
-
function mergeInventoryLinesByProduct(lines) {
|
|
2802
|
-
const m = /* @__PURE__ */ new Map();
|
|
2803
|
-
for (const { productId, quantity } of lines) {
|
|
2804
|
-
if (!Number.isFinite(productId) || productId <= 0 || quantity <= 0) continue;
|
|
2805
|
-
m.set(productId, (m.get(productId) ?? 0) + quantity);
|
|
2806
|
-
}
|
|
2807
|
-
return m;
|
|
2808
|
-
}
|
|
2809
|
-
__name(mergeInventoryLinesByProduct, "mergeInventoryLinesByProduct");
|
|
2810
|
-
async function validateInventoryForConfirmedOrderLines(dataSource, entityMap, calcLines) {
|
|
2811
|
-
const productRepo = dataSource.getRepository(entityMap.products);
|
|
2812
|
-
const merged = mergeInventoryLinesByProduct(calcLines.filter((l) => l.found && l.productId != null).map((l) => ({
|
|
2813
|
-
productId: l.productId,
|
|
2814
|
-
quantity: l.quantity
|
|
2815
|
-
})));
|
|
2816
|
-
for (const [productId, qty] of merged) {
|
|
2817
|
-
const row = await productRepo.findOne({
|
|
2818
|
-
where: {
|
|
2819
|
-
id: productId,
|
|
2820
|
-
deleted: false
|
|
2821
|
-
}
|
|
2822
|
-
});
|
|
2823
|
-
if (!row) return `Product #${productId} not found`;
|
|
2824
|
-
if (row.type === "service") continue;
|
|
2825
|
-
const cur = Number(row.quantity ?? 0);
|
|
2826
|
-
if (cur < qty) {
|
|
2827
|
-
const name = row.name ?? String(productId);
|
|
2828
|
-
return `Insufficient stock for ${name}: available ${cur}, required ${qty}`;
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2831
|
-
return null;
|
|
2832
|
-
}
|
|
2833
|
-
__name(validateInventoryForConfirmedOrderLines, "validateInventoryForConfirmedOrderLines");
|
|
2834
|
-
async function applyInventoryDeltaForMergedLines(dataSource, entityMap, merged, sign, validateDeduct) {
|
|
2835
|
-
const productRepo = dataSource.getRepository(entityMap.products);
|
|
2836
|
-
for (const [productId, qty] of merged) {
|
|
2837
|
-
const row = await productRepo.findOne({
|
|
2838
|
-
where: {
|
|
2839
|
-
id: productId,
|
|
2840
|
-
deleted: false
|
|
2841
|
-
}
|
|
2842
|
-
});
|
|
2843
|
-
if (!row) return `Product #${productId} not found`;
|
|
2844
|
-
if (row.type === "service") continue;
|
|
2845
|
-
const cur = Number(row.quantity ?? 0);
|
|
2846
|
-
if (sign === -1) {
|
|
2847
|
-
if (validateDeduct && cur < qty) {
|
|
2848
|
-
const name = row.name ?? String(productId);
|
|
2849
|
-
return `Insufficient stock for ${name}: available ${cur}, required ${qty}`;
|
|
2850
|
-
}
|
|
2851
|
-
await productRepo.update(productId, {
|
|
2852
|
-
quantity: Math.max(0, cur - qty)
|
|
2853
|
-
});
|
|
2854
|
-
} else {
|
|
2855
|
-
await productRepo.update(productId, {
|
|
2856
|
-
quantity: cur + qty
|
|
2857
|
-
});
|
|
2858
|
-
}
|
|
2859
|
-
}
|
|
2860
|
-
return null;
|
|
2861
|
-
}
|
|
2862
|
-
__name(applyInventoryDeltaForMergedLines, "applyInventoryDeltaForMergedLines");
|
|
2863
|
-
async function validateInventoryForOrderBecomingConfirmed(dataSource, entityMap, prevSnap, nextLines) {
|
|
2864
|
-
const mergedNext = mergeInventoryLinesByProduct(nextLines);
|
|
2865
|
-
const productRepo = dataSource.getRepository(entityMap.products);
|
|
2866
|
-
const prevByProduct = prevSnap?.status === ORDER_STATUS_CONFIRMED ? mergeInventoryLinesByProduct(prevSnap.lines) : /* @__PURE__ */ new Map();
|
|
2867
|
-
for (const [productId, need] of mergedNext) {
|
|
2868
|
-
const row = await productRepo.findOne({
|
|
2869
|
-
where: {
|
|
2870
|
-
id: productId,
|
|
2871
|
-
deleted: false
|
|
2872
|
-
}
|
|
2873
|
-
});
|
|
2874
|
-
if (!row) return `Product #${productId} not found`;
|
|
2875
|
-
if (row.type === "service") continue;
|
|
2876
|
-
let available = Number(row.quantity ?? 0);
|
|
2877
|
-
available += prevByProduct.get(productId) ?? 0;
|
|
2878
|
-
if (available < need) {
|
|
2879
|
-
const name = row.name ?? String(productId);
|
|
2880
|
-
return `Insufficient stock for ${name}: available ${available}, required ${need}`;
|
|
2881
|
-
}
|
|
2882
|
-
}
|
|
2883
|
-
return null;
|
|
2884
|
-
}
|
|
2885
|
-
__name(validateInventoryForOrderBecomingConfirmed, "validateInventoryForOrderBecomingConfirmed");
|
|
2886
|
-
async function reconcileOrderInventoryBetweenSnapshots(dataSource, entityMap, prev, next) {
|
|
2887
|
-
const wasConfirmed = prev.status === ORDER_STATUS_CONFIRMED;
|
|
2888
|
-
const isConfirmed = next.status === ORDER_STATUS_CONFIRMED;
|
|
2889
|
-
if (wasConfirmed) {
|
|
2890
|
-
const merged = mergeInventoryLinesByProduct(prev.lines);
|
|
2891
|
-
const err = await applyInventoryDeltaForMergedLines(dataSource, entityMap, merged, 1, false);
|
|
2892
|
-
if (err) return err;
|
|
2893
|
-
}
|
|
2894
|
-
if (isConfirmed) {
|
|
2895
|
-
const merged = mergeInventoryLinesByProduct(next.lines);
|
|
2896
|
-
const err = await applyInventoryDeltaForMergedLines(dataSource, entityMap, merged, -1, true);
|
|
2897
|
-
if (err) return err;
|
|
2898
|
-
}
|
|
2899
|
-
return null;
|
|
3008
|
+
var ORDER_STATUS_CONFIRMED = "confirmed";
|
|
3009
|
+
async function validateInventoryForConfirmedOrderLines2(dataSource, entityMap, calcLines) {
|
|
3010
|
+
const lines = calcLines.filter((l) => l.found && l.productId != null).map((l) => ({
|
|
3011
|
+
productId: l.productId,
|
|
3012
|
+
quantity: l.quantity
|
|
3013
|
+
}));
|
|
3014
|
+
return validateInventoryForConfirmedOrderLines(dataSource, entityMap, lines);
|
|
2900
3015
|
}
|
|
2901
|
-
__name(
|
|
3016
|
+
__name(validateInventoryForConfirmedOrderLines2, "validateInventoryForConfirmedOrderLines");
|
|
2902
3017
|
async function resolveProductTaxRate(dataSource, entityMap, productId, productVendorId) {
|
|
2903
3018
|
if (!entityMap.product_config || !entityMap.taxes) {
|
|
2904
3019
|
return {
|
|
@@ -3800,23 +3915,24 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
3800
3915
|
if (resource === "vendor_customers" && entityMap["customer"]) {
|
|
3801
3916
|
const scope = await resolveVendorCustomersScope(dataSource, entityMap, resolveScope);
|
|
3802
3917
|
const repo2 = dataSource.getRepository(entity);
|
|
3803
|
-
const qb = repo2.createQueryBuilder("vc").leftJoinAndSelect("vc.customer", "customer").leftJoinAndSelect("customer.user", "user").orderBy("vc.createdAt", sortOrder === "DESC" ? "DESC" : "ASC").skip(skip).take(limit);
|
|
3918
|
+
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);
|
|
3804
3919
|
applyVendorScopeToQueryBuilder(qb, "vc", scope);
|
|
3805
3920
|
if (search && typeof search === "string" && search.trim()) {
|
|
3806
3921
|
const term = `%${search.trim()}%`;
|
|
3807
|
-
qb.andWhere("(customer.name ILIKE :term OR customer.email ILIKE :term OR customer.phone ILIKE :term)", {
|
|
3922
|
+
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)", {
|
|
3808
3923
|
term
|
|
3809
3924
|
});
|
|
3810
3925
|
}
|
|
3811
3926
|
const [rows, total2] = await qb.getManyAndCount();
|
|
3812
3927
|
const data2 = rows.map((row) => {
|
|
3813
3928
|
const customer = row.customer;
|
|
3929
|
+
const contact = row.contact;
|
|
3814
3930
|
return {
|
|
3815
3931
|
...row,
|
|
3816
|
-
name: customer?.name ?? null,
|
|
3817
|
-
email: customer?.email ?? null,
|
|
3818
|
-
phone: customer?.phone ?? null,
|
|
3819
|
-
company: customer?.company ?? null
|
|
3932
|
+
name: customer?.name ?? contact?.name ?? null,
|
|
3933
|
+
email: customer?.email ?? contact?.email ?? null,
|
|
3934
|
+
phone: customer?.phone ?? contact?.phone ?? null,
|
|
3935
|
+
company: customer?.company ?? contact?.company ?? null
|
|
3820
3936
|
};
|
|
3821
3937
|
});
|
|
3822
3938
|
return json({
|
|
@@ -4150,7 +4266,11 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
4150
4266
|
} else if (resource === "collections") {
|
|
4151
4267
|
if (scope.type === "vendor") {
|
|
4152
4268
|
where = mergeVendorScopeIntoWhere(where, scope, resource, catalogFlags);
|
|
4153
|
-
} else if (searchParams.get("isCatalog")
|
|
4269
|
+
} else if (searchParams.get("isCatalog") === "true") {
|
|
4270
|
+
where = mergeListWhereAnd(where, {
|
|
4271
|
+
isCatalog: true
|
|
4272
|
+
});
|
|
4273
|
+
} else if (searchParams.get("isCatalog") === "false") {
|
|
4154
4274
|
where = mergeListWhereAnd(where, {
|
|
4155
4275
|
isCatalog: false
|
|
4156
4276
|
});
|
|
@@ -4347,41 +4467,17 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
4347
4467
|
status: 503
|
|
4348
4468
|
});
|
|
4349
4469
|
}
|
|
4350
|
-
if (!entityMap["
|
|
4351
|
-
return json({
|
|
4352
|
-
error: "Users entity not configured"
|
|
4353
|
-
}, {
|
|
4354
|
-
status: 503
|
|
4355
|
-
});
|
|
4356
|
-
}
|
|
4357
|
-
let customerGroupId = null;
|
|
4358
|
-
if (entityMap["user_groups"]) {
|
|
4359
|
-
const userGroupRepo = dataSource.getRepository(entityMap["user_groups"]);
|
|
4360
|
-
const customerGroup = await userGroupRepo.findOne({
|
|
4361
|
-
where: {
|
|
4362
|
-
name: "Customer",
|
|
4363
|
-
deleted: false
|
|
4364
|
-
}
|
|
4365
|
-
});
|
|
4366
|
-
if (!customerGroup) {
|
|
4367
|
-
return json({
|
|
4368
|
-
error: "User group 'customer' not found"
|
|
4369
|
-
}, {
|
|
4370
|
-
status: 500
|
|
4371
|
-
});
|
|
4372
|
-
}
|
|
4373
|
-
customerGroupId = Number(customerGroup.id);
|
|
4374
|
-
} else {
|
|
4470
|
+
if (!entityMap["contacts"]) {
|
|
4375
4471
|
return json({
|
|
4376
|
-
error: "
|
|
4472
|
+
error: "Contacts entity not configured"
|
|
4377
4473
|
}, {
|
|
4378
4474
|
status: 503
|
|
4379
4475
|
});
|
|
4380
4476
|
}
|
|
4381
4477
|
const name = String(body.name ?? "").trim();
|
|
4382
|
-
const email = String(body.email ?? "").trim();
|
|
4383
|
-
const
|
|
4384
|
-
const
|
|
4478
|
+
const email = String(body.email ?? "").trim().toLowerCase();
|
|
4479
|
+
const phoneRaw = String(body.phone ?? "").trim();
|
|
4480
|
+
const phone = phoneRaw || null;
|
|
4385
4481
|
if (!name) return json({
|
|
4386
4482
|
error: "name is required"
|
|
4387
4483
|
}, {
|
|
@@ -4392,25 +4488,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
4392
4488
|
}, {
|
|
4393
4489
|
status: 400
|
|
4394
4490
|
});
|
|
4395
|
-
if (!phone) return json({
|
|
4396
|
-
error: "phone is required"
|
|
4397
|
-
}, {
|
|
4398
|
-
status: 400
|
|
4399
|
-
});
|
|
4400
|
-
if (!rawPw) return json({
|
|
4401
|
-
error: "password is required"
|
|
4402
|
-
}, {
|
|
4403
|
-
status: 400
|
|
4404
|
-
});
|
|
4405
|
-
if (rawPw.length < 6) {
|
|
4406
|
-
return json({
|
|
4407
|
-
error: "Password must be at least 6 characters"
|
|
4408
|
-
}, {
|
|
4409
|
-
status: 400
|
|
4410
|
-
});
|
|
4411
|
-
}
|
|
4412
4491
|
const customerRepo = dataSource.getRepository(entityMap["customer"]);
|
|
4413
|
-
const userRepo = dataSource.getRepository(entityMap["users"]);
|
|
4414
4492
|
const dupCustEmail = await customerRepo.findOne({
|
|
4415
4493
|
where: {
|
|
4416
4494
|
email,
|
|
@@ -4424,49 +4502,26 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
4424
4502
|
status: 409
|
|
4425
4503
|
});
|
|
4426
4504
|
}
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
if (dupCustPhone) {
|
|
4434
|
-
return json({
|
|
4435
|
-
error: "A customer with this phone number already exists"
|
|
4436
|
-
}, {
|
|
4437
|
-
status: 409
|
|
4505
|
+
if (phone) {
|
|
4506
|
+
const dupCustPhone = await customerRepo.findOne({
|
|
4507
|
+
where: {
|
|
4508
|
+
phone,
|
|
4509
|
+
deleted: false
|
|
4510
|
+
}
|
|
4438
4511
|
});
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4512
|
+
if (dupCustPhone) {
|
|
4513
|
+
return json({
|
|
4514
|
+
error: "A customer with this phone number already exists"
|
|
4515
|
+
}, {
|
|
4516
|
+
status: 409
|
|
4517
|
+
});
|
|
4445
4518
|
}
|
|
4446
|
-
});
|
|
4447
|
-
if (dupUser) {
|
|
4448
|
-
userId = Number(dupUser.id);
|
|
4449
|
-
} else {
|
|
4450
|
-
const bcrypt = await import('bcryptjs');
|
|
4451
|
-
const hashedPassword = await bcrypt.hash(rawPw, 10);
|
|
4452
|
-
const savedUser = await userRepo.save(userRepo.create({
|
|
4453
|
-
name,
|
|
4454
|
-
email,
|
|
4455
|
-
phone,
|
|
4456
|
-
password: hashedPassword,
|
|
4457
|
-
groupId: customerGroupId,
|
|
4458
|
-
adminAccess: false,
|
|
4459
|
-
blocked: false
|
|
4460
|
-
}));
|
|
4461
|
-
userId = Number(savedUser.id);
|
|
4462
4519
|
}
|
|
4463
|
-
const savedCustomer = await
|
|
4464
|
-
id: userId,
|
|
4520
|
+
const savedCustomer = await ensureCustomerRecord(dataSource, entityMap["customer"], {
|
|
4465
4521
|
name,
|
|
4466
4522
|
email,
|
|
4467
|
-
phone
|
|
4468
|
-
|
|
4469
|
-
phone
|
|
4523
|
+
phone,
|
|
4524
|
+
userId: null
|
|
4470
4525
|
});
|
|
4471
4526
|
if (!savedCustomer) {
|
|
4472
4527
|
return json({
|
|
@@ -4475,37 +4530,37 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
4475
4530
|
status: 409
|
|
4476
4531
|
});
|
|
4477
4532
|
}
|
|
4478
|
-
const
|
|
4479
|
-
|
|
4533
|
+
const contactRepo = dataSource.getRepository(entityMap["contacts"]);
|
|
4534
|
+
let contact = await contactRepo.findOne({
|
|
4480
4535
|
where: {
|
|
4481
|
-
|
|
4536
|
+
email,
|
|
4482
4537
|
deleted: false
|
|
4483
4538
|
}
|
|
4484
|
-
})
|
|
4485
|
-
if (
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
}
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
}
|
|
4539
|
+
});
|
|
4540
|
+
if (!contact) {
|
|
4541
|
+
contact = await contactRepo.save(contactRepo.create({
|
|
4542
|
+
name,
|
|
4543
|
+
email,
|
|
4544
|
+
phone: phone || null,
|
|
4545
|
+
type: "customer"
|
|
4546
|
+
}));
|
|
4547
|
+
} else {
|
|
4548
|
+
const t = contact.type;
|
|
4549
|
+
const patch = {
|
|
4550
|
+
name,
|
|
4551
|
+
phone: phone || contact.phone || null
|
|
4552
|
+
};
|
|
4553
|
+
if (t == null || t === "" || String(t).toLowerCase() !== "customer") {
|
|
4554
|
+
patch.type = "customer";
|
|
4555
|
+
}
|
|
4556
|
+
await contactRepo.update(contact.id, patch);
|
|
4557
|
+
contact = {
|
|
4558
|
+
...contact,
|
|
4559
|
+
...patch,
|
|
4560
|
+
id: contact.id
|
|
4561
|
+
};
|
|
4508
4562
|
}
|
|
4563
|
+
const contactId = Number(contact.id);
|
|
4509
4564
|
const scope = await resolveVendorCustomersScope(dataSource, entityMap, resolveScope);
|
|
4510
4565
|
let vendorId = null;
|
|
4511
4566
|
if (scope.type === "vendor") {
|
|
@@ -4521,19 +4576,25 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
4521
4576
|
status: 400
|
|
4522
4577
|
});
|
|
4523
4578
|
}
|
|
4524
|
-
|
|
4525
|
-
|
|
4579
|
+
await ensureVendorCustomersForOrder(dataSource, entityMap, [
|
|
4580
|
+
vendorId
|
|
4581
|
+
], contactId, {
|
|
4582
|
+
name,
|
|
4583
|
+
email,
|
|
4584
|
+
phone
|
|
4585
|
+
});
|
|
4586
|
+
const customerRow = await customerRepo.findOne({
|
|
4526
4587
|
where: {
|
|
4527
|
-
|
|
4588
|
+
id: savedCustomer.id,
|
|
4589
|
+
deleted: false
|
|
4528
4590
|
}
|
|
4529
4591
|
});
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
}
|
|
4536
|
-
return json(customerRow, {
|
|
4592
|
+
return json(customerRow ?? {
|
|
4593
|
+
id: savedCustomer.id,
|
|
4594
|
+
name,
|
|
4595
|
+
email,
|
|
4596
|
+
phone
|
|
4597
|
+
}, {
|
|
4537
4598
|
status: 201
|
|
4538
4599
|
});
|
|
4539
4600
|
}
|
|
@@ -4938,6 +4999,22 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
4938
4999
|
status: 400
|
|
4939
5000
|
});
|
|
4940
5001
|
}
|
|
5002
|
+
if (resource === "combos") {
|
|
5003
|
+
const vendorIdForSlug = scopeCreate.type === "vendor" ? scopeCreate.vendorId : Number(persistBody.vendorId);
|
|
5004
|
+
if (!Number.isFinite(vendorIdForSlug) || vendorIdForSlug <= 0) {
|
|
5005
|
+
return json({
|
|
5006
|
+
error: "vendorId is required"
|
|
5007
|
+
}, {
|
|
5008
|
+
status: 400
|
|
5009
|
+
});
|
|
5010
|
+
}
|
|
5011
|
+
const comboSlugErr = await normalizeComboNameAndSlug(repo, persistBody, vendorIdForSlug);
|
|
5012
|
+
if (comboSlugErr) return json({
|
|
5013
|
+
error: comboSlugErr
|
|
5014
|
+
}, {
|
|
5015
|
+
status: 400
|
|
5016
|
+
});
|
|
5017
|
+
}
|
|
4941
5018
|
sanitizeBodyForEntity(repo, persistBody);
|
|
4942
5019
|
let created;
|
|
4943
5020
|
try {
|
|
@@ -4981,6 +5058,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
4981
5058
|
}
|
|
4982
5059
|
const nameRaw = String(body["contact.name"] ?? contact.name ?? "").trim();
|
|
4983
5060
|
const phoneRaw = body["contact.phone"] ?? contact.phone;
|
|
5061
|
+
const emailForVc = (emailRaw || String(contact.email ?? "")).trim().toLowerCase();
|
|
4984
5062
|
const phoneToSave = phoneRaw == null || phoneRaw === "" ? null : String(phoneRaw).trim();
|
|
4985
5063
|
if (phoneToSave && contact.phone !== phoneToSave) {
|
|
4986
5064
|
await contactRepo.update(contact.id, {
|
|
@@ -4989,21 +5067,45 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
4989
5067
|
contact.phone = phoneToSave;
|
|
4990
5068
|
}
|
|
4991
5069
|
const vendorIdForCustomer = resolveVendorIdForContactCheck(scopeCreate, persistBody);
|
|
4992
|
-
const
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
5070
|
+
const orderLinesForVendors = normalizeOrderLinesInput(body.orderLines);
|
|
5071
|
+
const vendorIdsForVc = [];
|
|
5072
|
+
if (vendorIdForCustomer != null) vendorIdsForVc.push(vendorIdForCustomer);
|
|
5073
|
+
if (orderLinesForVendors?.length && entityMap.products) {
|
|
5074
|
+
const productRepoForVc = dataSource.getRepository(entityMap.products);
|
|
5075
|
+
for (const line of orderLinesForVendors) {
|
|
5076
|
+
const pid = Number(line.productId);
|
|
5077
|
+
if (!Number.isFinite(pid)) continue;
|
|
5078
|
+
const product = await productRepoForVc.findOne({
|
|
5079
|
+
where: {
|
|
5080
|
+
id: pid
|
|
5081
|
+
}
|
|
5082
|
+
});
|
|
5083
|
+
const pVid = Number(product?.vendorId);
|
|
5084
|
+
if (Number.isFinite(pVid) && pVid > 0) vendorIdsForVc.push(pVid);
|
|
5085
|
+
}
|
|
5086
|
+
}
|
|
5087
|
+
if (vendorIdsForVc.length > 0 && emailForVc) {
|
|
5088
|
+
await ensureVendorCustomersForOrder(dataSource, entityMap, vendorIdsForVc, contact.id, {
|
|
5089
|
+
name: nameRaw || emailForVc.split("@")[0] || "Customer",
|
|
5090
|
+
email: emailForVc,
|
|
4997
5091
|
phone: phoneRaw == null || phoneRaw === "" ? null : String(phoneRaw)
|
|
4998
5092
|
});
|
|
4999
|
-
}
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5093
|
+
}
|
|
5094
|
+
const accountCustomerId = Number(body.accountCustomerId);
|
|
5095
|
+
if (entityMap.customer_contacts && Number.isFinite(accountCustomerId) && accountCustomerId > 0) {
|
|
5096
|
+
const ccRepo = dataSource.getRepository(entityMap.customer_contacts);
|
|
5097
|
+
const orderContactId = contact.id;
|
|
5098
|
+
const existingLink = await ccRepo.findOne({
|
|
5099
|
+
where: {
|
|
5100
|
+
customerId: accountCustomerId,
|
|
5101
|
+
contactId: orderContactId
|
|
5102
|
+
}
|
|
5103
|
+
});
|
|
5104
|
+
if (!existingLink) {
|
|
5105
|
+
await ccRepo.save(ccRepo.create({
|
|
5106
|
+
customerId: accountCustomerId,
|
|
5107
|
+
contactId: orderContactId
|
|
5108
|
+
}));
|
|
5007
5109
|
}
|
|
5008
5110
|
}
|
|
5009
5111
|
const randomPart = Math.floor(1e4 + Math.random() * 9e4);
|
|
@@ -5011,6 +5113,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
5011
5113
|
persistBody.orderNumber = `ORD00${randomPart}`;
|
|
5012
5114
|
persistBody.qrToken = crypto2.randomBytes(16).toString("hex");
|
|
5013
5115
|
}
|
|
5116
|
+
await retireSoftDeletedUniquesFromBody(repo, persistBody);
|
|
5014
5117
|
created = await repo.save(repo.create(persistBody));
|
|
5015
5118
|
if (resource === "orders") {
|
|
5016
5119
|
const orderItemRepo = dataSource.getRepository(entityMap.order_items);
|
|
@@ -5052,8 +5155,8 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
5052
5155
|
}
|
|
5053
5156
|
}
|
|
5054
5157
|
const createdStatus = String(persistBody.status ?? "pending");
|
|
5055
|
-
if (createdStatus
|
|
5056
|
-
const stockErr = await
|
|
5158
|
+
if (orderStatusHoldsStock(createdStatus)) {
|
|
5159
|
+
const stockErr = await validateInventoryForConfirmedOrderLines2(dataSource, entityMap, lines);
|
|
5057
5160
|
if (stockErr) {
|
|
5058
5161
|
await repo.delete(created.id);
|
|
5059
5162
|
return json({
|
|
@@ -5075,6 +5178,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
5075
5178
|
await orderItemRepo.save(orderItemRepo.create({
|
|
5076
5179
|
orderId: created.id,
|
|
5077
5180
|
productId: line.productId,
|
|
5181
|
+
variantId: null,
|
|
5078
5182
|
quantity: line.quantity,
|
|
5079
5183
|
unitPrice: line.unitPrice,
|
|
5080
5184
|
subTotal: line.subtotal,
|
|
@@ -5168,7 +5272,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
5168
5272
|
}
|
|
5169
5273
|
if (orderDiscountId) {
|
|
5170
5274
|
if (createdStatus === ORDER_STATUS_CONFIRMED) {
|
|
5171
|
-
const usageErr = await
|
|
5275
|
+
const usageErr = await checkAndIncrementDiscountUsage2(dataSource, entityMap, orderDiscountId, persistBody.contactId);
|
|
5172
5276
|
if (usageErr) {
|
|
5173
5277
|
await repo.delete(created.id);
|
|
5174
5278
|
return json({
|
|
@@ -5178,7 +5282,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
5178
5282
|
});
|
|
5179
5283
|
}
|
|
5180
5284
|
}
|
|
5181
|
-
await
|
|
5285
|
+
await recordDiscountUsage2(dataSource, entityMap, created.id, orderDiscountId, persistBody.contactId, discount);
|
|
5182
5286
|
}
|
|
5183
5287
|
const orderIdForNotify = created.id;
|
|
5184
5288
|
void import('./emit-order-notification-trigger-IVLWKKJL.js').then(({ fireOrderNotificationTrigger }) => {
|
|
@@ -5187,6 +5291,16 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
5187
5291
|
entityMap
|
|
5188
5292
|
});
|
|
5189
5293
|
});
|
|
5294
|
+
if (getCms) {
|
|
5295
|
+
void import('./send-order-placed-emails-WPI37KRZ.js').then(({ sendOrderPlacedEmailsAfterConfirmation }) => {
|
|
5296
|
+
return sendOrderPlacedEmailsAfterConfirmation(orderIdForNotify, {
|
|
5297
|
+
getDataSource: /* @__PURE__ */ __name(async () => dataSource, "getDataSource"),
|
|
5298
|
+
entityMap,
|
|
5299
|
+
getCms: /* @__PURE__ */ __name(async () => await getCms(), "getCms")
|
|
5300
|
+
});
|
|
5301
|
+
}).catch(() => {
|
|
5302
|
+
});
|
|
5303
|
+
}
|
|
5190
5304
|
}
|
|
5191
5305
|
} catch (err) {
|
|
5192
5306
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -5832,6 +5946,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
5832
5946
|
}
|
|
5833
5947
|
}
|
|
5834
5948
|
sanitizeBodyForEntity(repo, updatePayload2);
|
|
5949
|
+
await retireSoftDeletedUniquesFromBody(repo, updatePayload2);
|
|
5835
5950
|
await repo.update(numericId, updatePayload2);
|
|
5836
5951
|
if (Array.isArray(rawBody.tags)) {
|
|
5837
5952
|
const tagNames = rawBody.tags.map((t) => String(t).trim()).filter(Boolean);
|
|
@@ -5920,7 +6035,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
5920
6035
|
} : await calculateOrderTotalsFromLines(dataSource, entityMap, orderLinesNorm, effectiveCurrency, putDiscountId, Number.isFinite(putOrderVendorId) ? putOrderVendorId : null);
|
|
5921
6036
|
const scalarUpdate = pickColumnUpdates(repo, rawBody);
|
|
5922
6037
|
const nextStatus = typeof scalarUpdate.status === "string" && scalarUpdate.status ? String(scalarUpdate.status) : orderInvPrev?.status ?? "pending";
|
|
5923
|
-
if (nextStatus
|
|
6038
|
+
if (orderStatusHoldsStock(nextStatus) && lines.length > 0) {
|
|
5924
6039
|
const verr = await validateInventoryForOrderBecomingConfirmed(dataSource, entityMap, orderInvPrev, lines.filter((l) => l.found && l.productId != null).map((l) => ({
|
|
5925
6040
|
productId: l.productId,
|
|
5926
6041
|
quantity: l.quantity
|
|
@@ -6049,17 +6164,17 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
6049
6164
|
}) : null;
|
|
6050
6165
|
const existingDiscountId = existingOd ? existingOd.discountId : null;
|
|
6051
6166
|
if (!putDiscountId && existingDiscountId && wasConfirmed) {
|
|
6052
|
-
await
|
|
6167
|
+
await decrementDiscountUsage2(dataSource, entityMap, existingDiscountId, numericId);
|
|
6053
6168
|
if (entityMap["order_discounts"] && existingOd) {
|
|
6054
6169
|
await dataSource.getRepository(entityMap["order_discounts"]).delete(existingOd.id);
|
|
6055
6170
|
}
|
|
6056
6171
|
}
|
|
6057
6172
|
if (putDiscountId && existingDiscountId && putDiscountId !== existingDiscountId) {
|
|
6058
6173
|
if (wasConfirmed) {
|
|
6059
|
-
await
|
|
6174
|
+
await decrementDiscountUsage2(dataSource, entityMap, existingDiscountId, numericId);
|
|
6060
6175
|
}
|
|
6061
6176
|
if (isNowConfirmed) {
|
|
6062
|
-
const usageErr = await
|
|
6177
|
+
const usageErr = await checkAndIncrementDiscountUsage2(dataSource, entityMap, putDiscountId, cid);
|
|
6063
6178
|
if (usageErr) return json({
|
|
6064
6179
|
error: usageErr
|
|
6065
6180
|
}, {
|
|
@@ -6072,12 +6187,12 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
6072
6187
|
discountAmount: discount
|
|
6073
6188
|
});
|
|
6074
6189
|
} else {
|
|
6075
|
-
await
|
|
6190
|
+
await recordDiscountUsage2(dataSource, entityMap, numericId, putDiscountId, cid, discount);
|
|
6076
6191
|
}
|
|
6077
6192
|
}
|
|
6078
6193
|
if (putDiscountId && existingDiscountId && putDiscountId === existingDiscountId) {
|
|
6079
6194
|
if (!wasConfirmed && isNowConfirmed) {
|
|
6080
|
-
const usageErr = await
|
|
6195
|
+
const usageErr = await checkAndIncrementDiscountUsage2(dataSource, entityMap, putDiscountId, cid);
|
|
6081
6196
|
if (usageErr) return json({
|
|
6082
6197
|
error: usageErr
|
|
6083
6198
|
}, {
|
|
@@ -6085,7 +6200,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
6085
6200
|
});
|
|
6086
6201
|
}
|
|
6087
6202
|
if (wasConfirmed && !isNowConfirmed) {
|
|
6088
|
-
await
|
|
6203
|
+
await decrementDiscountUsage2(dataSource, entityMap, putDiscountId, numericId);
|
|
6089
6204
|
}
|
|
6090
6205
|
if (existingOd && entityMap["order_discounts"]) {
|
|
6091
6206
|
await dataSource.getRepository(entityMap["order_discounts"]).update(existingOd.id, {
|
|
@@ -6095,14 +6210,14 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
6095
6210
|
}
|
|
6096
6211
|
if (putDiscountId && !existingDiscountId) {
|
|
6097
6212
|
if (isNowConfirmed) {
|
|
6098
|
-
const usageErr = await
|
|
6213
|
+
const usageErr = await checkAndIncrementDiscountUsage2(dataSource, entityMap, putDiscountId, cid);
|
|
6099
6214
|
if (usageErr) return json({
|
|
6100
6215
|
error: usageErr
|
|
6101
6216
|
}, {
|
|
6102
6217
|
status: 400
|
|
6103
6218
|
});
|
|
6104
6219
|
}
|
|
6105
|
-
await
|
|
6220
|
+
await recordDiscountUsage2(dataSource, entityMap, numericId, putDiscountId, cid, discount);
|
|
6106
6221
|
}
|
|
6107
6222
|
}
|
|
6108
6223
|
}
|
|
@@ -6573,7 +6688,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
6573
6688
|
}
|
|
6574
6689
|
if (resource === "orders" && orderInvPrev) {
|
|
6575
6690
|
const nextSt = typeof updatePayload.status === "string" && updatePayload.status ? String(updatePayload.status) : orderInvPrev.status;
|
|
6576
|
-
if (nextSt
|
|
6691
|
+
if (orderStatusHoldsStock(nextSt) && orderInvPrev.lines.length > 0) {
|
|
6577
6692
|
const verr = await validateInventoryForOrderBecomingConfirmed(dataSource, entityMap, orderInvPrev, orderInvPrev.lines);
|
|
6578
6693
|
if (verr) return json({
|
|
6579
6694
|
error: verr
|
|
@@ -6582,6 +6697,33 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
6582
6697
|
});
|
|
6583
6698
|
}
|
|
6584
6699
|
}
|
|
6700
|
+
if (resource === "combos") {
|
|
6701
|
+
const currentCombo = await repo.findOne({
|
|
6702
|
+
where: {
|
|
6703
|
+
id: numericId,
|
|
6704
|
+
deleted: false
|
|
6705
|
+
}
|
|
6706
|
+
});
|
|
6707
|
+
if (!currentCombo) return json({
|
|
6708
|
+
message: "Not found"
|
|
6709
|
+
}, {
|
|
6710
|
+
status: 404
|
|
6711
|
+
});
|
|
6712
|
+
const vendorIdForSlug = scopePut.type === "vendor" ? scopePut.vendorId : Number(currentCombo.vendorId);
|
|
6713
|
+
if (!Number.isFinite(vendorIdForSlug) || vendorIdForSlug <= 0) {
|
|
6714
|
+
return json({
|
|
6715
|
+
error: "vendorId is required"
|
|
6716
|
+
}, {
|
|
6717
|
+
status: 400
|
|
6718
|
+
});
|
|
6719
|
+
}
|
|
6720
|
+
const comboSlugErr = await normalizeComboNameAndSlug(repo, updatePayload, vendorIdForSlug, currentCombo, numericId);
|
|
6721
|
+
if (comboSlugErr) return json({
|
|
6722
|
+
error: comboSlugErr
|
|
6723
|
+
}, {
|
|
6724
|
+
status: 400
|
|
6725
|
+
});
|
|
6726
|
+
}
|
|
6585
6727
|
if (Object.keys(updatePayload).length > 0) {
|
|
6586
6728
|
if (resource === "payments" && updatePayload.status === "paid" && updatePayload.paidAt == null) {
|
|
6587
6729
|
const existingPayment = await repo.findOne({
|
|
@@ -6595,6 +6737,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
6595
6737
|
}
|
|
6596
6738
|
}
|
|
6597
6739
|
sanitizeBodyForEntity(repo, updatePayload);
|
|
6740
|
+
await retireSoftDeletedUniquesFromBody(repo, updatePayload);
|
|
6598
6741
|
await repo.update(numericId, updatePayload);
|
|
6599
6742
|
}
|
|
6600
6743
|
if (resource === "orders" && rawBody && entityMap["order_addresses"]) {
|
|
@@ -6770,7 +6913,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
6770
6913
|
});
|
|
6771
6914
|
if (od) {
|
|
6772
6915
|
const odRow = od;
|
|
6773
|
-
await
|
|
6916
|
+
await decrementDiscountUsage2(dataSource, entityMap, odRow.discountId, numericId);
|
|
6774
6917
|
}
|
|
6775
6918
|
}
|
|
6776
6919
|
}
|
|
@@ -6783,6 +6926,22 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
6783
6926
|
}
|
|
6784
6927
|
}
|
|
6785
6928
|
await repo.update(numericId, buildSoftDeletePayload(repo.metadata, deletedBy));
|
|
6929
|
+
const softDeletedCols = repo.metadata.columns.map((c) => c.propertyName);
|
|
6930
|
+
const uniqueColsToRetire = [
|
|
6931
|
+
"slug",
|
|
6932
|
+
"email"
|
|
6933
|
+
].filter((col) => softDeletedCols.includes(col));
|
|
6934
|
+
if (existing && uniqueColsToRetire.length > 0) {
|
|
6935
|
+
for (const col of uniqueColsToRetire) {
|
|
6936
|
+
const raw = existing[col];
|
|
6937
|
+
if (typeof raw !== "string") continue;
|
|
6938
|
+
const value = raw.trim();
|
|
6939
|
+
if (!value || value.includes("__deleted_")) continue;
|
|
6940
|
+
await repo.update(numericId, {
|
|
6941
|
+
[col]: `${value}__deleted_${numericId}`
|
|
6942
|
+
});
|
|
6943
|
+
}
|
|
6944
|
+
}
|
|
6786
6945
|
return json({
|
|
6787
6946
|
message: "Deleted successfully"
|
|
6788
6947
|
}, {
|
|
@@ -6813,6 +6972,63 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
6813
6972
|
};
|
|
6814
6973
|
}
|
|
6815
6974
|
__name(createCrudByIdHandler, "createCrudByIdHandler");
|
|
6975
|
+
async function linkUnclaimedContactToUser(dataSource, contactsEntity, userId, email) {
|
|
6976
|
+
const repo = dataSource.getRepository(contactsEntity);
|
|
6977
|
+
const found = await repo.findOne({
|
|
6978
|
+
where: {
|
|
6979
|
+
email,
|
|
6980
|
+
userId: IsNull(),
|
|
6981
|
+
deleted: false
|
|
6982
|
+
}
|
|
6983
|
+
});
|
|
6984
|
+
if (found) await repo.update(found.id, {
|
|
6985
|
+
userId
|
|
6986
|
+
});
|
|
6987
|
+
}
|
|
6988
|
+
__name(linkUnclaimedContactToUser, "linkUnclaimedContactToUser");
|
|
6989
|
+
function buildUserInviteLink(baseUrl, token) {
|
|
6990
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
6991
|
+
return `${base}/admin/invite?token=${encodeURIComponent(token)}`;
|
|
6992
|
+
}
|
|
6993
|
+
__name(buildUserInviteLink, "buildUserInviteLink");
|
|
6994
|
+
function newUserInviteToken() {
|
|
6995
|
+
return randomBytes(32).toString("hex");
|
|
6996
|
+
}
|
|
6997
|
+
__name(newUserInviteToken, "newUserInviteToken");
|
|
6998
|
+
async function findUserByInviteToken(dataSource, userEntity, token) {
|
|
6999
|
+
const trimmed = token.trim();
|
|
7000
|
+
if (!trimmed) return null;
|
|
7001
|
+
const repo = dataSource.getRepository(userEntity);
|
|
7002
|
+
const row = await repo.findOne({
|
|
7003
|
+
where: {
|
|
7004
|
+
inviteToken: trimmed,
|
|
7005
|
+
deleted: false
|
|
7006
|
+
},
|
|
7007
|
+
select: [
|
|
7008
|
+
"id",
|
|
7009
|
+
"email",
|
|
7010
|
+
"blocked",
|
|
7011
|
+
"password",
|
|
7012
|
+
"inviteStatus",
|
|
7013
|
+
"inviteToken"
|
|
7014
|
+
]
|
|
7015
|
+
});
|
|
7016
|
+
if (!row) return null;
|
|
7017
|
+
const u = row;
|
|
7018
|
+
return u;
|
|
7019
|
+
}
|
|
7020
|
+
__name(findUserByInviteToken, "findUserByInviteToken");
|
|
7021
|
+
async function completeUserInviteAccept(dataSource, userEntity, userId, passwordHash) {
|
|
7022
|
+
await dataSource.getRepository(userEntity).update(userId, {
|
|
7023
|
+
password: passwordHash,
|
|
7024
|
+
blocked: false,
|
|
7025
|
+
inviteStatus: "active",
|
|
7026
|
+
inviteToken: null
|
|
7027
|
+
});
|
|
7028
|
+
}
|
|
7029
|
+
__name(completeUserInviteAccept, "completeUserInviteAccept");
|
|
7030
|
+
|
|
7031
|
+
// src/lib/vendor-invite-token.ts
|
|
6816
7032
|
var VENDOR_INVITE_METADATA_TOKEN = "inviteToken";
|
|
6817
7033
|
var VENDOR_INVITE_METADATA_EXPIRES = "inviteExpiresAt";
|
|
6818
7034
|
var VENDOR_INVITE_METADATA_SENT = "inviteSentAt";
|
|
@@ -6837,6 +7053,13 @@ function invalidatedInviteMessage() {
|
|
|
6837
7053
|
return "This invitation is no longer valid. Ask an admin to resend the invite.";
|
|
6838
7054
|
}
|
|
6839
7055
|
__name(invalidatedInviteMessage, "invalidatedInviteMessage");
|
|
7056
|
+
function userAlreadyActive(user) {
|
|
7057
|
+
const hasPassword = !!(user.password && String(user.password).trim());
|
|
7058
|
+
if (user.inviteStatus === "active" && hasPassword) return true;
|
|
7059
|
+
if (user.inviteStatus === "pending") return false;
|
|
7060
|
+
return !user.blocked && hasPassword;
|
|
7061
|
+
}
|
|
7062
|
+
__name(userAlreadyActive, "userAlreadyActive");
|
|
6840
7063
|
async function validateInviteTokenForActivation(dataSource, entityMap, token) {
|
|
6841
7064
|
const trimmedToken = String(token ?? "").trim();
|
|
6842
7065
|
if (!trimmedToken) {
|
|
@@ -6883,15 +7106,15 @@ async function validateInviteTokenForActivation(dataSource, entityMap, token) {
|
|
|
6883
7106
|
"id",
|
|
6884
7107
|
"email",
|
|
6885
7108
|
"blocked",
|
|
6886
|
-
"password"
|
|
7109
|
+
"password",
|
|
7110
|
+
"inviteStatus"
|
|
6887
7111
|
]
|
|
6888
7112
|
});
|
|
6889
7113
|
if (!user2) return {
|
|
6890
7114
|
ok: false,
|
|
6891
7115
|
error: "User not found"
|
|
6892
7116
|
};
|
|
6893
|
-
|
|
6894
|
-
if (!user2.blocked && hasPassword2) {
|
|
7117
|
+
if (userAlreadyActive(user2)) {
|
|
6895
7118
|
return {
|
|
6896
7119
|
ok: false,
|
|
6897
7120
|
error: "User is already active"
|
|
@@ -6907,6 +7130,24 @@ async function validateInviteTokenForActivation(dataSource, entityMap, token) {
|
|
|
6907
7130
|
};
|
|
6908
7131
|
}
|
|
6909
7132
|
}
|
|
7133
|
+
if (userRepo && entityMap.users) {
|
|
7134
|
+
const userMatch = await findUserByInviteToken(dataSource, entityMap.users, trimmedToken);
|
|
7135
|
+
if (userMatch) {
|
|
7136
|
+
if (userMatch.inviteStatus === "active" || userAlreadyActive(userMatch)) {
|
|
7137
|
+
return {
|
|
7138
|
+
ok: false,
|
|
7139
|
+
error: "This invitation has already been accepted"
|
|
7140
|
+
};
|
|
7141
|
+
}
|
|
7142
|
+
return {
|
|
7143
|
+
ok: true,
|
|
7144
|
+
target: {
|
|
7145
|
+
userId: userMatch.id,
|
|
7146
|
+
email: userMatch.email
|
|
7147
|
+
}
|
|
7148
|
+
};
|
|
7149
|
+
}
|
|
7150
|
+
}
|
|
6910
7151
|
let email;
|
|
6911
7152
|
try {
|
|
6912
7153
|
email = Buffer.from(trimmedToken, "base64").toString("utf8").trim();
|
|
@@ -6936,22 +7177,30 @@ async function validateInviteTokenForActivation(dataSource, entityMap, token) {
|
|
|
6936
7177
|
"id",
|
|
6937
7178
|
"email",
|
|
6938
7179
|
"blocked",
|
|
6939
|
-
"password"
|
|
7180
|
+
"password",
|
|
7181
|
+
"inviteStatus",
|
|
7182
|
+
"inviteToken"
|
|
6940
7183
|
]
|
|
6941
7184
|
});
|
|
6942
7185
|
if (!user) return {
|
|
6943
7186
|
ok: false,
|
|
6944
7187
|
error: "User not found"
|
|
6945
7188
|
};
|
|
6946
|
-
const
|
|
6947
|
-
if (
|
|
7189
|
+
const u = user;
|
|
7190
|
+
if (userAlreadyActive(u)) {
|
|
6948
7191
|
return {
|
|
6949
7192
|
ok: false,
|
|
6950
7193
|
error: "User is already active"
|
|
6951
7194
|
};
|
|
6952
7195
|
}
|
|
7196
|
+
if (u.inviteToken && String(u.inviteToken).trim()) {
|
|
7197
|
+
return {
|
|
7198
|
+
ok: false,
|
|
7199
|
+
error: invalidatedInviteMessage()
|
|
7200
|
+
};
|
|
7201
|
+
}
|
|
6953
7202
|
if (entityMap.vendors) {
|
|
6954
|
-
const ownerVendor = await loadOwnerVendorForUser(dataSource, entityMap.vendors,
|
|
7203
|
+
const ownerVendor = await loadOwnerVendorForUser(dataSource, entityMap.vendors, u.id);
|
|
6955
7204
|
if (ownerVendor && readVendorInviteToken(ownerVendor.metadata)) {
|
|
6956
7205
|
return {
|
|
6957
7206
|
ok: false,
|
|
@@ -6962,8 +7211,8 @@ async function validateInviteTokenForActivation(dataSource, entityMap, token) {
|
|
|
6962
7211
|
return {
|
|
6963
7212
|
ok: true,
|
|
6964
7213
|
target: {
|
|
6965
|
-
userId:
|
|
6966
|
-
email: String(
|
|
7214
|
+
userId: u.id,
|
|
7215
|
+
email: String(u.email)
|
|
6967
7216
|
}
|
|
6968
7217
|
};
|
|
6969
7218
|
}
|
|
@@ -7181,10 +7430,7 @@ function createInviteAcceptHandler(config) {
|
|
|
7181
7430
|
}
|
|
7182
7431
|
if (beforeActivate) await beforeActivate(email, userId);
|
|
7183
7432
|
const hashedPassword = await hashPassword(password);
|
|
7184
|
-
await dataSource
|
|
7185
|
-
password: hashedPassword,
|
|
7186
|
-
blocked: false
|
|
7187
|
-
});
|
|
7433
|
+
await completeUserInviteAccept(dataSource, entityMap.users, userId, hashedPassword);
|
|
7188
7434
|
if (vendorId != null && entityMap.vendors) {
|
|
7189
7435
|
await completeVendorInviteAccept(dataSource, entityMap.vendors, vendorId);
|
|
7190
7436
|
}
|
|
@@ -8611,6 +8857,9 @@ function createUploadHandler(config) {
|
|
|
8611
8857
|
"image/png",
|
|
8612
8858
|
"image/gif",
|
|
8613
8859
|
"image/webp",
|
|
8860
|
+
"video/mp4",
|
|
8861
|
+
"video/webm",
|
|
8862
|
+
"video/quicktime",
|
|
8614
8863
|
"application/pdf",
|
|
8615
8864
|
"text/plain",
|
|
8616
8865
|
"application/zip",
|
|
@@ -8638,8 +8887,11 @@ function createUploadHandler(config) {
|
|
|
8638
8887
|
});
|
|
8639
8888
|
const defaultMax = 10 * 1024 * 1024;
|
|
8640
8889
|
const maxZipBytes = 80 * 1024 * 1024;
|
|
8890
|
+
const maxVideoBytes = 100 * 1024 * 1024;
|
|
8641
8891
|
const baseMax = maxSizeBytes ?? defaultMax;
|
|
8642
|
-
const
|
|
8892
|
+
const isZip = file.type === "application/zip" || file.type === "application/x-zip-compressed";
|
|
8893
|
+
const isVideo = file.type === "video/mp4" || file.type === "video/webm" || file.type === "video/quicktime";
|
|
8894
|
+
const effectiveMax = isZip ? Math.max(baseMax, maxZipBytes) : isVideo ? Math.max(baseMax, maxVideoBytes) : baseMax;
|
|
8643
8895
|
if (file.size > effectiveMax) return json({
|
|
8644
8896
|
error: "File size exceeds limit"
|
|
8645
8897
|
}, {
|
|
@@ -8712,12 +8964,20 @@ function createUploadHandler(config) {
|
|
|
8712
8964
|
parentId
|
|
8713
8965
|
});
|
|
8714
8966
|
} catch (err) {
|
|
8967
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8715
8968
|
console.error("[upload] File upload failed", {
|
|
8716
|
-
error:
|
|
8969
|
+
error: message,
|
|
8717
8970
|
stack: err instanceof Error ? err.stack : void 0,
|
|
8718
8971
|
name: err instanceof Error ? err.name : void 0,
|
|
8719
8972
|
metadata: err && typeof err === "object" && "$metadata" in err ? err.$metadata : void 0
|
|
8720
8973
|
});
|
|
8974
|
+
if (/Failed to parse body as FormData/i.test(message)) {
|
|
8975
|
+
return json({
|
|
8976
|
+
error: "File upload failed: request body could not be read as FormData. Exclude /api/upload from middleware matcher and raise proxyClientMaxBodySize."
|
|
8977
|
+
}, {
|
|
8978
|
+
status: 500
|
|
8979
|
+
});
|
|
8980
|
+
}
|
|
8721
8981
|
return json({
|
|
8722
8982
|
error: "File upload failed"
|
|
8723
8983
|
}, {
|
|
@@ -8938,6 +9198,17 @@ function createFormSaveHandlers(config) {
|
|
|
8938
9198
|
});
|
|
8939
9199
|
const fields = Array.isArray(body.fields) ? body.fields : [];
|
|
8940
9200
|
const { fields: _f, ...formRow } = body;
|
|
9201
|
+
const slug = typeof formRow.slug === "string" ? formRow.slug.trim() : "";
|
|
9202
|
+
if (slug) {
|
|
9203
|
+
if (await activeUniqueValueExists(formRepo(), "slug", slug)) {
|
|
9204
|
+
return json({
|
|
9205
|
+
error: "A form with this slug already exists"
|
|
9206
|
+
}, {
|
|
9207
|
+
status: 400
|
|
9208
|
+
});
|
|
9209
|
+
}
|
|
9210
|
+
await retireSoftDeletedUniqueValue(formRepo(), "slug", slug);
|
|
9211
|
+
}
|
|
8941
9212
|
const form = await formRepo().save(formRepo().create(formRow));
|
|
8942
9213
|
for (let i = 0; i < fields.length; i++) {
|
|
8943
9214
|
const row = normalizeFieldRow(fields[i], form.id);
|
|
@@ -8961,6 +9232,14 @@ function createFormSaveHandlers(config) {
|
|
|
8961
9232
|
status: 201
|
|
8962
9233
|
});
|
|
8963
9234
|
} catch (e) {
|
|
9235
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
9236
|
+
if (/duplicate key|unique constraint/i.test(msg) && /slug/i.test(msg)) {
|
|
9237
|
+
return json({
|
|
9238
|
+
error: "A form with this slug already exists"
|
|
9239
|
+
}, {
|
|
9240
|
+
status: 400
|
|
9241
|
+
});
|
|
9242
|
+
}
|
|
8964
9243
|
return json({
|
|
8965
9244
|
error: "Server Error"
|
|
8966
9245
|
}, {
|
|
@@ -9009,6 +9288,20 @@ function createFormSaveHandlers(config) {
|
|
|
9009
9288
|
]) {
|
|
9010
9289
|
if (body[key] !== void 0) formRow[key] = body[key];
|
|
9011
9290
|
}
|
|
9291
|
+
if (typeof formRow.slug === "string") {
|
|
9292
|
+
const slug = formRow.slug.trim();
|
|
9293
|
+
formRow.slug = slug;
|
|
9294
|
+
if (slug) {
|
|
9295
|
+
if (await activeUniqueValueExists(formRepo(), "slug", slug, formId)) {
|
|
9296
|
+
return json({
|
|
9297
|
+
error: "A form with this slug already exists"
|
|
9298
|
+
}, {
|
|
9299
|
+
status: 400
|
|
9300
|
+
});
|
|
9301
|
+
}
|
|
9302
|
+
await retireSoftDeletedUniqueValue(formRepo(), "slug", slug);
|
|
9303
|
+
}
|
|
9304
|
+
}
|
|
9012
9305
|
if (Object.keys(formRow).length > 0) await formRepo().update(formId, formRow);
|
|
9013
9306
|
await fieldRepo().delete({
|
|
9014
9307
|
formId
|
|
@@ -9037,6 +9330,14 @@ function createFormSaveHandlers(config) {
|
|
|
9037
9330
|
status: 404
|
|
9038
9331
|
});
|
|
9039
9332
|
} catch (e) {
|
|
9333
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
9334
|
+
if (/duplicate key|unique constraint/i.test(msg) && /slug/i.test(msg)) {
|
|
9335
|
+
return json({
|
|
9336
|
+
error: "A form with this slug already exists"
|
|
9337
|
+
}, {
|
|
9338
|
+
status: 400
|
|
9339
|
+
});
|
|
9340
|
+
}
|
|
9040
9341
|
return json({
|
|
9041
9342
|
error: "Server Error"
|
|
9042
9343
|
}, {
|
|
@@ -9430,7 +9731,8 @@ function createUsersApiHandlers(config) {
|
|
|
9430
9731
|
"name",
|
|
9431
9732
|
"email",
|
|
9432
9733
|
"id",
|
|
9433
|
-
"blocked"
|
|
9734
|
+
"blocked",
|
|
9735
|
+
"inviteStatus"
|
|
9434
9736
|
]);
|
|
9435
9737
|
const sortField = allowedSort.has(sortFieldRaw) ? sortFieldRaw : "createdAt";
|
|
9436
9738
|
const sortOrder = url.searchParams.get("sortOrder") === "desc" ? "DESC" : "ASC";
|
|
@@ -9443,6 +9745,7 @@ function createUsersApiHandlers(config) {
|
|
|
9443
9745
|
"u.name",
|
|
9444
9746
|
"u.email",
|
|
9445
9747
|
"u.blocked",
|
|
9748
|
+
"u.inviteStatus",
|
|
9446
9749
|
"u.createdAt",
|
|
9447
9750
|
"u.updatedAt",
|
|
9448
9751
|
"u.groupId",
|
|
@@ -9538,6 +9841,7 @@ function createUsersApiHandlers(config) {
|
|
|
9538
9841
|
const isCustomer = !!(customerG && gid === customerG.id);
|
|
9539
9842
|
const adminAccess = isCustomer ? false : body.adminAccess === false ? false : true;
|
|
9540
9843
|
const blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
|
|
9844
|
+
const inviteToken = newUserInviteToken();
|
|
9541
9845
|
const newUser = existing?.deleted ? await (async () => {
|
|
9542
9846
|
await userRepo().update(existing.id, {
|
|
9543
9847
|
deleted: false,
|
|
@@ -9547,6 +9851,8 @@ function createUsersApiHandlers(config) {
|
|
|
9547
9851
|
email,
|
|
9548
9852
|
password: null,
|
|
9549
9853
|
blocked,
|
|
9854
|
+
inviteStatus: "pending",
|
|
9855
|
+
inviteToken,
|
|
9550
9856
|
groupId: gid,
|
|
9551
9857
|
adminAccess,
|
|
9552
9858
|
updatedAt: /* @__PURE__ */ new Date()
|
|
@@ -9563,6 +9869,8 @@ function createUsersApiHandlers(config) {
|
|
|
9563
9869
|
email,
|
|
9564
9870
|
password: null,
|
|
9565
9871
|
blocked,
|
|
9872
|
+
inviteStatus: "pending",
|
|
9873
|
+
inviteToken,
|
|
9566
9874
|
groupId: gid,
|
|
9567
9875
|
adminAccess
|
|
9568
9876
|
}));
|
|
@@ -9580,13 +9888,12 @@ function createUsersApiHandlers(config) {
|
|
|
9580
9888
|
phone
|
|
9581
9889
|
});
|
|
9582
9890
|
}
|
|
9583
|
-
const
|
|
9584
|
-
const inviteLink = `${baseUrl}/admin/invite?token=${emailToken}`;
|
|
9585
|
-
await trySendInviteEmail(newUser.email, inviteLink, newUser.name ?? "");
|
|
9891
|
+
const inviteLink = buildUserInviteLink(baseUrl, inviteToken);
|
|
9586
9892
|
return json({
|
|
9587
|
-
message:
|
|
9893
|
+
message: "User created successfully. Invite is pending until they set a password.",
|
|
9588
9894
|
user: newUser,
|
|
9589
|
-
inviteLink
|
|
9895
|
+
inviteLink,
|
|
9896
|
+
emailSent: false
|
|
9590
9897
|
}, {
|
|
9591
9898
|
status: 201
|
|
9592
9899
|
});
|
|
@@ -9618,7 +9925,9 @@ function createUsersApiHandlers(config) {
|
|
|
9618
9925
|
"id",
|
|
9619
9926
|
"name",
|
|
9620
9927
|
"email",
|
|
9928
|
+
"phone",
|
|
9621
9929
|
"blocked",
|
|
9930
|
+
"inviteStatus",
|
|
9622
9931
|
"createdAt",
|
|
9623
9932
|
"updatedAt",
|
|
9624
9933
|
"groupId"
|
|
@@ -9647,6 +9956,11 @@ function createUsersApiHandlers(config) {
|
|
|
9647
9956
|
}
|
|
9648
9957
|
try {
|
|
9649
9958
|
const uid = parseInt(id, 10);
|
|
9959
|
+
if (!Number.isFinite(uid)) return json({
|
|
9960
|
+
error: "Invalid id"
|
|
9961
|
+
}, {
|
|
9962
|
+
status: 400
|
|
9963
|
+
});
|
|
9650
9964
|
const existing = await userRepo().findOne({
|
|
9651
9965
|
where: {
|
|
9652
9966
|
id: uid,
|
|
@@ -9659,8 +9973,59 @@ function createUsersApiHandlers(config) {
|
|
|
9659
9973
|
status: 404
|
|
9660
9974
|
});
|
|
9661
9975
|
const body = await req.json();
|
|
9662
|
-
const
|
|
9663
|
-
|
|
9976
|
+
const patch = {
|
|
9977
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
9978
|
+
};
|
|
9979
|
+
if (typeof body.name === "string") patch.name = body.name.trim();
|
|
9980
|
+
if (typeof body.email === "string") patch.email = body.email.trim().toLowerCase();
|
|
9981
|
+
if (body.blocked !== void 0) {
|
|
9982
|
+
patch.blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
|
|
9983
|
+
}
|
|
9984
|
+
if (body.adminAccess !== void 0) {
|
|
9985
|
+
patch.adminAccess = body.adminAccess === true || body.adminAccess === "true" || body.adminAccess === 1 || body.adminAccess === "1";
|
|
9986
|
+
}
|
|
9987
|
+
if (body.groupId !== void 0) {
|
|
9988
|
+
if (body.groupId === null || body.groupId === "") {
|
|
9989
|
+
patch.groupId = null;
|
|
9990
|
+
} else {
|
|
9991
|
+
const gid = Number(body.groupId);
|
|
9992
|
+
if (!Number.isFinite(gid)) {
|
|
9993
|
+
return json({
|
|
9994
|
+
error: "Invalid groupId"
|
|
9995
|
+
}, {
|
|
9996
|
+
status: 400
|
|
9997
|
+
});
|
|
9998
|
+
}
|
|
9999
|
+
patch.groupId = gid;
|
|
10000
|
+
}
|
|
10001
|
+
}
|
|
10002
|
+
if (body.phone !== void 0) {
|
|
10003
|
+
const phone = body.phone == null ? null : String(body.phone).trim();
|
|
10004
|
+
patch.phone = phone || null;
|
|
10005
|
+
}
|
|
10006
|
+
if (Object.keys(patch).length <= 1) {
|
|
10007
|
+
return json({
|
|
10008
|
+
error: "No valid fields to update"
|
|
10009
|
+
}, {
|
|
10010
|
+
status: 400
|
|
10011
|
+
});
|
|
10012
|
+
}
|
|
10013
|
+
if (typeof patch.email === "string" && patch.email !== existing.email) {
|
|
10014
|
+
const emailTaken = await userRepo().findOne({
|
|
10015
|
+
where: {
|
|
10016
|
+
email: patch.email,
|
|
10017
|
+
deleted: false
|
|
10018
|
+
}
|
|
10019
|
+
});
|
|
10020
|
+
if (emailTaken && Number(emailTaken.id) !== uid) {
|
|
10021
|
+
return json({
|
|
10022
|
+
error: "Email already in use"
|
|
10023
|
+
}, {
|
|
10024
|
+
status: 400
|
|
10025
|
+
});
|
|
10026
|
+
}
|
|
10027
|
+
}
|
|
10028
|
+
await userRepo().update(uid, patch);
|
|
9664
10029
|
const updated = await userRepo().findOne({
|
|
9665
10030
|
where: {
|
|
9666
10031
|
id: uid,
|
|
@@ -9673,7 +10038,9 @@ function createUsersApiHandlers(config) {
|
|
|
9673
10038
|
"id",
|
|
9674
10039
|
"name",
|
|
9675
10040
|
"email",
|
|
10041
|
+
"phone",
|
|
9676
10042
|
"blocked",
|
|
10043
|
+
"inviteStatus",
|
|
9677
10044
|
"createdAt",
|
|
9678
10045
|
"updatedAt",
|
|
9679
10046
|
"groupId"
|
|
@@ -9684,7 +10051,8 @@ function createUsersApiHandlers(config) {
|
|
|
9684
10051
|
}, {
|
|
9685
10052
|
status: 404
|
|
9686
10053
|
});
|
|
9687
|
-
} catch {
|
|
10054
|
+
} catch (err) {
|
|
10055
|
+
console.error("[users.update]", err);
|
|
9688
10056
|
return json({
|
|
9689
10057
|
error: "Server Error"
|
|
9690
10058
|
}, {
|
|
@@ -9740,13 +10108,21 @@ function createUsersApiHandlers(config) {
|
|
|
9740
10108
|
});
|
|
9741
10109
|
}
|
|
9742
10110
|
},
|
|
9743
|
-
async regenerateInvite(
|
|
9744
|
-
const authErr = await requireAuth(new Request(
|
|
10111
|
+
async regenerateInvite(req, id) {
|
|
10112
|
+
const authErr = await requireAuth(new Request(req.url));
|
|
9745
10113
|
if (authErr) return authErr;
|
|
9746
10114
|
if (requireEntityPermission) {
|
|
9747
|
-
const pe = await requireEntityPermission(
|
|
10115
|
+
const pe = await requireEntityPermission(req, "users", "update");
|
|
9748
10116
|
if (pe) return pe;
|
|
9749
10117
|
}
|
|
10118
|
+
let sendEmail = true;
|
|
10119
|
+
let rotate = true;
|
|
10120
|
+
try {
|
|
10121
|
+
const body = await req.json().catch(() => ({}));
|
|
10122
|
+
if (body.sendEmail === false) sendEmail = false;
|
|
10123
|
+
if (body.rotate === false) rotate = false;
|
|
10124
|
+
} catch {
|
|
10125
|
+
}
|
|
9750
10126
|
try {
|
|
9751
10127
|
const user = await userRepo().findOne({
|
|
9752
10128
|
where: {
|
|
@@ -9756,7 +10132,11 @@ function createUsersApiHandlers(config) {
|
|
|
9756
10132
|
select: [
|
|
9757
10133
|
"id",
|
|
9758
10134
|
"email",
|
|
9759
|
-
"name"
|
|
10135
|
+
"name",
|
|
10136
|
+
"password",
|
|
10137
|
+
"inviteStatus",
|
|
10138
|
+
"inviteToken",
|
|
10139
|
+
"blocked"
|
|
9760
10140
|
]
|
|
9761
10141
|
});
|
|
9762
10142
|
if (!user) return json({
|
|
@@ -9764,34 +10144,75 @@ function createUsersApiHandlers(config) {
|
|
|
9764
10144
|
}, {
|
|
9765
10145
|
status: 404
|
|
9766
10146
|
});
|
|
9767
|
-
|
|
10147
|
+
const u = user;
|
|
10148
|
+
const hasPassword = !!(u.password && String(u.password).trim());
|
|
10149
|
+
if (u.inviteStatus === "active" && hasPassword) {
|
|
10150
|
+
return json({
|
|
10151
|
+
error: "User has already accepted the invite"
|
|
10152
|
+
}, {
|
|
10153
|
+
status: 400
|
|
10154
|
+
});
|
|
10155
|
+
}
|
|
10156
|
+
let inviteLink = "";
|
|
9768
10157
|
if (entityMap.vendors) {
|
|
9769
10158
|
const vendorRepo = dataSource.getRepository(entityMap.vendors);
|
|
9770
10159
|
const vendor = await vendorRepo.findOne({
|
|
9771
10160
|
where: {
|
|
9772
|
-
userId:
|
|
10161
|
+
userId: u.id,
|
|
9773
10162
|
deleted: false
|
|
9774
10163
|
}
|
|
9775
10164
|
});
|
|
9776
10165
|
const v = vendor;
|
|
9777
10166
|
if (v && (v.inviteStatus === "pending" || v.inviteStatus === "expired")) {
|
|
9778
|
-
const
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
10167
|
+
const nextMetadata = rotate ? applyRotatingVendorInvite(v.metadata) : {
|
|
10168
|
+
...v.metadata ?? {}
|
|
10169
|
+
};
|
|
10170
|
+
if (rotate || v.inviteStatus !== "pending") {
|
|
10171
|
+
await vendorRepo.update(v.id, {
|
|
10172
|
+
metadata: nextMetadata,
|
|
10173
|
+
inviteStatus: "pending"
|
|
10174
|
+
});
|
|
10175
|
+
}
|
|
10176
|
+
const token = readVendorInviteToken(nextMetadata);
|
|
9784
10177
|
if (token) {
|
|
9785
10178
|
inviteLink = buildVendorInviteLink(baseUrl, token);
|
|
9786
10179
|
}
|
|
10180
|
+
const { markVendorInvitePendingForUser } = await import('./vendor-invite-status-PWJ3DE76.js');
|
|
10181
|
+
await markVendorInvitePendingForUser(dataSource, entityMap.vendors, u.id);
|
|
10182
|
+
}
|
|
10183
|
+
}
|
|
10184
|
+
if (!inviteLink) {
|
|
10185
|
+
let token = u.inviteToken?.trim() || "";
|
|
10186
|
+
if (rotate || !token) {
|
|
10187
|
+
token = newUserInviteToken();
|
|
10188
|
+
await userRepo().update(u.id, {
|
|
10189
|
+
inviteToken: token,
|
|
10190
|
+
inviteStatus: "pending",
|
|
10191
|
+
password: null
|
|
10192
|
+
});
|
|
10193
|
+
} else if (u.inviteStatus !== "pending") {
|
|
10194
|
+
await userRepo().update(u.id, {
|
|
10195
|
+
inviteStatus: "pending"
|
|
10196
|
+
});
|
|
9787
10197
|
}
|
|
9788
|
-
|
|
9789
|
-
|
|
10198
|
+
inviteLink = buildUserInviteLink(baseUrl, token);
|
|
10199
|
+
} else if (rotate) {
|
|
10200
|
+
await userRepo().update(u.id, {
|
|
10201
|
+
inviteStatus: "pending",
|
|
10202
|
+
password: null
|
|
10203
|
+
});
|
|
9790
10204
|
}
|
|
9791
|
-
|
|
10205
|
+
let emailSent = false;
|
|
10206
|
+
if (sendEmail && inviteLink) {
|
|
10207
|
+
await trySendInviteEmail(u.email, inviteLink, u.name ?? "");
|
|
10208
|
+
emailSent = true;
|
|
10209
|
+
}
|
|
10210
|
+
const rotatedNote = rotate ? " Previous invite link is no longer valid." : "";
|
|
9792
10211
|
return json({
|
|
9793
|
-
message: "New invite link
|
|
9794
|
-
|
|
10212
|
+
message: emailSent ? `Invite email sent.${rotatedNote}` : sendEmail ? `Invite link ready (email may not have been sent \u2014 check email plugin).${rotatedNote}` : rotate ? "New invite link created. Previous invite link is no longer valid." : "Invite link ready.",
|
|
10213
|
+
emailSent,
|
|
10214
|
+
inviteLink,
|
|
10215
|
+
inviteStatus: "pending"
|
|
9795
10216
|
});
|
|
9796
10217
|
} catch {
|
|
9797
10218
|
return json({
|
|
@@ -12732,6 +13153,39 @@ function slugify(input) {
|
|
|
12732
13153
|
return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
12733
13154
|
}
|
|
12734
13155
|
__name(slugify, "slugify");
|
|
13156
|
+
async function userHasActiveVendor(em, entityMap, userId) {
|
|
13157
|
+
const vendorRepo = em.getRepository(entityMap.vendors);
|
|
13158
|
+
const owned = await vendorRepo.findOne({
|
|
13159
|
+
where: {
|
|
13160
|
+
userId,
|
|
13161
|
+
deleted: false
|
|
13162
|
+
}
|
|
13163
|
+
});
|
|
13164
|
+
if (owned) return true;
|
|
13165
|
+
if (!entityMap.vendor_users) return false;
|
|
13166
|
+
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', {
|
|
13167
|
+
userId
|
|
13168
|
+
}).getOne();
|
|
13169
|
+
return !!link;
|
|
13170
|
+
}
|
|
13171
|
+
__name(userHasActiveVendor, "userHasActiveVendor");
|
|
13172
|
+
async function userHadAnyVendor(em, entityMap, userId) {
|
|
13173
|
+
const vendorRepo = em.getRepository(entityMap.vendors);
|
|
13174
|
+
const owned = await vendorRepo.findOne({
|
|
13175
|
+
where: {
|
|
13176
|
+
userId
|
|
13177
|
+
}
|
|
13178
|
+
});
|
|
13179
|
+
if (owned) return true;
|
|
13180
|
+
if (!entityMap.vendor_users) return false;
|
|
13181
|
+
const link = await em.getRepository(entityMap.vendor_users).findOne({
|
|
13182
|
+
where: {
|
|
13183
|
+
userId
|
|
13184
|
+
}
|
|
13185
|
+
});
|
|
13186
|
+
return !!link;
|
|
13187
|
+
}
|
|
13188
|
+
__name(userHadAnyVendor, "userHadAnyVendor");
|
|
12735
13189
|
function vendorOnboardErrorResponse(json, err) {
|
|
12736
13190
|
const msg = err instanceof Error ? err.message : String(err);
|
|
12737
13191
|
console.error("[vendor-onboard]", err);
|
|
@@ -12830,14 +13284,80 @@ function createVendorOnboardHandlers(config) {
|
|
|
12830
13284
|
__name(gateAdmin, "gateAdmin");
|
|
12831
13285
|
async function resolveActiveVendorId(u) {
|
|
12832
13286
|
const scope = resolveVendorScopeFromSessionUser(u);
|
|
12833
|
-
|
|
12834
|
-
if (scope.type === "
|
|
12835
|
-
|
|
12836
|
-
|
|
13287
|
+
const candidates = [];
|
|
13288
|
+
if (scope.type === "vendor") candidates.push(scope.vendorId);
|
|
13289
|
+
if (scope.type === "all" || scope.type === "vendor") {
|
|
13290
|
+
const preferred = u.activeVendorId ?? u.vendorIds?.[0];
|
|
13291
|
+
if (preferred != null && Number.isFinite(preferred)) candidates.push(Number(preferred));
|
|
13292
|
+
for (const id of u.vendorIds ?? []) {
|
|
13293
|
+
if (Number.isFinite(id)) candidates.push(Number(id));
|
|
13294
|
+
}
|
|
13295
|
+
}
|
|
13296
|
+
if (entityMap.vendors && candidates.length > 0) {
|
|
13297
|
+
const unique = [
|
|
13298
|
+
...new Set(candidates.filter((id) => id > 0))
|
|
13299
|
+
];
|
|
13300
|
+
for (const id of unique) {
|
|
13301
|
+
try {
|
|
13302
|
+
const row = await dataSource.getRepository(entityMap.vendors).findOne({
|
|
13303
|
+
where: {
|
|
13304
|
+
id,
|
|
13305
|
+
deleted: false
|
|
13306
|
+
}
|
|
13307
|
+
});
|
|
13308
|
+
if (row) return id;
|
|
13309
|
+
} catch {
|
|
13310
|
+
}
|
|
13311
|
+
}
|
|
13312
|
+
}
|
|
13313
|
+
const uid = u.id != null ? Number(u.id) : NaN;
|
|
13314
|
+
if (Number.isFinite(uid) && entityMap.vendors) {
|
|
13315
|
+
try {
|
|
13316
|
+
const owned = await dataSource.getRepository(entityMap.vendors).findOne({
|
|
13317
|
+
where: {
|
|
13318
|
+
userId: uid,
|
|
13319
|
+
deleted: false
|
|
13320
|
+
},
|
|
13321
|
+
order: {
|
|
13322
|
+
id: "ASC"
|
|
13323
|
+
}
|
|
13324
|
+
});
|
|
13325
|
+
const vid = owned ? Number(owned.id) : NaN;
|
|
13326
|
+
if (Number.isFinite(vid) && vid > 0) return vid;
|
|
13327
|
+
} catch {
|
|
13328
|
+
}
|
|
12837
13329
|
}
|
|
12838
13330
|
return null;
|
|
12839
13331
|
}
|
|
12840
13332
|
__name(resolveActiveVendorId, "resolveActiveVendorId");
|
|
13333
|
+
async function gateVendorPortal() {
|
|
13334
|
+
const u = await getSessionUser();
|
|
13335
|
+
if (!u?.email) return json({
|
|
13336
|
+
error: "Unauthorized"
|
|
13337
|
+
}, {
|
|
13338
|
+
status: 401
|
|
13339
|
+
});
|
|
13340
|
+
if (!isVendorPortalUser(u) && !isPlatformAdministrator(u)) {
|
|
13341
|
+
return json({
|
|
13342
|
+
error: "Forbidden"
|
|
13343
|
+
}, {
|
|
13344
|
+
status: 403
|
|
13345
|
+
});
|
|
13346
|
+
}
|
|
13347
|
+
const vendorId = await resolveActiveVendorId(u);
|
|
13348
|
+
if (vendorId == null) {
|
|
13349
|
+
return json({
|
|
13350
|
+
error: "No vendor is linked to your account."
|
|
13351
|
+
}, {
|
|
13352
|
+
status: 400
|
|
13353
|
+
});
|
|
13354
|
+
}
|
|
13355
|
+
return {
|
|
13356
|
+
user: u,
|
|
13357
|
+
vendorId
|
|
13358
|
+
};
|
|
13359
|
+
}
|
|
13360
|
+
__name(gateVendorPortal, "gateVendorPortal");
|
|
12841
13361
|
async function gateVendorTeam() {
|
|
12842
13362
|
const u = await getSessionUser();
|
|
12843
13363
|
if (!u?.email) return json({
|
|
@@ -12891,6 +13411,40 @@ function createVendorOnboardHandlers(config) {
|
|
|
12891
13411
|
}
|
|
12892
13412
|
__name(trySendVendorOnboardEmails, "trySendVendorOnboardEmails");
|
|
12893
13413
|
return {
|
|
13414
|
+
/** GET /api/admin/vendor/profile — current user's store (server-resolved vendor id). */
|
|
13415
|
+
async getProfile() {
|
|
13416
|
+
const gated = await gateVendorPortal();
|
|
13417
|
+
if (gated instanceof Response) return gated;
|
|
13418
|
+
const { vendorId } = gated;
|
|
13419
|
+
if (!entityMap.vendors) {
|
|
13420
|
+
return json({
|
|
13421
|
+
error: "Vendors not configured"
|
|
13422
|
+
}, {
|
|
13423
|
+
status: 500
|
|
13424
|
+
});
|
|
13425
|
+
}
|
|
13426
|
+
try {
|
|
13427
|
+
const row = await dataSource.getRepository(entityMap.vendors).findOne({
|
|
13428
|
+
where: {
|
|
13429
|
+
id: vendorId,
|
|
13430
|
+
deleted: false
|
|
13431
|
+
}
|
|
13432
|
+
});
|
|
13433
|
+
if (!row) return json({
|
|
13434
|
+
error: "Vendor not found"
|
|
13435
|
+
}, {
|
|
13436
|
+
status: 404
|
|
13437
|
+
});
|
|
13438
|
+
return json(row);
|
|
13439
|
+
} catch (e) {
|
|
13440
|
+
console.error("[vendor.profile]", e);
|
|
13441
|
+
return json({
|
|
13442
|
+
error: "Failed to load vendor profile"
|
|
13443
|
+
}, {
|
|
13444
|
+
status: 500
|
|
13445
|
+
});
|
|
13446
|
+
}
|
|
13447
|
+
},
|
|
12894
13448
|
/** POST /api/admin/vendors/onboard — create vendor + owner user + vendor_users + invite */
|
|
12895
13449
|
async onboard(req) {
|
|
12896
13450
|
const err = await gateAdmin();
|
|
@@ -12959,6 +13513,7 @@ function createVendorOnboardHandlers(config) {
|
|
|
12959
13513
|
}
|
|
12960
13514
|
});
|
|
12961
13515
|
if (dupVendor) throw new Error("VENDOR_SLUG_EXISTS");
|
|
13516
|
+
await retireSoftDeletedUniqueValue(vendorRepo, "slug", slug);
|
|
12962
13517
|
let ownerGroup = await groupRepo.findOne({
|
|
12963
13518
|
where: {
|
|
12964
13519
|
name: VENDOR_OWNER_GROUP_NAME,
|
|
@@ -12987,8 +13542,15 @@ function createVendorOnboardHandlers(config) {
|
|
|
12987
13542
|
email: userEmail
|
|
12988
13543
|
}
|
|
12989
13544
|
});
|
|
12990
|
-
if (existingUser && !existingUser.deleted)
|
|
12991
|
-
|
|
13545
|
+
if (existingUser && !existingUser.deleted) {
|
|
13546
|
+
if (await userHasActiveVendor(em, entityMap, existingUser.id)) {
|
|
13547
|
+
throw new Error("USER_EMAIL_EXISTS");
|
|
13548
|
+
}
|
|
13549
|
+
if (!await userHadAnyVendor(em, entityMap, existingUser.id)) {
|
|
13550
|
+
throw new Error("USER_EMAIL_EXISTS");
|
|
13551
|
+
}
|
|
13552
|
+
}
|
|
13553
|
+
const newUser = existingUser ? await (async () => {
|
|
12992
13554
|
await userRepo.update(existingUser.id, {
|
|
12993
13555
|
deleted: false,
|
|
12994
13556
|
deletedAt: null,
|
|
@@ -14355,6 +14917,13 @@ var User = class {
|
|
|
14355
14917
|
phoneVerifiedAt;
|
|
14356
14918
|
emailVerifiedAt;
|
|
14357
14919
|
password;
|
|
14920
|
+
/**
|
|
14921
|
+
* `pending` = invite outstanding (cannot sign in until password is set).
|
|
14922
|
+
* `active` = invite accepted / account usable (subject to `blocked`).
|
|
14923
|
+
*/
|
|
14924
|
+
inviteStatus;
|
|
14925
|
+
/** Rotating invite token; null when active or no invite outstanding. */
|
|
14926
|
+
inviteToken;
|
|
14358
14927
|
blocked;
|
|
14359
14928
|
adminAccess;
|
|
14360
14929
|
groupId;
|
|
@@ -14407,6 +14976,24 @@ _ts_decorate4([
|
|
|
14407
14976
|
}),
|
|
14408
14977
|
_ts_metadata4("design:type", Object)
|
|
14409
14978
|
], User.prototype, "password", void 0);
|
|
14979
|
+
_ts_decorate4([
|
|
14980
|
+
Column({
|
|
14981
|
+
type: "enum",
|
|
14982
|
+
enum: [
|
|
14983
|
+
"pending",
|
|
14984
|
+
"active"
|
|
14985
|
+
],
|
|
14986
|
+
enumName: "users_invitestatus_enum",
|
|
14987
|
+
default: "active"
|
|
14988
|
+
}),
|
|
14989
|
+
_ts_metadata4("design:type", typeof UserInviteStatus === "undefined" ? Object : UserInviteStatus)
|
|
14990
|
+
], User.prototype, "inviteStatus", void 0);
|
|
14991
|
+
_ts_decorate4([
|
|
14992
|
+
Column("varchar", {
|
|
14993
|
+
nullable: true
|
|
14994
|
+
}),
|
|
14995
|
+
_ts_metadata4("design:type", Object)
|
|
14996
|
+
], User.prototype, "inviteToken", void 0);
|
|
14410
14997
|
_ts_decorate4([
|
|
14411
14998
|
Column("boolean", {
|
|
14412
14999
|
default: false
|
|
@@ -16071,10 +16658,12 @@ var Customer = class {
|
|
|
16071
16658
|
__name(this, "Customer");
|
|
16072
16659
|
}
|
|
16073
16660
|
id;
|
|
16661
|
+
/** Set only when the customer can log in (linked `users` row). Admin/guest customers stay null. */
|
|
16074
16662
|
userId;
|
|
16075
16663
|
user;
|
|
16076
16664
|
name;
|
|
16077
16665
|
email;
|
|
16666
|
+
/** Optional; multiple customers may have null (PostgreSQL UNIQUE allows multiple NULLs). */
|
|
16078
16667
|
phone;
|
|
16079
16668
|
createdAt;
|
|
16080
16669
|
updatedAt;
|
|
@@ -16089,8 +16678,10 @@ _ts_decorate18([
|
|
|
16089
16678
|
_ts_metadata18("design:type", Number)
|
|
16090
16679
|
], Customer.prototype, "id", void 0);
|
|
16091
16680
|
_ts_decorate18([
|
|
16092
|
-
Column("int"
|
|
16093
|
-
|
|
16681
|
+
Column("int", {
|
|
16682
|
+
nullable: true
|
|
16683
|
+
}),
|
|
16684
|
+
_ts_metadata18("design:type", Object)
|
|
16094
16685
|
], Customer.prototype, "userId", void 0);
|
|
16095
16686
|
_ts_decorate18([
|
|
16096
16687
|
ManyToOne(() => User, {
|
|
@@ -16113,9 +16704,10 @@ _ts_decorate18([
|
|
|
16113
16704
|
], Customer.prototype, "email", void 0);
|
|
16114
16705
|
_ts_decorate18([
|
|
16115
16706
|
Column("varchar", {
|
|
16116
|
-
unique: true
|
|
16707
|
+
unique: true,
|
|
16708
|
+
nullable: true
|
|
16117
16709
|
}),
|
|
16118
|
-
_ts_metadata18("design:type",
|
|
16710
|
+
_ts_metadata18("design:type", Object)
|
|
16119
16711
|
], Customer.prototype, "phone", void 0);
|
|
16120
16712
|
_ts_decorate18([
|
|
16121
16713
|
Column({
|
|
@@ -19135,6 +19727,8 @@ var OrderItem = class {
|
|
|
19135
19727
|
id;
|
|
19136
19728
|
orderId;
|
|
19137
19729
|
productId;
|
|
19730
|
+
/** Selected product variant at order time (optional). */
|
|
19731
|
+
variantId;
|
|
19138
19732
|
discountIds;
|
|
19139
19733
|
vendorId;
|
|
19140
19734
|
vendor;
|
|
@@ -19170,6 +19764,12 @@ _ts_decorate39([
|
|
|
19170
19764
|
Column("int"),
|
|
19171
19765
|
_ts_metadata39("design:type", Number)
|
|
19172
19766
|
], OrderItem.prototype, "productId", void 0);
|
|
19767
|
+
_ts_decorate39([
|
|
19768
|
+
Column("int", {
|
|
19769
|
+
nullable: true
|
|
19770
|
+
}),
|
|
19771
|
+
_ts_metadata39("design:type", Object)
|
|
19772
|
+
], OrderItem.prototype, "variantId", void 0);
|
|
19173
19773
|
_ts_decorate39([
|
|
19174
19774
|
Column({
|
|
19175
19775
|
type: "int",
|
|
@@ -19979,6 +20579,7 @@ var Type = /* @__PURE__ */ (function(Type2) {
|
|
|
19979
20579
|
Type2["CATEGORY"] = "category";
|
|
19980
20580
|
Type2["CART"] = "cart";
|
|
19981
20581
|
Type2["PAYMENT_METHOD"] = "paymentMethod";
|
|
20582
|
+
Type2["DEVICE_TYPE"] = "deviceType";
|
|
19982
20583
|
Type2["ORDER"] = "order";
|
|
19983
20584
|
Type2["QUANTITY"] = "quantity";
|
|
19984
20585
|
Type2["TIME"] = "time";
|
|
@@ -22001,6 +22602,7 @@ var Combo = class {
|
|
|
22001
22602
|
vendorId;
|
|
22002
22603
|
vendor;
|
|
22003
22604
|
name;
|
|
22605
|
+
slug;
|
|
22004
22606
|
desc;
|
|
22005
22607
|
eventId;
|
|
22006
22608
|
event;
|
|
@@ -22044,6 +22646,12 @@ _ts_decorate63([
|
|
|
22044
22646
|
}),
|
|
22045
22647
|
_ts_metadata63("design:type", String)
|
|
22046
22648
|
], Combo.prototype, "name", void 0);
|
|
22649
|
+
_ts_decorate63([
|
|
22650
|
+
Column({
|
|
22651
|
+
type: "varchar"
|
|
22652
|
+
}),
|
|
22653
|
+
_ts_metadata63("design:type", String)
|
|
22654
|
+
], Combo.prototype, "slug", void 0);
|
|
22047
22655
|
_ts_decorate63([
|
|
22048
22656
|
Column({
|
|
22049
22657
|
type: "text",
|
|
@@ -22181,7 +22789,11 @@ _ts_decorate63([
|
|
|
22181
22789
|
_ts_metadata63("design:type", Number)
|
|
22182
22790
|
], Combo.prototype, "deletedBy", void 0);
|
|
22183
22791
|
Combo = _ts_decorate63([
|
|
22184
|
-
Entity("combos")
|
|
22792
|
+
Entity("combos"),
|
|
22793
|
+
Unique("UQ_combos_vendor_slug", [
|
|
22794
|
+
"vendorId",
|
|
22795
|
+
"slug"
|
|
22796
|
+
])
|
|
22185
22797
|
], Combo);
|
|
22186
22798
|
function _ts_decorate64(decorators, target, key, desc) {
|
|
22187
22799
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -26107,8 +26719,11 @@ function createMessageTemplateRowLoader(dataSource, entityMap) {
|
|
|
26107
26719
|
};
|
|
26108
26720
|
}
|
|
26109
26721
|
__name(createMessageTemplateRowLoader, "createMessageTemplateRowLoader");
|
|
26110
|
-
function registerMessagingQueueProcessors(cms) {
|
|
26111
|
-
registerEmailQueueProcessor(cms
|
|
26722
|
+
function registerMessagingQueueProcessors(cms, entityMap) {
|
|
26723
|
+
registerEmailQueueProcessor(cms, {
|
|
26724
|
+
getDataSource: /* @__PURE__ */ __name(async () => cms.dataSource, "getDataSource"),
|
|
26725
|
+
entityMap
|
|
26726
|
+
});
|
|
26112
26727
|
registerWhatsAppQueueProcessor(cms);
|
|
26113
26728
|
}
|
|
26114
26729
|
__name(registerMessagingQueueProcessors, "registerMessagingQueueProcessors");
|
|
@@ -26160,7 +26775,7 @@ async function ensureMessagingPluginsOnCms(base, options) {
|
|
|
26160
26775
|
}
|
|
26161
26776
|
}
|
|
26162
26777
|
const merged = Object.keys(overlay).length > 0 ? overlayCmsPlugins(cms, overlay) : cms;
|
|
26163
|
-
registerMessagingQueueProcessors(merged);
|
|
26778
|
+
registerMessagingQueueProcessors(merged, entityMap);
|
|
26164
26779
|
return merged;
|
|
26165
26780
|
}
|
|
26166
26781
|
__name(ensureMessagingPluginsOnCms, "ensureMessagingPluginsOnCms");
|
|
@@ -26209,7 +26824,7 @@ async function createCmsAppWithMessaging(options) {
|
|
|
26209
26824
|
logger,
|
|
26210
26825
|
plugins: mergedPlugins
|
|
26211
26826
|
});
|
|
26212
|
-
registerMessagingQueueProcessors(cms);
|
|
26827
|
+
registerMessagingQueueProcessors(cms, entityMap);
|
|
26213
26828
|
return cms;
|
|
26214
26829
|
}
|
|
26215
26830
|
__name(createCmsAppWithMessaging, "createCmsAppWithMessaging");
|
|
@@ -26406,7 +27021,7 @@ function createCmsApiHandler(config) {
|
|
|
26406
27021
|
companyDetails
|
|
26407
27022
|
};
|
|
26408
27023
|
if (queue) {
|
|
26409
|
-
const { queueEmail: queueEmail2 } = await import('./email-queue-
|
|
27024
|
+
const { queueEmail: queueEmail2 } = await import('./email-queue-OI2HSCGE.js');
|
|
26410
27025
|
await queueEmail2(cms, {
|
|
26411
27026
|
to: opts.to,
|
|
26412
27027
|
templateName: "passwordReset",
|
|
@@ -26699,6 +27314,14 @@ function createCmsApiHandler(config) {
|
|
|
26699
27314
|
});
|
|
26700
27315
|
return vendorHandlers.switchVendor(req);
|
|
26701
27316
|
}
|
|
27317
|
+
if (path2[0] === "admin" && path2[1] === "vendor" && path2[2] === "profile" && path2.length === 3 && m === "GET") {
|
|
27318
|
+
if (!vendorHandlers) return config.json({
|
|
27319
|
+
error: "Not found"
|
|
27320
|
+
}, {
|
|
27321
|
+
status: 404
|
|
27322
|
+
});
|
|
27323
|
+
return vendorHandlers.getProfile();
|
|
27324
|
+
}
|
|
26702
27325
|
if (path2[0] === "admin" && path2[1] === "vendor" && path2[2] === "roles" && vendorRolesHandlers) {
|
|
26703
27326
|
if (path2.length === 3 && m === "GET") return vendorRolesHandlers.list();
|
|
26704
27327
|
if (path2.length === 3 && m === "POST") return vendorRolesHandlers.create(req);
|
|
@@ -27652,14 +28275,27 @@ function createCmsApiHandler(config) {
|
|
|
27652
28275
|
const pe = await requireEntityPermissionEffective(req, "orders", "read");
|
|
27653
28276
|
if (pe) return pe;
|
|
27654
28277
|
const cms = await getCms();
|
|
27655
|
-
const { streamOrderInvoicePdf: streamOrderInvoicePdf2 } = await import('./erp-order-invoice-
|
|
28278
|
+
const { streamOrderInvoicePdf: streamOrderInvoicePdf2 } = await import('./erp-order-invoice-3GXDE443.js');
|
|
27656
28279
|
const oid = Number(path2[1]);
|
|
27657
28280
|
if (!Number.isFinite(oid)) return config.json({
|
|
27658
28281
|
error: "Invalid id"
|
|
27659
28282
|
}, {
|
|
27660
28283
|
status: 400
|
|
27661
28284
|
});
|
|
27662
|
-
|
|
28285
|
+
try {
|
|
28286
|
+
return await streamOrderInvoicePdf2(cms, dataSource, entityMap, oid, {});
|
|
28287
|
+
} catch (err) {
|
|
28288
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28289
|
+
console.error("[invoice] route failed", {
|
|
28290
|
+
orderId: oid,
|
|
28291
|
+
error: msg
|
|
28292
|
+
});
|
|
28293
|
+
return config.json({
|
|
28294
|
+
error: `Invoice failed: ${msg}`
|
|
28295
|
+
}, {
|
|
28296
|
+
status: 500
|
|
28297
|
+
});
|
|
28298
|
+
}
|
|
27663
28299
|
}
|
|
27664
28300
|
if (path2[0] === "orders" && path2.length === 3 && path2[2] === "repost-erp" && getCms) {
|
|
27665
28301
|
const a = await config.requireAuth(req);
|
|
@@ -28006,7 +28642,7 @@ function createCmsApiHandler(config) {
|
|
|
28006
28642
|
status: 400
|
|
28007
28643
|
});
|
|
28008
28644
|
}
|
|
28009
|
-
const { resendOrderNotification } = await import('./order-notification-dispatcher-
|
|
28645
|
+
const { resendOrderNotification } = await import('./order-notification-dispatcher-ZJZB2HUN.js');
|
|
28010
28646
|
const result = await resendOrderNotification(triggerKey, orderId, {
|
|
28011
28647
|
dataSource,
|
|
28012
28648
|
entityMap,
|
|
@@ -28078,6 +28714,30 @@ function createCmsApiHandler(config) {
|
|
|
28078
28714
|
paymentId = Number(payment.id);
|
|
28079
28715
|
}
|
|
28080
28716
|
}
|
|
28717
|
+
const orderWithItems = await orderRepo.findOne({
|
|
28718
|
+
where: {
|
|
28719
|
+
id: orderId,
|
|
28720
|
+
deleted: false
|
|
28721
|
+
},
|
|
28722
|
+
relations: [
|
|
28723
|
+
"items"
|
|
28724
|
+
]
|
|
28725
|
+
});
|
|
28726
|
+
if (orderWithItems) {
|
|
28727
|
+
const { reconcileOrderInventoryBetweenSnapshots: reconcileOrderInventoryBetweenSnapshots2, orderInventorySnapshotFromRow: orderInventorySnapshotFromRow2 } = await import('./order-inventory-76V4XIGW.js');
|
|
28728
|
+
const prevSnap = orderInventorySnapshotFromRow2(orderWithItems);
|
|
28729
|
+
const invErr = await reconcileOrderInventoryBetweenSnapshots2(dataSource, entityMap, prevSnap, {
|
|
28730
|
+
status: "cancelled",
|
|
28731
|
+
lines: prevSnap.lines
|
|
28732
|
+
});
|
|
28733
|
+
if (invErr) {
|
|
28734
|
+
return config.json({
|
|
28735
|
+
error: invErr
|
|
28736
|
+
}, {
|
|
28737
|
+
status: 400
|
|
28738
|
+
});
|
|
28739
|
+
}
|
|
28740
|
+
}
|
|
28081
28741
|
await orderRepo.update(orderId, {
|
|
28082
28742
|
status: "cancelled"
|
|
28083
28743
|
});
|
|
@@ -29121,6 +29781,14 @@ function createStorefrontApiHandler(config) {
|
|
|
29121
29781
|
entityMap
|
|
29122
29782
|
});
|
|
29123
29783
|
}).catch((err) => console.error("[notification-triggers] import failed", err));
|
|
29784
|
+
if (!getCms) return;
|
|
29785
|
+
void import('./send-order-placed-emails-WPI37KRZ.js').then(({ sendOrderPlacedEmailsAfterConfirmation }) => {
|
|
29786
|
+
return sendOrderPlacedEmailsAfterConfirmation(orderId, {
|
|
29787
|
+
getDataSource: /* @__PURE__ */ __name(async () => dataSource, "getDataSource"),
|
|
29788
|
+
entityMap,
|
|
29789
|
+
getCms
|
|
29790
|
+
});
|
|
29791
|
+
}).catch((err) => console.error("[order-emails] sendOrderPlacedEmailsAfterConfirmation failed", err));
|
|
29124
29792
|
}
|
|
29125
29793
|
__name(fireOrderPlacedNotification, "fireOrderPlacedNotification");
|
|
29126
29794
|
const otpAllowPhoneLogin = config.otpAllowPhoneLogin !== false;
|
|
@@ -29168,13 +29836,36 @@ function createStorefrontApiHandler(config) {
|
|
|
29168
29836
|
status: 400
|
|
29169
29837
|
};
|
|
29170
29838
|
}
|
|
29839
|
+
const list = [
|
|
29840
|
+
...vendorIds
|
|
29841
|
+
];
|
|
29171
29842
|
return {
|
|
29172
|
-
vendorId: [
|
|
29173
|
-
|
|
29174
|
-
][0]
|
|
29843
|
+
vendorId: list[0],
|
|
29844
|
+
vendorIds: list
|
|
29175
29845
|
};
|
|
29176
29846
|
}
|
|
29177
29847
|
__name(resolveSingleVendorIdFromCart, "resolveSingleVendorIdFromCart");
|
|
29848
|
+
async function linkOrderContactToVendors(contactId, vendorIds) {
|
|
29849
|
+
try {
|
|
29850
|
+
const contact = await contactRepo().findOne({
|
|
29851
|
+
where: {
|
|
29852
|
+
id: contactId,
|
|
29853
|
+
deleted: false
|
|
29854
|
+
}
|
|
29855
|
+
});
|
|
29856
|
+
if (!contact) return;
|
|
29857
|
+
const email = String(contact.email ?? "").trim().toLowerCase();
|
|
29858
|
+
if (!email) return;
|
|
29859
|
+
await ensureVendorCustomersForOrder(dataSource, entityMap, vendorIds, contactId, {
|
|
29860
|
+
name: String(contact.name ?? "").trim() || email.split("@")[0] || "Customer",
|
|
29861
|
+
email,
|
|
29862
|
+
phone: contact.phone ?? null
|
|
29863
|
+
});
|
|
29864
|
+
} catch (err) {
|
|
29865
|
+
console.error("[storefront] vendor_customers link failed", err);
|
|
29866
|
+
}
|
|
29867
|
+
}
|
|
29868
|
+
__name(linkOrderContactToVendors, "linkOrderContactToVendors");
|
|
29178
29869
|
function roundMoney3(n) {
|
|
29179
29870
|
return Math.round(n * 100) / 100;
|
|
29180
29871
|
}
|
|
@@ -30866,9 +31557,12 @@ function createStorefrontApiHandler(config) {
|
|
|
30866
31557
|
}, {
|
|
30867
31558
|
status: 400
|
|
30868
31559
|
});
|
|
31560
|
+
const { retireSoftDeletedUniqueValue: retireSoftDeletedUniqueValue2 } = await import('./retire-soft-deleted-unique-NXQIH4BC.js');
|
|
31561
|
+
await retireSoftDeletedUniqueValue2(userRepo(), "email", email);
|
|
30869
31562
|
const existing = await userRepo().findOne({
|
|
30870
31563
|
where: {
|
|
30871
|
-
email
|
|
31564
|
+
email,
|
|
31565
|
+
deleted: false
|
|
30872
31566
|
}
|
|
30873
31567
|
});
|
|
30874
31568
|
if (existing) return json({
|
|
@@ -31496,6 +32190,7 @@ function createStorefrontApiHandler(config) {
|
|
|
31496
32190
|
taxCode: line.taxCode
|
|
31497
32191
|
}));
|
|
31498
32192
|
}
|
|
32193
|
+
await linkOrderContactToVendors(contactId, vendorRes.vendorIds);
|
|
31499
32194
|
fireOrderPlacedNotification(oid);
|
|
31500
32195
|
return json({
|
|
31501
32196
|
orderId: oid,
|
|
@@ -31568,6 +32263,7 @@ function createStorefrontApiHandler(config) {
|
|
|
31568
32263
|
taxCode: line.taxCode
|
|
31569
32264
|
}));
|
|
31570
32265
|
}
|
|
32266
|
+
await linkOrderContactToVendors(contactId, vendorResChk.vendorIds);
|
|
31571
32267
|
await cartItemRepo().delete({
|
|
31572
32268
|
cartId: cart.id
|
|
31573
32269
|
});
|
|
@@ -31747,6 +32443,7 @@ function createStorefrontApiHandler(config) {
|
|
|
31747
32443
|
unitPrice: line.unitPrice,
|
|
31748
32444
|
tax: line.tax,
|
|
31749
32445
|
total: line.total,
|
|
32446
|
+
metadata: line.metadata ?? null,
|
|
31750
32447
|
product: p ? {
|
|
31751
32448
|
name: p.name,
|
|
31752
32449
|
slug: p.slug,
|
|
@@ -31814,4 +32511,4 @@ function createStorefrontApiHandler(config) {
|
|
|
31814
32511
|
}
|
|
31815
32512
|
__name(createStorefrontApiHandler, "createStorefrontApiHandler");
|
|
31816
32513
|
|
|
31817
|
-
export { Address, Attendee, Attribute, BLOG_GENERATOR_AGENT_NAME, BLOG_GENERATOR_DEFAULT_SYSTEM_INSTRUCTION, BLOG_GENERATOR_DEFAULT_VALIDATION_RULES, BLOG_GENERATOR_LLM_AGENT_SLUG, BLOG_GENERATOR_MARKDOWN_ARTICLE_SEPARATOR, BLOG_METADATA_ENRICHER_AGENT_NAME, BLOG_METADATA_ENRICHER_DEFAULT_SYSTEM_INSTRUCTION, BLOG_METADATA_ENRICHER_DEFAULT_VALIDATION_RULES, BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG, Blog, BlogGeneratorService, Brand, CMS_ENTITY_MAP, Cart, CartItem, Category, ChatConversation, ChatMessage, Collection, Combo, ComboItem, Comment, Config, Contact, Currency, CurrencyExchange, Customer, Customer_Contacts, Discount, DiscountRules, Event, EventProduct, Form, FormField, FormSubmission, JobSchedule, JobScheduleRun, KnowledgeBaseChunk, KnowledgeBaseDocument, LlmAgent, Media, MessageTemplate, Order, OrderAddresses, OrderDiscounts, OrderItem, OrderNotificationBinding, OrderNotificationTrigger, OtpChallenge, Page, PasswordResetToken, Payment, Permission, Product, ProductAttribute, ProductCategory, ProductConfig, ProductVariant, RefundPolicy, RefundRequest, RssArticle, RssFeed, Seo, Tag, Tax, User, UserGroup, Vendor, VendorCustomer, VendorRole, VendorRolePermission, VendorUser, Wishlist, WishlistItem, ZIP_MIME_TYPES, applyApprovalStatusSideEffects, applyEventApprovalStatusSideEffects, applyRotatingVendorInvite, applyVendorCustomersContactFilter, applyVendorEventCreateApproval, applyVendorProductCreateApproval, assertCaptchaOk, assertContactAllowedForVendorOrder, assertEventApprovalUpdate, assertProductApprovalUpdate, buildBlogMetadataUserPrompt, buildCronFromSchedule, buildRssUserPromptFromFeeds, buildVendorInviteLink, calculateOrderRefundPreview, calculateRefundFromPolicy, checkEventsEnabled, checkMultiVendorEnabled, contactIsVendorCustomer, countRecentOtpSends, createAnalyticsHandlers, createBlogBySlugHandler, createChangePasswordHandler, createCmsApiHandler, createCmsApp, createCmsAppWithMessaging, createCrudByIdHandler, createCrudHandler, createDashboardStatsHandler, createEcommerceAnalyticsHandler, createEventOrderMessageTemplateHandlers, createForgotPasswordHandler, createFormBySlugHandler, createInviteAcceptHandler, createJobScheduleHandlers, createLlmAgentKnowledgeHandlers, createMediaZipExtractHandler, createMessageTemplateRowLoader, createOtpChallenge, createSetPasswordHandler, createSettingsApiHandlers, createSocialMediaHandlers, createStorefrontApiHandler, createUploadHandler, createUserAuthApiRouter, createUserAvatarHandler, createUserProfileHandler, createUsersApiHandlers, createVendorDashboardHandler, createVendorOnboardHandlers, customerPhoneForUser, daysBeforeEventStart, describeEventTierPolicy, ensureCustomerForUser, ensureMessagingPluginsOnCms, ensureScheduleQueueWorker, ensureVendorCustomerForOrderContact, findActiveRefundPolicyForVendor, findVendorByInviteToken, formatTierRange, generateNumericOtp, getPublicSettingsGroup, getRequireEventApproval, getRequireProductApproval, getRssArticleSummaryFromItem, getVendorCatalogCreateFlags, hashOtpCode, hydrateVendorSessionUser, invalidateEventsCache, invalidateMultiVendorCache, invalidateRequireEventApprovalCache, invalidateRequireProductApprovalCache, invalidateVendorCatalogCreateFlagsCache, isCustomerTypeContact, isZipMedia, linkUnclaimedContactToUser, llmAgentToChatAgentOptions, loadSettingsGroupFromDb, loadUserVendorContext, mergeGuardrailsIntoSystemPrompt, messagingPlugins, metaFetchUserManagedPages, metaPostPageFeed, metaPostPagePhoto, metaResolvePageAccessToken, normalizePhoneE164, normalizeRefundTiers, overlayCmsPlugins, parseBlogGeneratorAgentContent, parseBlogGeneratorModelOutput, parseBlogMetadataEnrichmentJson, parseLlmAgentValidationRules, pgBossScheduleNameForId, queueErpCreateContactIfEnabled, queueJobScheduleNow, queuePlugin, queueSms, registerJobRunnerWorker, registerMessagingQueueProcessors, registerSmsQueueProcessor, relativePathFromMediaParentId, resolveBlogCategoryIdByName, resolveSettingsEncryptionKey, resolveVendorIdForContactCheck, sanitizeMediaFolderPath, sanitizeStorageSegment, sendVendorOnboardEmails, simpleDecrypt, simpleEncrypt, syncJobScheduleToPgBoss, validateRefundTiers, validateScheduleInput, validateUserMessageAgainstAgentRules, validateUserMessageAgainstStructuredRules, verifyAndConsumeOtpChallenge, verifyOtpCodeHash, whatsappPlugin, wrapGetCmsWithMessaging };
|
|
32514
|
+
export { Address, Attendee, Attribute, BLOG_GENERATOR_AGENT_NAME, BLOG_GENERATOR_DEFAULT_SYSTEM_INSTRUCTION, BLOG_GENERATOR_DEFAULT_VALIDATION_RULES, BLOG_GENERATOR_LLM_AGENT_SLUG, BLOG_GENERATOR_MARKDOWN_ARTICLE_SEPARATOR, BLOG_METADATA_ENRICHER_AGENT_NAME, BLOG_METADATA_ENRICHER_DEFAULT_SYSTEM_INSTRUCTION, BLOG_METADATA_ENRICHER_DEFAULT_VALIDATION_RULES, BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG, Blog, BlogGeneratorService, Brand, CMS_ENTITY_MAP, Cart, CartItem, Category, ChatConversation, ChatMessage, Collection, Combo, ComboItem, Comment, Config, Contact, Currency, CurrencyExchange, Customer, Customer_Contacts, Discount, DiscountRules, Event, EventProduct, Form, FormField, FormSubmission, JobSchedule, JobScheduleRun, KnowledgeBaseChunk, KnowledgeBaseDocument, LlmAgent, Media, MessageTemplate, Order, OrderAddresses, OrderDiscounts, OrderItem, OrderNotificationBinding, OrderNotificationTrigger, OtpChallenge, Page, PasswordResetToken, Payment, Permission, Product, ProductAttribute, ProductCategory, ProductConfig, ProductVariant, RefundPolicy, RefundRequest, RssArticle, RssFeed, Seo, Tag, Tax, User, UserGroup, Vendor, VendorCustomer, VendorRole, VendorRolePermission, VendorUser, Wishlist, WishlistItem, ZIP_MIME_TYPES, applyApprovalStatusSideEffects, applyEventApprovalStatusSideEffects, applyRotatingVendorInvite, applyVendorCustomersContactFilter, applyVendorEventCreateApproval, applyVendorProductCreateApproval, assertCaptchaOk, assertContactAllowedForVendorOrder, assertEventApprovalUpdate, assertProductApprovalUpdate, buildBlogMetadataUserPrompt, buildCronFromSchedule, buildRssUserPromptFromFeeds, buildUserInviteLink, buildVendorInviteLink, calculateOrderRefundPreview, calculateRefundFromPolicy, checkAndIncrementDiscountUsage, checkEventsEnabled, checkMultiVendorEnabled, completeUserInviteAccept, consumeAppliedDiscountUsages, contactIsVendorCustomer, countDiscountOrdersForContact, countRecentOtpSends, createAnalyticsHandlers, createBlogBySlugHandler, createChangePasswordHandler, createCmsApiHandler, createCmsApp, createCmsAppWithMessaging, createCrudByIdHandler, createCrudHandler, createDashboardStatsHandler, createEcommerceAnalyticsHandler, createEventOrderMessageTemplateHandlers, createForgotPasswordHandler, createFormBySlugHandler, createInviteAcceptHandler, createJobScheduleHandlers, createLlmAgentKnowledgeHandlers, createMediaZipExtractHandler, createMessageTemplateRowLoader, createOtpChallenge, createSetPasswordHandler, createSettingsApiHandlers, createSocialMediaHandlers, createStorefrontApiHandler, createUploadHandler, createUserAuthApiRouter, createUserAvatarHandler, createUserProfileHandler, createUsersApiHandlers, createVendorDashboardHandler, createVendorOnboardHandlers, customerPhoneForEmail, customerPhoneForUser, daysBeforeEventStart, decrementDiscountUsage, describeEventTierPolicy, ensureCustomerForUser, ensureCustomerRecord, ensureMessagingPluginsOnCms, ensureScheduleQueueWorker, ensureVendorCustomerForOrderContact, ensureVendorCustomersForOrder, findActiveRefundPolicyForVendor, findUserByInviteToken, findVendorByInviteToken, formatTierRange, generateNumericOtp, getPublicSettingsGroup, getRequireEventApproval, getRequireProductApproval, getRssArticleSummaryFromItem, getVendorCatalogCreateFlags, hashOtpCode, hydrateVendorSessionUser, invalidateEventsCache, invalidateMultiVendorCache, invalidateRequireEventApprovalCache, invalidateRequireProductApprovalCache, invalidateVendorCatalogCreateFlagsCache, isCustomerTypeContact, isMaxPerUserUsageReached, isMaxTotalUsageReached, isSyntheticCustomerPhone, isZipMedia, linkUnclaimedContactToUser, llmAgentToChatAgentOptions, loadSettingsGroupFromDb, loadUserVendorContext, mergeGuardrailsIntoSystemPrompt, messagingPlugins, metaFetchUserManagedPages, metaPostPageFeed, metaPostPagePhoto, metaResolvePageAccessToken, newUserInviteToken, normalizeCustomerPhone, normalizePhoneE164, normalizeRefundTiers, overlayCmsPlugins, parseBlogGeneratorAgentContent, parseBlogGeneratorModelOutput, parseBlogMetadataEnrichmentJson, parseLlmAgentValidationRules, pgBossScheduleNameForId, queueErpCreateContactIfEnabled, queueJobScheduleNow, queuePlugin, queueSms, recordDiscountUsage, registerJobRunnerWorker, registerMessagingQueueProcessors, registerSmsQueueProcessor, relativePathFromMediaParentId, resolveBlogCategoryIdByName, resolveSettingsEncryptionKey, resolveVendorIdForContactCheck, sanitizeMediaFolderPath, sanitizeStorageSegment, sendVendorOnboardEmails, simpleDecrypt, simpleEncrypt, syncJobScheduleToPgBoss, validateRefundTiers, validateScheduleInput, validateUserMessageAgainstAgentRules, validateUserMessageAgainstStructuredRules, verifyAndConsumeOtpChallenge, verifyOtpCodeHash, whatsappPlugin, wrapGetCmsWithMessaging };
|