updated medicine schedules
This commit is contained in:
+17
-1
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user