@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.
Files changed (82) hide show
  1. package/README.md +19 -2
  2. package/dist/admin.cjs +4249 -2872
  3. package/dist/admin.d.cts +24 -4
  4. package/dist/admin.d.ts +24 -4
  5. package/dist/admin.js +3179 -1804
  6. package/dist/api.cjs +41 -39
  7. package/dist/api.d.cts +10 -4
  8. package/dist/api.d.ts +10 -4
  9. package/dist/api.js +9 -7
  10. package/dist/auth.cjs +55 -51
  11. package/dist/auth.d.cts +6 -3
  12. package/dist/auth.d.ts +6 -3
  13. package/dist/auth.js +3 -3
  14. package/dist/{chunk-KFFB6EMZ.js → chunk-2HU5R2JE.js} +2 -0
  15. package/dist/chunk-2IAXTKQN.cjs +19 -0
  16. package/dist/chunk-2KUCQVAQ.js +936 -0
  17. package/dist/chunk-3K5AIEIZ.js +256 -0
  18. package/dist/{chunk-SPQFNIYJ.cjs → chunk-4PMK3RNA.cjs} +2 -0
  19. package/dist/{chunk-X6UQFV4X.cjs → chunk-77NUXO6A.cjs} +14 -12
  20. package/dist/chunk-BXYZDMTZ.cjs +953 -0
  21. package/dist/chunk-DBPSJYLZ.js +47 -0
  22. package/dist/chunk-EIZKW2PF.js +13 -0
  23. package/dist/chunk-HY3AXLOI.cjs +265 -0
  24. package/dist/{chunk-X47PBV4I.js → chunk-JE22VP6S.js} +1 -0
  25. package/dist/{chunk-DN65KDIA.js → chunk-JIWUVQ6B.js} +14 -13
  26. package/dist/{chunk-HT5DI3XW.cjs → chunk-KOTXDSQB.cjs} +1 -0
  27. package/dist/chunk-L3525VXI.js +243 -0
  28. package/dist/{chunk-22FFHLTO.cjs → chunk-LMJ7RKPF.cjs} +421 -150
  29. package/dist/chunk-OY2YTBMP.cjs +50 -0
  30. package/dist/{chunk-TAHX46EI.cjs → chunk-Q3HQEM4R.cjs} +15 -7
  31. package/dist/chunk-V25RDUOU.cjs +248 -0
  32. package/dist/{chunk-ZMRQ72F6.cjs → chunk-VUQFARRT.cjs} +10 -10
  33. package/dist/{chunk-MVXLRANV.js → chunk-W42UZLQO.js} +1977 -891
  34. package/dist/{chunk-F5XFHYTG.cjs → chunk-XUCKZPML.cjs} +2088 -991
  35. package/dist/{chunk-I6NH72MO.js → chunk-YC4NUZCS.js} +10 -2
  36. package/dist/{chunk-NNQBWDCI.js → chunk-YXH2UUEZ.js} +418 -146
  37. package/dist/{chunk-BLR6H5GL.js → chunk-ZF2RQWXB.js} +4 -4
  38. package/dist/cli.cjs +9 -4
  39. package/dist/cli.js +9 -4
  40. package/dist/{email-queue-TPZWLTIV.cjs → email-queue-6NJY6HNM.cjs} +7 -5
  41. package/dist/email-queue-OI2HSCGE.js +4 -0
  42. package/dist/{emit-order-notification-trigger-IVLWKKJL.js → emit-order-notification-trigger-6XX7LSC4.js} +1 -1
  43. package/dist/{emit-order-notification-trigger-D2N3KFHL.cjs → emit-order-notification-trigger-NASG7ZEO.cjs} +3 -3
  44. package/dist/erp-order-invoice-3GXDE443.js +3 -0
  45. package/dist/erp-order-invoice-EYYNR526.cjs +24 -0
  46. package/dist/{event-order-defaults-EMWBIGL3.cjs → event-order-defaults-7Z3UZOEH.cjs} +9 -9
  47. package/dist/{event-order-defaults-SDRNAZHC.js → event-order-defaults-XQ3IDPD7.js} +1 -1
  48. package/dist/hooks.cjs +43 -0
  49. package/dist/hooks.d.cts +10 -1
  50. package/dist/hooks.d.ts +10 -1
  51. package/dist/hooks.js +39 -1
  52. package/dist/index.cjs +429 -298
  53. package/dist/index.d.cts +317 -30
  54. package/dist/index.d.ts +317 -30
  55. package/dist/index.js +17 -13
  56. package/dist/migrations/1781810000000-OrderItemsVariantId.ts +44 -0
  57. package/dist/migrations/1781900000000-UserInviteStatusToken.ts +46 -0
  58. package/dist/migrations/1782000000000-ComboSlug.ts +58 -0
  59. package/dist/migrations/1782100000000-DiscountDeviceType.ts +20 -0
  60. package/dist/migrations/1782200000000-VendorTeamProfiles.ts +233 -0
  61. package/dist/migrations/1782300000000-AddTypeToOrderNotificationBindings.ts +75 -0
  62. package/dist/order-inventory-2MYRKEPC.cjs +39 -0
  63. package/dist/order-inventory-76V4XIGW.js +2 -0
  64. package/dist/{order-notification-dispatcher-XWIJYDGA.js → order-notification-dispatcher-6WNG24NY.js} +4 -2
  65. package/dist/{order-notification-dispatcher-RU7BTGGX.cjs → order-notification-dispatcher-6XSU3AR7.cjs} +6 -4
  66. package/dist/{rbac-debug-db-2SGNLANF.js → rbac-debug-db-6EMIVMGF.js} +1 -1
  67. package/dist/{rbac-debug-db-4Z6A425H.cjs → rbac-debug-db-6PWTLBEL.cjs} +2 -2
  68. package/dist/retire-soft-deleted-unique-5VXBA5XX.cjs +15 -0
  69. package/dist/retire-soft-deleted-unique-NXQIH4BC.js +2 -0
  70. package/dist/theme.cjs +6 -1
  71. package/dist/theme.d.cts +4 -1
  72. package/dist/theme.d.ts +4 -1
  73. package/dist/theme.js +3 -1
  74. package/package.json +5 -4
  75. package/src/admin/admin.css +34 -24
  76. package/dist/chunk-5ELSW2UP.js +0 -150
  77. package/dist/chunk-PNOKNSG2.cjs +0 -222
  78. package/dist/chunk-WEMDMVHQ.js +0 -216
  79. package/dist/chunk-XW5ATPRW.cjs +0 -155
  80. package/dist/email-queue-CGZBVVAS.js +0 -2
  81. package/dist/erp-order-invoice-3YISSOWW.js +0 -3
  82. 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-5ELSW2UP.js';
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
- if (!trackUrl.trim()) return "";
29
- return `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${encodeURIComponent(trackUrl)}`;
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 contact = o.contact ?? {};
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 variables = event ? buildEventOrderTemplateVariables({
203
- event,
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-SDRNAZHC.js');
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -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: emailData.from || this.config.from,
1438
+ Source: from,
1411
1439
  Destination: {
1412
1440
  ToAddresses: [
1413
- emailData.to || this.config.to
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: emailData.from || this.config.from,
1440
- to: emailData.to || this.config.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 isOrderNotificationsEnabled(dataSource, entityMap) {
2087
+ async function isEmailNotificationsEnabled(dataSource, entityMap) {
2059
2088
  const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
2060
- return settings.enabled !== "false" && settings.enabled !== "0";
2089
+ if (settings.enabled === "false" || settings.enabled === "0") return false;
2090
+ return settings.email_enabled !== "false" && settings.email_enabled !== "0";
2061
2091
  }
2062
- __name(isOrderNotificationsEnabled, "isOrderNotificationsEnabled");
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 binding = await dataSource.getRepository(entityMap.order_notification_bindings).findOne({
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 sendEmailForTrigger(triggerKey, orderId, variables, contactEmail, dataSource, entityMap, getCms) {
2157
- const template = await loadBoundTemplate(triggerKey, "email", dataSource, entityMap);
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 bodyHtml = applyTemplateVars(template.body, variables);
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: bodyHtml.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim()
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
- const pluginEnabled = await isOrderNotificationsEnabled(dataSource, entityMap);
2236
- if (!pluginEnabled) {
2237
- console.warn("[order-notifications] skip: Plugins \u2192 Order notifications disabled");
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 (contactEmail) {
2250
- await sendEmailForTrigger(triggerKey, payload.orderId, variables, contactEmail, dataSource, entityMap, getCms);
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.warn("[order-notifications] skip email: no customer email", {
2253
- triggerKey,
2254
- orderId: payload.orderId
2255
- });
2518
+ console.log("[order-notifications] skip email: Email notifications channel is disabled");
2256
2519
  }
2257
- if (contactPhone) {
2258
- await sendWhatsAppForTrigger(triggerKey, payload.orderId, contactPhone, variables, dataSource, entityMap, getCms);
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.warn("[order-notifications] skip WhatsApp: no customer phone", {
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 pluginEnabled = await isOrderNotificationsEnabled(deps.dataSource, deps.entityMap);
2285
- if (!pluginEnabled) return {
2286
- ok: false,
2287
- error: "Order notifications plugin is disabled"
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, getCompanyDetailsFromSettings, initWhatsappTriggerDispatcher, isWhatsAppPluginEnabled, joinRecipientsForSend, mergeEmailLayoutCompanyDetails, mergeWhatsAppConfigLayers, parseEmailRecipientsFromConfig, parseIntentRecipientEmails, queueWhatsApp, registerWhatsAppQueueProcessor, renderEmail, renderLayout, resendOrderNotification, resolveChatEmailToolSettings, sendChatLeadEmail, serializeEmailRecipients, whatsAppConfigured };
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 };