// ============================================================================= // Atemschutz (Breathing Apparatus) — Domain Model // ============================================================================= import { z } from 'zod'; // ── Enums ─────────────────────────────────────────────────────────────────── export const UNTERSUCHUNG_ERGEBNIS_VALUES = ['tauglich', 'bedingt_tauglich', 'nicht_tauglich'] as const; export type UntersuchungErgebnis = typeof UNTERSUCHUNG_ERGEBNIS_VALUES[number]; // ── Core DB Row ───────────────────────────────────────────────────────────── export interface AtemschutzTraeger { id: string; user_id: string; atemschutz_lehrgang: boolean; lehrgang_datum: Date | null; untersuchung_datum: Date | null; untersuchung_gueltig_bis: Date | null; untersuchung_ergebnis: UntersuchungErgebnis | null; leistungstest_datum: Date | null; leistungstest_gueltig_bis: Date | null; leistungstest_bestanden: boolean | null; bemerkung: string | null; created_at: Date; updated_at: Date; } // ── View Row (extended with user info + computed fields) ──────────────────── export interface AtemschutzUebersicht extends AtemschutzTraeger { user_name: string | null; user_given_name: string | null; user_family_name: string | null; user_email: string; mitglied_status: string | null; dienstgrad: string | null; untersuchung_gueltig: boolean; untersuchung_tage_rest: number | null; leistungstest_gueltig: boolean; leistungstest_tage_rest: number | null; einsatzbereit: boolean; } // ── Dashboard KPI ─────────────────────────────────────────────────────────── export interface AtemschutzStats { total: number; // total tracked members mitLehrgang: number; // have completed course untersuchungGueltig: number; // valid medical exam untersuchungAbgelaufen: number; // expired medical exam untersuchungBaldFaellig: number; // expires within 90 days leistungstestGueltig: number; // valid performance test leistungstestAbgelaufen: number; // expired performance test leistungstestBaldFaellig: number; // expires within 30 days einsatzbereit: number; // fully qualified } // ── Zod Validation Schemas ────────────────────────────────────────────────── const uuidString = z.string().regex( /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, 'Ungültige UUID' ); const isoDate = z.string().regex( /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/, 'Erwartet ISO-Datum im Format YYYY-MM-DD' ); export const CreateAtemschutzSchema = z.object({ user_id: uuidString, atemschutz_lehrgang: z.boolean().default(false), lehrgang_datum: isoDate.optional(), untersuchung_datum: isoDate.optional(), untersuchung_gueltig_bis: isoDate.optional(), untersuchung_ergebnis: z.enum(UNTERSUCHUNG_ERGEBNIS_VALUES).optional(), leistungstest_datum: isoDate.optional(), leistungstest_gueltig_bis: isoDate.optional(), leistungstest_bestanden: z.boolean().optional(), bemerkung: z.string().max(2000).optional(), }); export type CreateAtemschutzData = z.infer; export const UpdateAtemschutzSchema = z.object({ atemschutz_lehrgang: z.boolean().optional(), lehrgang_datum: isoDate.nullable().optional(), untersuchung_datum: isoDate.nullable().optional(), untersuchung_gueltig_bis: isoDate.nullable().optional(), untersuchung_ergebnis: z.enum(UNTERSUCHUNG_ERGEBNIS_VALUES).nullable().optional(), leistungstest_datum: isoDate.nullable().optional(), leistungstest_gueltig_bis: isoDate.nullable().optional(), leistungstest_bestanden: z.boolean().nullable().optional(), bemerkung: z.string().max(2000).nullable().optional(), }); export type UpdateAtemschutzData = z.infer;