updated medicine schedules

This commit is contained in:
blaisadmin
2026-04-19 19:42:01 -04:00
parent 872b6c8663
commit d1657ef7ed
7 changed files with 363 additions and 66 deletions
+17 -1
View File
@@ -230,7 +230,17 @@ const medicationSchema = z
.object({
name: z.string().trim().min(1).max(160),
dosage: z.string().trim().min(1).max(160),
frequency: z.string().trim().min(1).max(160),
frequency: z.enum(['once_daily', 'twice_daily', 'every_8_hours', 'every_6_hours', 'as_needed']),
doseSchedule: z
.array(
z.object({
key: z.string().trim().min(1).max(80),
label: z.string().trim().min(1).max(80),
time: z.string().trim().regex(/^$|^\d{2}:\d{2}$/),
}),
)
.min(1)
.max(8),
route: z.string().trim().max(80).optional().or(z.literal('')),
startDate: dateStringSchema,
endDate: dateStringSchema.optional().or(z.literal('')),
@@ -243,6 +253,7 @@ const medicationSchema = z
const medicationAdministrationSchema = z.object({
administeredOn: dateStringSchema,
administrationSlot: z.string().trim().min(1).max(80).default('dose-1'),
status: z.enum(['administered', 'missed']),
notes: z.string().trim().max(500).optional().or(z.literal('')),
});
@@ -444,6 +455,7 @@ const normalizeMedication = (row: MedicationRow) => ({
name: row.name,
dosage: row.dosage,
frequency: row.frequency,
doseSchedule: row.dose_schedule,
route: row.route,
startDate: row.start_date,
endDate: row.end_date,
@@ -455,6 +467,7 @@ const normalizeMedicationAdministration = (row: MedicationAdministrationRow) =>
medicationId: row.medication_id,
birdId: row.bird_id,
administeredOn: row.administered_on,
administrationSlot: row.administration_slot,
status: row.status,
notes: row.notes,
createdByUserId: row.created_by_user_id,
@@ -2229,6 +2242,7 @@ app.post('/api/birds/:birdId/medications', requireAuth, requireWriteAccess, requ
parsed.data.name,
parsed.data.dosage,
parsed.data.frequency,
parsed.data.doseSchedule,
emptyToNull(parsed.data.route),
parsed.data.startDate,
emptyToNull(parsed.data.endDate),
@@ -2263,6 +2277,7 @@ app.put('/api/birds/:birdId/medications/:medicationId', requireAuth, requireWrit
parsed.data.name,
parsed.data.dosage,
parsed.data.frequency,
parsed.data.doseSchedule,
emptyToNull(parsed.data.route),
parsed.data.startDate,
emptyToNull(parsed.data.endDate),
@@ -2325,6 +2340,7 @@ app.post('/api/birds/:birdId/medications/:medicationId/administrations', require
req.params.birdId,
req.auth!.workspace.id,
parsed.data.administeredOn,
parsed.data.administrationSlot,
parsed.data.status,
emptyToNull(parsed.data.notes),
req.auth!.user.id,
+15 -2
View File
@@ -287,6 +287,7 @@ export const ensureSchema = async (database: DatabaseClient = db) => {
name VARCHAR(160) NOT NULL,
dosage VARCHAR(160) NOT NULL,
frequency VARCHAR(160) NOT NULL,
dose_schedule JSONB NOT NULL DEFAULT '[{"key":"dose-1","label":"Dose","time":""}]'::jsonb,
route VARCHAR(80),
start_date DATE NOT NULL,
end_date DATE,
@@ -295,18 +296,30 @@ export const ensureSchema = async (database: DatabaseClient = db) => {
CHECK (end_date IS NULL OR end_date >= start_date)
);
ALTER TABLE medications
ADD COLUMN IF NOT EXISTS dose_schedule JSONB NOT NULL DEFAULT '[{"key":"dose-1","label":"Dose","time":""}]'::jsonb;
CREATE TABLE IF NOT EXISTS medication_administrations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
medication_id UUID NOT NULL REFERENCES medications(id) ON DELETE CASCADE,
bird_id UUID NOT NULL REFERENCES birds(id) ON DELETE CASCADE,
administered_on DATE NOT NULL,
administration_slot VARCHAR(80) NOT NULL DEFAULT 'dose-1',
status VARCHAR(20) NOT NULL CHECK (status IN ('administered', 'missed')),
notes VARCHAR(500),
created_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (medication_id, administered_on)
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE medication_administrations
ADD COLUMN IF NOT EXISTS administration_slot VARCHAR(80) NOT NULL DEFAULT 'dose-1';
ALTER TABLE medication_administrations
DROP CONSTRAINT IF EXISTS medication_administrations_medication_id_administered_on_key;
CREATE UNIQUE INDEX IF NOT EXISTS idx_medication_administrations_unique_slot
ON medication_administrations (medication_id, administered_on, administration_slot);
CREATE INDEX IF NOT EXISTS idx_weight_records_bird_recorded_on
ON weight_records (bird_id, recorded_on DESC);
+22 -17
View File
@@ -4,6 +4,7 @@ import type {
BirdRow,
LostBirdMatchRow,
MedicationAdministrationRow,
MedicationDoseScheduleItem,
MedicationRow,
PendingBirdTransferRow,
VetVisitRow,
@@ -415,7 +416,7 @@ export const deleteVetVisitForBird = async (visitId: string, birdId: string) =>
export const listMedicationsForBird = async (birdId: string, workspaceId: number) => {
const result = await db.query<MedicationRow>(
`SELECT id, bird_id, name, dosage, frequency, route, start_date::text, end_date::text, notes
`SELECT id, bird_id, name, dosage, frequency, dose_schedule, route, start_date::text, end_date::text, notes
FROM medications
WHERE bird_id = $1
AND EXISTS (
@@ -436,16 +437,17 @@ export const createMedicationForBird = async (
name: string,
dosage: string,
frequency: string,
doseSchedule: MedicationDoseScheduleItem[],
route: string | null,
startDate: string,
endDate: string | null,
notes: string | null,
) => {
const result = await db.query<MedicationRow>(
`INSERT INTO medications (bird_id, name, dosage, frequency, route, start_date, end_date, notes)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id, bird_id, name, dosage, frequency, route, start_date::text, end_date::text, notes`,
[birdId, name, dosage, frequency, route, startDate, endDate, notes],
`INSERT INTO medications (bird_id, name, dosage, frequency, dose_schedule, route, start_date, end_date, notes)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id, bird_id, name, dosage, frequency, dose_schedule, route, start_date::text, end_date::text, notes`,
[birdId, name, dosage, frequency, JSON.stringify(doseSchedule), route, startDate, endDate, notes],
);
return result.rows[0] ?? null;
@@ -457,6 +459,7 @@ export const updateMedicationForBird = async (
name: string,
dosage: string,
frequency: string,
doseSchedule: MedicationDoseScheduleItem[],
route: string | null,
startDate: string,
endDate: string | null,
@@ -467,14 +470,15 @@ export const updateMedicationForBird = async (
SET name = $3,
dosage = $4,
frequency = $5,
route = $6,
start_date = $7,
end_date = $8,
notes = $9
dose_schedule = $6,
route = $7,
start_date = $8,
end_date = $9,
notes = $10
WHERE id = $1
AND bird_id = $2
RETURNING id, bird_id, name, dosage, frequency, route, start_date::text, end_date::text, notes`,
[medicationId, birdId, name, dosage, frequency, route, startDate, endDate, notes],
RETURNING id, bird_id, name, dosage, frequency, dose_schedule, route, start_date::text, end_date::text, notes`,
[medicationId, birdId, name, dosage, frequency, JSON.stringify(doseSchedule), route, startDate, endDate, notes],
);
return result.rows[0] ?? null;
@@ -494,7 +498,7 @@ export const deleteMedicationForBird = async (medicationId: string, birdId: stri
export const listMedicationAdministrationsForBird = async (birdId: string, workspaceId: number) => {
const result = await db.query<MedicationAdministrationRow>(
`SELECT id, medication_id, bird_id, administered_on::text, status, notes, created_by_user_id, created_at
`SELECT id, medication_id, bird_id, administered_on::text, administration_slot, status, notes, created_by_user_id, created_at
FROM medication_administrations
WHERE bird_id = $1
AND EXISTS (
@@ -515,13 +519,14 @@ export const upsertMedicationAdministrationForBird = async (
birdId: string,
workspaceId: number,
administeredOn: string,
administrationSlot: string,
status: 'administered' | 'missed',
notes: string | null,
createdByUserId: string | null,
) => {
const result = await db.query<MedicationAdministrationRow>(
`INSERT INTO medication_administrations (medication_id, bird_id, administered_on, status, notes, created_by_user_id)
SELECT $1, $2, $4, $5, $6, $7
`INSERT INTO medication_administrations (medication_id, bird_id, administered_on, administration_slot, status, notes, created_by_user_id)
SELECT $1, $2, $4, $5, $6, $7, $8
WHERE EXISTS (
SELECT 1
FROM medications
@@ -530,13 +535,13 @@ export const upsertMedicationAdministrationForBird = async (
AND medications.bird_id = $2
AND birds.workspace_id = $3
)
ON CONFLICT (medication_id, administered_on)
ON CONFLICT (medication_id, administered_on, administration_slot)
DO UPDATE SET status = EXCLUDED.status,
notes = EXCLUDED.notes,
created_by_user_id = EXCLUDED.created_by_user_id,
created_at = CURRENT_TIMESTAMP
RETURNING id, medication_id, bird_id, administered_on::text, status, notes, created_by_user_id, created_at`,
[medicationId, birdId, workspaceId, administeredOn, status, notes, createdByUserId],
RETURNING id, medication_id, bird_id, administered_on::text, administration_slot, status, notes, created_by_user_id, created_at`,
[medicationId, birdId, workspaceId, administeredOn, administrationSlot, status, notes, createdByUserId],
);
return result.rows[0] ?? null;
+8
View File
@@ -150,17 +150,25 @@ export type MedicationRow = {
name: string;
dosage: string;
frequency: string;
dose_schedule: MedicationDoseScheduleItem[];
route: string | null;
start_date: string;
end_date: string | null;
notes: string | null;
};
export type MedicationDoseScheduleItem = {
key: string;
label: string;
time: string;
};
export type MedicationAdministrationRow = {
id: string;
medication_id: string;
bird_id: string;
administered_on: string;
administration_slot: string;
status: 'administered' | 'missed';
notes: string | null;
created_by_user_id: string | null;