@voyantjs/notifications 0.6.8 → 0.7.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.
@@ -123,6 +123,134 @@ export declare const bookingDocumentNotificationsService: {
123
123
  updatedAt: Date;
124
124
  };
125
125
  }>;
126
+ /**
127
+ * Confirm-and-dispatch — single orchestrated call for the operator flow
128
+ * that wants "list the booking's documents, then send them to the client"
129
+ * to be one action instead of two round-trips.
130
+ *
131
+ * With `sendNotification: false`, the caller gets the bundle back without
132
+ * attempting a send — useful for rendering a preview/checkbox list before
133
+ * the operator confirms. With `sendNotification: true`, the same send
134
+ * guards as `sendBookingDocumentsNotification` apply (no_documents,
135
+ * no_recipient, no_attachments, send_failed); the result keeps the bundle
136
+ * regardless so the UI always has something to show.
137
+ */
138
+ confirmAndDispatchBooking(db: PostgresJsDatabase, dispatcher: NotificationService, bookingId: string, input: {
139
+ sendNotification?: boolean;
140
+ } & SendBookingDocumentsNotificationInput, runtime?: SendBookingDocumentsRuntimeOptions): Promise<{
141
+ status: "not_found";
142
+ bookingId?: undefined;
143
+ documents?: undefined;
144
+ skipReason?: undefined;
145
+ recipient?: undefined;
146
+ delivery?: undefined;
147
+ } | {
148
+ status: "preview";
149
+ bookingId: string;
150
+ documents: {
151
+ key: string;
152
+ source: "finance" | "legal";
153
+ documentType: "invoice" | "proforma" | "contract";
154
+ bookingId: string;
155
+ name: string;
156
+ createdAt: string;
157
+ contractId?: string | null | undefined;
158
+ invoiceId?: string | null | undefined;
159
+ attachmentId?: string | null | undefined;
160
+ renditionId?: string | null | undefined;
161
+ contractStatus?: string | null | undefined;
162
+ invoiceStatus?: string | null | undefined;
163
+ format?: string | null | undefined;
164
+ mimeType?: string | null | undefined;
165
+ storageKey?: string | null | undefined;
166
+ downloadUrl?: string | null | undefined;
167
+ language?: string | null | undefined;
168
+ metadata?: Record<string, unknown> | null | undefined;
169
+ }[];
170
+ skipReason?: undefined;
171
+ recipient?: undefined;
172
+ delivery?: undefined;
173
+ } | {
174
+ status: "skipped";
175
+ bookingId: string;
176
+ documents: {
177
+ key: string;
178
+ source: "finance" | "legal";
179
+ documentType: "invoice" | "proforma" | "contract";
180
+ bookingId: string;
181
+ name: string;
182
+ createdAt: string;
183
+ contractId?: string | null | undefined;
184
+ invoiceId?: string | null | undefined;
185
+ attachmentId?: string | null | undefined;
186
+ renditionId?: string | null | undefined;
187
+ contractStatus?: string | null | undefined;
188
+ invoiceStatus?: string | null | undefined;
189
+ format?: string | null | undefined;
190
+ mimeType?: string | null | undefined;
191
+ storageKey?: string | null | undefined;
192
+ downloadUrl?: string | null | undefined;
193
+ language?: string | null | undefined;
194
+ metadata?: Record<string, unknown> | null | undefined;
195
+ }[];
196
+ skipReason: "no_documents" | "no_recipient" | "no_attachments" | "send_failed";
197
+ recipient?: undefined;
198
+ delivery?: undefined;
199
+ } | {
200
+ status: "dispatched";
201
+ bookingId: string;
202
+ documents: {
203
+ key: string;
204
+ source: "finance" | "legal";
205
+ documentType: "invoice" | "proforma" | "contract";
206
+ bookingId: string;
207
+ name: string;
208
+ createdAt: string;
209
+ contractId?: string | null | undefined;
210
+ invoiceId?: string | null | undefined;
211
+ attachmentId?: string | null | undefined;
212
+ renditionId?: string | null | undefined;
213
+ contractStatus?: string | null | undefined;
214
+ invoiceStatus?: string | null | undefined;
215
+ format?: string | null | undefined;
216
+ mimeType?: string | null | undefined;
217
+ storageKey?: string | null | undefined;
218
+ downloadUrl?: string | null | undefined;
219
+ language?: string | null | undefined;
220
+ metadata?: Record<string, unknown> | null | undefined;
221
+ }[];
222
+ recipient: string;
223
+ delivery: {
224
+ id: string;
225
+ templateId: string | null;
226
+ templateSlug: string | null;
227
+ targetType: "organization" | "other" | "booking" | "invoice" | "booking_payment_schedule" | "booking_guarantee" | "person" | "payment_session";
228
+ targetId: string | null;
229
+ personId: string | null;
230
+ organizationId: string | null;
231
+ bookingId: string | null;
232
+ invoiceId: string | null;
233
+ paymentSessionId: string | null;
234
+ channel: "email" | "sms";
235
+ provider: string;
236
+ providerMessageId: string | null;
237
+ status: "pending" | "cancelled" | "failed" | "sent";
238
+ toAddress: string;
239
+ fromAddress: string | null;
240
+ subject: string | null;
241
+ htmlBody: string | null;
242
+ textBody: string | null;
243
+ payloadData: Record<string, unknown> | null;
244
+ metadata: Record<string, unknown> | null;
245
+ errorMessage: string | null;
246
+ scheduledFor: Date | null;
247
+ sentAt: Date | null;
248
+ failedAt: Date | null;
249
+ createdAt: Date;
250
+ updatedAt: Date;
251
+ };
252
+ skipReason?: undefined;
253
+ }>;
126
254
  };
127
255
  export { createDefaultAttachmentFromDocument as createDefaultBookingDocumentAttachment };
128
256
  //# sourceMappingURL=service-booking-documents.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service-booking-documents.d.ts","sourceRoot":"","sources":["../src/service-booking-documents.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAI9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAGjE,OAAO,KAAK,EACV,yBAAyB,EACzB,mBAAmB,EACnB,qCAAqC,EACtC,MAAM,qBAAqB,CAAA;AAE5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAExD,MAAM,MAAM,iCAAiC,GAAG,CAC9C,QAAQ,EAAE,yBAAyB,KAChC,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAA;AAE3C,MAAM,WAAW,kCAAkC;IACjD,kBAAkB,CAAC,EAAE,iCAAiC,CAAA;IACtD,QAAQ,CAAC,EAAE,QAAQ,CAAA;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AA2BD,iBAAS,mCAAmC,CAC1C,QAAQ,EAAE,yBAAyB,GAClC,sBAAsB,GAAG,IAAI,CAU/B;AAwLD,eAAO,MAAM,mCAAmC;kCACV,kBAAkB,aAAa,MAAM;;;;;;;;;;;;;;;;;;;;;;;yCAkBnE,kBAAkB,cACV,mBAAmB,aACpB,MAAM,SACV,qCAAqC,YACnC,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4G9C,CAAA;AAED,OAAO,EAAE,mCAAmC,IAAI,sCAAsC,EAAE,CAAA"}
1
+ {"version":3,"file":"service-booking-documents.d.ts","sourceRoot":"","sources":["../src/service-booking-documents.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAI9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAGjE,OAAO,KAAK,EACV,yBAAyB,EACzB,mBAAmB,EACnB,qCAAqC,EACtC,MAAM,qBAAqB,CAAA;AAM5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAExD,MAAM,MAAM,iCAAiC,GAAG,CAC9C,QAAQ,EAAE,yBAAyB,KAChC,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAA;AAE3C,MAAM,WAAW,kCAAkC;IACjD,kBAAkB,CAAC,EAAE,iCAAiC,CAAA;IACtD,QAAQ,CAAC,EAAE,QAAQ,CAAA;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AA2BD,iBAAS,mCAAmC,CAC1C,QAAQ,EAAE,yBAAyB,GAClC,sBAAsB,GAAG,IAAI,CAU/B;AAwLD,eAAO,MAAM,mCAAmC;kCACV,kBAAkB,aAAa,MAAM;;;;;;;;;;;;;;;;;;;;;;;yCAkBnE,kBAAkB,cACV,mBAAmB,aACpB,MAAM,SACV,qCAAqC,YACnC,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoH7C;;;;;;;;;;;OAWG;kCAEG,kBAAkB,cACV,mBAAmB,aACpB,MAAM,SACV;QAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,qCAAqC,YACpE,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4C9C,CAAA;AAED,OAAO,EAAE,mCAAmC,IAAI,sCAAsC,EAAE,CAAA"}
@@ -3,7 +3,7 @@ import { invoiceRenditions, invoices } from "@voyantjs/finance/schema";
3
3
  import { contractAttachments, contracts } from "@voyantjs/legal/contracts";
4
4
  import { and, desc, eq, ne, or } from "drizzle-orm";
5
5
  import { sendNotification } from "./service-deliveries.js";
6
- import { listBookingNotificationParticipants, resolveReminderRecipient } from "./service-shared.js";
6
+ import { listBookingNotificationItems, listBookingNotificationParticipants, resolveReminderRecipient, } from "./service-shared.js";
7
7
  function getMetadataRecord(value) {
8
8
  if (!value || typeof value !== "object" || Array.isArray(value)) {
9
9
  return null;
@@ -191,8 +191,11 @@ export const bookingDocumentNotificationsService = {
191
191
  if (documents.length === 0) {
192
192
  return { status: "no_documents" };
193
193
  }
194
- const participants = await listBookingNotificationParticipants(db, bookingId);
195
- const recipient = resolveReminderRecipient(participants);
194
+ const [participants, items] = await Promise.all([
195
+ listBookingNotificationParticipants(db, bookingId),
196
+ listBookingNotificationItems(db, bookingId),
197
+ ]);
198
+ const recipient = resolveReminderRecipient(booking, participants);
196
199
  const to = input.to ?? recipient?.email ?? null;
197
200
  if (!to) {
198
201
  return { status: "no_recipient" };
@@ -225,14 +228,18 @@ export const bookingDocumentNotificationsService = {
225
228
  startDate: booking.startDate,
226
229
  endDate: booking.endDate,
227
230
  },
228
- participant: recipient
231
+ traveler: recipient
229
232
  ? {
230
233
  firstName: recipient.firstName,
231
234
  lastName: recipient.lastName,
232
235
  email: recipient.email,
236
+ participantType: recipient.participantType,
237
+ isPrimary: recipient.isPrimary,
233
238
  }
234
239
  : null,
240
+ travelers: participants,
235
241
  documents,
242
+ items,
236
243
  ...(input.data ?? {}),
237
244
  },
238
245
  targetType: "booking",
@@ -267,5 +274,50 @@ export const bookingDocumentNotificationsService = {
267
274
  delivery,
268
275
  };
269
276
  },
277
+ /**
278
+ * Confirm-and-dispatch — single orchestrated call for the operator flow
279
+ * that wants "list the booking's documents, then send them to the client"
280
+ * to be one action instead of two round-trips.
281
+ *
282
+ * With `sendNotification: false`, the caller gets the bundle back without
283
+ * attempting a send — useful for rendering a preview/checkbox list before
284
+ * the operator confirms. With `sendNotification: true`, the same send
285
+ * guards as `sendBookingDocumentsNotification` apply (no_documents,
286
+ * no_recipient, no_attachments, send_failed); the result keeps the bundle
287
+ * regardless so the UI always has something to show.
288
+ */
289
+ async confirmAndDispatchBooking(db, dispatcher, bookingId, input, runtime = {}) {
290
+ const bundle = await this.listBookingDocumentBundle(db, bookingId);
291
+ if (!bundle)
292
+ return { status: "not_found" };
293
+ const documents = bundle.documents;
294
+ const sendNotification = input.sendNotification ?? true;
295
+ if (!sendNotification) {
296
+ return {
297
+ status: "preview",
298
+ bookingId,
299
+ documents,
300
+ };
301
+ }
302
+ const result = await this.sendBookingDocumentsNotification(db, dispatcher, bookingId, input, runtime);
303
+ if (result.status === "not_found") {
304
+ return { status: "not_found" };
305
+ }
306
+ if (result.status !== "sent") {
307
+ return {
308
+ status: "skipped",
309
+ bookingId,
310
+ documents,
311
+ skipReason: result.status,
312
+ };
313
+ }
314
+ return {
315
+ status: "dispatched",
316
+ bookingId: result.bookingId,
317
+ documents: result.documents,
318
+ recipient: result.recipient,
319
+ delivery: result.delivery,
320
+ };
321
+ },
270
322
  };
271
323
  export { createDefaultAttachmentFromDocument as createDefaultBookingDocumentAttachment };
@@ -1 +1 @@
1
- {"version":3,"file":"service-deliveries.d.ts","sourceRoot":"","sources":["../src/service-deliveries.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAGjE,OAAO,KAAK,EACV,6BAA6B,EAC7B,mBAAmB,EACnB,4BAA4B,EAC5B,qBAAqB,EACrB,mCAAmC,EACpC,MAAM,qBAAqB,CAAA;AAyC5B,wBAAsB,cAAc,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgChG;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAOvE;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,mBAAmB,EAC/B,KAAK,EAAE,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA8H7B;AAED,wBAAsB,8BAA8B,CAClD,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,mBAAmB,EAC/B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA8F3C;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,mBAAmB,EAC/B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA4FpC"}
1
+ {"version":3,"file":"service-deliveries.d.ts","sourceRoot":"","sources":["../src/service-deliveries.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAGjE,OAAO,KAAK,EACV,6BAA6B,EAC7B,mBAAmB,EACnB,4BAA4B,EAC5B,qBAAqB,EACrB,mCAAmC,EACpC,MAAM,qBAAqB,CAAA;AA0C5B,wBAAsB,cAAc,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgChG;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAOvE;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,mBAAmB,EAC/B,KAAK,EAAE,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA8H7B;AAED,wBAAsB,8BAA8B,CAClD,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,mBAAmB,EAC/B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAuG3C;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,mBAAmB,EAC/B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAqGpC"}
@@ -2,7 +2,7 @@ import { bookings } from "@voyantjs/bookings/schema";
2
2
  import { invoices, paymentSessions } from "@voyantjs/finance";
3
3
  import { desc, eq, sql } from "drizzle-orm";
4
4
  import { notificationDeliveries } from "./schema.js";
5
- import { buildWhereClause, listBookingNotificationParticipants, NotificationError, paginate, renderNotificationTemplate, resolveReminderRecipient, summarizeNotificationAttachments, toTimestamp, } from "./service-shared.js";
5
+ import { buildWhereClause, listBookingNotificationItems, listBookingNotificationParticipants, NotificationError, paginate, renderNotificationTemplate, resolveReminderRecipient, summarizeNotificationAttachments, toTimestamp, } from "./service-shared.js";
6
6
  import { getTemplateById, getTemplateBySlug } from "./service-templates.js";
7
7
  function normalizeAttachments(attachments) {
8
8
  if (!attachments || attachments.length === 0) {
@@ -194,8 +194,13 @@ export async function sendPaymentSessionNotification(db, dispatcher, sessionId,
194
194
  ? ((await db.select().from(invoices).where(eq(invoices.id, session.invoiceId)).limit(1))[0] ??
195
195
  null)
196
196
  : null;
197
- const participants = booking ? await listBookingNotificationParticipants(db, booking.id) : [];
198
- const recipient = resolveReminderRecipient(participants);
197
+ const [participants, items] = booking
198
+ ? await Promise.all([
199
+ listBookingNotificationParticipants(db, booking.id),
200
+ listBookingNotificationItems(db, booking.id),
201
+ ])
202
+ : [[], []];
203
+ const recipient = resolveReminderRecipient(booking ?? null, participants);
199
204
  const to = input.to ?? session.payerEmail ?? recipient?.email ?? null;
200
205
  if (!to) {
201
206
  throw new NotificationError("No recipient available for payment session notification");
@@ -247,13 +252,17 @@ export async function sendPaymentSessionNotification(db, dispatcher, sessionId,
247
252
  dueDate: invoice.dueDate,
248
253
  }
249
254
  : null,
250
- participant: recipient
255
+ traveler: recipient
251
256
  ? {
252
257
  firstName: recipient.firstName,
253
258
  lastName: recipient.lastName,
254
259
  email: recipient.email,
260
+ participantType: recipient.participantType,
261
+ isPrimary: recipient.isPrimary,
255
262
  }
256
263
  : null,
264
+ travelers: participants,
265
+ items,
257
266
  ...(input.data ?? {}),
258
267
  },
259
268
  targetType: "payment_session",
@@ -277,8 +286,13 @@ export async function sendInvoiceNotification(db, dispatcher, invoiceId, input)
277
286
  .from(bookings)
278
287
  .where(eq(bookings.id, invoice.bookingId))
279
288
  .limit(1);
280
- const participants = booking ? await listBookingNotificationParticipants(db, booking.id) : [];
281
- const recipient = resolveReminderRecipient(participants);
289
+ const [participants, items] = booking
290
+ ? await Promise.all([
291
+ listBookingNotificationParticipants(db, booking.id),
292
+ listBookingNotificationItems(db, booking.id),
293
+ ])
294
+ : [[], []];
295
+ const recipient = resolveReminderRecipient(booking ?? null, participants);
282
296
  const [latestSession] = await db
283
297
  .select()
284
298
  .from(paymentSessions)
@@ -335,13 +349,17 @@ export async function sendInvoiceNotification(db, dispatcher, invoiceId, input)
335
349
  currency: latestSession.currency,
336
350
  }
337
351
  : null,
338
- participant: recipient
352
+ traveler: recipient
339
353
  ? {
340
354
  firstName: recipient.firstName,
341
355
  lastName: recipient.lastName,
342
356
  email: recipient.email,
357
+ participantType: recipient.participantType,
358
+ isPrimary: recipient.isPrimary,
343
359
  }
344
360
  : null,
361
+ travelers: participants,
362
+ items,
345
363
  ...(input.data ?? {}),
346
364
  },
347
365
  targetType: "invoice",
@@ -17,7 +17,7 @@ export declare function deliverReminderRun(db: PostgresJsDatabase, dispatcher: N
17
17
  organizationId: string | null;
18
18
  paymentSessionId: string | null;
19
19
  notificationDeliveryId: string | null;
20
- status: "failed" | "sent" | "processing" | "queued" | "skipped";
20
+ status: "failed" | "sent" | "processing" | "skipped" | "queued";
21
21
  recipient: string | null;
22
22
  scheduledFor: Date;
23
23
  processedAt: Date;
@@ -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;AAU5B,KAAK,wBAAwB,GAAG,CAAC,KAAK,EAAE;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAgxBnF,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;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,9 +1,9 @@
1
- import { bookingParticipants, bookings } from "@voyantjs/bookings/schema";
1
+ import { bookings, bookingTravelers } from "@voyantjs/bookings/schema";
2
2
  import { bookingPaymentSchedules, invoices } from "@voyantjs/finance";
3
3
  import { and, desc, eq, gt, or } from "drizzle-orm";
4
4
  import { notificationReminderRules, notificationReminderRuns } from "./schema.js";
5
5
  import { sendInvoiceNotification, sendNotification } from "./service-deliveries.js";
6
- import { addUtcDays, buildReminderDedupeKey, resolveReminderRecipient, startOfUtcDay, toDateString, toTimestamp, } from "./service-shared.js";
6
+ import { addUtcDays, buildReminderDedupeKey, listBookingNotificationItems, resolveReminderRecipient, startOfUtcDay, toDateString, toTimestamp, } from "./service-shared.js";
7
7
  function buildReminderSweepSummary() {
8
8
  return {
9
9
  processed: 0,
@@ -156,21 +156,23 @@ async function queueBookingPaymentScheduleReminder(db, enqueueDelivery, rule, sc
156
156
  if (!booking) {
157
157
  return markReminderRunSkipped(db, reminderRun.id, now, "Booking not found for payment schedule");
158
158
  }
159
- const participants = await db
160
- .select({
161
- id: bookingParticipants.id,
162
- firstName: bookingParticipants.firstName,
163
- lastName: bookingParticipants.lastName,
164
- email: bookingParticipants.email,
165
- participantType: bookingParticipants.participantType,
166
- isPrimary: bookingParticipants.isPrimary,
167
- })
168
- .from(bookingParticipants)
169
- .where(eq(bookingParticipants.bookingId, booking.id))
170
- .orderBy(desc(bookingParticipants.isPrimary), bookingParticipants.createdAt);
171
- const recipient = resolveReminderRecipient(participants);
159
+ const [participants] = await Promise.all([
160
+ db
161
+ .select({
162
+ id: bookingTravelers.id,
163
+ firstName: bookingTravelers.firstName,
164
+ lastName: bookingTravelers.lastName,
165
+ email: bookingTravelers.email,
166
+ participantType: bookingTravelers.participantType,
167
+ isPrimary: bookingTravelers.isPrimary,
168
+ })
169
+ .from(bookingTravelers)
170
+ .where(eq(bookingTravelers.bookingId, booking.id))
171
+ .orderBy(desc(bookingTravelers.isPrimary), bookingTravelers.createdAt),
172
+ ]);
173
+ const recipient = resolveReminderRecipient(booking, participants);
172
174
  if (!recipient?.email) {
173
- return markReminderRunSkipped(db, reminderRun.id, now, "No participant email available for booking payment reminder");
175
+ return markReminderRunSkipped(db, reminderRun.id, now, "No traveler email available for booking payment reminder");
174
176
  }
175
177
  return enqueueReminderRun(db, enqueueDelivery, { ...reminderRun, recipient: recipient.email }, now);
176
178
  }
@@ -225,21 +227,23 @@ async function queueInvoiceReminder(db, enqueueDelivery, rule, invoice, now) {
225
227
  if (!booking) {
226
228
  return markReminderRunSkipped(db, reminderRun.id, now, "Booking not found for invoice reminder");
227
229
  }
228
- const participants = await db
229
- .select({
230
- id: bookingParticipants.id,
231
- firstName: bookingParticipants.firstName,
232
- lastName: bookingParticipants.lastName,
233
- email: bookingParticipants.email,
234
- participantType: bookingParticipants.participantType,
235
- isPrimary: bookingParticipants.isPrimary,
236
- })
237
- .from(bookingParticipants)
238
- .where(eq(bookingParticipants.bookingId, booking.id))
239
- .orderBy(desc(bookingParticipants.isPrimary), bookingParticipants.createdAt);
240
- const recipient = resolveReminderRecipient(participants);
230
+ const [participants] = await Promise.all([
231
+ db
232
+ .select({
233
+ id: bookingTravelers.id,
234
+ firstName: bookingTravelers.firstName,
235
+ lastName: bookingTravelers.lastName,
236
+ email: bookingTravelers.email,
237
+ participantType: bookingTravelers.participantType,
238
+ isPrimary: bookingTravelers.isPrimary,
239
+ })
240
+ .from(bookingTravelers)
241
+ .where(eq(bookingTravelers.bookingId, booking.id))
242
+ .orderBy(desc(bookingTravelers.isPrimary), bookingTravelers.createdAt),
243
+ ]);
244
+ const recipient = resolveReminderRecipient(booking, participants);
241
245
  if (!recipient?.email) {
242
- return markReminderRunSkipped(db, reminderRun.id, now, "No participant email available for invoice reminder");
246
+ return markReminderRunSkipped(db, reminderRun.id, now, "No traveler email available for invoice reminder");
243
247
  }
244
248
  return enqueueReminderRun(db, enqueueDelivery, { ...reminderRun, recipient: recipient.email }, now);
245
249
  }
@@ -285,19 +289,22 @@ async function sendBookingPaymentScheduleReminder(db, dispatcher, rule, schedule
285
289
  .returning();
286
290
  return run ?? null;
287
291
  }
288
- const participants = await db
289
- .select({
290
- id: bookingParticipants.id,
291
- firstName: bookingParticipants.firstName,
292
- lastName: bookingParticipants.lastName,
293
- email: bookingParticipants.email,
294
- participantType: bookingParticipants.participantType,
295
- isPrimary: bookingParticipants.isPrimary,
296
- })
297
- .from(bookingParticipants)
298
- .where(eq(bookingParticipants.bookingId, booking.id))
299
- .orderBy(desc(bookingParticipants.isPrimary), bookingParticipants.createdAt);
300
- const recipient = resolveReminderRecipient(participants);
292
+ const [participants, items] = await Promise.all([
293
+ db
294
+ .select({
295
+ id: bookingTravelers.id,
296
+ firstName: bookingTravelers.firstName,
297
+ lastName: bookingTravelers.lastName,
298
+ email: bookingTravelers.email,
299
+ participantType: bookingTravelers.participantType,
300
+ isPrimary: bookingTravelers.isPrimary,
301
+ })
302
+ .from(bookingTravelers)
303
+ .where(eq(bookingTravelers.bookingId, booking.id))
304
+ .orderBy(desc(bookingTravelers.isPrimary), bookingTravelers.createdAt),
305
+ listBookingNotificationItems(db, booking.id),
306
+ ]);
307
+ const recipient = resolveReminderRecipient(booking, participants);
301
308
  const [processingRun] = await db
302
309
  .insert(notificationReminderRuns)
303
310
  .values({
@@ -327,7 +334,7 @@ async function sendBookingPaymentScheduleReminder(db, dispatcher, rule, schedule
327
334
  return null;
328
335
  }
329
336
  if (!recipient?.email) {
330
- return markReminderRunSkipped(db, processingRun.id, now, "No participant email available for booking payment reminder");
337
+ return markReminderRunSkipped(db, processingRun.id, now, "No traveler email available for booking payment reminder");
331
338
  }
332
339
  try {
333
340
  const delivery = await sendNotification(db, dispatcher, {
@@ -344,11 +351,14 @@ async function sendBookingPaymentScheduleReminder(db, dispatcher, rule, schedule
344
351
  currency: schedule.currency,
345
352
  scheduleType: schedule.scheduleType,
346
353
  reminderOffsetDays: rule.relativeDaysFromDueDate,
347
- participant: {
354
+ traveler: {
348
355
  firstName: recipient.firstName,
349
356
  lastName: recipient.lastName,
350
357
  email: recipient.email,
358
+ participantType: recipient.participantType,
359
+ isPrimary: recipient.isPrimary,
351
360
  },
361
+ travelers: participants,
352
362
  booking: {
353
363
  id: booking.id,
354
364
  bookingNumber: booking.bookingNumber,
@@ -365,6 +375,7 @@ async function sendBookingPaymentScheduleReminder(db, dispatcher, rule, schedule
365
375
  scheduleType: schedule.scheduleType,
366
376
  status: schedule.status,
367
377
  },
378
+ items,
368
379
  },
369
380
  targetType: "booking_payment_schedule",
370
381
  targetId: schedule.id,
@@ -428,19 +439,21 @@ async function sendInvoiceReminder(db, dispatcher, rule, invoice, now) {
428
439
  .returning();
429
440
  return run ?? null;
430
441
  }
431
- const participants = await db
432
- .select({
433
- id: bookingParticipants.id,
434
- firstName: bookingParticipants.firstName,
435
- lastName: bookingParticipants.lastName,
436
- email: bookingParticipants.email,
437
- participantType: bookingParticipants.participantType,
438
- isPrimary: bookingParticipants.isPrimary,
439
- })
440
- .from(bookingParticipants)
441
- .where(eq(bookingParticipants.bookingId, booking.id))
442
- .orderBy(desc(bookingParticipants.isPrimary), bookingParticipants.createdAt);
443
- const recipient = resolveReminderRecipient(participants);
442
+ const [participants] = await Promise.all([
443
+ db
444
+ .select({
445
+ id: bookingTravelers.id,
446
+ firstName: bookingTravelers.firstName,
447
+ lastName: bookingTravelers.lastName,
448
+ email: bookingTravelers.email,
449
+ participantType: bookingTravelers.participantType,
450
+ isPrimary: bookingTravelers.isPrimary,
451
+ })
452
+ .from(bookingTravelers)
453
+ .where(eq(bookingTravelers.bookingId, booking.id))
454
+ .orderBy(desc(bookingTravelers.isPrimary), bookingTravelers.createdAt),
455
+ ]);
456
+ const recipient = resolveReminderRecipient(booking, participants);
444
457
  const [processingRun] = await db
445
458
  .insert(notificationReminderRuns)
446
459
  .values({
@@ -472,7 +485,7 @@ async function sendInvoiceReminder(db, dispatcher, rule, invoice, now) {
472
485
  return null;
473
486
  }
474
487
  if (!recipient?.email) {
475
- return markReminderRunSkipped(db, processingRun.id, now, "No participant email available for invoice reminder");
488
+ return markReminderRunSkipped(db, processingRun.id, now, "No traveler email available for invoice reminder");
476
489
  }
477
490
  try {
478
491
  const delivery = await sendInvoiceNotification(db, dispatcher, invoice.id, {
@@ -515,23 +528,26 @@ async function sendQueuedBookingPaymentScheduleReminder(db, dispatcher, run, rul
515
528
  if (!booking) {
516
529
  return markReminderRunSkipped(db, run.id, now, "Booking not found for payment schedule");
517
530
  }
518
- const participants = await db
519
- .select({
520
- id: bookingParticipants.id,
521
- firstName: bookingParticipants.firstName,
522
- lastName: bookingParticipants.lastName,
523
- email: bookingParticipants.email,
524
- participantType: bookingParticipants.participantType,
525
- isPrimary: bookingParticipants.isPrimary,
526
- })
527
- .from(bookingParticipants)
528
- .where(eq(bookingParticipants.bookingId, booking.id))
529
- .orderBy(desc(bookingParticipants.isPrimary), bookingParticipants.createdAt);
530
- const fallbackRecipient = resolveReminderRecipient(participants);
531
- const participant = participants.find((entry) => entry.email === run.recipient) ?? fallbackRecipient ?? null;
532
- const recipientEmail = run.recipient ?? participant?.email ?? null;
531
+ const [participants, items] = await Promise.all([
532
+ db
533
+ .select({
534
+ id: bookingTravelers.id,
535
+ firstName: bookingTravelers.firstName,
536
+ lastName: bookingTravelers.lastName,
537
+ email: bookingTravelers.email,
538
+ participantType: bookingTravelers.participantType,
539
+ isPrimary: bookingTravelers.isPrimary,
540
+ })
541
+ .from(bookingTravelers)
542
+ .where(eq(bookingTravelers.bookingId, booking.id))
543
+ .orderBy(desc(bookingTravelers.isPrimary), bookingTravelers.createdAt),
544
+ listBookingNotificationItems(db, booking.id),
545
+ ]);
546
+ const fallbackRecipient = resolveReminderRecipient(booking, participants);
547
+ const traveler = participants.find((entry) => entry.email === run.recipient) ?? fallbackRecipient ?? null;
548
+ const recipientEmail = run.recipient ?? traveler?.email ?? null;
533
549
  if (!recipientEmail) {
534
- return markReminderRunSkipped(db, run.id, now, "No participant email available for booking payment reminder");
550
+ return markReminderRunSkipped(db, run.id, now, "No traveler email available for booking payment reminder");
535
551
  }
536
552
  try {
537
553
  const delivery = await sendNotification(db, dispatcher, {
@@ -548,13 +564,16 @@ async function sendQueuedBookingPaymentScheduleReminder(db, dispatcher, run, rul
548
564
  currency: schedule.currency,
549
565
  scheduleType: schedule.scheduleType,
550
566
  reminderOffsetDays: rule.relativeDaysFromDueDate,
551
- participant: participant
567
+ traveler: traveler
552
568
  ? {
553
- firstName: participant.firstName,
554
- lastName: participant.lastName,
569
+ firstName: traveler.firstName,
570
+ lastName: traveler.lastName,
555
571
  email: recipientEmail,
572
+ participantType: traveler.participantType,
573
+ isPrimary: traveler.isPrimary,
556
574
  }
557
575
  : null,
576
+ travelers: participants,
558
577
  booking: {
559
578
  id: booking.id,
560
579
  bookingNumber: booking.bookingNumber,
@@ -571,6 +590,7 @@ async function sendQueuedBookingPaymentScheduleReminder(db, dispatcher, run, rul
571
590
  scheduleType: schedule.scheduleType,
572
591
  status: schedule.status,
573
592
  },
593
+ items,
574
594
  },
575
595
  targetType: "booking_payment_schedule",
576
596
  targetId: schedule.id,