diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index a45cb99dd..63dc3cdf8 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -1663,6 +1663,8 @@ components: type: integer minimum: 1 maximum: 100 + notificationsEnabled: + type: boolean startsOn: description: Date the medication course begins (ISO `YYYY-MM-DD`). Anchors RRULE BYDAY / BYMONTHDAY patterns and the rolling-interval countdown's first window. NULL = active from creation (the legacy implicit behaviour). diff --git a/src/app/api/medications/route.ts b/src/app/api/medications/route.ts index cc0dfdce8..dd599a02d 100644 --- a/src/app/api/medications/route.ts +++ b/src/app/api/medications/route.ts @@ -121,6 +121,7 @@ export const POST = apiHandler(async (request: NextRequest) => { category, treatmentClass, dosesPerUnit, + notificationsEnabled, schedules, startsOn, endsOn, @@ -180,6 +181,10 @@ export const POST = apiHandler(async (request: NextRequest) => { // wire schema; Prisma fills the default GENERIC when omitted. ...(treatmentClass !== undefined && { treatmentClass }), ...(dosesPerUnit !== undefined && { dosesPerUnit }), + // v1.5 — wizard's reminders toggle now ships through the create + // payload (was orphaned in the initial diff). Prisma defaults to + // true when omitted, matching the legacy form's behaviour. + ...(notificationsEnabled !== undefined && { notificationsEnabled }), // v1.5 scheduling primitives — pass-through when supplied. ...(startsOn !== undefined && { startsOn }), ...(normalisedEndsOn !== undefined && { endsOn: normalisedEndsOn }), diff --git a/src/lib/validations/medication.ts b/src/lib/validations/medication.ts index e53e98b10..20ad7ebdc 100644 --- a/src/lib/validations/medication.ts +++ b/src/lib/validations/medication.ts @@ -143,6 +143,17 @@ export const scheduleSchema = z path: ["rrule"], }, ) + .refine( + (s) => + s.rollingIntervalDays === undefined || + s.rollingIntervalDays === null || + !s.timesOfDay || + s.timesOfDay.length <= 1, + { + message: "rolling-cadence schedules accept at most one time of day", + path: ["timesOfDay"], + }, + ) .meta({ id: "MedicationScheduleInput", description: @@ -203,9 +214,22 @@ export const createMedicationSchema = z treatmentClass: z.enum(MEDICATION_TREATMENT_CLASS_VALUES).optional(), /** v1.4.25 W4d — doses per pen/vial for inventory tracking. */ dosesPerUnit: z.number().int().min(1).max(100).optional(), + notificationsEnabled: z.boolean().optional(), ...courseWindowFields, schedules: z.array(scheduleSchema).min(1, "Mindestens ein Zeitfenster"), }) + .refine((b) => b.oneShot !== true || !!b.startsOn, { + message: "startsOn is required when oneShot is true", + path: ["startsOn"], + }) + .refine( + (b) => + !b.startsOn || !b.endsOn || b.endsOn.getTime() >= b.startsOn.getTime(), + { + message: "endsOn must be on or after startsOn", + path: ["endsOn"], + }, + ) .meta({ id: "CreateMedicationRequest", description: @@ -224,6 +248,18 @@ export const updateMedicationSchema = z ...courseWindowFields, schedules: z.array(scheduleSchema).optional(), }) + .refine((b) => b.oneShot !== true || !!b.startsOn, { + message: "startsOn is required when oneShot is true", + path: ["startsOn"], + }) + .refine( + (b) => + !b.startsOn || !b.endsOn || b.endsOn.getTime() >= b.startsOn.getTime(), + { + message: "endsOn must be on or after startsOn", + path: ["endsOn"], + }, + ) .meta({ id: "UpdateMedicationRequest", description: