@infuro/cms-core 1.0.45 → 1.0.47
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 +4249 -2872
- package/dist/admin.d.cts +24 -4
- package/dist/admin.d.ts +24 -4
- package/dist/admin.js +3179 -1804
- package/dist/api.cjs +41 -39
- package/dist/api.d.cts +10 -4
- package/dist/api.d.ts +10 -4
- package/dist/api.js +9 -7
- package/dist/auth.cjs +55 -51
- package/dist/auth.d.cts +6 -3
- package/dist/auth.d.ts +6 -3
- package/dist/auth.js +3 -3
- package/dist/{chunk-KFFB6EMZ.js → chunk-2HU5R2JE.js} +2 -0
- package/dist/chunk-2IAXTKQN.cjs +19 -0
- package/dist/chunk-2KUCQVAQ.js +936 -0
- package/dist/chunk-3K5AIEIZ.js +256 -0
- package/dist/{chunk-SPQFNIYJ.cjs → chunk-4PMK3RNA.cjs} +2 -0
- package/dist/{chunk-X6UQFV4X.cjs → chunk-77NUXO6A.cjs} +14 -12
- package/dist/chunk-BXYZDMTZ.cjs +953 -0
- package/dist/chunk-DBPSJYLZ.js +47 -0
- package/dist/chunk-EIZKW2PF.js +13 -0
- package/dist/chunk-HY3AXLOI.cjs +265 -0
- package/dist/{chunk-X47PBV4I.js → chunk-JE22VP6S.js} +1 -0
- package/dist/{chunk-DN65KDIA.js → chunk-JIWUVQ6B.js} +14 -13
- package/dist/{chunk-HT5DI3XW.cjs → chunk-KOTXDSQB.cjs} +1 -0
- package/dist/chunk-L3525VXI.js +243 -0
- package/dist/{chunk-22FFHLTO.cjs → chunk-LMJ7RKPF.cjs} +421 -150
- package/dist/chunk-OY2YTBMP.cjs +50 -0
- package/dist/{chunk-TAHX46EI.cjs → chunk-Q3HQEM4R.cjs} +15 -7
- package/dist/chunk-V25RDUOU.cjs +248 -0
- package/dist/{chunk-ZMRQ72F6.cjs → chunk-VUQFARRT.cjs} +10 -10
- package/dist/{chunk-MVXLRANV.js → chunk-W42UZLQO.js} +1977 -891
- package/dist/{chunk-F5XFHYTG.cjs → chunk-XUCKZPML.cjs} +2088 -991
- package/dist/{chunk-I6NH72MO.js → chunk-YC4NUZCS.js} +10 -2
- package/dist/{chunk-NNQBWDCI.js → chunk-YXH2UUEZ.js} +418 -146
- package/dist/{chunk-BLR6H5GL.js → chunk-ZF2RQWXB.js} +4 -4
- 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/{emit-order-notification-trigger-IVLWKKJL.js → emit-order-notification-trigger-6XX7LSC4.js} +1 -1
- package/dist/{emit-order-notification-trigger-D2N3KFHL.cjs → emit-order-notification-trigger-NASG7ZEO.cjs} +3 -3
- package/dist/erp-order-invoice-3GXDE443.js +3 -0
- package/dist/erp-order-invoice-EYYNR526.cjs +24 -0
- package/dist/{event-order-defaults-EMWBIGL3.cjs → event-order-defaults-7Z3UZOEH.cjs} +9 -9
- package/dist/{event-order-defaults-SDRNAZHC.js → event-order-defaults-XQ3IDPD7.js} +1 -1
- package/dist/hooks.cjs +43 -0
- package/dist/hooks.d.cts +10 -1
- package/dist/hooks.d.ts +10 -1
- package/dist/hooks.js +39 -1
- package/dist/index.cjs +429 -298
- package/dist/index.d.cts +317 -30
- package/dist/index.d.ts +317 -30
- package/dist/index.js +17 -13
- 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/migrations/1782200000000-VendorTeamProfiles.ts +233 -0
- package/dist/migrations/1782300000000-AddTypeToOrderNotificationBindings.ts +75 -0
- package/dist/order-inventory-2MYRKEPC.cjs +39 -0
- package/dist/order-inventory-76V4XIGW.js +2 -0
- package/dist/{order-notification-dispatcher-XWIJYDGA.js → order-notification-dispatcher-6WNG24NY.js} +4 -2
- package/dist/{order-notification-dispatcher-RU7BTGGX.cjs → order-notification-dispatcher-6XSU3AR7.cjs} +6 -4
- package/dist/{rbac-debug-db-2SGNLANF.js → rbac-debug-db-6EMIVMGF.js} +1 -1
- package/dist/{rbac-debug-db-4Z6A425H.cjs → rbac-debug-db-6PWTLBEL.cjs} +2 -2
- package/dist/retire-soft-deleted-unique-5VXBA5XX.cjs +15 -0
- package/dist/retire-soft-deleted-unique-NXQIH4BC.js +2 -0
- package/dist/theme.cjs +6 -1
- package/dist/theme.d.cts +4 -1
- package/dist/theme.d.ts +4 -1
- package/dist/theme.js +3 -1
- package/package.json +5 -4
- package/src/admin/admin.css +34 -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
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { notificationTriggerEmitter } from './chunk-HCIRL37O.js';
|
|
2
|
-
import { queueEmail } from './chunk-
|
|
2
|
+
import { queueEmail } from './chunk-L3525VXI.js';
|
|
3
|
+
import { mergeEmailLayoutCompanyDetails } from './chunk-2KUCQVAQ.js';
|
|
3
4
|
import { applyTemplateVars } from './chunk-CRFV5WJK.js';
|
|
4
5
|
import { __name } from './chunk-SHUYVCID.js';
|
|
5
|
-
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';
|
|
6
|
+
import { SESClient, SendRawEmailCommand, SendEmailCommand } from '@aws-sdk/client-ses';
|
|
7
|
+
import { createRequire } from 'module';
|
|
6
8
|
import nodemailer from 'nodemailer';
|
|
7
9
|
|
|
8
10
|
// src/lib/event-order-template-context.ts
|
|
@@ -16,17 +18,18 @@ function resolvePublicSiteUrl() {
|
|
|
16
18
|
return "";
|
|
17
19
|
}
|
|
18
20
|
__name(resolvePublicSiteUrl, "resolvePublicSiteUrl");
|
|
19
|
-
function buildOrderTrackUrl(qrToken, siteUrl) {
|
|
20
|
-
const token = qrToken?.trim();
|
|
21
|
+
function buildOrderTrackUrl(qrToken, siteUrl, orderNumber) {
|
|
22
|
+
const token = qrToken?.trim() || orderNumber?.trim();
|
|
21
23
|
if (!token) return "";
|
|
22
24
|
const base = (siteUrl ?? resolvePublicSiteUrl()).replace(/\/+$/, "");
|
|
23
25
|
if (!base) return `/track/${token}`;
|
|
24
26
|
return `${base}/track/${token}`;
|
|
25
27
|
}
|
|
26
28
|
__name(buildOrderTrackUrl, "buildOrderTrackUrl");
|
|
27
|
-
function buildOrderQrImageUrl(trackUrl, size = 200) {
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
function buildOrderQrImageUrl(trackUrl, size = 200, fallbackData) {
|
|
30
|
+
const data = trackUrl.trim() || fallbackData?.trim() || "";
|
|
31
|
+
if (!data) return "";
|
|
32
|
+
return `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${encodeURIComponent(data)}`;
|
|
30
33
|
}
|
|
31
34
|
__name(buildOrderQrImageUrl, "buildOrderQrImageUrl");
|
|
32
35
|
function formatOrderDetailsText(input) {
|
|
@@ -48,9 +51,13 @@ function formatOrderDetailsHtml(input) {
|
|
|
48
51
|
}
|
|
49
52
|
__name(formatOrderDetailsHtml, "formatOrderDetailsHtml");
|
|
50
53
|
function buildEventOrderTemplateVariables(input) {
|
|
51
|
-
const { event, order, contact, productNames, orderLines = [], siteUrl } = input;
|
|
52
|
-
const trackUrl = buildOrderTrackUrl(order.qrToken, siteUrl);
|
|
53
|
-
const qrImageUrl = buildOrderQrImageUrl(trackUrl);
|
|
54
|
+
const { event, order, contact, productNames, vendorName = "", orderLines = [], siteUrl } = input;
|
|
55
|
+
const trackUrl = buildOrderTrackUrl(order.qrToken, siteUrl, order.orderNumber);
|
|
56
|
+
const qrImageUrl = buildOrderQrImageUrl(trackUrl, 200, order.orderNumber);
|
|
57
|
+
const ticketQr = qrImageUrl ? `<img src="${qrImageUrl}" alt="Ticket QR Code" width="200" height="200" style="display:block;margin:12px 0;border:1px solid #e5e7eb;border-radius:8px;" />` : "";
|
|
58
|
+
const baseSite = (siteUrl ?? resolvePublicSiteUrl()).replace(/\/+$/, "");
|
|
59
|
+
const invoiceNumber = `INV-${order.orderNumber ?? ""}`;
|
|
60
|
+
const invoiceUrl = baseSite ? `${baseSite}/api/orders/${order.orderNumber ?? ""}/invoice` : `/api/orders/${order.orderNumber ?? ""}/invoice`;
|
|
54
61
|
const orderDetailsPayload = {
|
|
55
62
|
orderNumber: String(order.orderNumber ?? ""),
|
|
56
63
|
total: String(order.total ?? ""),
|
|
@@ -58,7 +65,8 @@ function buildEventOrderTemplateVariables(input) {
|
|
|
58
65
|
lines: orderLines
|
|
59
66
|
};
|
|
60
67
|
return {
|
|
61
|
-
eventName: event.name ?? "",
|
|
68
|
+
eventName: event.name ?? productNames ?? "",
|
|
69
|
+
vendorName: vendorName.trim(),
|
|
62
70
|
eventSlug: event.slug ?? "",
|
|
63
71
|
venue: event.venue ?? "",
|
|
64
72
|
startDate: formatEventDate(event.startDate),
|
|
@@ -71,6 +79,9 @@ function buildEventOrderTemplateVariables(input) {
|
|
|
71
79
|
productNames,
|
|
72
80
|
trackUrl,
|
|
73
81
|
qrImageUrl,
|
|
82
|
+
ticketQr,
|
|
83
|
+
invoiceNumber,
|
|
84
|
+
invoiceUrl,
|
|
74
85
|
orderDetails: formatOrderDetailsText(orderDetailsPayload),
|
|
75
86
|
orderDetailsHtml: formatOrderDetailsHtml(orderDetailsPayload)
|
|
76
87
|
};
|
|
@@ -168,6 +179,25 @@ async function loadPrimaryEvent(dataSource, entityMap, itemRows) {
|
|
|
168
179
|
};
|
|
169
180
|
}
|
|
170
181
|
__name(loadPrimaryEvent, "loadPrimaryEvent");
|
|
182
|
+
async function isFeatureGroupEnabled(dataSource, entityMap, group) {
|
|
183
|
+
if (!entityMap.configs) return true;
|
|
184
|
+
try {
|
|
185
|
+
const rows = await dataSource.getRepository(entityMap.configs).find({
|
|
186
|
+
where: {
|
|
187
|
+
settings: group,
|
|
188
|
+
deleted: false
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
const settings = Object.fromEntries(rows.map((r) => [
|
|
192
|
+
r.key,
|
|
193
|
+
r.value
|
|
194
|
+
]));
|
|
195
|
+
return settings.enabled !== "false" && settings.enabled !== "0";
|
|
196
|
+
} catch {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
__name(isFeatureGroupEnabled, "isFeatureGroupEnabled");
|
|
171
201
|
async function loadOrderNotificationContexts(orderId, deps) {
|
|
172
202
|
if (!deps.entityMap.orders) return null;
|
|
173
203
|
const order = await deps.dataSource.getRepository(deps.entityMap.orders).findOne({
|
|
@@ -181,7 +211,16 @@ async function loadOrderNotificationContexts(orderId, deps) {
|
|
|
181
211
|
});
|
|
182
212
|
if (!order) return null;
|
|
183
213
|
const o = order;
|
|
184
|
-
const
|
|
214
|
+
const meta = o.metadata && typeof o.metadata === "object" && !Array.isArray(o.metadata) ? o.metadata : {};
|
|
215
|
+
const metaBuyerEmail = typeof meta.buyerEmail === "string" && meta.buyerEmail.includes("@") ? meta.buyerEmail.trim() : null;
|
|
216
|
+
const metaCheckoutEmail = typeof meta.checkoutEmail === "string" && meta.checkoutEmail.includes("@") ? meta.checkoutEmail.trim() : null;
|
|
217
|
+
const metaBuyerName = typeof meta.buyerName === "string" && meta.buyerName.trim() ? meta.buyerName.trim() : null;
|
|
218
|
+
const rawContact = o.contact ?? {};
|
|
219
|
+
const contact = {
|
|
220
|
+
name: metaBuyerName || rawContact.name,
|
|
221
|
+
email: metaBuyerEmail || metaCheckoutEmail || rawContact.email,
|
|
222
|
+
phone: rawContact.phone
|
|
223
|
+
};
|
|
185
224
|
const currency = String(o.currency ?? "INR");
|
|
186
225
|
const siteUrl = resolvePublicSiteUrl();
|
|
187
226
|
const itemRows = await deps.dataSource.getRepository(deps.entityMap.order_items).find({
|
|
@@ -199,8 +238,31 @@ async function loadOrderNotificationContexts(orderId, deps) {
|
|
|
199
238
|
const { event, productNames: eventProductNames } = await loadPrimaryEvent(deps.dataSource, deps.entityMap, itemRows);
|
|
200
239
|
const allProductNames = orderLines.map((line) => line.name);
|
|
201
240
|
const productNames = eventProductNames.length > 0 ? eventProductNames.join(", ") : allProductNames.join(", ");
|
|
202
|
-
const
|
|
203
|
-
|
|
241
|
+
const [multiVendorEnabled, eventsEnabled] = await Promise.all([
|
|
242
|
+
isFeatureGroupEnabled(deps.dataSource, deps.entityMap, "multi_vendor"),
|
|
243
|
+
isFeatureGroupEnabled(deps.dataSource, deps.entityMap, "events")
|
|
244
|
+
]);
|
|
245
|
+
let vendorName = "";
|
|
246
|
+
if (multiVendorEnabled) {
|
|
247
|
+
const vendorId = o.vendorId ?? event?.vendorId;
|
|
248
|
+
if (vendorId && deps.entityMap.vendors) {
|
|
249
|
+
try {
|
|
250
|
+
const vRow = await deps.dataSource.getRepository(deps.entityMap.vendors).findOne({
|
|
251
|
+
where: {
|
|
252
|
+
id: vendorId
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
if (vRow) {
|
|
256
|
+
const v = vRow;
|
|
257
|
+
vendorName = String(v.storeName?.trim() || v.name?.trim() || v.companyName?.trim() || "");
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const effectiveEvent = eventsEnabled ? event : void 0;
|
|
264
|
+
const variables = effectiveEvent ? buildEventOrderTemplateVariables({
|
|
265
|
+
event: effectiveEvent,
|
|
204
266
|
order: {
|
|
205
267
|
orderNumber: o.orderNumber,
|
|
206
268
|
total: o.total,
|
|
@@ -209,11 +271,12 @@ async function loadOrderNotificationContexts(orderId, deps) {
|
|
|
209
271
|
},
|
|
210
272
|
contact,
|
|
211
273
|
productNames,
|
|
274
|
+
vendorName,
|
|
212
275
|
orderLines,
|
|
213
276
|
siteUrl
|
|
214
277
|
}) : buildEventOrderTemplateVariables({
|
|
215
278
|
event: {
|
|
216
|
-
name: productNames || "Your order",
|
|
279
|
+
name: eventsEnabled ? productNames || "Your order" : "",
|
|
217
280
|
slug: "",
|
|
218
281
|
venue: "",
|
|
219
282
|
startDate: null
|
|
@@ -226,6 +289,7 @@ async function loadOrderNotificationContexts(orderId, deps) {
|
|
|
226
289
|
},
|
|
227
290
|
contact,
|
|
228
291
|
productNames,
|
|
292
|
+
vendorName,
|
|
229
293
|
orderLines,
|
|
230
294
|
siteUrl
|
|
231
295
|
});
|
|
@@ -433,7 +497,7 @@ var WhatsAppService = class {
|
|
|
433
497
|
let body = opts.body?.trim() ?? "";
|
|
434
498
|
const variables = opts.variables ?? {};
|
|
435
499
|
if (opts.templateKey?.trim() && this.getMessageTemplateRow) {
|
|
436
|
-
const { resolveEventOrderTemplate, EVENT_ORDER_TEMPLATE_KEY } = await import('./event-order-defaults-
|
|
500
|
+
const { resolveEventOrderTemplate, EVENT_ORDER_TEMPLATE_KEY } = await import('./event-order-defaults-XQ3IDPD7.js');
|
|
437
501
|
if (opts.templateKey.trim() === EVENT_ORDER_TEMPLATE_KEY) {
|
|
438
502
|
const resolved = await resolveEventOrderTemplate("whatsapp", async (ch, key) => {
|
|
439
503
|
const row = await this.getMessageTemplateRow(ch, key);
|
|
@@ -649,96 +713,6 @@ function renderLayout(options) {
|
|
|
649
713
|
}
|
|
650
714
|
__name(renderLayout, "renderLayout");
|
|
651
715
|
|
|
652
|
-
// src/plugins/email/templates/types.ts
|
|
653
|
-
function normalizeSocialLinkItem(o) {
|
|
654
|
-
const url = String(o.url ?? "").trim();
|
|
655
|
-
if (!url) return null;
|
|
656
|
-
let iconUrl = String(o.iconUrl ?? o.icon_image ?? "").trim();
|
|
657
|
-
let icon = String(o.icon ?? "").trim();
|
|
658
|
-
if (!iconUrl && /^https?:\/\//i.test(icon)) {
|
|
659
|
-
iconUrl = icon;
|
|
660
|
-
icon = "";
|
|
661
|
-
}
|
|
662
|
-
const item = {
|
|
663
|
-
url
|
|
664
|
-
};
|
|
665
|
-
if (iconUrl) item.iconUrl = iconUrl;
|
|
666
|
-
if (icon) item.icon = icon;
|
|
667
|
-
return item;
|
|
668
|
-
}
|
|
669
|
-
__name(normalizeSocialLinkItem, "normalizeSocialLinkItem");
|
|
670
|
-
function parseSocialLinksJson(raw) {
|
|
671
|
-
if (raw == null || raw.trim() === "") return void 0;
|
|
672
|
-
try {
|
|
673
|
-
const parsed = JSON.parse(raw);
|
|
674
|
-
if (!Array.isArray(parsed)) return void 0;
|
|
675
|
-
const out = [];
|
|
676
|
-
for (const item of parsed) {
|
|
677
|
-
if (item && typeof item === "object" && "url" in item) {
|
|
678
|
-
const n = normalizeSocialLinkItem(item);
|
|
679
|
-
if (n) out.push(n);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
return out.length ? out : void 0;
|
|
683
|
-
} catch {
|
|
684
|
-
return void 0;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
__name(parseSocialLinksJson, "parseSocialLinksJson");
|
|
688
|
-
function mergeEmailLayoutCompanyDetails(branding, emailSettings) {
|
|
689
|
-
const fromBranding = getCompanyDetailsFromSettings(branding);
|
|
690
|
-
const pick2 = /* @__PURE__ */ __name((emailVal, fallback) => {
|
|
691
|
-
const t = emailVal?.trim();
|
|
692
|
-
return t || fallback?.trim() || void 0;
|
|
693
|
-
}, "pick");
|
|
694
|
-
const logoUrl = pick2(emailSettings.logoUrl ?? emailSettings.emailLogoUrl, fromBranding.logoUrl);
|
|
695
|
-
const companyName = pick2(emailSettings.companyName ?? emailSettings.emailCompanyName, fromBranding.companyName);
|
|
696
|
-
const supportEmail = pick2(emailSettings.supportEmail ?? emailSettings.emailSupportEmail, fromBranding.supportEmail);
|
|
697
|
-
const supportPhone = pick2(emailSettings.supportPhone, void 0);
|
|
698
|
-
const footerDisclaimer = pick2(emailSettings.footerDisclaimer, void 0);
|
|
699
|
-
const followUsTitle = pick2(emailSettings.followUsTitle, "Follow Us") || "Follow Us";
|
|
700
|
-
const socialFromEmail = parseSocialLinksJson(emailSettings.socialLinks);
|
|
701
|
-
const socialLinks = socialFromEmail?.length ? socialFromEmail : fromBranding.socialLinks;
|
|
702
|
-
return {
|
|
703
|
-
logoUrl,
|
|
704
|
-
companyName,
|
|
705
|
-
supportEmail,
|
|
706
|
-
supportPhone,
|
|
707
|
-
socialLinks,
|
|
708
|
-
footerDisclaimer,
|
|
709
|
-
followUsTitle
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
__name(mergeEmailLayoutCompanyDetails, "mergeEmailLayoutCompanyDetails");
|
|
713
|
-
function getCompanyDetailsFromSettings(settingsGroup) {
|
|
714
|
-
const logoUrl = settingsGroup.logo ?? settingsGroup.logoUrl ?? "";
|
|
715
|
-
const companyName = settingsGroup.companyName ?? settingsGroup.company_name ?? "";
|
|
716
|
-
const supportEmail = settingsGroup.supportEmail ?? settingsGroup.support_email ?? "";
|
|
717
|
-
let socialLinks = [];
|
|
718
|
-
const raw = settingsGroup.socialLinks ?? settingsGroup.social_links;
|
|
719
|
-
if (typeof raw === "string") {
|
|
720
|
-
try {
|
|
721
|
-
const arr = JSON.parse(raw);
|
|
722
|
-
if (Array.isArray(arr)) {
|
|
723
|
-
for (const item of arr) {
|
|
724
|
-
if (item && typeof item === "object") {
|
|
725
|
-
const n = normalizeSocialLinkItem(item);
|
|
726
|
-
if (n) socialLinks.push(n);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
} catch {
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
return {
|
|
734
|
-
logoUrl: logoUrl || void 0,
|
|
735
|
-
companyName: companyName || void 0,
|
|
736
|
-
supportEmail: supportEmail || void 0,
|
|
737
|
-
socialLinks: socialLinks.length ? socialLinks : void 0
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
__name(getCompanyDetailsFromSettings, "getCompanyDetailsFromSettings");
|
|
741
|
-
|
|
742
716
|
// src/plugins/email/templates/inline-cta.ts
|
|
743
717
|
function escapeHtml2(s) {
|
|
744
718
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -943,7 +917,7 @@ function renderAddressesText(billing, shipping) {
|
|
|
943
917
|
}
|
|
944
918
|
__name(renderAddressesText, "renderAddressesText");
|
|
945
919
|
function render4(ctx) {
|
|
946
|
-
const { orderNumber, total, subtotal, tax, currency, customerName, companyDetails, audience = "customer", internalCustomerEmail, lineItems = [], billingAddress, shippingAddress } = ctx;
|
|
920
|
+
const { orderNumber, total, subtotal, tax, currency, customerName, companyDetails, audience = "customer", internalCustomerEmail, lineItems = [], billingAddress, shippingAddress, invoiceAttached = false } = ctx;
|
|
947
921
|
const itemsHtml = renderLineItemsHtml(lineItems, currency);
|
|
948
922
|
const itemsText = renderLineItemsText(lineItems, currency);
|
|
949
923
|
const addrHtml = renderAddressesHtml(billingAddress, shippingAddress);
|
|
@@ -982,6 +956,7 @@ ${subtotalLine}${taxLine}${totalLine}`;
|
|
|
982
956
|
${addrHtml}
|
|
983
957
|
${itemsHtml}
|
|
984
958
|
${subtotalLine}${taxLine}${totalLine}
|
|
959
|
+
${invoiceAttached ? `<p style="margin:16px 0 0 0;font-size:14px;line-height:1.5;color:#333;">Your order invoice is attached as a PDF.</p>` : ""}
|
|
985
960
|
<p style="margin:16px 0 0 0;font-size:13px;line-height:1.5;color:#666;">If you have questions, reply to this email or contact us using the details below.</p>`;
|
|
986
961
|
text = [
|
|
987
962
|
`Order confirmed #${orderNumber}`,
|
|
@@ -992,6 +967,7 @@ ${subtotalLine}${taxLine}${totalLine}
|
|
|
992
967
|
subtotal != null ? `Subtotal: ${subtotal}${currency ? ` ${currency}` : ""}` : "",
|
|
993
968
|
tax != null && Number(tax) !== 0 ? `Tax: ${tax}${currency ? ` ${currency}` : ""}` : "",
|
|
994
969
|
total != null ? `Order total: ${total}${currency ? ` ${currency}` : ""}` : "",
|
|
970
|
+
invoiceAttached ? "Your order invoice is attached as a PDF." : "",
|
|
995
971
|
"",
|
|
996
972
|
"We will process your order shortly."
|
|
997
973
|
].filter((line, i, arr) => !(line === "" && arr[i - 1] === "")).join("\n");
|
|
@@ -1220,6 +1196,19 @@ ${slugNote}
|
|
|
1220
1196
|
`Owner / invitee: ${ownerName?.trim() || "\u2014"} (${ownerEmail})`,
|
|
1221
1197
|
`Activation: ${activation === "invite" ? "Invitation" : "Password set"}`
|
|
1222
1198
|
].filter(Boolean).join("\n");
|
|
1199
|
+
} else if (kind === "team_invite" && activation === "membership" && inviteLink) {
|
|
1200
|
+
subject = `You're invited to ${storeLabel}`;
|
|
1201
|
+
bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${greeting}</p>
|
|
1202
|
+
<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">You have been invited to join the vendor team for <strong>${escapeHtml2(storeLabel)}</strong>. You already have an account \u2014 accept below to join this store. You will not need to set a new password.</p>
|
|
1203
|
+
${primaryCtaButton(inviteLink, "Accept invitation")}
|
|
1204
|
+
<p style="margin:16px 0 0 0;font-size:12px;line-height:1.5;color:#888;">If the button does not work, copy and paste this URL into your browser:<br/><span style="word-break:break-all;">${escapeHtml2(inviteLink)}</span></p>`;
|
|
1205
|
+
text = [
|
|
1206
|
+
ownerName?.trim() ? `Hello ${ownerName.trim()},` : "Hello,",
|
|
1207
|
+
"",
|
|
1208
|
+
`You have been invited to join ${storeLabel}.`,
|
|
1209
|
+
"Open this link to accept (no new password required):",
|
|
1210
|
+
inviteLink
|
|
1211
|
+
].join("\n");
|
|
1223
1212
|
} else if (kind === "team_invite" && activation === "invite" && inviteLink) {
|
|
1224
1213
|
subject = `You're invited to ${storeLabel}`;
|
|
1225
1214
|
bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${greeting}</p>
|
|
@@ -1320,6 +1309,8 @@ function renderEmail(templateName, ctx, options) {
|
|
|
1320
1309
|
__name(renderEmail, "renderEmail");
|
|
1321
1310
|
|
|
1322
1311
|
// src/plugins/email/email-service.ts
|
|
1312
|
+
var require2 = createRequire(import.meta.url);
|
|
1313
|
+
var MailComposer = require2("nodemailer/lib/mail-composer");
|
|
1323
1314
|
var SES_REGIONS_WITHOUT_SMTP = /* @__PURE__ */ new Set([
|
|
1324
1315
|
"af-south-1",
|
|
1325
1316
|
"ap-south-2",
|
|
@@ -1340,6 +1331,10 @@ function sesRegionSupportsSmtp(region) {
|
|
|
1340
1331
|
return !SES_REGIONS_WITHOUT_SMTP.has(region.trim());
|
|
1341
1332
|
}
|
|
1342
1333
|
__name(sesRegionSupportsSmtp, "sesRegionSupportsSmtp");
|
|
1334
|
+
function toBuffer(content) {
|
|
1335
|
+
return Buffer.isBuffer(content) ? content : Buffer.from(content);
|
|
1336
|
+
}
|
|
1337
|
+
__name(toBuffer, "toBuffer");
|
|
1343
1338
|
var EmailService = class {
|
|
1344
1339
|
static {
|
|
1345
1340
|
__name(this, "EmailService");
|
|
@@ -1403,14 +1398,47 @@ var EmailService = class {
|
|
|
1403
1398
|
throw new Error(`Unsupported email type: ${config.type}`);
|
|
1404
1399
|
}
|
|
1405
1400
|
}
|
|
1401
|
+
nodemailerAttachments(attachments) {
|
|
1402
|
+
if (!attachments?.length) return void 0;
|
|
1403
|
+
return attachments.map((a) => ({
|
|
1404
|
+
filename: a.filename,
|
|
1405
|
+
content: toBuffer(a.content),
|
|
1406
|
+
contentType: a.contentType || "application/octet-stream"
|
|
1407
|
+
}));
|
|
1408
|
+
}
|
|
1406
1409
|
async send(emailData) {
|
|
1407
1410
|
try {
|
|
1411
|
+
const from = emailData.from || this.config.from;
|
|
1412
|
+
const to = emailData.to || this.config.to;
|
|
1413
|
+
const attachments = this.nodemailerAttachments(emailData.attachments);
|
|
1408
1414
|
if (this.config.type === "AWS" && this.sesClient) {
|
|
1415
|
+
if (attachments?.length) {
|
|
1416
|
+
const composer = new MailComposer({
|
|
1417
|
+
from,
|
|
1418
|
+
to,
|
|
1419
|
+
subject: emailData.subject,
|
|
1420
|
+
html: emailData.html,
|
|
1421
|
+
text: emailData.text,
|
|
1422
|
+
attachments
|
|
1423
|
+
});
|
|
1424
|
+
const raw = await new Promise((resolve, reject) => {
|
|
1425
|
+
composer.compile().build((err, message) => {
|
|
1426
|
+
if (err) reject(err);
|
|
1427
|
+
else resolve(message);
|
|
1428
|
+
});
|
|
1429
|
+
});
|
|
1430
|
+
await this.sesClient.send(new SendRawEmailCommand({
|
|
1431
|
+
RawMessage: {
|
|
1432
|
+
Data: raw
|
|
1433
|
+
}
|
|
1434
|
+
}));
|
|
1435
|
+
return true;
|
|
1436
|
+
}
|
|
1409
1437
|
await this.sesClient.send(new SendEmailCommand({
|
|
1410
|
-
Source:
|
|
1438
|
+
Source: from,
|
|
1411
1439
|
Destination: {
|
|
1412
1440
|
ToAddresses: [
|
|
1413
|
-
|
|
1441
|
+
to
|
|
1414
1442
|
]
|
|
1415
1443
|
},
|
|
1416
1444
|
Message: {
|
|
@@ -1436,11 +1464,12 @@ var EmailService = class {
|
|
|
1436
1464
|
}
|
|
1437
1465
|
if (this.transporter && (this.config.type === "SMTP" || this.config.type === "GMAIL" || this.config.type === "AWS" && this.awsViaSmtp)) {
|
|
1438
1466
|
await this.transporter.sendMail({
|
|
1439
|
-
from
|
|
1440
|
-
to
|
|
1467
|
+
from,
|
|
1468
|
+
to,
|
|
1441
1469
|
subject: emailData.subject,
|
|
1442
1470
|
html: emailData.html,
|
|
1443
|
-
text: emailData.text
|
|
1471
|
+
text: emailData.text,
|
|
1472
|
+
attachments
|
|
1444
1473
|
});
|
|
1445
1474
|
return true;
|
|
1446
1475
|
}
|
|
@@ -2055,11 +2084,18 @@ async function getSettingsGroup(dataSource, entityMap, group) {
|
|
|
2055
2084
|
]));
|
|
2056
2085
|
}
|
|
2057
2086
|
__name(getSettingsGroup, "getSettingsGroup");
|
|
2058
|
-
async function
|
|
2087
|
+
async function isEmailNotificationsEnabled(dataSource, entityMap) {
|
|
2059
2088
|
const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
|
|
2060
|
-
|
|
2089
|
+
if (settings.enabled === "false" || settings.enabled === "0") return false;
|
|
2090
|
+
return settings.email_enabled !== "false" && settings.email_enabled !== "0";
|
|
2061
2091
|
}
|
|
2062
|
-
__name(
|
|
2092
|
+
__name(isEmailNotificationsEnabled, "isEmailNotificationsEnabled");
|
|
2093
|
+
async function isWhatsappNotificationsEnabled(dataSource, entityMap) {
|
|
2094
|
+
const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
|
|
2095
|
+
if (settings.enabled === "false" || settings.enabled === "0") return false;
|
|
2096
|
+
return settings.whatsapp_enabled !== "false" && settings.whatsapp_enabled !== "0";
|
|
2097
|
+
}
|
|
2098
|
+
__name(isWhatsappNotificationsEnabled, "isWhatsappNotificationsEnabled");
|
|
2063
2099
|
async function isTriggerCatalogEnabled(triggerKey, dataSource, entityMap) {
|
|
2064
2100
|
if (!entityMap.order_notification_triggers) {
|
|
2065
2101
|
console.warn("[order-notifications] order_notification_triggers missing \u2014 run migration 1781230000000-AddOrderNotificationTriggers");
|
|
@@ -2087,22 +2123,34 @@ async function isTriggerCatalogEnabled(triggerKey, dataSource, entityMap) {
|
|
|
2087
2123
|
}
|
|
2088
2124
|
}
|
|
2089
2125
|
__name(isTriggerCatalogEnabled, "isTriggerCatalogEnabled");
|
|
2090
|
-
async function loadBoundTemplate(triggerKey, channel, dataSource, entityMap) {
|
|
2126
|
+
async function loadBoundTemplate(triggerKey, channel, audienceType = "customers", dataSource, entityMap) {
|
|
2091
2127
|
if (!entityMap.order_notification_bindings || !entityMap.message_templates) {
|
|
2092
2128
|
console.warn("[order-notifications] order_notification_bindings or message_templates missing from entityMap");
|
|
2093
2129
|
return null;
|
|
2094
2130
|
}
|
|
2095
|
-
const
|
|
2131
|
+
const bindingRepo = dataSource.getRepository(entityMap.order_notification_bindings);
|
|
2132
|
+
let binding = await bindingRepo.findOne({
|
|
2096
2133
|
where: {
|
|
2097
2134
|
triggerKey,
|
|
2098
2135
|
channel,
|
|
2136
|
+
type: audienceType,
|
|
2099
2137
|
enabled: true
|
|
2100
2138
|
}
|
|
2101
2139
|
});
|
|
2140
|
+
if (!binding && audienceType === "customers") {
|
|
2141
|
+
binding = await bindingRepo.findOne({
|
|
2142
|
+
where: {
|
|
2143
|
+
triggerKey,
|
|
2144
|
+
channel,
|
|
2145
|
+
enabled: true
|
|
2146
|
+
}
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2102
2149
|
if (!binding) {
|
|
2103
2150
|
console.warn("[order-notifications] no order_notification_bindings row", {
|
|
2104
2151
|
triggerKey,
|
|
2105
|
-
channel
|
|
2152
|
+
channel,
|
|
2153
|
+
audienceType
|
|
2106
2154
|
});
|
|
2107
2155
|
return null;
|
|
2108
2156
|
}
|
|
@@ -2110,7 +2158,8 @@ async function loadBoundTemplate(triggerKey, channel, dataSource, entityMap) {
|
|
|
2110
2158
|
if (templateId == null) {
|
|
2111
2159
|
console.warn("[order-notifications] binding exists but no template selected", {
|
|
2112
2160
|
triggerKey,
|
|
2113
|
-
channel
|
|
2161
|
+
channel,
|
|
2162
|
+
audienceType
|
|
2114
2163
|
});
|
|
2115
2164
|
return null;
|
|
2116
2165
|
}
|
|
@@ -2126,6 +2175,7 @@ async function loadBoundTemplate(triggerKey, channel, dataSource, entityMap) {
|
|
|
2126
2175
|
console.warn("[order-notifications] bound template not found or disabled", {
|
|
2127
2176
|
triggerKey,
|
|
2128
2177
|
channel,
|
|
2178
|
+
audienceType,
|
|
2129
2179
|
templateId
|
|
2130
2180
|
});
|
|
2131
2181
|
return null;
|
|
@@ -2153,8 +2203,136 @@ function mergeTriggerVariables(payload, rich) {
|
|
|
2153
2203
|
};
|
|
2154
2204
|
}
|
|
2155
2205
|
__name(mergeTriggerVariables, "mergeTriggerVariables");
|
|
2156
|
-
async function
|
|
2157
|
-
const
|
|
2206
|
+
async function isAudienceEmailEnabled(dataSource, entityMap, audience) {
|
|
2207
|
+
const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
|
|
2208
|
+
if (settings.enabled === "false" || settings.enabled === "0") return false;
|
|
2209
|
+
if (settings.email_enabled === "false" || settings.email_enabled === "0") return false;
|
|
2210
|
+
const key = `email_${audience}_enabled`;
|
|
2211
|
+
return settings[key] !== "false" && settings[key] !== "0";
|
|
2212
|
+
}
|
|
2213
|
+
__name(isAudienceEmailEnabled, "isAudienceEmailEnabled");
|
|
2214
|
+
async function getOrderVendorEmails(orderId, orderVendorId, dataSource, entityMap) {
|
|
2215
|
+
const emails = /* @__PURE__ */ new Set();
|
|
2216
|
+
if (!entityMap.vendors) return [];
|
|
2217
|
+
const vendorRepo = dataSource.getRepository(entityMap.vendors);
|
|
2218
|
+
if (orderVendorId) {
|
|
2219
|
+
try {
|
|
2220
|
+
const v = await vendorRepo.findOne({
|
|
2221
|
+
where: {
|
|
2222
|
+
id: orderVendorId
|
|
2223
|
+
}
|
|
2224
|
+
});
|
|
2225
|
+
if (v) {
|
|
2226
|
+
const row = v;
|
|
2227
|
+
if (row.email?.trim()) emails.add(row.email.trim());
|
|
2228
|
+
if (row.supportEmail?.trim()) emails.add(row.supportEmail.trim());
|
|
2229
|
+
}
|
|
2230
|
+
} catch {
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
if (entityMap.order_items) {
|
|
2234
|
+
try {
|
|
2235
|
+
const items = await dataSource.getRepository(entityMap.order_items).find({
|
|
2236
|
+
where: {
|
|
2237
|
+
orderId
|
|
2238
|
+
},
|
|
2239
|
+
relations: [
|
|
2240
|
+
"product"
|
|
2241
|
+
]
|
|
2242
|
+
});
|
|
2243
|
+
for (const item of items) {
|
|
2244
|
+
const row = item;
|
|
2245
|
+
const vId = row.product?.vendorId;
|
|
2246
|
+
if (vId && vId !== orderVendorId) {
|
|
2247
|
+
const v = await vendorRepo.findOne({
|
|
2248
|
+
where: {
|
|
2249
|
+
id: vId
|
|
2250
|
+
}
|
|
2251
|
+
});
|
|
2252
|
+
if (v) {
|
|
2253
|
+
const vRow = v;
|
|
2254
|
+
if (vRow.email?.trim()) emails.add(vRow.email.trim());
|
|
2255
|
+
if (vRow.supportEmail?.trim()) emails.add(vRow.supportEmail.trim());
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
} catch {
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
return Array.from(emails);
|
|
2263
|
+
}
|
|
2264
|
+
__name(getOrderVendorEmails, "getOrderVendorEmails");
|
|
2265
|
+
async function getAdminRecipientEmails(dataSource, entityMap) {
|
|
2266
|
+
const emails = /* @__PURE__ */ new Set();
|
|
2267
|
+
const emailSettings = await getSettingsGroup(dataSource, entityMap, "email");
|
|
2268
|
+
const rawRecipients = [
|
|
2269
|
+
emailSettings.salesTeamEmails,
|
|
2270
|
+
emailSettings.salesTeamEmail,
|
|
2271
|
+
emailSettings.fulfilmentTeamEmails,
|
|
2272
|
+
emailSettings.fulfilmentTeamEmail,
|
|
2273
|
+
emailSettings.adminEmail
|
|
2274
|
+
];
|
|
2275
|
+
for (const raw of rawRecipients) {
|
|
2276
|
+
if (!raw) continue;
|
|
2277
|
+
const parts = String(raw).split(/[,;]/);
|
|
2278
|
+
for (const p of parts) {
|
|
2279
|
+
const trimmed = p.trim();
|
|
2280
|
+
if (trimmed && trimmed.includes("@")) emails.add(trimmed.toLowerCase());
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
if (entityMap.users) {
|
|
2284
|
+
try {
|
|
2285
|
+
const userRepo = dataSource.getRepository(entityMap.users);
|
|
2286
|
+
const adminUsers = await userRepo.find({
|
|
2287
|
+
where: [
|
|
2288
|
+
{
|
|
2289
|
+
groupId: 1,
|
|
2290
|
+
blocked: false
|
|
2291
|
+
},
|
|
2292
|
+
{
|
|
2293
|
+
adminAccess: true,
|
|
2294
|
+
blocked: false
|
|
2295
|
+
}
|
|
2296
|
+
]
|
|
2297
|
+
});
|
|
2298
|
+
for (const u of adminUsers) {
|
|
2299
|
+
const em = u.email?.trim().toLowerCase();
|
|
2300
|
+
if (em && em.includes("@")) {
|
|
2301
|
+
emails.add(em);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
} catch {
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
try {
|
|
2308
|
+
const rows = await dataSource.query(`SELECT DISTINCT LOWER(TRIM(email)) AS email
|
|
2309
|
+
FROM users
|
|
2310
|
+
WHERE ("groupId" = 1 OR "adminAccess" = true)
|
|
2311
|
+
AND COALESCE(blocked, false) = false
|
|
2312
|
+
AND COALESCE(deleted, false) = false
|
|
2313
|
+
AND "deletedAt" IS NULL`);
|
|
2314
|
+
for (const r of rows) {
|
|
2315
|
+
const em = r.email?.trim().toLowerCase();
|
|
2316
|
+
if (em && em.includes("@")) {
|
|
2317
|
+
emails.add(em);
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
} catch {
|
|
2321
|
+
}
|
|
2322
|
+
return Array.from(emails);
|
|
2323
|
+
}
|
|
2324
|
+
__name(getAdminRecipientEmails, "getAdminRecipientEmails");
|
|
2325
|
+
function formatBodyForEmailHtml(body) {
|
|
2326
|
+
if (!body) return "";
|
|
2327
|
+
if (/^\s*<(html|body|table|section)[\s>]/i.test(body.trim())) {
|
|
2328
|
+
return body;
|
|
2329
|
+
}
|
|
2330
|
+
const withBreaks = body.replace(/\r\n/g, "\n").replace(/\n/g, "<br />");
|
|
2331
|
+
return `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-size: 15px; line-height: 1.6; color: #1f2937;">${withBreaks}</div>`;
|
|
2332
|
+
}
|
|
2333
|
+
__name(formatBodyForEmailHtml, "formatBodyForEmailHtml");
|
|
2334
|
+
async function sendEmailForTrigger(triggerKey, orderId, audienceType, variables, contactEmail, dataSource, entityMap, getCms) {
|
|
2335
|
+
const template = await loadBoundTemplate(triggerKey, "email", audienceType, dataSource, entityMap);
|
|
2158
2336
|
if (!template) return;
|
|
2159
2337
|
const [branding, emailSettings] = await Promise.all([
|
|
2160
2338
|
getSettingsGroup(dataSource, entityMap, "branding"),
|
|
@@ -2162,27 +2340,35 @@ async function sendEmailForTrigger(triggerKey, orderId, variables, contactEmail,
|
|
|
2162
2340
|
]);
|
|
2163
2341
|
const companyDetails = mergeEmailLayoutCompanyDetails(branding, emailSettings);
|
|
2164
2342
|
const subject = applyTemplateVars(template.subject ?? "Order update", variables);
|
|
2165
|
-
const
|
|
2343
|
+
const rawBody = applyTemplateVars(template.body, variables);
|
|
2344
|
+
const bodyHtml = formatBodyForEmailHtml(rawBody);
|
|
2166
2345
|
const html = renderLayout({
|
|
2167
2346
|
bodyHtml,
|
|
2168
2347
|
companyDetails
|
|
2169
2348
|
});
|
|
2170
2349
|
const cms = await getCms();
|
|
2350
|
+
const isCustomer = audienceType === "customers";
|
|
2171
2351
|
await queueEmail(cms, {
|
|
2172
2352
|
to: contactEmail,
|
|
2173
2353
|
subject,
|
|
2174
2354
|
html,
|
|
2175
|
-
text:
|
|
2355
|
+
text: rawBody.replace(/<[^>]+>/g, "").trim(),
|
|
2356
|
+
...isCustomer ? {
|
|
2357
|
+
orderId,
|
|
2358
|
+
attachInvoice: true
|
|
2359
|
+
} : {}
|
|
2176
2360
|
});
|
|
2177
2361
|
console.log("[order-notifications] email queued", {
|
|
2178
2362
|
triggerKey,
|
|
2363
|
+
audienceType,
|
|
2179
2364
|
orderId,
|
|
2180
|
-
to: contactEmail
|
|
2365
|
+
to: contactEmail,
|
|
2366
|
+
attachInvoice: isCustomer
|
|
2181
2367
|
});
|
|
2182
2368
|
}
|
|
2183
2369
|
__name(sendEmailForTrigger, "sendEmailForTrigger");
|
|
2184
2370
|
async function sendWhatsAppForTrigger(triggerKey, orderId, phone, variables, dataSource, entityMap, getCms) {
|
|
2185
|
-
const template = await loadBoundTemplate(triggerKey, "whatsapp", dataSource, entityMap);
|
|
2371
|
+
const template = await loadBoundTemplate(triggerKey, "whatsapp", "customers", dataSource, entityMap);
|
|
2186
2372
|
if (!template) return;
|
|
2187
2373
|
const paramOrder = template.providerMeta?.paramOrder ?? [];
|
|
2188
2374
|
if (paramOrder.length === 0) {
|
|
@@ -2226,15 +2412,73 @@ async function sendWhatsAppForTrigger(triggerKey, orderId, phone, variables, dat
|
|
|
2226
2412
|
}
|
|
2227
2413
|
}
|
|
2228
2414
|
__name(sendWhatsAppForTrigger, "sendWhatsAppForTrigger");
|
|
2415
|
+
async function isOrderPaymentConfirmed(orderId, dataSource, entityMap) {
|
|
2416
|
+
if (!entityMap.orders) return true;
|
|
2417
|
+
try {
|
|
2418
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
2419
|
+
const order = await orderRepo.findOne({
|
|
2420
|
+
where: {
|
|
2421
|
+
id: orderId
|
|
2422
|
+
}
|
|
2423
|
+
});
|
|
2424
|
+
if (!order) return false;
|
|
2425
|
+
const o = order;
|
|
2426
|
+
const status = String(o.status ?? "").toLowerCase().trim();
|
|
2427
|
+
const total = Number(o.total ?? 0);
|
|
2428
|
+
if (total <= 0) return true;
|
|
2429
|
+
const confirmedStatuses = [
|
|
2430
|
+
"confirmed",
|
|
2431
|
+
"completed",
|
|
2432
|
+
"processing",
|
|
2433
|
+
"paid",
|
|
2434
|
+
"fulfilled",
|
|
2435
|
+
"shipped"
|
|
2436
|
+
];
|
|
2437
|
+
if (confirmedStatuses.includes(status)) {
|
|
2438
|
+
return true;
|
|
2439
|
+
}
|
|
2440
|
+
if (entityMap.payments) {
|
|
2441
|
+
const paymentRepo = dataSource.getRepository(entityMap.payments);
|
|
2442
|
+
const payment = await paymentRepo.findOne({
|
|
2443
|
+
where: {
|
|
2444
|
+
orderId
|
|
2445
|
+
}
|
|
2446
|
+
});
|
|
2447
|
+
if (payment) {
|
|
2448
|
+
const pStatus = String(payment.status ?? "").toLowerCase().trim();
|
|
2449
|
+
if (pStatus === "paid" || pStatus === "captured" || pStatus === "completed") {
|
|
2450
|
+
return true;
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
return false;
|
|
2455
|
+
} catch (err) {
|
|
2456
|
+
console.error("[order-notifications] failed to check order payment confirmation", err);
|
|
2457
|
+
return false;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
__name(isOrderPaymentConfirmed, "isOrderPaymentConfirmed");
|
|
2229
2461
|
async function handleTrigger(triggerKey, payload, dataSource, entityMap, getCms) {
|
|
2230
2462
|
console.log("[order-notifications] received", {
|
|
2231
2463
|
triggerKey,
|
|
2232
2464
|
orderId: payload.orderId,
|
|
2233
2465
|
phone: maskPhone3(payload.customerPhone)
|
|
2234
2466
|
});
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2467
|
+
if (triggerKey === "order_placed") {
|
|
2468
|
+
const isConfirmed = await isOrderPaymentConfirmed(payload.orderId, dataSource, entityMap);
|
|
2469
|
+
if (!isConfirmed) {
|
|
2470
|
+
console.log("[order-notifications] skip order_placed: order payment not confirmed yet", {
|
|
2471
|
+
orderId: payload.orderId
|
|
2472
|
+
});
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
const [emailChannelEnabled, whatsappChannelEnabled] = await Promise.all([
|
|
2477
|
+
isEmailNotificationsEnabled(dataSource, entityMap),
|
|
2478
|
+
isWhatsappNotificationsEnabled(dataSource, entityMap)
|
|
2479
|
+
]);
|
|
2480
|
+
if (!emailChannelEnabled && !whatsappChannelEnabled) {
|
|
2481
|
+
console.warn("[order-notifications] skip: Both Email and WhatsApp notifications are disabled");
|
|
2238
2482
|
return;
|
|
2239
2483
|
}
|
|
2240
2484
|
const triggerEnabled = await isTriggerCatalogEnabled(triggerKey, dataSource, entityMap);
|
|
@@ -2246,21 +2490,44 @@ async function handleTrigger(triggerKey, payload, dataSource, entityMap, getCms)
|
|
|
2246
2490
|
const variables = mergeTriggerVariables(payload, context?.variables);
|
|
2247
2491
|
const contactEmail = context?.contact.email?.trim() || "";
|
|
2248
2492
|
const contactPhone = context?.contact.phone?.trim() || payload.customerPhone?.trim() || "";
|
|
2249
|
-
if (
|
|
2250
|
-
|
|
2493
|
+
if (emailChannelEnabled) {
|
|
2494
|
+
const [customerEnabled, vendorEnabled, adminEnabled] = await Promise.all([
|
|
2495
|
+
isAudienceEmailEnabled(dataSource, entityMap, "customer"),
|
|
2496
|
+
isAudienceEmailEnabled(dataSource, entityMap, "vendor"),
|
|
2497
|
+
isAudienceEmailEnabled(dataSource, entityMap, "admin")
|
|
2498
|
+
]);
|
|
2499
|
+
if (customerEnabled && contactEmail) {
|
|
2500
|
+
await sendEmailForTrigger(triggerKey, payload.orderId, "customers", variables, contactEmail, dataSource, entityMap, getCms);
|
|
2501
|
+
}
|
|
2502
|
+
if (vendorEnabled) {
|
|
2503
|
+
const multiVendorSettings = await getSettingsGroup(dataSource, entityMap, "multi_vendor");
|
|
2504
|
+
if (multiVendorSettings.enabled !== "false" && multiVendorSettings.enabled !== "0") {
|
|
2505
|
+
const vendorEmails = await getOrderVendorEmails(payload.orderId, payload.vendorId, dataSource, entityMap);
|
|
2506
|
+
for (const vEmail of vendorEmails) {
|
|
2507
|
+
await sendEmailForTrigger(triggerKey, payload.orderId, "vendors", variables, vEmail, dataSource, entityMap, getCms);
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
if (adminEnabled) {
|
|
2512
|
+
const adminEmails = await getAdminRecipientEmails(dataSource, entityMap);
|
|
2513
|
+
for (const aEmail of adminEmails) {
|
|
2514
|
+
await sendEmailForTrigger(triggerKey, payload.orderId, "admin", variables, aEmail, dataSource, entityMap, getCms);
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2251
2517
|
} else {
|
|
2252
|
-
console.
|
|
2253
|
-
triggerKey,
|
|
2254
|
-
orderId: payload.orderId
|
|
2255
|
-
});
|
|
2518
|
+
console.log("[order-notifications] skip email: Email notifications channel is disabled");
|
|
2256
2519
|
}
|
|
2257
|
-
if (
|
|
2258
|
-
|
|
2520
|
+
if (whatsappChannelEnabled) {
|
|
2521
|
+
if (contactPhone) {
|
|
2522
|
+
await sendWhatsAppForTrigger(triggerKey, payload.orderId, contactPhone, variables, dataSource, entityMap, getCms);
|
|
2523
|
+
} else {
|
|
2524
|
+
console.warn("[order-notifications] skip WhatsApp: no customer phone", {
|
|
2525
|
+
triggerKey,
|
|
2526
|
+
orderId: payload.orderId
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2259
2529
|
} else {
|
|
2260
|
-
console.
|
|
2261
|
-
triggerKey,
|
|
2262
|
-
orderId: payload.orderId
|
|
2263
|
-
});
|
|
2530
|
+
console.log("[order-notifications] skip WhatsApp: WhatsApp notifications channel is disabled");
|
|
2264
2531
|
}
|
|
2265
2532
|
}
|
|
2266
2533
|
__name(handleTrigger, "handleTrigger");
|
|
@@ -2281,11 +2548,16 @@ async function resendOrderNotification(triggerKey, orderId, deps) {
|
|
|
2281
2548
|
ok: false,
|
|
2282
2549
|
error: "orders entity missing"
|
|
2283
2550
|
};
|
|
2284
|
-
const
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2551
|
+
const [emailEnabled, whatsappEnabled] = await Promise.all([
|
|
2552
|
+
isEmailNotificationsEnabled(deps.dataSource, deps.entityMap),
|
|
2553
|
+
isWhatsappNotificationsEnabled(deps.dataSource, deps.entityMap)
|
|
2554
|
+
]);
|
|
2555
|
+
if (!emailEnabled && !whatsappEnabled) {
|
|
2556
|
+
return {
|
|
2557
|
+
ok: false,
|
|
2558
|
+
error: "Both Email and WhatsApp notifications are disabled"
|
|
2559
|
+
};
|
|
2560
|
+
}
|
|
2289
2561
|
const triggerEnabled = await isTriggerCatalogEnabled(triggerKey, deps.dataSource, deps.entityMap);
|
|
2290
2562
|
if (!triggerEnabled) {
|
|
2291
2563
|
return {
|
|
@@ -2322,4 +2594,4 @@ async function resendOrderNotification(triggerKey, orderId, deps) {
|
|
|
2322
2594
|
}
|
|
2323
2595
|
__name(resendOrderNotification, "resendOrderNotification");
|
|
2324
2596
|
|
|
2325
|
-
export { CHAT_EMAIL_LOG, EmailService, WhatsAppService, buildChatbotSystemPromptWithEmailTool, buildEventOrderTemplateVariables, buildTranscriptForLeadEmail, detectChatLeadIntent, emailPlugin, emailTemplates,
|
|
2597
|
+
export { CHAT_EMAIL_LOG, EmailService, WhatsAppService, buildChatbotSystemPromptWithEmailTool, buildEventOrderTemplateVariables, buildTranscriptForLeadEmail, detectChatLeadIntent, emailPlugin, emailTemplates, initWhatsappTriggerDispatcher, isWhatsAppPluginEnabled, joinRecipientsForSend, mergeWhatsAppConfigLayers, parseEmailRecipientsFromConfig, parseIntentRecipientEmails, queueWhatsApp, registerWhatsAppQueueProcessor, renderEmail, renderLayout, resendOrderNotification, resolveChatEmailToolSettings, sendChatLeadEmail, serializeEmailRecipients, whatsAppConfigured };
|