@voyantjs/notifications 0.19.0 → 0.21.0

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.
@@ -17,6 +17,88 @@ function normalizeAttachments(attachments) {
17
17
  ...(attachment.contentId ? { contentId: attachment.contentId } : {}),
18
18
  }));
19
19
  }
20
+ function truncateLogValue(value, maxLength = 4000) {
21
+ if (value.length <= maxLength)
22
+ return value;
23
+ return `${value.slice(0, maxLength)}…`;
24
+ }
25
+ function readErrorField(error, field) {
26
+ if (!error || typeof error !== "object" || !(field in error))
27
+ return null;
28
+ const value = error[field];
29
+ if (typeof value === "string")
30
+ return truncateLogValue(value);
31
+ if (typeof value === "number" || typeof value === "boolean")
32
+ return value;
33
+ if (value == null)
34
+ return null;
35
+ try {
36
+ return truncateLogValue(JSON.stringify(value));
37
+ }
38
+ catch {
39
+ return String(value);
40
+ }
41
+ }
42
+ function serializeNotificationError(error) {
43
+ const base = error instanceof Error
44
+ ? {
45
+ name: error.name,
46
+ message: truncateLogValue(error.message),
47
+ stack: error.stack ? truncateLogValue(error.stack) : null,
48
+ cause: error.cause instanceof Error
49
+ ? {
50
+ name: error.cause.name,
51
+ message: truncateLogValue(error.cause.message),
52
+ stack: error.cause.stack ? truncateLogValue(error.cause.stack) : null,
53
+ }
54
+ : readErrorField(error, "cause"),
55
+ }
56
+ : {
57
+ name: typeof error,
58
+ message: truncateLogValue(String(error)),
59
+ stack: null,
60
+ cause: null,
61
+ };
62
+ return {
63
+ ...base,
64
+ code: readErrorField(error, "code"),
65
+ status: readErrorField(error, "status"),
66
+ statusCode: readErrorField(error, "statusCode"),
67
+ responseStatus: readErrorField(error, "responseStatus"),
68
+ responseBody: readErrorField(error, "responseBody") ?? readErrorField(error, "body"),
69
+ data: readErrorField(error, "data"),
70
+ notificationRequest: readErrorField(error, "notificationRequest"),
71
+ };
72
+ }
73
+ function metadataWithoutFailureLog(metadata) {
74
+ if (!metadata)
75
+ return null;
76
+ const rest = { ...metadata };
77
+ delete rest.failureLog;
78
+ return rest;
79
+ }
80
+ function isAttachmentSummary(value) {
81
+ if (!value || typeof value !== "object" || Array.isArray(value))
82
+ return false;
83
+ const record = value;
84
+ return (typeof record.filename === "string" &&
85
+ record.filename.length > 0 &&
86
+ (typeof record.path === "string" || typeof record.contentBase64 === "string"));
87
+ }
88
+ function attachmentsFromMetadata(metadata) {
89
+ const rawAttachments = metadata?.attachments;
90
+ if (!Array.isArray(rawAttachments))
91
+ return undefined;
92
+ const attachments = rawAttachments.filter(isAttachmentSummary).map((attachment) => ({
93
+ filename: attachment.filename,
94
+ ...(attachment.contentBase64 ? { contentBase64: attachment.contentBase64 } : {}),
95
+ ...(attachment.path ? { path: attachment.path } : {}),
96
+ ...(attachment.contentType ? { contentType: attachment.contentType } : {}),
97
+ ...(attachment.disposition ? { disposition: attachment.disposition } : {}),
98
+ ...(attachment.contentId ? { contentId: attachment.contentId } : {}),
99
+ }));
100
+ return attachments.length > 0 ? attachments : undefined;
101
+ }
20
102
  export async function listDeliveries(db, query) {
21
103
  const conditions = [];
22
104
  if (query.channel)
@@ -60,6 +142,38 @@ export async function getDeliveryById(db, id) {
60
142
  .limit(1);
61
143
  return row ?? null;
62
144
  }
145
+ export async function resendDelivery(db, dispatcher, id) {
146
+ const original = await getDeliveryById(db, id);
147
+ if (!original)
148
+ return null;
149
+ const previousMetadata = metadataWithoutFailureLog(original.metadata);
150
+ return sendNotification(db, dispatcher, {
151
+ templateId: original.templateId,
152
+ templateSlug: original.templateSlug,
153
+ channel: original.channel,
154
+ provider: original.provider,
155
+ to: original.toAddress,
156
+ from: original.fromAddress,
157
+ subject: original.subject,
158
+ html: original.htmlBody,
159
+ text: original.textBody,
160
+ attachments: attachmentsFromMetadata(original.metadata),
161
+ data: original.payloadData,
162
+ targetType: original.targetType,
163
+ targetId: original.targetId,
164
+ bookingId: original.bookingId,
165
+ invoiceId: original.invoiceId,
166
+ paymentSessionId: original.paymentSessionId,
167
+ personId: original.personId,
168
+ organizationId: original.organizationId,
169
+ metadata: {
170
+ ...(previousMetadata ?? {}),
171
+ resendOfDeliveryId: original.id,
172
+ previousStatus: original.status,
173
+ },
174
+ scheduledFor: null,
175
+ });
176
+ }
63
177
  export async function sendNotification(db, dispatcher, input) {
64
178
  let template = null;
65
179
  if (input.templateId) {
@@ -164,12 +278,17 @@ export async function sendNotification(db, dispatcher, input) {
164
278
  }
165
279
  catch (error) {
166
280
  const message = error instanceof Error ? error.message : "Notification send failed";
281
+ const failureLog = serializeNotificationError(error);
167
282
  const [failed] = await db
168
283
  .update(notificationDeliveries)
169
284
  .set({
170
285
  status: "failed",
171
286
  failedAt: new Date(),
172
287
  errorMessage: message,
288
+ metadata: {
289
+ ...(pending.metadata ?? {}),
290
+ failureLog,
291
+ },
173
292
  updatedAt: new Date(),
174
293
  })
175
294
  .where(eq(notificationDeliveries.id, pending.id))
@@ -1,15 +1,47 @@
1
1
  import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
- import type { NotificationService, ReminderQueueResult, ReminderSweepResult, RunDueRemindersInput } from "./service-shared.js";
2
+ import { type BookingDocumentAttachmentResolver } from "./service-booking-documents.js";
3
+ import type { NotificationReminderRuleRow, NotificationService, ReminderQueueResult, ReminderSweepResult, RunDueRemindersInput } from "./service-shared.js";
3
4
  type ReminderDeliveryEnqueuer = (input: {
4
5
  reminderRunId: string;
5
6
  }) => Promise<void>;
7
+ type ReminderTargetType = NotificationReminderRuleRow["targetType"];
8
+ type BookingEventReminderTargetType = Extract<ReminderTargetType, "booking_confirmed" | "payment_complete" | "booking_cancelled_non_payment">;
9
+ export interface BookingEventReminderRuntimeOptions {
10
+ documentAttachmentResolver?: BookingDocumentAttachmentResolver;
11
+ }
12
+ export declare function dispatchReminderEventRules(db: PostgresJsDatabase, dispatcher: NotificationService, input: {
13
+ targetType: BookingEventReminderTargetType;
14
+ bookingId: string;
15
+ paymentSessionId?: string | null;
16
+ eventData?: Record<string, unknown>;
17
+ }, runtime?: BookingEventReminderRuntimeOptions): Promise<({
18
+ metadata: Record<string, unknown> | null;
19
+ id: string;
20
+ createdAt: Date;
21
+ updatedAt: Date;
22
+ organizationId: string | null;
23
+ status: "failed" | "sent" | "processing" | "skipped" | "queued";
24
+ scheduledFor: Date;
25
+ errorMessage: string | null;
26
+ bookingId: string | null;
27
+ personId: string | null;
28
+ targetType: "booking_confirmed" | "invoice" | "booking_payment_schedule" | "payment_complete" | "booking_cancelled_non_payment";
29
+ targetId: string;
30
+ paymentSessionId: string | null;
31
+ reminderRuleId: string;
32
+ dedupeKey: string;
33
+ notificationDeliveryId: string | null;
34
+ recipient: string | null;
35
+ processedAt: Date;
36
+ } | null)[]>;
37
+ export declare function bookingIsPaidInFullForNotification(db: PostgresJsDatabase, bookingId: string): Promise<boolean>;
6
38
  export declare function queueDueReminders(db: PostgresJsDatabase, input: RunDueRemindersInput | undefined, enqueueDelivery: ReminderDeliveryEnqueuer): Promise<ReminderQueueResult>;
7
39
  export declare function deliverReminderRun(db: PostgresJsDatabase, dispatcher: NotificationService, input: {
8
40
  reminderRunId: string;
9
41
  }): Promise<{
10
42
  id: string;
11
43
  reminderRuleId: string;
12
- targetType: "invoice" | "booking_payment_schedule";
44
+ targetType: "booking_confirmed" | "invoice" | "booking_payment_schedule" | "payment_complete" | "booking_cancelled_non_payment";
13
45
  targetId: string;
14
46
  dedupeKey: string;
15
47
  bookingId: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"service-reminders.d.ts","sourceRoot":"","sources":["../src/service-reminders.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAIjE,OAAO,KAAK,EAGV,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,qBAAqB,CAAA;AAW5B,KAAK,wBAAwB,GAAG,CAAC,KAAK,EAAE;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAoyBnF,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,oBAAoB,YAAK,EAChC,eAAe,EAAE,wBAAwB,gCAoF1C;AAED,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,mBAAmB,EAC/B,KAAK,EAAE;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;UAkDjC;AAED,wBAAsB,eAAe,CACnC,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,mBAAmB,EAC/B,KAAK,GAAE,oBAAyB,gCA4EjC"}
1
+ {"version":3,"file":"service-reminders.d.ts","sourceRoot":"","sources":["../src/service-reminders.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAGjE,OAAO,EACL,KAAK,iCAAiC,EAGvC,MAAM,gCAAgC,CAAA;AAEvC,OAAO,KAAK,EAGV,2BAA2B,EAC3B,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,qBAAqB,CAAA;AAY5B,KAAK,wBAAwB,GAAG,CAAC,KAAK,EAAE;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAGnF,KAAK,kBAAkB,GAAG,2BAA2B,CAAC,YAAY,CAAC,CAAA;AACnE,KAAK,8BAA8B,GAAG,OAAO,CAC3C,kBAAkB,EAClB,mBAAmB,GAAG,kBAAkB,GAAG,+BAA+B,CAC3E,CAAA;AAED,MAAM,WAAW,kCAAkC;IACjD,0BAA0B,CAAC,EAAE,iCAAiC,CAAA;CAC/D;AAmmCD,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,mBAAmB,EAC/B,KAAK,EAAE;IACL,UAAU,EAAE,8BAA8B,CAAA;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC,EACD,OAAO,GAAE,kCAAuC;;;;;;;;;;;;;;;;;;;aAmBjD;AAED,wBAAsB,kCAAkC,CACtD,EAAE,EAAE,kBAAkB,EACtB,SAAS,EAAE,MAAM,oBAGlB;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,oBAAoB,YAAK,EAChC,eAAe,EAAE,wBAAwB,gCAoF1C;AAED,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,mBAAmB,EAC/B,KAAK,EAAE;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;UAkDjC;AAED,wBAAsB,eAAe,CACnC,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,mBAAmB,EAC/B,KAAK,GAAE,oBAAyB,gCA4EjC"}
@@ -1,9 +1,110 @@
1
1
  import { bookings, bookingTravelers } from "@voyantjs/bookings/schema";
2
- import { bookingPaymentSchedules, invoices } from "@voyantjs/finance";
3
- import { and, desc, eq, gt, or } from "drizzle-orm";
2
+ import { bookingPaymentSchedules, invoices, paymentSessions } from "@voyantjs/finance";
3
+ import { and, asc, desc, eq, gt, or } from "drizzle-orm";
4
4
  import { notificationReminderRules, notificationReminderRuns } from "./schema.js";
5
+ import { bookingDocumentNotificationsService, createDefaultBookingDocumentAttachment, } from "./service-booking-documents.js";
5
6
  import { sendInvoiceNotification, sendNotification } from "./service-deliveries.js";
6
7
  import { addUtcDays, buildReminderDedupeKey, listBookingNotificationItems, resolveReminderRecipient, startOfUtcDay, toDateString, toTimestamp, } from "./service-shared.js";
8
+ async function getBookingPaymentNotificationContext(db, bookingId) {
9
+ const [[paymentSchedule], [invoice], [paymentSession]] = await Promise.all([
10
+ db
11
+ .select()
12
+ .from(bookingPaymentSchedules)
13
+ .where(and(eq(bookingPaymentSchedules.bookingId, bookingId), or(eq(bookingPaymentSchedules.status, "pending"), eq(bookingPaymentSchedules.status, "due"))))
14
+ .orderBy(asc(bookingPaymentSchedules.dueDate), asc(bookingPaymentSchedules.createdAt))
15
+ .limit(1),
16
+ db
17
+ .select()
18
+ .from(invoices)
19
+ .where(eq(invoices.bookingId, bookingId))
20
+ .orderBy(desc(invoices.createdAt))
21
+ .limit(1),
22
+ db
23
+ .select()
24
+ .from(paymentSessions)
25
+ .where(eq(paymentSessions.bookingId, bookingId))
26
+ .orderBy(desc(paymentSessions.createdAt))
27
+ .limit(1),
28
+ ]);
29
+ return {
30
+ paymentSchedule: paymentSchedule ?? null,
31
+ invoice: invoice ?? null,
32
+ paymentSession: paymentSession ?? null,
33
+ };
34
+ }
35
+ async function getBookingEventDocumentContext(db, bookingId, attachmentResolver) {
36
+ const bundle = await bookingDocumentNotificationsService.listBookingDocumentBundle(db, bookingId);
37
+ const documents = bundle?.documents ?? [];
38
+ if (documents.length === 0) {
39
+ return { documents, attachments: [] };
40
+ }
41
+ const resolver = attachmentResolver ??
42
+ (async (document) => createDefaultBookingDocumentAttachment(document));
43
+ const attachments = (await Promise.all(documents.map((document) => resolver(document)))).filter((attachment) => Boolean(attachment));
44
+ return { documents, attachments };
45
+ }
46
+ function serializeBookingPaymentContext(context, paymentScheduleOverride) {
47
+ const schedule = paymentScheduleOverride ?? context.paymentSchedule;
48
+ return {
49
+ invoice: context.invoice
50
+ ? {
51
+ id: context.invoice.id,
52
+ invoiceNumber: context.invoice.invoiceNumber,
53
+ invoiceType: context.invoice.invoiceType,
54
+ status: context.invoice.status,
55
+ currency: context.invoice.currency,
56
+ subtotalCents: context.invoice.subtotalCents,
57
+ taxCents: context.invoice.taxCents,
58
+ totalCents: context.invoice.totalCents,
59
+ paidCents: context.invoice.paidCents,
60
+ balanceDueCents: context.invoice.balanceDueCents,
61
+ issueDate: context.invoice.issueDate,
62
+ dueDate: context.invoice.dueDate,
63
+ }
64
+ : null,
65
+ paymentSession: context.paymentSession
66
+ ? {
67
+ id: context.paymentSession.id,
68
+ status: context.paymentSession.status,
69
+ provider: context.paymentSession.provider,
70
+ currency: context.paymentSession.currency,
71
+ amountCents: context.paymentSession.amountCents,
72
+ redirectUrl: context.paymentSession.redirectUrl,
73
+ returnUrl: context.paymentSession.returnUrl,
74
+ cancelUrl: context.paymentSession.cancelUrl,
75
+ expiresAt: context.paymentSession.expiresAt,
76
+ paymentMethod: context.paymentSession.paymentMethod,
77
+ externalReference: context.paymentSession.externalReference,
78
+ }
79
+ : null,
80
+ paymentSchedule: schedule
81
+ ? {
82
+ id: schedule.id,
83
+ dueDate: schedule.dueDate,
84
+ amountCents: schedule.amountCents,
85
+ currency: schedule.currency,
86
+ scheduleType: schedule.scheduleType,
87
+ status: schedule.status,
88
+ }
89
+ : null,
90
+ };
91
+ }
92
+ async function hasOutstandingBookingBalance(db, bookingId) {
93
+ const [openSchedule] = await db
94
+ .select({ id: bookingPaymentSchedules.id })
95
+ .from(bookingPaymentSchedules)
96
+ .where(and(eq(bookingPaymentSchedules.bookingId, bookingId), or(eq(bookingPaymentSchedules.status, "pending"), eq(bookingPaymentSchedules.status, "due"))))
97
+ .limit(1);
98
+ if (openSchedule) {
99
+ return true;
100
+ }
101
+ const [openInvoice] = await db
102
+ .select({ id: invoices.id })
103
+ .from(invoices)
104
+ .where(and(eq(invoices.bookingId, bookingId), gt(invoices.balanceDueCents, 0), or(eq(invoices.status, "sent"), eq(invoices.status, "partially_paid"), eq(invoices.status, "overdue"))))
105
+ .limit(1);
106
+ return Boolean(openInvoice);
107
+ }
7
108
  function buildReminderSweepSummary() {
8
109
  return {
9
110
  processed: 0,
@@ -129,7 +230,7 @@ async function queueBookingPaymentScheduleReminder(db, enqueueDelivery, rule, sc
129
230
  .insert(notificationReminderRuns)
130
231
  .values({
131
232
  reminderRuleId: rule.id,
132
- targetType: "booking_payment_schedule",
233
+ targetType: rule.targetType,
133
234
  targetId: schedule.id,
134
235
  dedupeKey,
135
236
  bookingId: schedule.bookingId,
@@ -268,7 +369,7 @@ async function sendBookingPaymentScheduleReminder(db, dispatcher, rule, schedule
268
369
  .insert(notificationReminderRuns)
269
370
  .values({
270
371
  reminderRuleId: rule.id,
271
- targetType: "booking_payment_schedule",
372
+ targetType: rule.targetType,
272
373
  targetId: schedule.id,
273
374
  dedupeKey,
274
375
  bookingId: schedule.bookingId,
@@ -289,7 +390,7 @@ async function sendBookingPaymentScheduleReminder(db, dispatcher, rule, schedule
289
390
  .returning();
290
391
  return run ?? null;
291
392
  }
292
- const [participants, items] = await Promise.all([
393
+ const [participants, items, paymentContext] = await Promise.all([
293
394
  db
294
395
  .select({
295
396
  id: bookingTravelers.id,
@@ -303,13 +404,14 @@ async function sendBookingPaymentScheduleReminder(db, dispatcher, rule, schedule
303
404
  .where(eq(bookingTravelers.bookingId, booking.id))
304
405
  .orderBy(desc(bookingTravelers.isPrimary), bookingTravelers.createdAt),
305
406
  listBookingNotificationItems(db, booking.id),
407
+ getBookingPaymentNotificationContext(db, booking.id),
306
408
  ]);
307
409
  const recipient = resolveReminderRecipient(booking, participants);
308
410
  const [processingRun] = await db
309
411
  .insert(notificationReminderRuns)
310
412
  .values({
311
413
  reminderRuleId: rule.id,
312
- targetType: "booking_payment_schedule",
414
+ targetType: rule.targetType,
313
415
  targetId: schedule.id,
314
416
  dedupeKey,
315
417
  bookingId: booking.id,
@@ -367,14 +469,7 @@ async function sendBookingPaymentScheduleReminder(db, dispatcher, rule, schedule
367
469
  sellCurrency: booking.sellCurrency,
368
470
  sellAmountCents: booking.sellAmountCents,
369
471
  },
370
- paymentSchedule: {
371
- id: schedule.id,
372
- dueDate: schedule.dueDate,
373
- amountCents: schedule.amountCents,
374
- currency: schedule.currency,
375
- scheduleType: schedule.scheduleType,
376
- status: schedule.status,
377
- },
472
+ ...serializeBookingPaymentContext(paymentContext, schedule),
378
473
  items,
379
474
  },
380
475
  targetType: "booking_payment_schedule",
@@ -528,7 +623,7 @@ async function sendQueuedBookingPaymentScheduleReminder(db, dispatcher, run, rul
528
623
  if (!booking) {
529
624
  return markReminderRunSkipped(db, run.id, now, "Booking not found for payment schedule");
530
625
  }
531
- const [participants, items] = await Promise.all([
626
+ const [participants, items, paymentContext] = await Promise.all([
532
627
  db
533
628
  .select({
534
629
  id: bookingTravelers.id,
@@ -542,6 +637,7 @@ async function sendQueuedBookingPaymentScheduleReminder(db, dispatcher, run, rul
542
637
  .where(eq(bookingTravelers.bookingId, booking.id))
543
638
  .orderBy(desc(bookingTravelers.isPrimary), bookingTravelers.createdAt),
544
639
  listBookingNotificationItems(db, booking.id),
640
+ getBookingPaymentNotificationContext(db, booking.id),
545
641
  ]);
546
642
  const fallbackRecipient = resolveReminderRecipient(booking, participants);
547
643
  const traveler = participants.find((entry) => entry.email === run.recipient) ?? fallbackRecipient ?? null;
@@ -582,14 +678,7 @@ async function sendQueuedBookingPaymentScheduleReminder(db, dispatcher, run, rul
582
678
  sellCurrency: booking.sellCurrency,
583
679
  sellAmountCents: booking.sellAmountCents,
584
680
  },
585
- paymentSchedule: {
586
- id: schedule.id,
587
- dueDate: schedule.dueDate,
588
- amountCents: schedule.amountCents,
589
- currency: schedule.currency,
590
- scheduleType: schedule.scheduleType,
591
- status: schedule.status,
592
- },
681
+ ...serializeBookingPaymentContext(paymentContext, schedule),
593
682
  items,
594
683
  },
595
684
  targetType: "booking_payment_schedule",
@@ -632,6 +721,177 @@ async function sendQueuedInvoiceReminder(db, dispatcher, run, rule, now) {
632
721
  }
633
722
  return markReminderRunSent(db, run.id, new Date(), delivery.id ?? null);
634
723
  }
724
+ async function sendBookingEventReminder(db, dispatcher, rule, input, runtime = {}) {
725
+ const now = new Date();
726
+ const dedupeKey = buildReminderDedupeKey(rule.id, input.bookingId, input.targetType);
727
+ const [existingRun] = await db
728
+ .select({ id: notificationReminderRuns.id })
729
+ .from(notificationReminderRuns)
730
+ .where(eq(notificationReminderRuns.dedupeKey, dedupeKey))
731
+ .limit(1);
732
+ if (existingRun) {
733
+ return null;
734
+ }
735
+ const [booking] = await db
736
+ .select()
737
+ .from(bookings)
738
+ .where(eq(bookings.id, input.bookingId))
739
+ .limit(1);
740
+ if (!booking) {
741
+ const [run] = await db
742
+ .insert(notificationReminderRuns)
743
+ .values({
744
+ reminderRuleId: rule.id,
745
+ targetType: input.targetType,
746
+ targetId: input.bookingId,
747
+ dedupeKey,
748
+ bookingId: input.bookingId,
749
+ personId: null,
750
+ organizationId: null,
751
+ paymentSessionId: input.paymentSessionId ?? null,
752
+ notificationDeliveryId: null,
753
+ status: "skipped",
754
+ recipient: null,
755
+ scheduledFor: now,
756
+ processedAt: now,
757
+ errorMessage: "Booking not found for notification event",
758
+ metadata: {
759
+ eventTargetType: input.targetType,
760
+ ...(input.eventData ?? {}),
761
+ },
762
+ })
763
+ .returning();
764
+ return run ?? null;
765
+ }
766
+ const [participants, items, paymentContext, documentContext] = await Promise.all([
767
+ db
768
+ .select({
769
+ id: bookingTravelers.id,
770
+ firstName: bookingTravelers.firstName,
771
+ lastName: bookingTravelers.lastName,
772
+ email: bookingTravelers.email,
773
+ participantType: bookingTravelers.participantType,
774
+ isPrimary: bookingTravelers.isPrimary,
775
+ })
776
+ .from(bookingTravelers)
777
+ .where(eq(bookingTravelers.bookingId, booking.id))
778
+ .orderBy(desc(bookingTravelers.isPrimary), bookingTravelers.createdAt),
779
+ listBookingNotificationItems(db, booking.id),
780
+ getBookingPaymentNotificationContext(db, booking.id),
781
+ input.targetType === "booking_confirmed" && rule.channel === "email"
782
+ ? getBookingEventDocumentContext(db, booking.id, runtime.documentAttachmentResolver)
783
+ : Promise.resolve({ documents: [], attachments: [] }),
784
+ ]);
785
+ const recipient = resolveReminderRecipient(booking, participants);
786
+ const [processingRun] = await db
787
+ .insert(notificationReminderRuns)
788
+ .values({
789
+ reminderRuleId: rule.id,
790
+ targetType: input.targetType,
791
+ targetId: booking.id,
792
+ dedupeKey,
793
+ bookingId: booking.id,
794
+ personId: booking.personId ?? null,
795
+ organizationId: booking.organizationId ?? null,
796
+ paymentSessionId: input.paymentSessionId ?? null,
797
+ notificationDeliveryId: null,
798
+ status: "processing",
799
+ recipient: recipient?.email ?? null,
800
+ scheduledFor: now,
801
+ processedAt: now,
802
+ errorMessage: null,
803
+ metadata: {
804
+ eventTargetType: input.targetType,
805
+ bookingNumber: booking.bookingNumber,
806
+ ...(input.eventData ?? {}),
807
+ },
808
+ })
809
+ .onConflictDoNothing({ target: notificationReminderRuns.dedupeKey })
810
+ .returning();
811
+ if (!processingRun) {
812
+ return null;
813
+ }
814
+ if (!recipient?.email) {
815
+ return markReminderRunSkipped(db, processingRun.id, now, "No traveler email available for booking notification event");
816
+ }
817
+ try {
818
+ const delivery = await sendNotification(db, dispatcher, {
819
+ templateId: rule.templateId ?? null,
820
+ templateSlug: rule.templateSlug ?? null,
821
+ channel: rule.channel,
822
+ provider: rule.provider ?? null,
823
+ to: recipient.email,
824
+ data: {
825
+ bookingId: booking.id,
826
+ bookingNumber: booking.bookingNumber,
827
+ trigger: input.targetType,
828
+ event: input.eventData ?? {},
829
+ traveler: {
830
+ firstName: recipient.firstName,
831
+ lastName: recipient.lastName,
832
+ email: recipient.email,
833
+ participantType: recipient.participantType,
834
+ isPrimary: recipient.isPrimary,
835
+ },
836
+ travelers: participants,
837
+ booking: {
838
+ id: booking.id,
839
+ bookingNumber: booking.bookingNumber,
840
+ status: booking.status,
841
+ startDate: booking.startDate,
842
+ endDate: booking.endDate,
843
+ sellCurrency: booking.sellCurrency,
844
+ sellAmountCents: booking.sellAmountCents,
845
+ },
846
+ ...serializeBookingPaymentContext(paymentContext),
847
+ payment: input.targetType === "payment_complete"
848
+ ? {
849
+ isPaidInFull: true,
850
+ paymentSessionId: input.paymentSessionId ?? null,
851
+ }
852
+ : null,
853
+ documents: documentContext.documents,
854
+ items,
855
+ },
856
+ attachments: documentContext.attachments.length > 0 ? documentContext.attachments : null,
857
+ targetType: input.targetType === "payment_complete" ? "payment_session" : "booking",
858
+ targetId: input.targetType === "payment_complete"
859
+ ? (input.paymentSessionId ?? booking.id)
860
+ : booking.id,
861
+ bookingId: booking.id,
862
+ paymentSessionId: input.paymentSessionId ?? null,
863
+ personId: booking.personId ?? null,
864
+ organizationId: booking.organizationId ?? null,
865
+ metadata: {
866
+ reminderRuleId: rule.id,
867
+ reminderRunId: processingRun.id,
868
+ eventTargetType: input.targetType,
869
+ bookingDocumentKeys: documentContext.documents.map((document) => document.key),
870
+ },
871
+ scheduledFor: now.toISOString(),
872
+ });
873
+ return markReminderRunSent(db, processingRun.id, new Date(), delivery?.id ?? null);
874
+ }
875
+ catch (error) {
876
+ const message = error instanceof Error ? error.message : "Notification event delivery failed";
877
+ return markReminderRunFailed(db, processingRun.id, new Date(), message);
878
+ }
879
+ }
880
+ export async function dispatchReminderEventRules(db, dispatcher, input, runtime = {}) {
881
+ const rules = await db
882
+ .select()
883
+ .from(notificationReminderRules)
884
+ .where(and(eq(notificationReminderRules.status, "active"), eq(notificationReminderRules.targetType, input.targetType)))
885
+ .orderBy(notificationReminderRules.createdAt);
886
+ const results = [];
887
+ for (const rule of rules) {
888
+ results.push(await sendBookingEventReminder(db, dispatcher, rule, input, runtime));
889
+ }
890
+ return results;
891
+ }
892
+ export async function bookingIsPaidInFullForNotification(db, bookingId) {
893
+ return !(await hasOutstandingBookingBalance(db, bookingId));
894
+ }
635
895
  export async function queueDueReminders(db, input = {}, enqueueDelivery) {
636
896
  const now = toTimestamp(input.now) ?? new Date();
637
897
  const today = startOfUtcDay(now);
@@ -70,19 +70,7 @@ export declare function normalizeNotificationTemplateData(data: Record<string, u
70
70
  invoice: unknown;
71
71
  paymentSession: unknown;
72
72
  paymentSchedule: unknown;
73
- payment: {
74
- amount: {} | null;
75
- currency: {} | null;
76
- dueDate: {} | null;
77
- daysLeft: {} | null;
78
- reference: {} | null;
79
- method: {} | null;
80
- link: {} | null;
81
- payMode: {} | null;
82
- paidAmount: {} | null;
83
- balanceDue: {} | null;
84
- isPaidInFull: boolean;
85
- } | null;
73
+ payment: Record<string, unknown> | null;
86
74
  documents: unknown[];
87
75
  documentsCount: number;
88
76
  items: unknown[];
@@ -1 +1 @@
1
- {"version":3,"file":"service-shared.d.ts","sourceRoot":"","sources":["../src/service-shared.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service-shared.d.ts","sourceRoot":"","sources":["../src/service-shared.ts"],"names":[],"mappings":"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"}