@voyantjs/notifications 0.28.1 → 0.29.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 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/routes.d.ts +428 -8
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +63 -1
- package/dist/schema.d.ts +729 -16
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +114 -1
- package/dist/service-reminders.d.ts +4 -2
- package/dist/service-reminders.d.ts.map +1 -1
- package/dist/service-reminders.js +392 -545
- package/dist/service-sequence.d.ts +113 -0
- package/dist/service-sequence.d.ts.map +1 -0
- package/dist/service-sequence.js +432 -0
- package/dist/service-stages.d.ts +23 -0
- package/dist/service-stages.d.ts.map +1 -0
- package/dist/service-stages.js +203 -0
- package/dist/service-templates.d.ts +10 -8
- package/dist/service-templates.d.ts.map +1 -1
- package/dist/service-templates.js +0 -3
- package/dist/service.d.ts +15 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +15 -0
- package/dist/tasks/deliver-reminder.d.ts +1 -1
- package/dist/validation.d.ts +143 -13
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +110 -7
- package/package.json +7 -7
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import { type NotificationReminderRule, type NotificationReminderRuleStage, type NotificationReminderStageCadenceInterval, type NotificationReminderStageChannel, type NotificationSettings } from "./schema.js";
|
|
3
|
+
export type ReminderTargetSnapshot = {
|
|
4
|
+
id: string;
|
|
5
|
+
bookingId: string | null;
|
|
6
|
+
dueDate: string | null;
|
|
7
|
+
issuedAt: string | null;
|
|
8
|
+
departureDate: string | null;
|
|
9
|
+
bookingCreatedAt: string | null;
|
|
10
|
+
status: string;
|
|
11
|
+
isTerminal: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type SequenceHistoryEntry = {
|
|
14
|
+
scheduledFor: Date;
|
|
15
|
+
sentAt: Date | null;
|
|
16
|
+
status: string;
|
|
17
|
+
};
|
|
18
|
+
export type StageDecision = {
|
|
19
|
+
fire: false;
|
|
20
|
+
reason: string;
|
|
21
|
+
stage?: NotificationReminderRuleStage;
|
|
22
|
+
} | {
|
|
23
|
+
fire: true;
|
|
24
|
+
stage: NotificationReminderRuleStage;
|
|
25
|
+
anchorDate: Date;
|
|
26
|
+
sendCountAtFire: number;
|
|
27
|
+
};
|
|
28
|
+
export declare const DEFAULT_NOTIFICATION_SETTINGS: NotificationSettings;
|
|
29
|
+
export declare function getNotificationSettings(db: PostgresJsDatabase, scope?: string): Promise<NotificationSettings>;
|
|
30
|
+
export declare function listStagesForRule(db: PostgresJsDatabase, reminderRuleId: string): Promise<NotificationReminderRuleStage[]>;
|
|
31
|
+
export declare function listChannelsForStage(db: PostgresJsDatabase, stageId: string): Promise<NotificationReminderStageChannel[]>;
|
|
32
|
+
export declare function pickActiveStage(stages: NotificationReminderRuleStage[], sendsSoFar: number): NotificationReminderRuleStage | null;
|
|
33
|
+
export declare function inWindow(today: Date, anchorDate: Date, stage: NotificationReminderRuleStage): boolean;
|
|
34
|
+
export declare function pickEscalatingInterval(intervals: NotificationReminderStageCadenceInterval[] | null | undefined, daysUntilDue: number | null): NotificationReminderStageCadenceInterval | null;
|
|
35
|
+
export declare function cadenceElapsed(today: Date, lastSentAt: Date | null, stage: NotificationReminderRuleStage, daysUntilDue: number | null): boolean;
|
|
36
|
+
export declare function resolveAnchor(anchor: NotificationReminderRuleStage["anchor"], target: ReminderTargetSnapshot, history: SequenceHistoryEntry[]): Date | null;
|
|
37
|
+
export declare function evaluateStage(_rule: NotificationReminderRule, stages: NotificationReminderRuleStage[], target: ReminderTargetSnapshot, history: SequenceHistoryEntry[], today: Date): StageDecision;
|
|
38
|
+
export declare function applyQuietHours(now: Date, stage: NotificationReminderRuleStage, settings: NotificationSettings, recipientTimezone?: string | null): {
|
|
39
|
+
scheduledAt: Date;
|
|
40
|
+
deferred: boolean;
|
|
41
|
+
};
|
|
42
|
+
export declare function exceedsRecipientRateLimit(db: PostgresJsDatabase, recipient: string, channel: string, settings: NotificationSettings, now: Date): Promise<boolean>;
|
|
43
|
+
export declare function suppressedByGroup(db: PostgresJsDatabase, recipient: string | null, suppressionGroup: string | null, settings: NotificationSettings, now: Date): Promise<boolean>;
|
|
44
|
+
export declare function loadHistory(db: PostgresJsDatabase, reminderRuleId: string, targetId: string): Promise<SequenceHistoryEntry[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Computes the date range a target's `due_date` (or `issue_date`) needs to
|
|
47
|
+
* fall in for any of the rule's stages to be inside their eligibility window
|
|
48
|
+
* today.
|
|
49
|
+
*
|
|
50
|
+
* From `inWindow`: today must satisfy
|
|
51
|
+
* anchor + windowStartDays ≤ today ≤ anchor + windowEndDays
|
|
52
|
+
* Solving for anchor:
|
|
53
|
+
* today − windowEndDays ≤ anchor ≤ today − windowStartDays
|
|
54
|
+
*
|
|
55
|
+
* Across all stages with anchor=`due_date`, we union the [start, end] ranges
|
|
56
|
+
* and use the resulting envelope as a SQL `BETWEEN` filter. Returns null when
|
|
57
|
+
* no stage is anchored on the requested column (e.g. all stages anchor on
|
|
58
|
+
* `departure_date`) — caller should skip the pushdown in that case.
|
|
59
|
+
*/
|
|
60
|
+
export declare function computeAnchorDateEnvelope(stages: NotificationReminderRuleStage[], today: Date, anchor: NotificationReminderRuleStage["anchor"]): {
|
|
61
|
+
from: string;
|
|
62
|
+
to: string;
|
|
63
|
+
} | null;
|
|
64
|
+
export type DateEnvelopes = {
|
|
65
|
+
/** When set, only fetch payment schedules whose `due_date` falls in this range. */
|
|
66
|
+
paymentScheduleDueDate?: {
|
|
67
|
+
from: string;
|
|
68
|
+
to: string;
|
|
69
|
+
};
|
|
70
|
+
/** When set, only fetch invoices whose `due_date` falls in this range. */
|
|
71
|
+
invoiceDueDate?: {
|
|
72
|
+
from: string;
|
|
73
|
+
to: string;
|
|
74
|
+
};
|
|
75
|
+
/** When set, only fetch invoices whose `issue_date` falls in this range. */
|
|
76
|
+
invoiceIssueDate?: {
|
|
77
|
+
from: string;
|
|
78
|
+
to: string;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
export declare function fetchOpenPaymentScheduleTargets(db: PostgresJsDatabase, envelopes?: DateEnvelopes): Promise<ReminderTargetSnapshot[]>;
|
|
82
|
+
export declare function fetchOpenInvoiceTargets(db: PostgresJsDatabase, envelopes?: DateEnvelopes): Promise<ReminderTargetSnapshot[]>;
|
|
83
|
+
/**
|
|
84
|
+
* Per-rule target fetch that pushes a date envelope into the WHERE when all
|
|
85
|
+
* relevant stages share an anchor we can SQL-filter on (`due_date` for both
|
|
86
|
+
* target types, `invoice_issued_at` for invoices). Other anchors fall through
|
|
87
|
+
* to the unfiltered fetch — they're expected to be rare and the in-app
|
|
88
|
+
* window check still rejects misses.
|
|
89
|
+
*/
|
|
90
|
+
export declare function fetchTargetsForRule(db: PostgresJsDatabase, rule: NotificationReminderRule, stages?: NotificationReminderRuleStage[], today?: Date): Promise<ReminderTargetSnapshot[]>;
|
|
91
|
+
export declare function listActiveRulesByPriority(db: PostgresJsDatabase): Promise<NotificationReminderRule[]>;
|
|
92
|
+
export type PreviewRow = {
|
|
93
|
+
ruleId: string;
|
|
94
|
+
ruleName: string;
|
|
95
|
+
ruleSlug: string;
|
|
96
|
+
targetType: string;
|
|
97
|
+
targetId: string;
|
|
98
|
+
bookingId: string | null;
|
|
99
|
+
stageId: string;
|
|
100
|
+
stageName: string | null;
|
|
101
|
+
stageOrderIndex: number;
|
|
102
|
+
anchor: string;
|
|
103
|
+
anchorDate: string;
|
|
104
|
+
scheduledAt: string;
|
|
105
|
+
sendCountAtFire: number;
|
|
106
|
+
reasoning: string;
|
|
107
|
+
};
|
|
108
|
+
export declare function previewReminders(db: PostgresJsDatabase, options?: {
|
|
109
|
+
now?: Date;
|
|
110
|
+
ruleId?: string;
|
|
111
|
+
targetId?: string;
|
|
112
|
+
}): Promise<PreviewRow[]>;
|
|
113
|
+
//# sourceMappingURL=service-sequence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-sequence.d.ts","sourceRoot":"","sources":["../src/service-sequence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { bookings } from "@voyantjs/bookings/schema";
|
|
2
|
+
import { bookingPaymentSchedules, invoices } from "@voyantjs/finance";
|
|
3
|
+
import { and, asc, eq, gt, gte, lte, or, sql } from "drizzle-orm";
|
|
4
|
+
import { notificationReminderRuleStages, notificationReminderRules, notificationReminderRuns, notificationReminderStageChannels, notificationSettings, } from "./schema.js";
|
|
5
|
+
import { addUtcDays, startOfUtcDay } from "./service-shared.js";
|
|
6
|
+
export const DEFAULT_NOTIFICATION_SETTINGS = {
|
|
7
|
+
id: "nset_default",
|
|
8
|
+
scope: "default",
|
|
9
|
+
quietHoursLocal: null,
|
|
10
|
+
blackoutDates: null,
|
|
11
|
+
skipWeekends: false,
|
|
12
|
+
recipientRateLimitPerDay: null,
|
|
13
|
+
suppressionWindowHours: 24,
|
|
14
|
+
metadata: null,
|
|
15
|
+
createdAt: new Date(0),
|
|
16
|
+
updatedAt: new Date(0),
|
|
17
|
+
};
|
|
18
|
+
export async function getNotificationSettings(db, scope = "default") {
|
|
19
|
+
const [row] = await db
|
|
20
|
+
.select()
|
|
21
|
+
.from(notificationSettings)
|
|
22
|
+
.where(eq(notificationSettings.scope, scope))
|
|
23
|
+
.limit(1);
|
|
24
|
+
return row ?? { ...DEFAULT_NOTIFICATION_SETTINGS, scope };
|
|
25
|
+
}
|
|
26
|
+
export async function listStagesForRule(db, reminderRuleId) {
|
|
27
|
+
return db
|
|
28
|
+
.select()
|
|
29
|
+
.from(notificationReminderRuleStages)
|
|
30
|
+
.where(eq(notificationReminderRuleStages.reminderRuleId, reminderRuleId))
|
|
31
|
+
.orderBy(asc(notificationReminderRuleStages.orderIndex));
|
|
32
|
+
}
|
|
33
|
+
export async function listChannelsForStage(db, stageId) {
|
|
34
|
+
return db
|
|
35
|
+
.select()
|
|
36
|
+
.from(notificationReminderStageChannels)
|
|
37
|
+
.where(eq(notificationReminderStageChannels.stageId, stageId))
|
|
38
|
+
.orderBy(asc(notificationReminderStageChannels.orderIndex), asc(notificationReminderStageChannels.createdAt));
|
|
39
|
+
}
|
|
40
|
+
export function pickActiveStage(stages, sendsSoFar) {
|
|
41
|
+
if (stages.length === 0)
|
|
42
|
+
return null;
|
|
43
|
+
let cumulative = 0;
|
|
44
|
+
for (const stage of stages) {
|
|
45
|
+
const cap = stage.maxSendsInStage ?? Number.POSITIVE_INFINITY;
|
|
46
|
+
if (sendsSoFar < cumulative + cap) {
|
|
47
|
+
return stage;
|
|
48
|
+
}
|
|
49
|
+
cumulative += cap;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
export function inWindow(today, anchorDate, stage) {
|
|
54
|
+
const start = addUtcDays(startOfUtcDay(anchorDate), stage.windowStartDays);
|
|
55
|
+
const end = addUtcDays(startOfUtcDay(anchorDate), stage.windowEndDays);
|
|
56
|
+
const todayStart = startOfUtcDay(today);
|
|
57
|
+
return todayStart >= start && todayStart <= end;
|
|
58
|
+
}
|
|
59
|
+
function daysBetweenUtc(a, b) {
|
|
60
|
+
const ms = startOfUtcDay(a).getTime() - startOfUtcDay(b).getTime();
|
|
61
|
+
return Math.round(ms / (24 * 60 * 60 * 1000));
|
|
62
|
+
}
|
|
63
|
+
export function pickEscalatingInterval(intervals, daysUntilDue) {
|
|
64
|
+
if (!intervals || intervals.length === 0)
|
|
65
|
+
return null;
|
|
66
|
+
for (const interval of intervals) {
|
|
67
|
+
const gtOk = interval.whenDaysUntilDueGT == null || daysUntilDue == null
|
|
68
|
+
? interval.whenDaysUntilDueGT == null
|
|
69
|
+
: daysUntilDue > interval.whenDaysUntilDueGT;
|
|
70
|
+
const ltOk = interval.whenDaysUntilDueLT == null || daysUntilDue == null
|
|
71
|
+
? interval.whenDaysUntilDueLT == null
|
|
72
|
+
: daysUntilDue < interval.whenDaysUntilDueLT;
|
|
73
|
+
if (interval.whenDaysUntilDueGT == null && interval.whenDaysUntilDueLT == null) {
|
|
74
|
+
return interval;
|
|
75
|
+
}
|
|
76
|
+
if (gtOk && ltOk)
|
|
77
|
+
return interval;
|
|
78
|
+
}
|
|
79
|
+
// Fall back to the last interval with no constraints (the "default bucket").
|
|
80
|
+
const fallback = intervals.find((i) => i.whenDaysUntilDueGT == null && i.whenDaysUntilDueLT == null);
|
|
81
|
+
return fallback ?? null;
|
|
82
|
+
}
|
|
83
|
+
export function cadenceElapsed(today, lastSentAt, stage, daysUntilDue) {
|
|
84
|
+
if (stage.cadenceKind === "once") {
|
|
85
|
+
return lastSentAt === null;
|
|
86
|
+
}
|
|
87
|
+
if (stage.cadenceKind === "every_n_days") {
|
|
88
|
+
if (lastSentAt === null)
|
|
89
|
+
return true;
|
|
90
|
+
const every = stage.cadenceEveryDays ?? 1;
|
|
91
|
+
return daysBetweenUtc(today, lastSentAt) >= every;
|
|
92
|
+
}
|
|
93
|
+
if (stage.cadenceKind === "escalating") {
|
|
94
|
+
if (lastSentAt === null)
|
|
95
|
+
return true;
|
|
96
|
+
const interval = pickEscalatingInterval(stage.cadenceIntervals, daysUntilDue);
|
|
97
|
+
if (!interval)
|
|
98
|
+
return false;
|
|
99
|
+
return daysBetweenUtc(today, lastSentAt) >= interval.repeatEveryDays;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
export function resolveAnchor(anchor, target, history) {
|
|
104
|
+
switch (anchor) {
|
|
105
|
+
case "due_date":
|
|
106
|
+
return target.dueDate ? new Date(`${target.dueDate}T00:00:00Z`) : null;
|
|
107
|
+
case "booking_created_at":
|
|
108
|
+
return target.bookingCreatedAt ? new Date(target.bookingCreatedAt) : null;
|
|
109
|
+
case "departure_date":
|
|
110
|
+
return target.departureDate ? new Date(`${target.departureDate}T00:00:00Z`) : null;
|
|
111
|
+
case "invoice_issued_at":
|
|
112
|
+
return target.issuedAt ? new Date(`${target.issuedAt}T00:00:00Z`) : null;
|
|
113
|
+
case "last_send_at": {
|
|
114
|
+
const sent = history.filter((h) => h.status === "sent" && h.sentAt);
|
|
115
|
+
if (sent.length === 0)
|
|
116
|
+
return null;
|
|
117
|
+
const last = sent.reduce((acc, e) => (e.sentAt && e.sentAt > acc ? e.sentAt : acc), sent[0].sentAt);
|
|
118
|
+
return last;
|
|
119
|
+
}
|
|
120
|
+
default:
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export function evaluateStage(_rule, stages, target, history, today) {
|
|
125
|
+
if (target.isTerminal) {
|
|
126
|
+
return { fire: false, reason: "target_terminal_state" };
|
|
127
|
+
}
|
|
128
|
+
const sentHistory = history.filter((h) => h.status === "sent");
|
|
129
|
+
const stage = pickActiveStage(stages, sentHistory.length);
|
|
130
|
+
if (!stage) {
|
|
131
|
+
return { fire: false, reason: "no_active_stage" };
|
|
132
|
+
}
|
|
133
|
+
const anchorDate = resolveAnchor(stage.anchor, target, history);
|
|
134
|
+
if (!anchorDate) {
|
|
135
|
+
return { fire: false, reason: "anchor_unresolved", stage };
|
|
136
|
+
}
|
|
137
|
+
if (!inWindow(today, anchorDate, stage)) {
|
|
138
|
+
return { fire: false, reason: "outside_window", stage };
|
|
139
|
+
}
|
|
140
|
+
const lastSent = sentHistory.reduce((acc, e) => {
|
|
141
|
+
const stamp = e.sentAt ?? e.scheduledFor;
|
|
142
|
+
return acc && acc > stamp ? acc : stamp;
|
|
143
|
+
}, null);
|
|
144
|
+
let daysUntilDue = null;
|
|
145
|
+
if (target.dueDate) {
|
|
146
|
+
daysUntilDue = daysBetweenUtc(new Date(`${target.dueDate}T00:00:00Z`), today);
|
|
147
|
+
}
|
|
148
|
+
if (!cadenceElapsed(today, lastSent, stage, daysUntilDue)) {
|
|
149
|
+
return { fire: false, reason: "cadence_not_elapsed", stage };
|
|
150
|
+
}
|
|
151
|
+
return { fire: true, stage, anchorDate, sendCountAtFire: sentHistory.length + 1 };
|
|
152
|
+
}
|
|
153
|
+
function parseTimeOfDayUtcMinutes(value) {
|
|
154
|
+
const [hh, mm] = value.split(":").map((part) => Number(part));
|
|
155
|
+
return (hh ?? 0) * 60 + (mm ?? 0);
|
|
156
|
+
}
|
|
157
|
+
export function applyQuietHours(now, stage, settings, recipientTimezone) {
|
|
158
|
+
if (!stage.respectQuietHours) {
|
|
159
|
+
return { scheduledAt: now, deferred: false };
|
|
160
|
+
}
|
|
161
|
+
const blackoutDates = settings.blackoutDates ?? [];
|
|
162
|
+
const skipWeekends = settings.skipWeekends ?? false;
|
|
163
|
+
const quiet = settings.quietHoursLocal;
|
|
164
|
+
const tz = recipientTimezone ?? quiet?.tz ?? "UTC";
|
|
165
|
+
const effectiveTz = tz === "recipient" ? "UTC" : tz;
|
|
166
|
+
let candidate = now;
|
|
167
|
+
for (let attempts = 0; attempts < 24; attempts += 1) {
|
|
168
|
+
const local = formatInTimeZone(candidate, effectiveTz);
|
|
169
|
+
const blackout = blackoutDates.includes(local.dateString);
|
|
170
|
+
const isWeekend = local.dayOfWeek === 0 || local.dayOfWeek === 6;
|
|
171
|
+
let inQuiet = false;
|
|
172
|
+
if (quiet) {
|
|
173
|
+
const startMin = parseTimeOfDayUtcMinutes(quiet.start);
|
|
174
|
+
const endMin = parseTimeOfDayUtcMinutes(quiet.end);
|
|
175
|
+
const nowMin = local.hour * 60 + local.minute;
|
|
176
|
+
inQuiet =
|
|
177
|
+
startMin <= endMin
|
|
178
|
+
? nowMin >= startMin && nowMin < endMin
|
|
179
|
+
: nowMin >= startMin || nowMin < endMin;
|
|
180
|
+
}
|
|
181
|
+
if (!blackout && !(skipWeekends && isWeekend) && !inQuiet) {
|
|
182
|
+
return { scheduledAt: candidate, deferred: candidate !== now };
|
|
183
|
+
}
|
|
184
|
+
candidate = new Date(candidate.getTime() + 60 * 60 * 1000);
|
|
185
|
+
}
|
|
186
|
+
return { scheduledAt: candidate, deferred: true };
|
|
187
|
+
}
|
|
188
|
+
function formatInTimeZone(date, timeZone) {
|
|
189
|
+
const fmt = new Intl.DateTimeFormat("en-US", {
|
|
190
|
+
timeZone,
|
|
191
|
+
year: "numeric",
|
|
192
|
+
month: "2-digit",
|
|
193
|
+
day: "2-digit",
|
|
194
|
+
hour: "2-digit",
|
|
195
|
+
minute: "2-digit",
|
|
196
|
+
weekday: "short",
|
|
197
|
+
hour12: false,
|
|
198
|
+
});
|
|
199
|
+
const parts = Object.fromEntries(fmt.formatToParts(date).map((part) => [part.type, part.value]));
|
|
200
|
+
const dayLookup = {
|
|
201
|
+
Sun: 0,
|
|
202
|
+
Mon: 1,
|
|
203
|
+
Tue: 2,
|
|
204
|
+
Wed: 3,
|
|
205
|
+
Thu: 4,
|
|
206
|
+
Fri: 5,
|
|
207
|
+
Sat: 6,
|
|
208
|
+
};
|
|
209
|
+
return {
|
|
210
|
+
dateString: `${parts.year}-${parts.month}-${parts.day}`,
|
|
211
|
+
hour: Number(parts.hour ?? "0") % 24,
|
|
212
|
+
minute: Number(parts.minute ?? "0"),
|
|
213
|
+
dayOfWeek: dayLookup[parts.weekday ?? "Mon"] ?? 1,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
export async function exceedsRecipientRateLimit(db, recipient, channel, settings, now) {
|
|
217
|
+
const limit = settings.recipientRateLimitPerDay;
|
|
218
|
+
if (limit == null || limit <= 0)
|
|
219
|
+
return false;
|
|
220
|
+
const since = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
221
|
+
const [row] = await db
|
|
222
|
+
.select({ count: sql `count(*)::int` })
|
|
223
|
+
.from(notificationReminderRuns)
|
|
224
|
+
.where(and(eq(notificationReminderRuns.recipient, recipient), eq(notificationReminderRuns.status, "sent"), gte(notificationReminderRuns.processedAt, since), sql `${notificationReminderRuns.metadata}->>'channel' = ${channel}`));
|
|
225
|
+
return (row?.count ?? 0) >= limit;
|
|
226
|
+
}
|
|
227
|
+
export async function suppressedByGroup(db, recipient, suppressionGroup, settings, now) {
|
|
228
|
+
if (!recipient || !suppressionGroup)
|
|
229
|
+
return false;
|
|
230
|
+
const windowHours = settings.suppressionWindowHours ?? 24;
|
|
231
|
+
if (windowHours <= 0)
|
|
232
|
+
return false;
|
|
233
|
+
const since = new Date(now.getTime() - windowHours * 60 * 60 * 1000);
|
|
234
|
+
const [row] = await db
|
|
235
|
+
.select({ id: notificationReminderRuns.id })
|
|
236
|
+
.from(notificationReminderRuns)
|
|
237
|
+
.innerJoin(notificationReminderRules, eq(notificationReminderRuns.reminderRuleId, notificationReminderRules.id))
|
|
238
|
+
.where(and(eq(notificationReminderRuns.recipient, recipient), eq(notificationReminderRuns.status, "sent"), eq(notificationReminderRules.suppressionGroup, suppressionGroup), gte(notificationReminderRuns.processedAt, since)))
|
|
239
|
+
.limit(1);
|
|
240
|
+
return Boolean(row);
|
|
241
|
+
}
|
|
242
|
+
export async function loadHistory(db, reminderRuleId, targetId) {
|
|
243
|
+
const rows = await db
|
|
244
|
+
.select({
|
|
245
|
+
scheduledFor: notificationReminderRuns.scheduledFor,
|
|
246
|
+
processedAt: notificationReminderRuns.processedAt,
|
|
247
|
+
status: notificationReminderRuns.status,
|
|
248
|
+
})
|
|
249
|
+
.from(notificationReminderRuns)
|
|
250
|
+
.where(and(eq(notificationReminderRuns.reminderRuleId, reminderRuleId), eq(notificationReminderRuns.targetId, targetId)))
|
|
251
|
+
.orderBy(asc(notificationReminderRuns.scheduledFor));
|
|
252
|
+
return rows.map((row) => ({
|
|
253
|
+
scheduledFor: row.scheduledFor,
|
|
254
|
+
sentAt: row.status === "sent" ? row.processedAt : null,
|
|
255
|
+
status: row.status,
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Computes the date range a target's `due_date` (or `issue_date`) needs to
|
|
260
|
+
* fall in for any of the rule's stages to be inside their eligibility window
|
|
261
|
+
* today.
|
|
262
|
+
*
|
|
263
|
+
* From `inWindow`: today must satisfy
|
|
264
|
+
* anchor + windowStartDays ≤ today ≤ anchor + windowEndDays
|
|
265
|
+
* Solving for anchor:
|
|
266
|
+
* today − windowEndDays ≤ anchor ≤ today − windowStartDays
|
|
267
|
+
*
|
|
268
|
+
* Across all stages with anchor=`due_date`, we union the [start, end] ranges
|
|
269
|
+
* and use the resulting envelope as a SQL `BETWEEN` filter. Returns null when
|
|
270
|
+
* no stage is anchored on the requested column (e.g. all stages anchor on
|
|
271
|
+
* `departure_date`) — caller should skip the pushdown in that case.
|
|
272
|
+
*/
|
|
273
|
+
export function computeAnchorDateEnvelope(stages, today, anchor) {
|
|
274
|
+
const matching = stages.filter((s) => s.anchor === anchor);
|
|
275
|
+
if (matching.length === 0)
|
|
276
|
+
return null;
|
|
277
|
+
const todayStart = startOfUtcDay(today);
|
|
278
|
+
let from = Number.POSITIVE_INFINITY;
|
|
279
|
+
let to = Number.NEGATIVE_INFINITY;
|
|
280
|
+
for (const stage of matching) {
|
|
281
|
+
const fromDays = -stage.windowEndDays;
|
|
282
|
+
const toDays = -stage.windowStartDays;
|
|
283
|
+
if (fromDays < from)
|
|
284
|
+
from = fromDays;
|
|
285
|
+
if (toDays > to)
|
|
286
|
+
to = toDays;
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
from: addUtcDays(todayStart, from).toISOString().slice(0, 10),
|
|
290
|
+
to: addUtcDays(todayStart, to).toISOString().slice(0, 10),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
export async function fetchOpenPaymentScheduleTargets(db, envelopes = {}) {
|
|
294
|
+
const conditions = [
|
|
295
|
+
or(eq(bookingPaymentSchedules.status, "pending"), eq(bookingPaymentSchedules.status, "due")),
|
|
296
|
+
];
|
|
297
|
+
if (envelopes.paymentScheduleDueDate) {
|
|
298
|
+
conditions.push(gte(bookingPaymentSchedules.dueDate, envelopes.paymentScheduleDueDate.from), lte(bookingPaymentSchedules.dueDate, envelopes.paymentScheduleDueDate.to));
|
|
299
|
+
}
|
|
300
|
+
const rows = await db
|
|
301
|
+
.select({
|
|
302
|
+
id: bookingPaymentSchedules.id,
|
|
303
|
+
bookingId: bookingPaymentSchedules.bookingId,
|
|
304
|
+
dueDate: bookingPaymentSchedules.dueDate,
|
|
305
|
+
status: bookingPaymentSchedules.status,
|
|
306
|
+
bookingCreatedAt: bookings.createdAt,
|
|
307
|
+
departureDate: bookings.startDate,
|
|
308
|
+
})
|
|
309
|
+
.from(bookingPaymentSchedules)
|
|
310
|
+
.leftJoin(bookings, eq(bookings.id, bookingPaymentSchedules.bookingId))
|
|
311
|
+
.where(and(...conditions));
|
|
312
|
+
return rows.map((row) => ({
|
|
313
|
+
id: row.id,
|
|
314
|
+
bookingId: row.bookingId,
|
|
315
|
+
dueDate: row.dueDate,
|
|
316
|
+
issuedAt: null,
|
|
317
|
+
departureDate: row.departureDate,
|
|
318
|
+
bookingCreatedAt: row.bookingCreatedAt ? row.bookingCreatedAt.toISOString() : null,
|
|
319
|
+
status: row.status,
|
|
320
|
+
isTerminal: row.status !== "pending" && row.status !== "due",
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
export async function fetchOpenInvoiceTargets(db, envelopes = {}) {
|
|
324
|
+
const conditions = [
|
|
325
|
+
gt(invoices.balanceDueCents, 0),
|
|
326
|
+
or(eq(invoices.invoiceType, "invoice"), eq(invoices.invoiceType, "proforma")),
|
|
327
|
+
or(eq(invoices.status, "sent"), eq(invoices.status, "partially_paid"), eq(invoices.status, "overdue")),
|
|
328
|
+
];
|
|
329
|
+
if (envelopes.invoiceDueDate) {
|
|
330
|
+
conditions.push(gte(invoices.dueDate, envelopes.invoiceDueDate.from), lte(invoices.dueDate, envelopes.invoiceDueDate.to));
|
|
331
|
+
}
|
|
332
|
+
if (envelopes.invoiceIssueDate) {
|
|
333
|
+
conditions.push(gte(invoices.issueDate, envelopes.invoiceIssueDate.from), lte(invoices.issueDate, envelopes.invoiceIssueDate.to));
|
|
334
|
+
}
|
|
335
|
+
const rows = await db
|
|
336
|
+
.select({
|
|
337
|
+
id: invoices.id,
|
|
338
|
+
bookingId: invoices.bookingId,
|
|
339
|
+
dueDate: invoices.dueDate,
|
|
340
|
+
issueDate: invoices.issueDate,
|
|
341
|
+
balanceDueCents: invoices.balanceDueCents,
|
|
342
|
+
invoiceType: invoices.invoiceType,
|
|
343
|
+
status: invoices.status,
|
|
344
|
+
bookingCreatedAt: bookings.createdAt,
|
|
345
|
+
departureDate: bookings.startDate,
|
|
346
|
+
})
|
|
347
|
+
.from(invoices)
|
|
348
|
+
.leftJoin(bookings, eq(bookings.id, invoices.bookingId))
|
|
349
|
+
.where(and(...conditions));
|
|
350
|
+
return rows.map((row) => ({
|
|
351
|
+
id: row.id,
|
|
352
|
+
bookingId: row.bookingId,
|
|
353
|
+
dueDate: row.dueDate,
|
|
354
|
+
issuedAt: row.issueDate,
|
|
355
|
+
departureDate: row.departureDate,
|
|
356
|
+
bookingCreatedAt: row.bookingCreatedAt ? row.bookingCreatedAt.toISOString() : null,
|
|
357
|
+
status: row.status,
|
|
358
|
+
isTerminal: row.balanceDueCents <= 0,
|
|
359
|
+
}));
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Per-rule target fetch that pushes a date envelope into the WHERE when all
|
|
363
|
+
* relevant stages share an anchor we can SQL-filter on (`due_date` for both
|
|
364
|
+
* target types, `invoice_issued_at` for invoices). Other anchors fall through
|
|
365
|
+
* to the unfiltered fetch — they're expected to be rare and the in-app
|
|
366
|
+
* window check still rejects misses.
|
|
367
|
+
*/
|
|
368
|
+
export async function fetchTargetsForRule(db, rule, stages = [], today = new Date()) {
|
|
369
|
+
if (rule.targetType === "booking_payment_schedule") {
|
|
370
|
+
const dueEnv = computeAnchorDateEnvelope(stages, today, "due_date");
|
|
371
|
+
return fetchOpenPaymentScheduleTargets(db, dueEnv ? { paymentScheduleDueDate: dueEnv } : {});
|
|
372
|
+
}
|
|
373
|
+
if (rule.targetType === "invoice") {
|
|
374
|
+
const dueEnv = computeAnchorDateEnvelope(stages, today, "due_date");
|
|
375
|
+
const issueEnv = computeAnchorDateEnvelope(stages, today, "invoice_issued_at");
|
|
376
|
+
return fetchOpenInvoiceTargets(db, {
|
|
377
|
+
invoiceDueDate: dueEnv ?? undefined,
|
|
378
|
+
invoiceIssueDate: issueEnv ?? undefined,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
return [];
|
|
382
|
+
}
|
|
383
|
+
export async function listActiveRulesByPriority(db) {
|
|
384
|
+
return db
|
|
385
|
+
.select()
|
|
386
|
+
.from(notificationReminderRules)
|
|
387
|
+
.where(eq(notificationReminderRules.status, "active"))
|
|
388
|
+
.orderBy(sql `${notificationReminderRules.priority} desc nulls last`, asc(notificationReminderRules.createdAt));
|
|
389
|
+
}
|
|
390
|
+
export async function previewReminders(db, options = {}) {
|
|
391
|
+
const now = options.now ?? new Date();
|
|
392
|
+
const today = startOfUtcDay(now);
|
|
393
|
+
const settings = await getNotificationSettings(db);
|
|
394
|
+
const allRules = await listActiveRulesByPriority(db);
|
|
395
|
+
const rules = options.ruleId ? allRules.filter((r) => r.id === options.ruleId) : allRules;
|
|
396
|
+
const rows = [];
|
|
397
|
+
for (const rule of rules) {
|
|
398
|
+
const stages = await listStagesForRule(db, rule.id);
|
|
399
|
+
if (stages.length === 0)
|
|
400
|
+
continue;
|
|
401
|
+
const targets = await fetchTargetsForRule(db, rule, stages, today);
|
|
402
|
+
const filteredTargets = options.targetId
|
|
403
|
+
? targets.filter((t) => t.id === options.targetId)
|
|
404
|
+
: targets;
|
|
405
|
+
for (const target of filteredTargets) {
|
|
406
|
+
const history = await loadHistory(db, rule.id, target.id);
|
|
407
|
+
const decision = evaluateStage(rule, stages, target, history, today);
|
|
408
|
+
if (!decision.fire)
|
|
409
|
+
continue;
|
|
410
|
+
const { scheduledAt } = applyQuietHours(now, decision.stage, settings);
|
|
411
|
+
rows.push({
|
|
412
|
+
ruleId: rule.id,
|
|
413
|
+
ruleName: rule.name,
|
|
414
|
+
ruleSlug: rule.slug,
|
|
415
|
+
targetType: rule.targetType,
|
|
416
|
+
targetId: target.id,
|
|
417
|
+
bookingId: target.bookingId,
|
|
418
|
+
stageId: decision.stage.id,
|
|
419
|
+
stageName: decision.stage.name,
|
|
420
|
+
stageOrderIndex: decision.stage.orderIndex,
|
|
421
|
+
anchor: decision.stage.anchor,
|
|
422
|
+
anchorDate: decision.anchorDate.toISOString(),
|
|
423
|
+
scheduledAt: scheduledAt.toISOString(),
|
|
424
|
+
sendCountAtFire: decision.sendCountAtFire,
|
|
425
|
+
reasoning: `stage[${decision.stage.orderIndex}] anchor=${decision.stage.anchor} window=[${decision.stage.windowStartDays},${decision.stage.windowEndDays}] cadence=${decision.stage.cadenceKind}`,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return rows;
|
|
430
|
+
}
|
|
431
|
+
const _markUnusedImports = [lte]; // keep tree-shaking honest if helpers added later
|
|
432
|
+
void _markUnusedImports;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
import { type NotificationReminderRuleStage, type NotificationReminderStageChannel, type NotificationSettings } from "./schema.js";
|
|
4
|
+
import type { insertNotificationReminderRuleStageSchema, insertNotificationReminderStageChannelSchema, reorderReminderRuleStagesSchema, updateNotificationReminderRuleStageSchema, updateNotificationReminderStageChannelSchema, updateNotificationSettingsSchema } from "./validation.js";
|
|
5
|
+
export type CreateReminderRuleStageInput = z.infer<typeof insertNotificationReminderRuleStageSchema>;
|
|
6
|
+
export type UpdateReminderRuleStageInput = z.infer<typeof updateNotificationReminderRuleStageSchema>;
|
|
7
|
+
export type CreateReminderStageChannelInput = z.infer<typeof insertNotificationReminderStageChannelSchema>;
|
|
8
|
+
export type UpdateReminderStageChannelInput = z.infer<typeof updateNotificationReminderStageChannelSchema>;
|
|
9
|
+
export type ReorderReminderRuleStagesInput = z.infer<typeof reorderReminderRuleStagesSchema>;
|
|
10
|
+
export type UpdateNotificationSettingsInput = z.infer<typeof updateNotificationSettingsSchema>;
|
|
11
|
+
export declare function listReminderRuleStages(db: PostgresJsDatabase, reminderRuleId: string): Promise<NotificationReminderRuleStage[]>;
|
|
12
|
+
export declare function getReminderRuleStageById(db: PostgresJsDatabase, stageId: string): Promise<NotificationReminderRuleStage | null>;
|
|
13
|
+
export declare function createReminderRuleStage(db: PostgresJsDatabase, reminderRuleId: string, input: CreateReminderRuleStageInput): Promise<NotificationReminderRuleStage>;
|
|
14
|
+
export declare function updateReminderRuleStage(db: PostgresJsDatabase, stageId: string, input: UpdateReminderRuleStageInput): Promise<NotificationReminderRuleStage | null>;
|
|
15
|
+
export declare function deleteReminderRuleStage(db: PostgresJsDatabase, stageId: string): Promise<boolean>;
|
|
16
|
+
export declare function reorderReminderRuleStages(db: PostgresJsDatabase, reminderRuleId: string, input: ReorderReminderRuleStagesInput): Promise<NotificationReminderRuleStage[]>;
|
|
17
|
+
export declare function listStageChannels(db: PostgresJsDatabase, stageId: string): Promise<NotificationReminderStageChannel[]>;
|
|
18
|
+
export declare function createStageChannel(db: PostgresJsDatabase, stageId: string, input: CreateReminderStageChannelInput): Promise<NotificationReminderStageChannel>;
|
|
19
|
+
export declare function updateStageChannel(db: PostgresJsDatabase, channelId: string, input: UpdateReminderStageChannelInput): Promise<NotificationReminderStageChannel | null>;
|
|
20
|
+
export declare function deleteStageChannel(db: PostgresJsDatabase, channelId: string): Promise<boolean>;
|
|
21
|
+
export declare function getNotificationSettingsRecord(db: PostgresJsDatabase, scope?: string): Promise<NotificationSettings>;
|
|
22
|
+
export declare function upsertNotificationSettings(db: PostgresJsDatabase, input: UpdateNotificationSettingsInput): Promise<NotificationSettings>;
|
|
23
|
+
//# sourceMappingURL=service-stages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-stages.d.ts","sourceRoot":"","sources":["../src/service-stages.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAE5B,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,gCAAgC,EACrC,KAAK,oBAAoB,EAI1B,MAAM,aAAa,CAAA;AAEpB,OAAO,KAAK,EACV,yCAAyC,EACzC,4CAA4C,EAC5C,+BAA+B,EAC/B,yCAAyC,EACzC,4CAA4C,EAC5C,gCAAgC,EACjC,MAAM,iBAAiB,CAAA;AAExB,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yCAAyC,CAAC,CAAA;AACpG,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yCAAyC,CAAC,CAAA;AACpG,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CACnD,OAAO,4CAA4C,CACpD,CAAA;AACD,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CACnD,OAAO,4CAA4C,CACpD,CAAA;AACD,MAAM,MAAM,8BAA8B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAA;AAC5F,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAA;AAE9F,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,kBAAkB,EACtB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,6BAA6B,EAAE,CAAC,CAM1C;AAED,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,6BAA6B,GAAG,IAAI,CAAC,CAO/C;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,kBAAkB,EACtB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,6BAA6B,CAAC,CAoBxC;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,6BAA6B,GAAG,IAAI,CAAC,CAqB/C;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAMlB;AAED,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,kBAAkB,EACtB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,6BAA6B,EAAE,CAAC,CAiB1C;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,gCAAgC,EAAE,CAAC,CAS7C;AAED,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,+BAA+B,GACrC,OAAO,CAAC,gCAAgC,CAAC,CAiB3C;AAED,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,kBAAkB,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,+BAA+B,GACrC,OAAO,CAAC,gCAAgC,GAAG,IAAI,CAAC,CAgBlD;AAED,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,kBAAkB,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAMlB;AAED,wBAAsB,6BAA6B,CACjD,EAAE,EAAE,kBAAkB,EACtB,KAAK,SAAY,GAChB,OAAO,CAAC,oBAAoB,CAAC,CAO/B;AAED,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,+BAA+B,GACrC,OAAO,CAAC,oBAAoB,CAAC,CA0C/B"}
|