@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.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +74 -0
- package/dist/providers/voyant-cloud-email.d.ts.map +1 -1
- package/dist/providers/voyant-cloud-email.js +46 -3
- package/dist/routes.d.ts +86 -17
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +14 -0
- package/dist/schema.d.ts +6 -6
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +3 -0
- package/dist/service-booking-documents.d.ts +2 -2
- package/dist/service-deliveries.d.ts +34 -5
- package/dist/service-deliveries.d.ts.map +1 -1
- package/dist/service-deliveries.js +119 -0
- package/dist/service-reminders.d.ts +34 -2
- package/dist/service-reminders.d.ts.map +1 -1
- package/dist/service-reminders.js +283 -23
- package/dist/service-shared.d.ts +1 -13
- package/dist/service-shared.d.ts.map +1 -1
- package/dist/service-shared.js +45 -5
- package/dist/service-templates.d.ts +10 -10
- package/dist/service.d.ts +4 -3
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +2 -1
- package/dist/validation.d.ts +37 -7
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +7 -1
- package/package.json +8 -8
|
@@ -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
|
|
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;
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
package/dist/service-shared.d.ts
CHANGED
|
@@ -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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"}
|