Added memorial settings

This commit is contained in:
Corey Blais
2026-04-22 10:42:43 -04:00
parent 36e074c1fd
commit 646f895ed6
8 changed files with 466 additions and 32 deletions
+109 -6
View File
@@ -27,6 +27,10 @@ const birdSelectFields = `
birds.photo_data_url,
birds.notify_on_dob,
birds.notify_on_gotcha_day,
birds.memorialized_at,
birds.memorialized_on::text,
birds.memorial_note,
birds.notify_on_memorial_day,
birds.created_at,
latest.weight_grams AS latest_weight_grams,
latest.recorded_on::text AS latest_recorded_on
@@ -65,6 +69,7 @@ export const listBirds = async (workspaceId: number) => {
LIMIT 1
) latest ON TRUE
WHERE birds.workspace_id = $1
AND birds.memorialized_at IS NULL
ORDER BY birds.name ASC`,
[workspaceId],
);
@@ -72,6 +77,27 @@ export const listBirds = async (workspaceId: number) => {
return result.rows;
};
export const listMemorializedBirds = async (workspaceId: number) => {
const result = await db.query<BirdRow>(
`SELECT
${birdSelectFields}
FROM birds
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.workspace_id = $1
AND birds.memorialized_at IS NOT NULL
ORDER BY birds.memorialized_on DESC NULLS LAST, birds.name ASC`,
[workspaceId],
);
return result.rows;
};
export const findBirdsByBandId = async (tagId: string) => {
const result = await db.query<LostBirdMatchRow>(
`SELECT
@@ -87,9 +113,10 @@ export const findBirdsByBandId = async (tagId: string) => {
ORDER BY recorded_on DESC
LIMIT 1
) latest ON TRUE
WHERE LOWER(birds.tag_id) = LOWER($1)
ORDER BY birds.created_at ASC
LIMIT 10`,
WHERE LOWER(birds.tag_id) = LOWER($1)
AND birds.memorialized_at IS NULL
ORDER BY birds.created_at ASC
LIMIT 10`,
[tagId],
);
@@ -119,6 +146,7 @@ export const listDueBirdMilestoneReminders = async (runDate: string) => {
LIMIT 1
) latest ON TRUE
WHERE birds.notify_on_dob = TRUE
AND birds.memorialized_at IS NULL
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)
@@ -147,6 +175,7 @@ export const listDueBirdMilestoneReminders = async (runDate: string) => {
LIMIT 1
) latest ON TRUE
WHERE birds.notify_on_gotcha_day = TRUE
AND birds.memorialized_at IS NULL
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)
@@ -157,6 +186,35 @@ export const listDueBirdMilestoneReminders = async (runDate: string) => {
AND deliveries.reminder_type = 'gotcha_day'
AND deliveries.reminder_year = reminder_context.reminder_year
)
UNION ALL
SELECT
${birdSelectFields},
workspaces.name AS workspace_name,
'memorial_day'::text AS reminder_type,
birds.memorialized_on::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_memorial_day = TRUE
AND birds.memorialized_at IS NOT NULL
AND birds.memorialized_on IS NOT NULL
AND EXTRACT(MONTH FROM birds.memorialized_on) = EXTRACT(MONTH FROM reminder_context.run_date)
AND EXTRACT(DAY FROM birds.memorialized_on) = 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 = 'memorial_day'
AND deliveries.reminder_year = reminder_context.reminder_year
)
ORDER BY workspace_name ASC, name ASC, reminder_type ASC`,
[runDate],
);
@@ -216,7 +274,7 @@ export const createBird = async ({
const result = await db.query<BirdRow>(
`INSERT INTO birds (workspace_id, name, tag_id, species, gender, date_of_birth, gotcha_day, chart_color, photo_data_url, notify_on_dob, notify_on_gotcha_day)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING id, workspace_id, name, tag_id, species, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, notify_on_dob, notify_on_gotcha_day, created_at, NULL::text AS latest_weight_grams, NULL::text AS latest_recorded_on`,
RETURNING id, workspace_id, name, tag_id, species, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, notify_on_dob, notify_on_gotcha_day, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at, NULL::text AS latest_weight_grams, NULL::text AS latest_recorded_on`,
[workspaceId, name, tagId, species, gender, dateOfBirth, gotchaDay, chartColor, photoDataUrl, notifyOnDob, notifyOnGotchaDay],
);
@@ -264,7 +322,8 @@ export const updateBird = async ({
notify_on_gotcha_day = $11
WHERE id = $1
AND workspace_id = $12
RETURNING id, workspace_id, name, tag_id, species, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, notify_on_dob, notify_on_gotcha_day, created_at,
AND memorialized_at IS NULL
RETURNING id, workspace_id, name, tag_id, species, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, notify_on_dob, notify_on_gotcha_day, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
(
SELECT weight_grams::text
FROM weight_records
@@ -285,6 +344,49 @@ export const updateBird = async ({
return result.rows[0] ?? null;
};
export const memorializeBird = async ({
birdId,
workspaceId,
memorializedOn,
memorialNote,
notifyOnMemorialDay,
}: {
birdId: string;
workspaceId: number;
memorializedOn: string;
memorialNote: string | null;
notifyOnMemorialDay: boolean;
}) => {
const result = await db.query<BirdRow>(
`UPDATE birds
SET memorialized_at = CURRENT_TIMESTAMP,
memorialized_on = $3,
memorial_note = $4,
notify_on_memorial_day = $5
WHERE id = $1
AND workspace_id = $2
AND memorialized_at IS NULL
RETURNING id, workspace_id, name, tag_id, species, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, notify_on_dob, notify_on_gotcha_day, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
(
SELECT weight_grams::text
FROM weight_records
WHERE bird_id = birds.id
ORDER BY recorded_on DESC
LIMIT 1
) AS latest_weight_grams,
(
SELECT recorded_on::text
FROM weight_records
WHERE bird_id = birds.id
ORDER BY recorded_on DESC
LIMIT 1
) AS latest_recorded_on`,
[birdId, workspaceId, memorializedOn, memorialNote, notifyOnMemorialDay],
);
return result.rows[0] ?? null;
};
export const deleteBird = async (birdId: string, workspaceId: number) => {
const result = await db.query<{ id: string }>(
`DELETE FROM birds
@@ -303,7 +405,8 @@ export const transferBirdToWorkspace = async (birdId: string, sourceWorkspaceId:
SET workspace_id = $3
WHERE id = $1
AND workspace_id = $2
RETURNING id, workspace_id, name, tag_id, species, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, notify_on_dob, notify_on_gotcha_day, created_at,
AND memorialized_at IS NULL
RETURNING id, workspace_id, name, tag_id, species, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, notify_on_dob, notify_on_gotcha_day, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
(
SELECT weight_grams::text
FROM weight_records