Ask for a residence permit only where one is needed
Employees from outside the EU, the EEA and Switzerland need a residence permit, and HR needs to know when it expires — about eighty people in the current data, across Türkei, Serbien and Bosnien. Two columns, the same shape as the dismissal protection: a flag and a date that only means anything with it. The date is optional, because an open-ended permit has none and a mandatory field would force an invented one. The nationality coupling deliberately stays out of the database. Putting it there would mean keeping the country list in two places — SQL and lib/countries.ts, where the picker needs it anyway — so an EU accession would become a migration instead of a line in a list. Worse, correcting somebody's nationality would fail the constraint while the old permit was still attached, which is exactly the moment someone is fixing a mistake. The UI decides whether the fields appear, and clears them when the nationality moves into the free-movement area. So the list is the load-bearing part, and it is tested: 31 entries, all of them values the picker can actually produce, no duplicates, no third countries. A missing nationality reads as "no permit required" — an unanswered question is a reason to record it, not to demand papers. The permit shows on the Stammdaten tab only for the nationalities it applies to. A line reading "Aufenthaltstitel: Nein" under an Austrian citizenship would look like information rather than a question that does not arise. Filter by it and by when it expires — the question behind that being "whose permit runs out next quarter" — plus columns in the export. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -197,3 +197,63 @@ export const UN_COUNTRIES: string[] = [
|
||||
"Zentralafrikanische Republik",
|
||||
"Zypern",
|
||||
].sort((a, b) => a.localeCompare(b, "de"));
|
||||
|
||||
// Wer keinen Aufenthaltstitel braucht.
|
||||
//
|
||||
// EU-27, dazu Island, Liechtenstein und Norwegen (EWR) sowie die Schweiz.
|
||||
// Für diese Staatsangehörigen gilt Freizügigkeit; ein Aufenthaltstitel ist
|
||||
// weder nötig noch zu erfassen. Für alle übrigen ist er es, und dann will HR
|
||||
// wissen, bis wann er läuft.
|
||||
//
|
||||
// Die Liste steht hier und nicht in der Datenbank: eine Prüfbedingung, die
|
||||
// Staatsbürgerschaft und Titel koppelt, würde bei jeder Korrektur der
|
||||
// Staatsbürgerschaft zuschlagen — und Beitritte müssten als Migration
|
||||
// nachgezogen werden, statt als Zeile in einer Liste.
|
||||
export const FREIZUEGIGKEIT: readonly string[] = [
|
||||
// EU
|
||||
"Belgien",
|
||||
"Bulgarien",
|
||||
"Dänemark",
|
||||
"Deutschland",
|
||||
"Estland",
|
||||
"Finnland",
|
||||
"Frankreich",
|
||||
"Griechenland",
|
||||
"Irland",
|
||||
"Italien",
|
||||
"Kroatien",
|
||||
"Lettland",
|
||||
"Litauen",
|
||||
"Luxemburg",
|
||||
"Malta",
|
||||
"Niederlande",
|
||||
"Österreich",
|
||||
"Polen",
|
||||
"Portugal",
|
||||
"Rumänien",
|
||||
"Schweden",
|
||||
"Slowakei",
|
||||
"Slowenien",
|
||||
"Spanien",
|
||||
"Tschechien",
|
||||
"Ungarn",
|
||||
"Zypern",
|
||||
// EWR
|
||||
"Island",
|
||||
"Liechtenstein",
|
||||
"Norwegen",
|
||||
// Bilaterale Freizügigkeit
|
||||
"Schweiz",
|
||||
];
|
||||
|
||||
/**
|
||||
* Braucht jemand mit dieser Staatsbürgerschaft einen Aufenthaltstitel?
|
||||
*
|
||||
* Bei leerer Angabe: nein. Eine fehlende Staatsbürgerschaft ist kein Anlass,
|
||||
* jemandem einen Titel abzuverlangen — sie ist ein Anlass, sie zu erfassen.
|
||||
*/
|
||||
export function brauchtAufenthaltstitel(nationality: string | null | undefined): boolean {
|
||||
const n = nationality?.trim();
|
||||
if (!n) return false;
|
||||
return !FREIZUEGIGKEIT.includes(n);
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ export const JANEIN_KRITERIEN: readonly JaNeinKriterium[] = [
|
||||
{ key: "clevel", label: "C-Level" },
|
||||
{ key: "kuendigungsschutz", label: "Besonderer Kündigungsschutz" },
|
||||
{ key: "teilzeit", label: "In einer Teilzeitvariante" },
|
||||
{ key: "aufenthaltstitel", label: "Aufenthaltstitel" },
|
||||
{ key: "dependents", label: "Angehörige erfasst" },
|
||||
] as const;
|
||||
|
||||
@@ -89,6 +90,7 @@ export const ZEITRAUM_KRITERIEN: readonly ZeitraumKriterium[] = [
|
||||
{ key: "birth", label: "Geburtsdatum" },
|
||||
{ key: "schutz", label: "Kündigungsschutz bis" },
|
||||
{ key: "teilzeitEnde", label: "Teilzeit endet" },
|
||||
{ key: "titelEnde", label: "Aufenthaltstitel läuft ab" },
|
||||
] as const;
|
||||
|
||||
export type Criteria = {
|
||||
|
||||
@@ -76,6 +76,7 @@ export function applyCriteria<O>(
|
||||
// „In einer Teilzeitvariante" heisst: es steht eine drin, gleich welche.
|
||||
if (j.teilzeit === "ja") q = q.where("teilzeit_art", "is not", null);
|
||||
if (j.teilzeit === "nein") q = q.where("teilzeit_art", "is", null);
|
||||
if (j.aufenthaltstitel) q = q.where("hat_aufenthaltstitel", "=", j.aufenthaltstitel === "ja");
|
||||
|
||||
// Ein Austrittszeitraum schliesst alle ohne Austritt aus — ein Vergleich
|
||||
// mit NULL ist nicht wahr, und das ist hier genau die richtige Bedeutung.
|
||||
@@ -89,6 +90,10 @@ export function applyCriteria<O>(
|
||||
if (c.zeitraum.schutz?.bis) q = q.where("kuendigungsschutz_bis", "<=", c.zeitraum.schutz.bis);
|
||||
if (c.zeitraum.teilzeitEnde?.von) q = q.where("teilzeit_bis", ">=", c.zeitraum.teilzeitEnde.von);
|
||||
if (c.zeitraum.teilzeitEnde?.bis) q = q.where("teilzeit_bis", "<=", c.zeitraum.teilzeitEnde.bis);
|
||||
// Wer bis wann einen Titel hat — die Frage hinter „wessen Titel läuft im
|
||||
// nächsten Quartal aus".
|
||||
if (c.zeitraum.titelEnde?.von) q = q.where("aufenthaltstitel_bis", ">=", c.zeitraum.titelEnde.von);
|
||||
if (c.zeitraum.titelEnde?.bis) q = q.where("aufenthaltstitel_bis", "<=", c.zeitraum.titelEnde.bis);
|
||||
|
||||
if (c.stundenVon !== undefined) q = q.where("weekly_hours", ">=", c.stundenVon);
|
||||
if (c.stundenBis !== undefined) q = q.where("weekly_hours", "<=", c.stundenBis);
|
||||
|
||||
@@ -149,6 +149,10 @@ export type Database = {
|
||||
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;
|
||||
@@ -208,6 +212,8 @@ export type Database = {
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user