Files
alpenwerk-hr/lib/wiedereintritt.ts
Andrei Laas 57acbfae9d
All checks were successful
CI / Lint, Typen, Tests, Build (push) Successful in 11m29s
CI / Migrationen auf leerer Datenbank (push) Successful in 10m27s
Die Firmen-E-Mail als eigenes, freiwilliges Feld
employees.email ist die private Adresse (20260811140000). Sie taugt nicht
als Dienstadresse und darf auch nicht als solche benutzt werden: der
Honestly-Export geht an einen fremden Anbieter, der damit im Namen des
Arbeitgebers einlaedt. Die Spalte "Email" stand dort deshalb seit jeher
leer, mit einem Vermerk in lib/honestly.ts, dass die Firmenadresse im
Datenmodell fehlt. Jetzt gibt es sie, und die Spalte fuellt sich.

Eindeutig, aber freiwillig -- mehrere Personen ohne Adresse stoeren den
Index nicht, weil null nie gleich null ist. Geschrieben wird ueber
`case when ? then nullif` statt `coalesce`: eine Dienstadresse muss sich
auch wieder entfernen lassen.

Vier SQL-Funktionen mussten mit, weil `create or replace` die ganze
Fassung ersetzt und ein ausgelassenes Feld dort still verschwindet:
hire_employee und rehire_employee (beide teilen sich den Schritt
"Person" -- das Formular haette das Feld gezeigt und den Wert
weggeworfen), change_employee_data (sonst nicht aenderbar),
apply_due_pending_changes (sonst verfiele eine auf spaeter datierte
Aenderung) und die Feldkarte (sonst waere der Eintrag in der Historie
nicht korrigierbar). Die Selbstpruefung am Ende prueft jede einzeln.
2026-09-23 10:52:26 +02:00

93 lines
4.6 KiB
TypeScript

import type { HireDraftData } from "@/components/hire/types";
import { MITARBEITERART_STANDARD } from "./mitarbeiterart";
import type { Database } from "./types";
type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"];
// Eine bestehende Person als Vorbelegung für den Wiedereintritt.
//
// Der Kunde wollte für die Wiedereinstellung denselben Assistenten wie für
// eine Neueinstellung, „bereits mit den hier festgelegten Daten
// prepopulated". Diese Abbildung ist genau das: die Zeile der Person in die
// Form, die der Assistent führt.
//
// ── Warum genau die Felder des Assistenten und keines mehr ──────────
//
// Der Rückgabetyp ist `Partial<HireDraftData>` und nicht ein eigener. Damit
// lässt sich hier nur eintragen, was der Assistent auch zeigt — und das ist
// die Bedingung dafür, dass er nichts verschickt, was niemand gesehen hat.
//
// Konkret fehlen Anschrift, Staatsbürgerschaft und Aufenthaltstitel: der
// Assistent fragt sie auch bei einer Neueinstellung nicht (sie werden später
// über „Daten ändern" gepflegt). `rehire_employee` lässt jedes nicht
// übermittelte Feld unangetastet stehen, sie bleiben also, wie sie sind.
//
// ── Was bewusst NICHT übernommen wird ───────────────────────────────
//
// * **Die Planstelle.** Sie ist beim Wiedereintritt immer neu zu wählen:
// die alte kann besetzt, ausgelaufen oder ganz entfallen sein. Ein
// vorbelegter Platz, den es so nicht mehr gibt, wäre schlimmer als ein
// leeres Feld — er sieht nach einer Antwort aus.
// * **Das Eintrittsdatum.** Der alte Eintritt ist Geschichte; das neue
// Datum ist die eigentliche Angabe dieses Vorgangs.
// * **Der Austritt.** Datum, Grund und Austrittsart räumt
// `rehire_employee` weg.
// * **Die Angehörigen.** Sie stehen schon in der Akte; der Assistent würde
// sie ein zweites Mal anlegen.
//
// Die Personalnummer kommt mit, aber nur zur Anzeige: es ist dieselbe Person,
// und `rehire_employee` fasst die Spalte nicht an.
export function vorbelegungAus(employee: EmployeeRow): Partial<HireDraftData> {
return {
personnelNumber: String(employee.personnel_number),
firstName: employee.first_name,
lastName: employee.last_name,
titlePrefix: employee.title_prefix ?? [],
titleSuffix: employee.title_suffix ?? [],
gender: employee.gender,
birthDate: employee.birth_date,
svNummer: employee.sv_nummer ?? "",
email: employee.email ?? "",
// Vorbelegt wie die private Adresse, obwohl eine Dienstadresse beim
// Austritt meist abgedreht wird: der Assistent schickt das Feld in jedem
// Fall mit, und leer geliefert löschte es die gespeicherte Adresse
// stillschweigend. Sichtbar vorbelegt lässt sie sich bewusst ersetzen
// oder leeren.
companyEmail: employee.company_email ?? "",
phone: employee.phone ?? "",
locationId: employee.location_id,
besetzung: employee.source,
contractType: employee.contract_type,
contractEndDate: employee.contract_end_date ?? "",
employmentType: employee.employment_type,
// Als Text, weil das Feld im Assistenten eines ist: ein leeres Zahlenfeld
// landete sonst als 0 im Entwurf.
weeklyHours: String(employee.weekly_hours),
paygrade: employee.paygrade,
workerType: employee.worker_type,
mitarbeiterart: employee.mitarbeiterart ?? MITARBEITERART_STANDARD,
collectiveAgreement: employee.collective_agreement,
workDays: employee.work_days,
isBetriebsrat: employee.is_betriebsrat,
hasDienstwagen: employee.has_dienstwagen,
// Die Antriebsart steht auf null, sobald kein Wagen da ist. Der Assistent
// führt sie trotzdem mit, damit sie beim Anhaken wieder da ist —
// „Verbrenner" ist dabei die Vorgabe, nicht eine Behauptung.
dienstwagenArt: employee.dienstwagen_art ?? "Verbrenner",
isLateraleFuehrung: employee.is_laterale_fuehrung,
isCLevel: employee.is_c_level,
emergencyContactName: employee.emergency_contact_name ?? "",
emergencyContactPhone: employee.emergency_contact_phone ?? "",
emergencyContactRelation: employee.emergency_contact_relation ?? "",
hasKuendigungsschutz: employee.has_kuendigungsschutz,
kuendigungsschutzGrund: employee.kuendigungsschutz_grund ?? "",
kuendigungsschutzAb: employee.kuendigungsschutz_ab ?? "",
kuendigungsschutzBis: employee.kuendigungsschutz_bis ?? "",
istBeguenstigtBehindert: employee.ist_beguenstigt_behindert,
behinderungGrad: employee.behinderung_grad === null ? "" : String(employee.behinderung_grad),
behinderungAb: employee.behinderung_ab ?? "",
behinderungBis: employee.behinderung_bis ?? "",
};
}