// Von Hand gepflegt, passend zu db/migrations/*.sql. // // Erzeugt wird hier nichts: scripts/check-schema-types.mjs haelt die Datei // Spalte fuer Spalte gegen die Migrationen und meldet jede Abweichung. export type EmploymentStatus = "Aktiv" | "Karenz" | "Geplant" | "Ausgetreten"; export type EmploymentType = "Vollzeit" | "Teilzeit"; export type ContractType = "unbefristet" | "befristet"; export type PaygradeType = "A" | "B" | "C" | "D" | "E" | "F"; export type SourceType = "Intern" | "Extern"; export type GenderType = "m" | "w"; export type WorkerType = "Angestellte:r" | "Arbeiter:in"; export type CollectiveAgreement = "Handel" | "Süßwaren"; export type Weekday = "Mo" | "Di" | "Mi" | "Do" | "Fr" | "Sa" | "So"; /** * Antriebsart des Dienstwagens. * * Bewusst keine dritte Möglichkeit „unbekannt": die Angabe hängt per CHECK * an has_dienstwagen, und wer einen Dienstwagen hat, weiss auch, ob er lädt * oder tankt. */ export type DienstwagenArt = "Verbrenner" | "Elektro"; /** * Gesetzlich geregelte Teilzeiten. * * Keine Abwesenheiten: die Person arbeitet, nur kürzer. Als Zustand geführt, * damit sich auswerten lässt, wer gerade in einer ist — mit einem optionalen * Enddatum, weil nicht jede ein bekanntes Ende hat. */ export type TeilzeitArt = "Bildungsteilzeit" | "Elternteilzeit" | "Pflegeteilzeit" | "Wiedereingliederungsteilzeit"; /** * Verhältnis zum Notfallkontakt. * * Auswahlliste statt Freitext, damit sich danach auswerten lässt und nicht * „Gattin", „Ehefrau" und „Frau" nebeneinander stehen. „Sonstige" ist der * Ausweg für alles, was hier fehlt — ohne ihn wäre die Liste eine Anmassung. * * In der Datenbank bleibt die Spalte `text`: eine Aufzählung dort würde jede * Ergänzung zu einer Migration machen, und diese Liste wird sich ändern. */ export const EMERGENCY_RELATIONS = [ "Gattin/Gatte", "Lebensgefährtin/Lebensgefährte", "Mutter", "Vater", "Tochter", "Sohn", "Schwester", "Bruder", "Freundin/Freund", "Sonstige", ] as const; export type EmergencyRelation = (typeof EMERGENCY_RELATIONS)[number]; /** * Eine einzelne Feldänderung im Protokoll. * * `vorher` und `nachher` sind bewusst Text: die Datenbank stellt jeden Typ * über ::text dar, damit ein Datum, eine Zahl und eine Liste von Arbeitstagen * in derselben Spalte nebeneinander stehen können. Für die Anzeige reicht * das; gerechnet wird damit nicht. */ export type AuditChange = { feld: string; vorher: string | null; nachher: string | null }; export type RelationshipType = "Ehepartner:in" | "Lebenspartner:in" | "Kind" | "Sonstige"; export type NoteCategory = "Allgemein" | "Vertraulich" | "Personalgespräch" | "Wiedervorlage" | "Lob / Anerkennung"; // Single HR-only role (see docs/decisions/0001-hr-only-access.md). Kept as a // union (not a string literal) so a future hr_admin/hr_user split, if ever // technically required, is a type-level addition, not a rewrite. export type ProfileRole = "hr"; /** Etikett einer Organisationseinheit; die Struktur steckt in parent_id. */ export type OrgUnitType = "Gesellschaft" | "Bereich" | "Abteilung" | "Team"; export type HistoryEventType = | "Eintritt" | "Beförderung" | "Versetzung" | "Karenz" | "Vertragsänderung" | "Stammdatenänderung" | "Austritt" | "Wiedereintritt" | "Reorganisation" | "Gehaltsanpassung" | "Rückkehr"; export type PendingChangeType = | "transfer" | "promotion" | "karenz_start" | "karenz_return" | "contract_change" | "reorg"; export type PendingChangeStatus = "pending" | "applied" | "cancelled"; export type Database = { public: { Tables: { locations: { Row: { id: string; name: string; country: string }; Insert: { id?: string; name: string; country: string }; Update: Partial<{ id: string; name: string; country: string }>; }; profiles: { Row: { id: string; email: string; full_name: string | null; role: ProfileRole; is_active: boolean; created_by: string | null; created_at: string; updated_at: string; }; Insert: { id: string; email: string; full_name?: string | null; role?: ProfileRole; is_active?: boolean; created_by?: string | null; created_at?: string; updated_at?: string; }; Update: Partial<{ id: string; email: string; full_name: string | null; role: ProfileRole; is_active: boolean; created_by: string | null; created_at: string; updated_at: string; }>; }; employees: { Row: { id: string; personnel_number: number; first_name: string; last_name: string; gender: GenderType; birth_date: string; sv_nummer: string | null; nationality: string; /** Aufenthaltstitel — nur ausserhalb von EU, EWR und Schweiz erhoben. */ hat_aufenthaltstitel: boolean; /** Gültig bis, falls befristet. Nur mit dem Kennzeichen zusammen erlaubt. */ aufenthaltstitel_bis: string | null; address: string | null; postal_code: string | null; city: string | null; address_country: string | null; /** Private Adresse, freiwillig — eindeutig, wenn angegeben. */ email: string | null; phone: string | null; job_title: string; location_id: string; employment_type: EmploymentType; weekly_hours: number; /** @deprecated Salary is out of MVP scope; column kept only for pre-existing data. */ monthly_salary_gross: number | null; contract_type: ContractType; contract_end_date: string | null; paygrade: PaygradeType; source: SourceType; status: EmploymentStatus; entry_date: string; exit_date: string | null; exit_reason: string | null; karenz_start_date: string | null; karenz_return_date: string | null; absence_type: string | null; avatar_color: string | null; worker_type: WorkerType; collective_agreement: CollectiveAgreement; work_days: Weekday[]; is_betriebsrat: boolean; has_dienstwagen: boolean; /** Null genau dann, wenn kein Dienstwagen vorhanden ist — chk_dienstwagen_art. */ dienstwagen_art: DienstwagenArt | null; /** Besonderer Kündigungsschutz — löst beim Austritt eine Warnung aus. */ has_kuendigungsschutz: boolean; /** Ende des Schutzes, falls bekannt. Nur mit dem Kennzeichen zusammen erlaubt. */ kuendigungsschutz_bis: string | null; /** Bildungs-, Eltern-, Pflege- oder Wiedereingliederungsteilzeit; null bei einer gewöhnlichen Regelung. */ teilzeit_art: TeilzeitArt | null; /** Ende der Teilzeit, falls bekannt. Nur mit einer Variante zusammen erlaubt. */ teilzeit_bis: string | null; /** Nur gemeinsam gesetzt oder gemeinsam leer — chk_emergency_contact. */ emergency_contact_name: string | null; emergency_contact_phone: string | null; emergency_contact_relation: string | null; is_laterale_fuehrung: boolean; is_c_level: boolean; title_prefix: string[]; title_suffix: string[]; created_at: string; updated_at: string; }; Insert: { id?: string; first_name: string; last_name: string; gender: GenderType; birth_date: string; sv_nummer?: string | null; nationality?: string; hat_aufenthaltstitel?: boolean; aufenthaltstitel_bis?: string | null; address?: string | null; postal_code?: string | null; city?: string | null; address_country?: string | null; email?: string | null; phone?: string | null; job_title: string; location_id: string; employment_type?: EmploymentType; weekly_hours?: number; contract_type?: ContractType; contract_end_date?: string | null; paygrade?: PaygradeType; source?: SourceType; status?: EmploymentStatus; entry_date: string; exit_date?: string | null; exit_reason?: string | null; karenz_start_date?: string | null; karenz_return_date?: string | null; absence_type?: string | null; avatar_color?: string | null; worker_type?: WorkerType; collective_agreement?: CollectiveAgreement; work_days?: Weekday[]; is_betriebsrat?: boolean; has_dienstwagen?: boolean; dienstwagen_art?: DienstwagenArt | null; has_kuendigungsschutz?: boolean; kuendigungsschutz_bis?: string | null; teilzeit_art?: TeilzeitArt | null; teilzeit_bis?: string | null; emergency_contact_name?: string | null; emergency_contact_phone?: string | null; emergency_contact_relation?: string | null; is_laterale_fuehrung?: boolean; is_c_level?: boolean; title_prefix?: string[]; title_suffix?: string[]; created_at?: string; updated_at?: string; }; Update: Partial; }; employee_history: { Row: { id: string; employee_id: string; event_date: string; event_type: HistoryEventType; description: string; /** Feldweise Änderungen — dieselbe Form wie audit_log.changes. */ changes: AuditChange[] | null; /** Der geplante Vorgang, solange die Änderung noch nicht wirksam ist. */ pending_id: string | null; created_at: string; }; Insert: { id?: string; employee_id: string; event_date: string; event_type: HistoryEventType; description: string; changes?: AuditChange[] | null; pending_id?: string | null; created_at?: string; }; Update: Partial; }; employee_dependents: { Row: { id: string; employee_id: string; first_name: string; last_name: string; relationship: RelationshipType; sv_nummer: string | null; birth_date: string; created_at: string; }; Insert: { id?: string; employee_id: string; first_name: string; last_name: string; relationship: RelationshipType; sv_nummer?: string | null; birth_date: string; created_at?: string; }; Update: Partial; }; employee_notes: { Row: { id: string; employee_id: string; author_user_id: string | null; author_name: string; category: NoteCategory; note_text: string; due_date: string | null; done: boolean; done_at: string | null; done_by: string | null; created_at: string; }; Insert: { id?: string; employee_id: string; author_user_id?: string | null; author_name: string; category?: NoteCategory; note_text: string; due_date?: string | null; done?: boolean; done_at?: string | null; done_by?: string | null; created_at?: string; }; Update: Partial; }; hire_drafts: { Row: { id: string; created_by: string | null; step: number; payload: Record; updated_at: string }; Insert: { id?: string; created_by?: string | null; step?: number; payload: Record; updated_at?: string }; Update: Partial; }; saved_reports: { Row: { id: string; created_by: string | null; name: string; config: Record; created_at: string }; Insert: { id?: string; created_by?: string | null; name: string; config: Record; created_at?: string }; Update: Partial; }; audit_log: { Row: { id: string; occurred_at: string; actor_user_id: string | null; actor_name: string; action: string; target_label: string; target_employee_id: string | null; details: string | null; /** * Feldweise Änderungen. Null bei Einträgen aus der Zeit vor * 20260803140000_audit_changes_detail.sql — dort wurden nur die * Feldnamen behalten, nicht die Werte, und das lässt sich nicht * nachliefern. */ changes: AuditChange[] | null; }; Insert: { id?: string; occurred_at?: string; actor_user_id?: string | null; actor_name: string; action: string; target_label: string; target_employee_id?: string | null; details?: string | null; changes?: AuditChange[] | null; }; Update: Partial; }; pending_org_changes: { Row: { id: string; employee_id: string; change_type: PendingChangeType; effective_date: string; payload: Record; status: PendingChangeStatus; created_by: string | null; created_at: string; applied_at: string | null; }; Insert: { id?: string; employee_id: string; change_type: PendingChangeType; effective_date: string; payload: Record; status?: PendingChangeStatus; created_by?: string | null; created_at?: string; applied_at?: string | null; }; Update: Partial; }; // ── SAP-OM-Modell ────────────────────────────────────────── // O: rekursiv über parent_id, unit_type ist nur ein Etikett. org_units: { Row: { id: string; org_number: string; name: string; parent_id: string | null; unit_type: OrgUnitType; valid_from: string; valid_to: string | null; created_at: string; }; Insert: { id?: string; org_number: string; name: string; parent_id?: string | null; unit_type: OrgUnitType; valid_from?: string; valid_to?: string | null; created_at?: string; }; Update: Partial; }; // C: Katalog der Tätigkeiten. jobs: { Row: { id: string; code: string; title: string; created_at: string }; Insert: { id?: string; code: string; title: string; created_at?: string }; Update: Partial; }; // S: Planstelle. Der Name om_positions stammt aus der Zeit, in der die // alte positions-Tabelle noch danebenstand; sie ist inzwischen weg. om_positions: { Row: { id: string; position_number: string; org_unit_id: string; job_id: string; is_chief: boolean; valid_from: string; valid_to: string | null; created_at: string; }; Insert: { id?: string; position_number: string; org_unit_id: string; job_id: string; is_chief?: boolean; valid_from?: string; valid_to?: string | null; created_at?: string; }; Update: Partial; }; // Die Onboarding-Checkliste: je Person und Punkt eine Zeile. Welche // Punkte es gibt, steht in lib/onboarding.ts — nicht hier. onboarding_tasks: { Row: { id: string; employee_id: string; item_key: string; erledigt: boolean; wert: string | null; kommentar: string | null; created_at: string; updated_at: string; updated_by: string | null; updated_by_name: string | null; }; Insert: { id?: string; employee_id: string; item_key: string; erledigt?: boolean; wert?: string | null; kommentar?: string | null; created_at?: string; updated_at?: string; updated_by?: string | null; updated_by_name?: string | null; }; Update: Partial; }; // Die Offboarding-Checkliste — dieselbe Form wie onboarding_tasks, für // den anderen Weg. Punkte in lib/offboarding.ts. offboarding_tasks: { Row: { id: string; employee_id: string; item_key: string; erledigt: boolean; wert: string | null; kommentar: string | null; created_at: string; updated_at: string; updated_by: string | null; updated_by_name: string | null; }; Insert: { id?: string; employee_id: string; item_key: string; erledigt?: boolean; wert?: string | null; kommentar?: string | null; created_at?: string; updated_at?: string; updated_by?: string | null; updated_by_name?: string | null; }; Update: Partial; }; // Kostenstellen. Die Zuordnung hängt an der Planstelle, nicht an der // Person: der Sitz kostet Geld, auch wenn niemand darauf sitzt. cost_centers: { Row: { id: string; code: string; name: string; org_unit_id: string | null; valid_from: string; valid_to: string | null; created_at: string; }; Insert: { id?: string; code: string; name: string; org_unit_id?: string | null; valid_from?: string; valid_to?: string | null; created_at?: string; }; Update: Partial; }; // A011: Planstelle kontiert auf Kostenstelle, zeitabhängig. position_cost_centers: { Row: { id: string; position_id: string; cost_center_id: string; valid_from: string; valid_to: string | null; created_at: string; }; Insert: { id?: string; position_id: string; cost_center_id: string; valid_from: string; valid_to?: string | null; created_at?: string; }; Update: Partial; }; // A008: Person besetzt Planstelle, zeitabhängig. position_assignments: { Row: { id: string; position_id: string; employee_id: string; valid_from: string; valid_to: string | null; created_at: string; }; Insert: { id?: string; position_id: string; employee_id: string; valid_from: string; valid_to?: string | null; created_at?: string; }; Update: Partial; // Beide Richtungen: über die Planstelle hängt die Verortung in der // Organisation, über die Person die Verortung in der Akte. Die // Einbettung erspart an einem Dutzend Stellen eine zweite Abfrage. }; }; Views: Record; Functions: { hire_employee: { Args: { payload: Record }; Returns: string }; terminate_employee: { Args: { payload: Record }; Returns: void }; transfer_employee: { Args: { payload: Record }; Returns: void }; promote_employee: { Args: { payload: Record }; Returns: void }; start_karenz: { Args: { payload: Record }; Returns: void }; adjust_karenz_return: { Args: { payload: Record }; Returns: void }; record_karenz_return: { Args: { payload: Record }; Returns: void }; change_employee_data: { Args: { payload: Record }; Returns: void }; rehire_employee: { Args: { payload: Record }; Returns: void }; add_employee_dependent: { Args: { payload: Record }; Returns: void }; delete_employee_dependent: { Args: { payload: Record }; Returns: void }; add_employee_note: { Args: { payload: Record }; Returns: string }; complete_employee_note: { Args: { payload: Record }; Returns: void }; // Eigenes Passwort wechseln. Die einzige mutierende Funktion **ohne** // require_hr_admin() — sonst käme niemand aus dem erzwungenen Wechsel // heraus, denn solange er aussteht, ist is_hr_user() false. app_passwort_aendern: { Args: { payload: Record }; Returns: void }; // Nimmt eine irrtümliche Stammdaten-/Vertragsänderung zurück. Der einzige // Weg an der fehlenden delete-Policy auf employee_history vorbei. delete_history_entry: { Args: { payload: Record }; Returns: void }; // Berichtigt Wert und/oder Datum eines Historieneintrags. update_history_entry: { Args: { payload: Record }; Returns: void }; // Planstelle anlegen bzw. schliessen — im OM-Modell Operationen auf // om_positions, nicht mehr auf einer eigenen Ausschreibungstabelle. create_position: { Args: { payload: Record }; Returns: string }; update_position: { Args: { payload: Record }; Returns: void }; set_position_cost_center: { Args: { payload: Record }; Returns: void }; set_onboarding_task: { Args: { payload: Record }; Returns: void }; start_onboarding: { Args: { payload: Record }; Returns: number }; set_offboarding_task: { Args: { payload: Record }; Returns: void }; start_offboarding: { Args: { payload: Record }; Returns: number }; delete_position: { Args: { payload: Record }; Returns: void }; is_valid_svnr: { Args: { p_svnr: string; p_birth_date?: string | null }; Returns: boolean }; apply_due_pending_changes: { Args: Record; Returns: number }; om_reporting_lines: { Args: { p_as_of?: string }; Returns: { employee_id: string; position_id: string; org_unit_id: string; is_chief: boolean; formal_manager_id: string | null; acting_manager_id: string | null; }[]; }; }; }; };