Files
alpenwerk-hr/lib/types.ts
Andrei Laas c6cff9656e
Some checks failed
CI / Lint, Typen, Tests, Build (push) Failing after 5m27s
CI / Migrationen auf leerer Datenbank (push) Successful in 10m3s
Passwort-Anmeldung: Provider, Formulare, drei Zustaende der Shell
2026-09-08 16:51:14 +02:00

630 lines
24 KiB
TypeScript

// 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<Database["public"]["Tables"]["employees"]["Insert"]>;
};
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<Database["public"]["Tables"]["employee_history"]["Insert"]>;
};
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<Database["public"]["Tables"]["employee_dependents"]["Insert"]>;
};
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<Database["public"]["Tables"]["employee_notes"]["Insert"]>;
};
hire_drafts: {
Row: { id: string; created_by: string | null; step: number; payload: Record<string, unknown>; updated_at: string };
Insert: { id?: string; created_by?: string | null; step?: number; payload: Record<string, unknown>; updated_at?: string };
Update: Partial<Database["public"]["Tables"]["hire_drafts"]["Insert"]>;
};
saved_reports: {
Row: { id: string; created_by: string | null; name: string; config: Record<string, unknown>; created_at: string };
Insert: { id?: string; created_by?: string | null; name: string; config: Record<string, unknown>; created_at?: string };
Update: Partial<Database["public"]["Tables"]["saved_reports"]["Insert"]>;
};
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<Database["public"]["Tables"]["audit_log"]["Insert"]>;
};
pending_org_changes: {
Row: {
id: string;
employee_id: string;
change_type: PendingChangeType;
effective_date: string;
payload: Record<string, unknown>;
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<string, unknown>;
status?: PendingChangeStatus;
created_by?: string | null;
created_at?: string;
applied_at?: string | null;
};
Update: Partial<Database["public"]["Tables"]["pending_org_changes"]["Insert"]>;
};
// ── 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<Database["public"]["Tables"]["org_units"]["Insert"]>;
};
// 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<Database["public"]["Tables"]["jobs"]["Insert"]>;
};
// 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<Database["public"]["Tables"]["om_positions"]["Insert"]>;
};
// 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<Database["public"]["Tables"]["onboarding_tasks"]["Insert"]>;
};
// 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<Database["public"]["Tables"]["offboarding_tasks"]["Insert"]>;
};
// 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<Database["public"]["Tables"]["cost_centers"]["Insert"]>;
};
// 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<Database["public"]["Tables"]["position_cost_centers"]["Insert"]>;
};
// 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<Database["public"]["Tables"]["position_assignments"]["Insert"]>;
// 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<string, never>;
Functions: {
hire_employee: { Args: { payload: Record<string, unknown> }; Returns: string };
terminate_employee: { Args: { payload: Record<string, unknown> }; Returns: void };
transfer_employee: { Args: { payload: Record<string, unknown> }; Returns: void };
promote_employee: { Args: { payload: Record<string, unknown> }; Returns: void };
start_karenz: { Args: { payload: Record<string, unknown> }; Returns: void };
adjust_karenz_return: { Args: { payload: Record<string, unknown> }; Returns: void };
record_karenz_return: { Args: { payload: Record<string, unknown> }; Returns: void };
change_employee_data: { Args: { payload: Record<string, unknown> }; Returns: void };
rehire_employee: { Args: { payload: Record<string, unknown> }; Returns: void };
add_employee_dependent: { Args: { payload: Record<string, unknown> }; Returns: void };
delete_employee_dependent: { Args: { payload: Record<string, unknown> }; Returns: void };
add_employee_note: { Args: { payload: Record<string, unknown> }; Returns: string };
complete_employee_note: { Args: { payload: Record<string, unknown> }; 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<string, unknown> }; 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<string, unknown> }; Returns: void };
// Berichtigt Wert und/oder Datum eines Historieneintrags.
update_history_entry: { Args: { payload: Record<string, unknown> }; Returns: void };
// Planstelle anlegen bzw. schliessen — im OM-Modell Operationen auf
// om_positions, nicht mehr auf einer eigenen Ausschreibungstabelle.
create_position: { Args: { payload: Record<string, unknown> }; Returns: string };
update_position: { Args: { payload: Record<string, unknown> }; Returns: void };
set_position_cost_center: { Args: { payload: Record<string, unknown> }; Returns: void };
set_onboarding_task: { Args: { payload: Record<string, unknown> }; Returns: void };
start_onboarding: { Args: { payload: Record<string, unknown> }; Returns: number };
set_offboarding_task: { Args: { payload: Record<string, unknown> }; Returns: void };
start_offboarding: { Args: { payload: Record<string, unknown> }; Returns: number };
delete_position: { Args: { payload: Record<string, unknown> }; Returns: void };
is_valid_svnr: { Args: { p_svnr: string; p_birth_date?: string | null }; Returns: boolean };
apply_due_pending_changes: { Args: Record<string, never>; 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;
}[];
};
};
};
};