Added reminder emails

This commit is contained in:
Corey Blais
2026-04-21 15:04:44 -04:00
parent af40b9901f
commit ee09c95f05
13 changed files with 435 additions and 24 deletions
@@ -1,6 +1,9 @@
import { db } from '../db/client.js';
import type {
BirdGender,
BirdMilestoneReminderCandidateRow,
BirdMilestoneReminderDeliveryRow,
BirdMilestoneReminderType,
BirdRow,
LostBirdMatchRow,
MedicationAdministrationRow,
@@ -93,6 +96,98 @@ export const findBirdsByBandId = async (tagId: string) => {
return result.rows;
};
export const listDueBirdMilestoneReminders = async (runDate: string) => {
const result = await db.query<BirdMilestoneReminderCandidateRow>(
`WITH reminder_context AS (
SELECT $1::date AS run_date,
EXTRACT(YEAR FROM $1::date)::int AS reminder_year
)
SELECT
${birdSelectFields},
workspaces.name AS workspace_name,
'hatch_day'::text AS reminder_type,
birds.date_of_birth::text AS reminder_date,
reminder_context.reminder_year
FROM birds
INNER JOIN workspaces ON workspaces.id = birds.workspace_id
CROSS JOIN reminder_context
LEFT JOIN LATERAL (
SELECT weight_grams, recorded_on
FROM weight_records
WHERE weight_records.bird_id = birds.id
ORDER BY recorded_on DESC
LIMIT 1
) latest ON TRUE
WHERE birds.notify_on_dob = TRUE
AND birds.date_of_birth IS NOT NULL
AND EXTRACT(MONTH FROM birds.date_of_birth) = EXTRACT(MONTH FROM reminder_context.run_date)
AND EXTRACT(DAY FROM birds.date_of_birth) = EXTRACT(DAY FROM reminder_context.run_date)
AND NOT EXISTS (
SELECT 1
FROM bird_milestone_reminder_deliveries deliveries
WHERE deliveries.bird_id = birds.id
AND deliveries.reminder_type = 'hatch_day'
AND deliveries.reminder_year = reminder_context.reminder_year
)
UNION ALL
SELECT
${birdSelectFields},
workspaces.name AS workspace_name,
'gotcha_day'::text AS reminder_type,
birds.gotcha_day::text AS reminder_date,
reminder_context.reminder_year
FROM birds
INNER JOIN workspaces ON workspaces.id = birds.workspace_id
CROSS JOIN reminder_context
LEFT JOIN LATERAL (
SELECT weight_grams, recorded_on
FROM weight_records
WHERE weight_records.bird_id = birds.id
ORDER BY recorded_on DESC
LIMIT 1
) latest ON TRUE
WHERE birds.notify_on_gotcha_day = TRUE
AND birds.gotcha_day IS NOT NULL
AND EXTRACT(MONTH FROM birds.gotcha_day) = EXTRACT(MONTH FROM reminder_context.run_date)
AND EXTRACT(DAY FROM birds.gotcha_day) = EXTRACT(DAY FROM reminder_context.run_date)
AND NOT EXISTS (
SELECT 1
FROM bird_milestone_reminder_deliveries deliveries
WHERE deliveries.bird_id = birds.id
AND deliveries.reminder_type = 'gotcha_day'
AND deliveries.reminder_year = reminder_context.reminder_year
)
ORDER BY workspace_name ASC, name ASC, reminder_type ASC`,
[runDate],
);
return result.rows;
};
export const createBirdMilestoneReminderDelivery = async ({
birdId,
workspaceId,
reminderType,
reminderYear,
deliveredOn,
}: {
birdId: string;
workspaceId: number;
reminderType: BirdMilestoneReminderType;
reminderYear: number;
deliveredOn: string;
}) => {
const result = await db.query<BirdMilestoneReminderDeliveryRow>(
`INSERT INTO bird_milestone_reminder_deliveries (bird_id, workspace_id, reminder_type, reminder_year, delivered_on)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (bird_id, reminder_type, reminder_year) DO NOTHING
RETURNING id, bird_id, workspace_id, reminder_type, reminder_year, delivered_on::text, created_at`,
[birdId, workspaceId, reminderType, reminderYear, deliveredOn],
);
return result.rows[0] ?? null;
};
export const createBird = async ({
workspaceId,
name,