@infuro/cms-core 1.0.63 → 1.0.65

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 (50) hide show
  1. package/dist/admin.cjs +183 -65
  2. package/dist/admin.js +183 -65
  3. package/dist/api.cjs +40 -40
  4. package/dist/api.d.cts +2 -0
  5. package/dist/api.d.ts +2 -0
  6. package/dist/api.js +8 -8
  7. package/dist/{chunk-KAOHOHCA.js → chunk-6YOQ7G5O.js} +92 -51
  8. package/dist/{chunk-BWOITUNI.js → chunk-ACBN6UWZ.js} +320 -81
  9. package/dist/chunk-BXAKL2TF.cjs +84 -0
  10. package/dist/{chunk-7L556RGL.cjs → chunk-GLD263KW.cjs} +3 -3
  11. package/dist/{chunk-EWXG4E74.cjs → chunk-NIRYYJIJ.cjs} +330 -91
  12. package/dist/{chunk-MWEK7N3F.js → chunk-OPJEV44S.js} +2 -2
  13. package/dist/{chunk-DTUL3Q5L.js → chunk-RDEVZMO5.js} +36 -20
  14. package/dist/{chunk-77YWLP4X.js → chunk-SANKKGB3.js} +68 -32
  15. package/dist/chunk-THL5JDWJ.cjs +101 -0
  16. package/dist/{chunk-RXTUSF3R.js → chunk-TU23GJPJ.js} +2 -2
  17. package/dist/{chunk-JLDHK2QE.cjs → chunk-TZEMLHSP.cjs} +20 -14
  18. package/dist/{chunk-RHYVNRYG.cjs → chunk-V6EYAC7X.cjs} +38 -22
  19. package/dist/{chunk-YVP4NQII.cjs → chunk-VM6OVUUM.cjs} +93 -52
  20. package/dist/{chunk-MXABMVF7.cjs → chunk-XFDIIVZB.cjs} +4 -4
  21. package/dist/chunk-XJNNOSJR.js +98 -0
  22. package/dist/chunk-YEAXE4U2.js +77 -0
  23. package/dist/{chunk-CJEOH6QC.cjs → chunk-YHEITFHR.cjs} +112 -76
  24. package/dist/{chunk-3VZAAQUV.js → chunk-YRCKEGL2.js} +20 -14
  25. package/dist/{email-queue-VTYL2KFA.cjs → email-queue-4OY45JIU.cjs} +7 -7
  26. package/dist/{email-queue-YPRSVG56.js → email-queue-HGXB2FIS.js} +3 -3
  27. package/dist/{email-service-ATCPJFWG.cjs → email-service-5O24BSFR.cjs} +7 -7
  28. package/dist/{email-service-DPKVQOKP.js → email-service-ADDF52PJ.js} +2 -2
  29. package/dist/{emit-order-notification-trigger-WI4HGMKH.cjs → emit-order-notification-trigger-DR74TQXN.cjs} +4 -4
  30. package/dist/{emit-order-notification-trigger-XVG2B5ZF.js → emit-order-notification-trigger-UCKJHDQK.js} +2 -2
  31. package/dist/{erp-order-invoice-3UGWLICU.js → erp-order-invoice-H6HCJ7YS.js} +2 -2
  32. package/dist/{erp-order-invoice-KJYJNWMG.cjs → erp-order-invoice-YUSPZ7CE.cjs} +6 -6
  33. package/dist/index.cjs +279 -265
  34. package/dist/index.d.cts +46 -22
  35. package/dist/index.d.ts +46 -22
  36. package/dist/index.js +43 -29
  37. package/dist/migrations/1783000000000-AddOrderPendingProcessingCompletedTriggers.ts +15 -39
  38. package/dist/migrations/1783300000000-SeedOrderNotificationTriggersAndTemplates.ts +155 -0
  39. package/dist/{order-completion-otp-handlers-WGM7HWG4.js → order-completion-otp-handlers-CH22ZRAI.js} +5 -5
  40. package/dist/{order-completion-otp-handlers-IMMQGHDR.cjs → order-completion-otp-handlers-RW5RSXH3.cjs} +7 -7
  41. package/dist/{order-notification-dispatcher-JKFQJYGG.js → order-notification-dispatcher-3TXUAJ7L.js} +6 -6
  42. package/dist/{order-notification-dispatcher-5TUFCDU7.cjs → order-notification-dispatcher-Y7NZAALH.cjs} +11 -11
  43. package/dist/{pg-boss-service-L6MTF4EQ.cjs → pg-boss-service-BDGOKM2T.cjs} +3 -3
  44. package/dist/pg-boss-service-WCBJD4JB.js +2 -0
  45. package/package.json +1 -1
  46. package/dist/chunk-AYWDQFJZ.js +0 -27
  47. package/dist/chunk-GO7PPYNU.js +0 -76
  48. package/dist/chunk-UEE4PTTB.cjs +0 -30
  49. package/dist/chunk-XQ3QUHWR.cjs +0 -83
  50. package/dist/pg-boss-service-4XF2TQ2E.js +0 -2
@@ -1,8 +1,8 @@
1
1
  import { queueWhatsApp } from './chunk-GWDI752Q.js';
2
- import { notificationTriggerEmitter } from './chunk-AYWDQFJZ.js';
3
- import { sesRegionSupportsSmtp, sesSmtpHost, EmailService, renderLayout } from './chunk-KAOHOHCA.js';
4
- import { queueEmail } from './chunk-RXTUSF3R.js';
5
- import { mergeEmailLayoutCompanyDetails } from './chunk-3VZAAQUV.js';
2
+ import { notifyLog, maskNotifyToken, maskNotifyEmail, maskNotifyPhone, notificationTriggerEmitter } from './chunk-YEAXE4U2.js';
3
+ import { sesRegionSupportsSmtp, sesSmtpHost, EmailService, renderLayout } from './chunk-6YOQ7G5O.js';
4
+ import { queueEmail } from './chunk-TU23GJPJ.js';
5
+ import { mergeEmailLayoutCompanyDetails } from './chunk-YRCKEGL2.js';
6
6
  import { applyTemplateVars } from './chunk-CRFV5WJK.js';
7
7
  import { __name } from './chunk-SHUYVCID.js';
8
8
  import * as crypto from 'crypto';
@@ -201,7 +201,10 @@ async function loadOrderNotificationContexts(orderId, deps) {
201
201
  deleted: false
202
202
  },
203
203
  relations: [
204
- "contact"
204
+ "contact",
205
+ "assignedUser",
206
+ "shippingAddress",
207
+ "billingAddress"
205
208
  ]
206
209
  });
207
210
  if (!order) return null;
@@ -256,7 +259,7 @@ async function loadOrderNotificationContexts(orderId, deps) {
256
259
  }
257
260
  }
258
261
  const effectiveEvent = eventsEnabled ? event : void 0;
259
- const variables = effectiveEvent ? buildEventOrderTemplateVariables({
262
+ const baseVariables = effectiveEvent ? buildEventOrderTemplateVariables({
260
263
  event: effectiveEvent,
261
264
  order: {
262
265
  orderNumber: o.orderNumber,
@@ -288,13 +291,42 @@ async function loadOrderNotificationContexts(orderId, deps) {
288
291
  orderLines,
289
292
  siteUrl
290
293
  });
294
+ const assignedUser = o.assignedUser ?? null;
295
+ const assigneeName = assignedUser?.name?.trim() || "";
296
+ const assigneePhone = assignedUser?.phone?.trim() || "";
297
+ const assigneeEmail = assignedUser?.email?.trim() || "";
298
+ const timeSlot = String(meta.timeSlot || meta.slot || meta.serviceSlot || "").trim();
299
+ const rawServiceDate = meta.bookingDate || meta.serviceDate || meta.orderDate || o.createdAt;
300
+ const serviceDate = rawServiceDate ? formatScheduleDate(rawServiceDate) : "";
301
+ const addr = o.shippingAddress || o.billingAddress;
302
+ const customerAddress = addr ? [
303
+ addr.line1,
304
+ addr.line2,
305
+ addr.city,
306
+ addr.state,
307
+ addr.postalCode,
308
+ addr.country
309
+ ].map((p) => String(p ?? "").trim()).filter(Boolean).join(", ") : "";
310
+ const taskDescription = productNames || (orderLines.length > 0 ? orderLines.map((l) => `${l.quantity}x ${l.name}`).join(", ") : "Order service");
311
+ const variables = {
312
+ ...baseVariables,
313
+ assigneeName,
314
+ assigneePhone,
315
+ assigneeEmail,
316
+ timeSlot,
317
+ serviceDate,
318
+ customerAddress,
319
+ taskDescription
320
+ };
291
321
  return {
292
322
  order: {
293
323
  id: o.id,
294
324
  orderNumber: String(o.orderNumber ?? ""),
295
325
  total: o.total,
296
326
  currency,
297
- qrToken: o.qrToken
327
+ qrToken: o.qrToken,
328
+ assignedUserId: o.assignedUserId ?? null,
329
+ assignedUser
298
330
  },
299
331
  contact,
300
332
  orderLines,
@@ -304,6 +336,16 @@ async function loadOrderNotificationContexts(orderId, deps) {
304
336
  };
305
337
  }
306
338
  __name(loadOrderNotificationContexts, "loadOrderNotificationContexts");
339
+ function formatScheduleDate(val) {
340
+ if (!val || typeof val !== "string" && !(val instanceof Date)) return "";
341
+ const str = String(val).trim();
342
+ if (!str) return "";
343
+ const d = new Date(str.includes("T") ? str : `${str}T00:00:00`);
344
+ return isNaN(d.getTime()) ? str : d.toLocaleDateString(void 0, {
345
+ dateStyle: "medium"
346
+ });
347
+ }
348
+ __name(formatScheduleDate, "formatScheduleDate");
307
349
 
308
350
  // src/plugins/whatsapp/whatsapp-service.ts
309
351
  function norm(v) {
@@ -1254,60 +1296,68 @@ __name(validateFcmCredentials, "validateFcmCredentials");
1254
1296
  var ORDER_NOTIFICATION_DEFAULTS = {
1255
1297
  email: {
1256
1298
  pending: {
1257
- subject: "Your Order #{{orderNumber}} is Pending",
1258
- body: "<p>Thank you for your order! It is currently pending.</p>"
1299
+ subject: "Your order {{orderNumber}} is Pending",
1300
+ body: "Hi {{customerName}},\n\nYour order {{orderNumber}} for {{total}} {{currency}} is currently pending.\n\nOrder Details:\n{{orderDetails}}\n\nWe will update you as soon as your order status changes.\n\nThank you!"
1259
1301
  },
1260
1302
  order_pending: {
1261
- subject: "Your Order #{{orderNumber}} is Pending",
1262
- body: "<p>Thank you for your order! It is currently pending.</p>"
1303
+ subject: "Your order {{orderNumber}} is Pending",
1304
+ body: "Hi {{customerName}},\n\nYour order {{orderNumber}} for {{total}} {{currency}} is currently pending.\n\nOrder Details:\n{{orderDetails}}\n\nWe will update you as soon as your order status changes.\n\nThank you!"
1263
1305
  },
1264
1306
  confirmed: {
1265
- subject: "Your Order #{{orderNumber}} is Confirmed",
1266
- body: "<p>Your order has been confirmed!</p>"
1307
+ subject: "Your order {{orderNumber}} is confirmed",
1308
+ body: "Hi {{customerName}},\n\nThank you for your order! Your order {{orderNumber}} for {{total}} {{currency}} has been confirmed.\n\nOrder Details:\n{{orderDetails}}\n\nYour Tax Invoice PDF is attached to this email for your records.\n\nThank you!"
1267
1309
  },
1268
1310
  order_confirmed: {
1269
- subject: "Your Order #{{orderNumber}} is Confirmed",
1270
- body: "<p>Your order has been confirmed!</p>"
1311
+ subject: "Your order {{orderNumber}} is confirmed",
1312
+ body: "Hi {{customerName}},\n\nThank you for your order! Your order {{orderNumber}} for {{total}} {{currency}} has been confirmed.\n\nOrder Details:\n{{orderDetails}}\n\nYour Tax Invoice PDF is attached to this email for your records.\n\nThank you!"
1271
1313
  },
1272
1314
  order_placed: {
1273
- subject: "Your Order #{{orderNumber}} is Placed",
1274
- body: "<p>Thank you for your order! Your order #{{orderNumber}} has been placed.</p>"
1315
+ subject: "Your order {{orderNumber}} is confirmed",
1316
+ body: "Hi {{customerName}},\n\nThank you for your order! Your order {{orderNumber}} for {{total}} {{currency}} has been confirmed.\n\nOrder Details:\n{{orderDetails}}\n\nYour Tax Invoice PDF is attached to this email for your records.\n\nThank you!"
1275
1317
  },
1276
1318
  processing: {
1277
- subject: "Your Order #{{orderNumber}} is Processing",
1278
- body: "<p>Your order is now being processed by our team.</p>"
1319
+ subject: "Your order {{orderNumber}} is now Processing",
1320
+ body: "Hi {{customerName}},\n\nYour order {{orderNumber}} for {{total}} {{currency}} is currently being processed.\n\nOrder Details:\n{{orderDetails}}\n\nWe will notify you once it is completed.\n\nThank you!"
1279
1321
  },
1280
1322
  order_processing: {
1281
- subject: "Your Order #{{orderNumber}} is Processing",
1282
- body: "<p>Your order is now being processed by our team.</p>"
1323
+ subject: "Your order {{orderNumber}} is now Processing",
1324
+ body: "Hi {{customerName}},\n\nYour order {{orderNumber}} for {{total}} {{currency}} is currently being processed.\n\nOrder Details:\n{{orderDetails}}\n\nWe will notify you once it is completed.\n\nThank you!"
1283
1325
  },
1284
1326
  completed: {
1285
- subject: "Your Order #{{orderNumber}} is Completed",
1286
- body: "<p>Your order has been completed. Thank you!</p>"
1327
+ subject: "Your order {{orderNumber}} is Completed",
1328
+ body: "Hi {{customerName}},\n\nYour order {{orderNumber}} for {{total}} {{currency}} has been completed.\n\nOrder Details:\n{{orderDetails}}\n\nThank you for choosing us!"
1287
1329
  },
1288
1330
  order_completed: {
1289
- subject: "Your Order #{{orderNumber}} is Completed",
1290
- body: "<p>Your order has been completed. Thank you!</p>"
1331
+ subject: "Your order {{orderNumber}} is Completed",
1332
+ body: "Hi {{customerName}},\n\nYour order {{orderNumber}} for {{total}} {{currency}} has been completed.\n\nOrder Details:\n{{orderDetails}}\n\nThank you for choosing us!"
1291
1333
  },
1292
1334
  cancelled: {
1293
- subject: "Your Order #{{orderNumber}} is Cancelled",
1294
- body: "<p>Your order has been cancelled.</p>"
1335
+ subject: "Your order {{orderNumber}} has been cancelled",
1336
+ body: "Hi {{customerName}},\n\nYour order {{orderNumber}} for {{total}} {{currency}} has been cancelled.\n\nIf a refund is due, you will receive {{refundAmount}} {{currency}} back to your original payment method.\n\nIf you have any questions, please contact us.\n\nThank you!"
1295
1337
  },
1296
1338
  order_cancelled: {
1297
- subject: "Your Order #{{orderNumber}} is Cancelled",
1298
- body: "<p>Your order has been cancelled.</p>"
1339
+ subject: "Your order {{orderNumber}} has been cancelled",
1340
+ body: "Hi {{customerName}},\n\nYour order {{orderNumber}} for {{total}} {{currency}} has been cancelled.\n\nIf a refund is due, you will receive {{refundAmount}} {{currency}} back to your original payment method.\n\nIf you have any questions, please contact us.\n\nThank you!"
1299
1341
  },
1300
1342
  payment_success: {
1301
- subject: "Payment Received for Order #{{orderNumber}}",
1302
- body: "<p>Payment of {{currency}} {{total}} has been successfully received for order #{{orderNumber}}.</p>"
1343
+ subject: "Payment Successful for Order {{orderNumber}}",
1344
+ body: "Hi {{customerName}},\n\nWe have received your payment for order {{orderNumber}} of {{total}} {{currency}}.\n\nOrder Details:\n{{orderDetails}}\n\nThank you!"
1303
1345
  },
1304
1346
  payment_failed: {
1305
- subject: "Payment Failed for Order #{{orderNumber}}",
1306
- body: "<p>Payment for order #{{orderNumber}} could not be completed.</p>"
1347
+ subject: "Payment Failed for Order {{orderNumber}}",
1348
+ body: "Hi {{customerName}},\n\nYour payment for order {{orderNumber}} of {{total}} {{currency}} could not be completed or remains unpaid.\n\nIf you were charged, please contact us with your order number {{orderNumber}}.\n\nThank you!"
1307
1349
  },
1308
1350
  payment_refund_initiated: {
1309
- subject: "Refund Initiated for Order #{{orderNumber}}",
1310
- body: "<p>A refund of {{currency}} {{refundAmount}} has been initiated for order #{{orderNumber}}.</p>"
1351
+ subject: "Refund Initiated for Order {{orderNumber}}",
1352
+ body: "Hi {{customerName}},\n\nYour order {{orderNumber}} has been cancelled and a refund of {{refundAmount}} {{currency}} has been initiated.\n\nThe amount will be credited back to your original payment method.\n\nThank you!"
1353
+ },
1354
+ order_assigned: {
1355
+ subject: "Your service expert has been assigned for order {{orderNumber}}",
1356
+ body: "Hi {{customerName}},\n\nA service expert has been assigned to your order {{orderNumber}}.\n\nAssignee Details:\n\u2022 Name: {{assigneeName}}\n\u2022 Phone: {{assigneePhone}}\n\nYour expert will reach you by your scheduled time slot:\n\u2022 Date: {{serviceDate}}\n\u2022 Slot: {{timeSlot}}\n\nOrder Details:\n{{orderDetails}}\n\nThank you!"
1357
+ },
1358
+ order_assigned_assignee: {
1359
+ subject: "New task assigned: Order {{orderNumber}}",
1360
+ body: "Hi {{assigneeName}},\n\nA new task has been assigned to you for order {{orderNumber}}.\n\nTask Details:\n{{taskDescription}}\n\nCustomer Details:\n\u2022 Name: {{customerName}}\n\u2022 Phone: {{customerPhone}}\n\u2022 Address: {{customerAddress}}\n\nSchedule:\n\u2022 Date: {{serviceDate}}\n\u2022 Slot: {{timeSlot}}\n\nPlease arrive promptly within the scheduled time slot."
1311
1361
  }
1312
1362
  },
1313
1363
  sms: {
@@ -1316,6 +1366,8 @@ var ORDER_NOTIFICATION_DEFAULTS = {
1316
1366
  confirmed: "Your Order #{{orderNumber}} is confirmed. Total: {{total}}.",
1317
1367
  order_confirmed: "Your Order #{{orderNumber}} is confirmed. Total: {{total}}.",
1318
1368
  order_placed: "Your Order #{{orderNumber}} is placed. Total: {{total}}.",
1369
+ order_assigned: "Hi {{customerName}}, your service expert {{assigneeName}} ({{assigneePhone}}) has been assigned for order {{orderNumber}}. Arrival slot: {{timeSlot}}.",
1370
+ order_assigned_assignee: "Hi {{assigneeName}}, new task assigned: Order {{orderNumber}}. Customer: {{customerName}}, Phone: {{customerPhone}}, Address: {{customerAddress}}, Slot: {{timeSlot}}.",
1319
1371
  processing: "Your Order #{{orderNumber}} is now processing.",
1320
1372
  order_processing: "Your Order #{{orderNumber}} is now processing.",
1321
1373
  completed: "Your Order #{{orderNumber}} has been completed. Thank you!",
@@ -1332,6 +1384,8 @@ var ORDER_NOTIFICATION_DEFAULTS = {
1332
1384
  confirmed: "Hi {{customerName}}, your Order #{{orderNumber}} is confirmed.",
1333
1385
  order_confirmed: "Hi {{customerName}}, your Order #{{orderNumber}} is confirmed.",
1334
1386
  order_placed: "Hi {{customerName}}, your Order #{{orderNumber}} is placed.",
1387
+ order_assigned: "Hi {{customerName}}, expert {{assigneeName}} ({{assigneePhone}}) has been assigned to your order #{{orderNumber}}. Scheduled slot: {{timeSlot}} on {{serviceDate}}.",
1388
+ order_assigned_assignee: "Hi {{assigneeName}}, you have been assigned to order #{{orderNumber}}. Customer: {{customerName}} ({{customerPhone}}), Address: {{customerAddress}}, Slot: {{timeSlot}} on {{serviceDate}}.",
1335
1389
  processing: "Hi {{customerName}}, your Order #{{orderNumber}} is now being processed.",
1336
1390
  order_processing: "Hi {{customerName}}, your Order #{{orderNumber}} is now being processed.",
1337
1391
  completed: "Hi {{customerName}}, your Order #{{orderNumber}} is completed.",
@@ -1346,12 +1400,6 @@ var ORDER_NOTIFICATION_DEFAULTS = {
1346
1400
 
1347
1401
  // src/lib/order-notification-dispatcher.ts
1348
1402
  var initialized = false;
1349
- function maskPhone2(raw) {
1350
- const digits = String(raw ?? "").replace(/\D/g, "");
1351
- if (digits.length <= 4) return "(none)";
1352
- return `***${digits.slice(-4)}`;
1353
- }
1354
- __name(maskPhone2, "maskPhone");
1355
1403
  async function getSettingsGroup(dataSource, entityMap, group) {
1356
1404
  if (!entityMap.configs) return {};
1357
1405
  const rows = await dataSource.getRepository(entityMap.configs).find({
@@ -1561,11 +1609,26 @@ async function sendPushNotificationForTrigger(triggerKey, orderId, audienceType,
1561
1609
  if (!template) {
1562
1610
  template = await loadBoundTemplate(triggerKey, "push", audienceType, dataSource, entityMap);
1563
1611
  }
1564
- if (!template || !template.body) return;
1612
+ if (!template || !template.body) {
1613
+ notifyLog.skip("PUSH", "no mobile/push template or empty body", {
1614
+ triggerKey,
1615
+ orderId,
1616
+ audienceType,
1617
+ targetId
1618
+ });
1619
+ return;
1620
+ }
1565
1621
  const rawTitle = template.subject?.trim() || "Order Notification";
1566
1622
  const title = applyTemplateVars(rawTitle, variables);
1567
1623
  const body = applyTemplateVars(template.body, variables);
1568
- if (!entityMap.user_device_tokens) return;
1624
+ if (!entityMap.user_device_tokens) {
1625
+ notifyLog.skip("PUSH", "user_device_tokens entity not registered", {
1626
+ triggerKey,
1627
+ orderId,
1628
+ audienceType
1629
+ });
1630
+ return;
1631
+ }
1569
1632
  const tokenRepo = dataSource.getRepository(entityMap.user_device_tokens);
1570
1633
  let targetTokens = [];
1571
1634
  try {
@@ -1600,19 +1663,60 @@ async function sendPushNotificationForTrigger(triggerKey, orderId, audienceType,
1600
1663
  });
1601
1664
  }
1602
1665
  } catch (err) {
1603
- console.error("[order-notifications] Error querying device tokens for push", err);
1666
+ notifyLog.error("PUSH", "device token query failed", {
1667
+ triggerKey,
1668
+ orderId,
1669
+ audienceType,
1670
+ targetId,
1671
+ error: err instanceof Error ? err.message : String(err)
1672
+ });
1604
1673
  return;
1605
1674
  }
1606
- for (const t of targetTokens) {
1607
- if (t.token?.trim()) {
1608
- await sendFcmPushNotification(dataSource, entityMap, {
1609
- token: t.token.trim(),
1610
- title,
1611
- body,
1612
- data: {
1613
- orderId: String(orderId),
1614
- triggerKey
1615
- }
1675
+ const tokens = targetTokens.map((t) => t.token?.trim()).filter(Boolean);
1676
+ notifyLog.push("sending", {
1677
+ triggerKey,
1678
+ orderId,
1679
+ audienceType,
1680
+ targetId,
1681
+ title,
1682
+ tokenCount: tokens.length,
1683
+ tokens: tokens.map((t) => maskNotifyToken(t))
1684
+ });
1685
+ if (tokens.length === 0) {
1686
+ notifyLog.skip("PUSH", "no active FCM tokens for target", {
1687
+ triggerKey,
1688
+ orderId,
1689
+ audienceType,
1690
+ targetId,
1691
+ hint: "App must POST /api/user-device-tokens after login"
1692
+ });
1693
+ return;
1694
+ }
1695
+ for (const token of tokens) {
1696
+ const res = await sendFcmPushNotification(dataSource, entityMap, {
1697
+ token,
1698
+ title,
1699
+ body,
1700
+ data: {
1701
+ orderId: String(orderId),
1702
+ triggerKey
1703
+ }
1704
+ });
1705
+ if (res.success) {
1706
+ notifyLog.push("FCM sent", {
1707
+ triggerKey,
1708
+ orderId,
1709
+ audienceType,
1710
+ token: maskNotifyToken(token),
1711
+ messageId: res.messageId ?? null
1712
+ });
1713
+ } else {
1714
+ notifyLog.error("PUSH", "FCM send failed", {
1715
+ triggerKey,
1716
+ orderId,
1717
+ audienceType,
1718
+ token: maskNotifyToken(token),
1719
+ error: res.error ?? "unknown"
1616
1720
  });
1617
1721
  }
1618
1722
  }
@@ -1756,7 +1860,15 @@ function formatBodyForEmailHtml(body) {
1756
1860
  __name(formatBodyForEmailHtml, "formatBodyForEmailHtml");
1757
1861
  async function sendEmailForTrigger(triggerKey, orderId, audienceType, variables, contactEmail, dataSource, entityMap, getCms) {
1758
1862
  const template = await loadBoundTemplate(triggerKey, "email", audienceType, dataSource, entityMap);
1759
- if (!template) return;
1863
+ if (!template) {
1864
+ notifyLog.skip("EMAIL", "no bound/default email template", {
1865
+ triggerKey,
1866
+ orderId,
1867
+ audienceType,
1868
+ to: maskNotifyEmail(contactEmail)
1869
+ });
1870
+ return;
1871
+ }
1760
1872
  const [branding, emailSettings, storeSettings] = await Promise.all([
1761
1873
  getSettingsGroup(dataSource, entityMap, "branding"),
1762
1874
  getSettingsGroup(dataSource, entityMap, "email"),
@@ -1791,22 +1903,31 @@ async function sendEmailForTrigger(triggerKey, orderId, audienceType, variables,
1791
1903
  attachInvoice: true
1792
1904
  } : {}
1793
1905
  });
1794
- console.log("[order-notifications] email queued", {
1906
+ notifyLog.email("queued", {
1795
1907
  triggerKey,
1796
1908
  audienceType,
1797
1909
  orderId,
1798
- to: contactEmail,
1910
+ to: maskNotifyEmail(contactEmail),
1911
+ subject,
1799
1912
  attachInvoice: shouldAttachInvoice
1800
1913
  });
1801
1914
  }
1802
1915
  __name(sendEmailForTrigger, "sendEmailForTrigger");
1803
1916
  async function sendWhatsAppForTrigger(triggerKey, orderId, phone, variables, dataSource, entityMap, getCms) {
1804
1917
  const template = await loadBoundTemplate(triggerKey, "whatsapp", "customers", dataSource, entityMap);
1805
- if (!template) return;
1918
+ if (!template) {
1919
+ notifyLog.skip("WHATSAPP", "no bound/default WhatsApp template", {
1920
+ triggerKey,
1921
+ orderId,
1922
+ phone: maskNotifyPhone(phone)
1923
+ });
1924
+ return;
1925
+ }
1806
1926
  const paramOrder = template.providerMeta?.paramOrder ?? [];
1807
1927
  if (paramOrder.length === 0) {
1808
- console.warn("[order-notifications] WhatsApp template has no paramOrder set \u2014 variables may send blank/wrong", {
1809
- triggerKey
1928
+ notifyLog.skip("WHATSAPP", "template has no paramOrder \u2014 variables may send blank/wrong", {
1929
+ triggerKey,
1930
+ orderId
1810
1931
  });
1811
1932
  }
1812
1933
  const cms = await getCms();
@@ -1815,8 +1936,10 @@ async function sendWhatsAppForTrigger(triggerKey, orderId, phone, variables, dat
1815
1936
  name: template.name
1816
1937
  });
1817
1938
  if (!metaTemplateName) {
1818
- console.warn("[order-notifications] bound WhatsApp template has no Meta template name", {
1939
+ notifyLog.skip("WHATSAPP", "template has no Meta template name", {
1819
1940
  triggerKey,
1941
+ orderId,
1942
+ phone: maskNotifyPhone(phone),
1820
1943
  templateId: template.id
1821
1944
  });
1822
1945
  return;
@@ -1831,37 +1954,57 @@ async function sendWhatsAppForTrigger(triggerKey, orderId, phone, variables, dat
1831
1954
  templateParamOrder: paramOrder
1832
1955
  });
1833
1956
  if (!sent) {
1834
- console.warn("[order-notifications] WhatsApp send failed", {
1957
+ notifyLog.error("WHATSAPP", "send failed / not queued", {
1835
1958
  triggerKey,
1836
1959
  orderId,
1960
+ phone: maskNotifyPhone(phone),
1837
1961
  hint: "Register whatsappPlugin() + queuePlugin() in getCms(), or set WHATSAPP_ACCESS_TOKEN and WHATSAPP_PHONE_NUMBER_ID"
1838
1962
  });
1839
1963
  } else {
1840
- console.log("[order-notifications] WhatsApp queued/sent", {
1964
+ notifyLog.whatsapp("queued/sent", {
1841
1965
  triggerKey,
1842
1966
  orderId,
1843
- phone: maskPhone2(phone)
1967
+ phone: maskNotifyPhone(phone),
1968
+ metaTemplateName
1844
1969
  });
1845
1970
  }
1846
1971
  }
1847
1972
  __name(sendWhatsAppForTrigger, "sendWhatsAppForTrigger");
1848
1973
  async function handleTrigger(triggerKey, payload, dataSource, entityMap, getCms) {
1849
- console.log("[order-notifications] received", {
1974
+ notifyLog.trigger("dispatcher received", {
1850
1975
  triggerKey,
1851
1976
  orderId: payload.orderId,
1852
- phone: maskPhone2(payload.customerPhone)
1977
+ orderNumber: payload.orderNumber,
1978
+ phone: maskNotifyPhone(payload.customerPhone),
1979
+ vendorId: payload.vendorId ?? null
1853
1980
  });
1854
1981
  const [emailChannelEnabled, whatsappChannelEnabled, pushChannelEnabled] = await Promise.all([
1855
1982
  isEmailNotificationsEnabled(dataSource, entityMap),
1856
1983
  isWhatsappNotificationsEnabled(dataSource, entityMap),
1857
1984
  isPushNotificationsEnabled(dataSource, entityMap)
1858
1985
  ]);
1986
+ notifyLog.trigger("channel switches", {
1987
+ triggerKey,
1988
+ orderId: payload.orderId,
1989
+ email: emailChannelEnabled,
1990
+ whatsapp: whatsappChannelEnabled,
1991
+ push: pushChannelEnabled
1992
+ });
1859
1993
  if (!emailChannelEnabled && !whatsappChannelEnabled && !pushChannelEnabled) {
1860
- console.warn("[order-notifications] skip: Email, WhatsApp, and Mobile Push notifications are disabled");
1994
+ notifyLog.skip("ALL", "Email, WhatsApp, and Mobile Push are disabled in event_notifications", {
1995
+ triggerKey,
1996
+ orderId: payload.orderId
1997
+ });
1861
1998
  return;
1862
1999
  }
1863
2000
  const triggerEnabled = await isTriggerCatalogEnabled(triggerKey, dataSource, entityMap);
1864
- if (!triggerEnabled) return;
2001
+ if (!triggerEnabled) {
2002
+ notifyLog.skip("TRIGGER", "trigger catalog disabled", {
2003
+ triggerKey,
2004
+ orderId: payload.orderId
2005
+ });
2006
+ return;
2007
+ }
1865
2008
  const context = await loadOrderNotificationContexts(payload.orderId, {
1866
2009
  dataSource,
1867
2010
  entityMap
@@ -1869,32 +2012,86 @@ async function handleTrigger(triggerKey, payload, dataSource, entityMap, getCms)
1869
2012
  const variables = mergeTriggerVariables(payload, context?.variables);
1870
2013
  const contactEmail = context?.contact.email?.trim() || "";
1871
2014
  const contactPhone = context?.contact.phone?.trim() || payload.customerPhone?.trim() || "";
2015
+ notifyLog.trigger("recipients resolved", {
2016
+ triggerKey,
2017
+ orderId: payload.orderId,
2018
+ contactEmail: maskNotifyEmail(contactEmail),
2019
+ contactPhone: maskNotifyPhone(contactPhone)
2020
+ });
1872
2021
  if (emailChannelEnabled) {
1873
2022
  const [customerEnabled, vendorEnabled, adminEnabled] = await Promise.all([
1874
2023
  isAudienceEmailEnabled(dataSource, entityMap, "customer"),
1875
2024
  isAudienceEmailEnabled(dataSource, entityMap, "vendor"),
1876
2025
  isAudienceEmailEnabled(dataSource, entityMap, "admin")
1877
2026
  ]);
1878
- if (customerEnabled && contactEmail) {
2027
+ notifyLog.email("audience switches", {
2028
+ triggerKey,
2029
+ orderId: payload.orderId,
2030
+ customer: customerEnabled,
2031
+ vendor: vendorEnabled,
2032
+ admin: adminEnabled
2033
+ });
2034
+ if (triggerKey === "order_assigned_assignee") {
2035
+ const assigneeEmail = context?.order?.assignedUser?.email?.trim();
2036
+ if (assigneeEmail) {
2037
+ await sendEmailForTrigger(triggerKey, payload.orderId, "customers", variables, assigneeEmail, dataSource, entityMap, getCms);
2038
+ } else {
2039
+ notifyLog.skip("EMAIL", "order_assigned_assignee has no assignee email", {
2040
+ triggerKey,
2041
+ orderId: payload.orderId
2042
+ });
2043
+ }
2044
+ } else if (customerEnabled && contactEmail) {
1879
2045
  await sendEmailForTrigger(triggerKey, payload.orderId, "customers", variables, contactEmail, dataSource, entityMap, getCms);
2046
+ } else if (!customerEnabled) {
2047
+ notifyLog.skip("EMAIL", "customer email audience disabled", {
2048
+ triggerKey,
2049
+ orderId: payload.orderId
2050
+ });
2051
+ } else {
2052
+ notifyLog.skip("EMAIL", "no customer email on order contact", {
2053
+ triggerKey,
2054
+ orderId: payload.orderId
2055
+ });
1880
2056
  }
1881
- if (vendorEnabled) {
2057
+ if (vendorEnabled && triggerKey !== "order_assigned_assignee") {
1882
2058
  const multiVendorSettings = await getSettingsGroup(dataSource, entityMap, "multi_vendor");
1883
2059
  if (multiVendorSettings.enabled !== "false" && multiVendorSettings.enabled !== "0") {
1884
2060
  const vendorEmails = await getOrderVendorEmails(payload.orderId, payload.vendorId, dataSource, entityMap);
2061
+ if (vendorEmails.length === 0) {
2062
+ notifyLog.skip("EMAIL", "no vendor emails for order", {
2063
+ triggerKey,
2064
+ orderId: payload.orderId,
2065
+ vendorId: payload.vendorId ?? null
2066
+ });
2067
+ }
1885
2068
  for (const vEmail of vendorEmails) {
1886
2069
  await sendEmailForTrigger(triggerKey, payload.orderId, "vendors", variables, vEmail, dataSource, entityMap, getCms);
1887
2070
  }
2071
+ } else {
2072
+ notifyLog.skip("EMAIL", "multi-vendor off \u2014 skip vendor email", {
2073
+ triggerKey,
2074
+ orderId: payload.orderId
2075
+ });
1888
2076
  }
1889
2077
  }
1890
- if (adminEnabled) {
2078
+ if (adminEnabled && triggerKey !== "order_assigned_assignee") {
1891
2079
  const adminEmails = await getAdminRecipientEmails(dataSource, entityMap);
2080
+ if (adminEmails.length === 0) {
2081
+ notifyLog.skip("EMAIL", "no admin recipient emails", {
2082
+ triggerKey,
2083
+ orderId: payload.orderId
2084
+ });
2085
+ }
1892
2086
  for (const aEmail of adminEmails) {
1893
2087
  await sendEmailForTrigger(triggerKey, payload.orderId, "admin", variables, aEmail, dataSource, entityMap, getCms);
1894
2088
  }
1895
2089
  }
1896
2090
  } else {
1897
- console.log("[order-notifications] skip email: Email notifications channel is disabled");
2091
+ notifyLog.skip("EMAIL", "email channel disabled", {
2092
+ triggerKey,
2093
+ orderId: payload.orderId
2094
+ });
1898
2095
  }
1899
2096
  if (pushChannelEnabled) {
1900
2097
  const [pushCustomerEnabled, pushVendorEnabled, pushAdminEnabled] = await Promise.all([
@@ -1902,28 +2099,59 @@ async function handleTrigger(triggerKey, payload, dataSource, entityMap, getCms)
1902
2099
  isAudiencePushEnabled(dataSource, entityMap, "vendor"),
1903
2100
  isAudiencePushEnabled(dataSource, entityMap, "admin")
1904
2101
  ]);
1905
- const targetCustomerId = context?.order?.customerContactId || context?.order?.contactId || context?.order?.userId || context?.order?.id;
2102
+ notifyLog.push("audience switches", {
2103
+ triggerKey,
2104
+ orderId: payload.orderId,
2105
+ customer: pushCustomerEnabled,
2106
+ vendor: pushVendorEnabled,
2107
+ admin: pushAdminEnabled
2108
+ });
2109
+ const targetCustomerId = triggerKey === "order_assigned_assignee" ? context?.order?.assignedUser?.id : context?.order?.customerContactId || context?.order?.contactId || context?.order?.userId || context?.order?.id;
1906
2110
  if (pushCustomerEnabled && targetCustomerId) {
1907
2111
  await sendPushNotificationForTrigger(triggerKey, payload.orderId, "customers", String(targetCustomerId), variables, dataSource, entityMap);
2112
+ } else if (!pushCustomerEnabled) {
2113
+ notifyLog.skip("PUSH", "customer push audience disabled", {
2114
+ triggerKey,
2115
+ orderId: payload.orderId
2116
+ });
2117
+ } else {
2118
+ notifyLog.skip("PUSH", "no customer user/contact id for push", {
2119
+ triggerKey,
2120
+ orderId: payload.orderId
2121
+ });
1908
2122
  }
1909
- if (pushVendorEnabled && payload.vendorId) {
2123
+ if (pushVendorEnabled && payload.vendorId && triggerKey !== "order_assigned_assignee") {
1910
2124
  await sendPushNotificationForTrigger(triggerKey, payload.orderId, "vendors", String(payload.vendorId), variables, dataSource, entityMap);
2125
+ } else if (pushVendorEnabled && triggerKey !== "order_assigned_assignee" && !payload.vendorId) {
2126
+ notifyLog.skip("PUSH", "no vendorId on order \u2014 skip vendor push", {
2127
+ triggerKey,
2128
+ orderId: payload.orderId
2129
+ });
1911
2130
  }
1912
- if (pushAdminEnabled) {
2131
+ if (pushAdminEnabled && triggerKey !== "order_assigned_assignee") {
1913
2132
  await sendPushNotificationForTrigger(triggerKey, payload.orderId, "admin", "admin", variables, dataSource, entityMap);
1914
2133
  }
2134
+ } else {
2135
+ notifyLog.skip("PUSH", "push channel disabled", {
2136
+ triggerKey,
2137
+ orderId: payload.orderId
2138
+ });
1915
2139
  }
1916
2140
  if (whatsappChannelEnabled) {
1917
- if (contactPhone) {
1918
- await sendWhatsAppForTrigger(triggerKey, payload.orderId, contactPhone, variables, dataSource, entityMap, getCms);
2141
+ const targetPhone = triggerKey === "order_assigned_assignee" ? context?.order?.assignedUser?.phone?.trim() : contactPhone;
2142
+ if (targetPhone) {
2143
+ await sendWhatsAppForTrigger(triggerKey, payload.orderId, targetPhone, variables, dataSource, entityMap, getCms);
1919
2144
  } else {
1920
- console.warn("[order-notifications] skip WhatsApp: no customer phone", {
2145
+ notifyLog.skip("WHATSAPP", "no recipient phone", {
1921
2146
  triggerKey,
1922
2147
  orderId: payload.orderId
1923
2148
  });
1924
2149
  }
1925
2150
  } else {
1926
- console.log("[order-notifications] skip WhatsApp: WhatsApp notifications channel is disabled");
2151
+ notifyLog.skip("WHATSAPP", "WhatsApp channel disabled", {
2152
+ triggerKey,
2153
+ orderId: payload.orderId
2154
+ });
1927
2155
  }
1928
2156
  }
1929
2157
  __name(handleTrigger, "handleTrigger");
@@ -1937,10 +2165,21 @@ function initWhatsappTriggerDispatcher(deps) {
1937
2165
  });
1938
2166
  notificationTriggerEmitter.onAnyTrigger((triggerKey, payload) => {
1939
2167
  void handleTrigger(triggerKey, payload, dataSource, entityMap, getCms).catch((err) => {
1940
- console.error("[order-notifications]", triggerKey, err);
2168
+ notifyLog.error("TRIGGER", "dispatcher handleTrigger failed", {
2169
+ triggerKey,
2170
+ orderId: payload.orderId,
2171
+ error: err instanceof Error ? err.message : String(err),
2172
+ stack: err instanceof Error ? err.stack : void 0
2173
+ });
1941
2174
  });
1942
2175
  });
1943
- console.log("[order-notifications] dispatcher initialized (email + WhatsApp, unified via order_notification_bindings)");
2176
+ notifyLog.trigger("dispatcher initialized", {
2177
+ channels: [
2178
+ "email",
2179
+ "whatsapp",
2180
+ "push"
2181
+ ]
2182
+ });
1944
2183
  }
1945
2184
  __name(initWhatsappTriggerDispatcher, "initWhatsappTriggerDispatcher");
1946
2185
  async function dispatchOrderNotificationDirectly(triggerKey, payload, dataSource, entityMap, getCms) {