Make the part-time arrangement a state you can report on
Last step moved the four part-time arrangements out of the absence list and recorded the reason in the history text. That answered "what happened" but not "who is in one right now", and the profile showed nothing at all. So it becomes a real field: teilzeit_art, with an optional end date. The objection I raised then still holds — a state goes stale, because nobody goes back to note when a Bildungsteilzeit ended. teilzeit_bis is the answer to it: with an end date a report decides for itself what is still running instead of trusting that someone maintained the row. Left empty it means "open end", which is an honest thing to say. It runs through the ordinary change machinery rather than beside it. It sits in app_feld_karte, so it shows up in the history as a field with before and after, and can be corrected there like any other. The description suffix from last step is gone — writing the same thing twice is how two versions start disagreeing. Reporting: filter by variant, by "in one at all", and by when it ends; group headcount by variant, where the absence of one reads "Keine" rather than a dash, because in a report that is an answer and not a gap. Plus columns in the export and the import. One gap found while rehearsing, and only because the probe happened to pick a return date in the future: a scheduled return carries its payload through pending_org_changes, and that payload did not include the variant. Someone would have come back on reduced hours in April with the reason gone. The daily run now carries it too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,12 @@ export const AUSWAHL_KRITERIEN: readonly AuswahlKriterium[] = [
|
||||
},
|
||||
{ key: "dienstwagenArt", label: "Antriebsart Dienstwagen", alle: "Alle Antriebsarten", optionen: werte("Verbrenner", "Elektro") },
|
||||
{ key: "absence", label: "Art der Langzeitabwesenheit", alle: "Alle Arten", optionen: werte(...ABSENCE_TYPES) },
|
||||
{
|
||||
key: "teilzeitArt",
|
||||
label: "Teilzeitvariante",
|
||||
alle: "Alle Teilzeitvarianten",
|
||||
optionen: werte("Bildungsteilzeit", "Elternteilzeit", "Pflegeteilzeit", "Wiedereingliederungsteilzeit"),
|
||||
},
|
||||
{ key: "weekday", label: "Arbeitet am", alle: "Beliebiger Wochentag", optionen: werte("Mo", "Di", "Mi", "Do", "Fr", "Sa", "So") },
|
||||
] as const;
|
||||
|
||||
@@ -70,6 +76,7 @@ export const JANEIN_KRITERIEN: readonly JaNeinKriterium[] = [
|
||||
{ key: "lateral", label: "Laterale Führung" },
|
||||
{ key: "clevel", label: "C-Level" },
|
||||
{ key: "kuendigungsschutz", label: "Besonderer Kündigungsschutz" },
|
||||
{ key: "teilzeit", label: "In einer Teilzeitvariante" },
|
||||
{ key: "dependents", label: "Angehörige erfasst" },
|
||||
] as const;
|
||||
|
||||
@@ -81,6 +88,7 @@ export const ZEITRAUM_KRITERIEN: readonly ZeitraumKriterium[] = [
|
||||
{ key: "exit", label: "Austritt" },
|
||||
{ key: "birth", label: "Geburtsdatum" },
|
||||
{ key: "schutz", label: "Kündigungsschutz bis" },
|
||||
{ key: "teilzeitEnde", label: "Teilzeit endet" },
|
||||
] as const;
|
||||
|
||||
export type Criteria = {
|
||||
|
||||
@@ -23,6 +23,7 @@ import type {
|
||||
HistoryEventType,
|
||||
PaygradeType,
|
||||
SourceType,
|
||||
TeilzeitArt,
|
||||
WorkerType,
|
||||
} from "./supabase/types";
|
||||
|
||||
@@ -63,6 +64,7 @@ export function applyCriteria<O>(
|
||||
if (a.source) q = q.where("source", "=", a.source as SourceType);
|
||||
if (a.gender) q = q.where("gender", "=", a.gender as GenderType);
|
||||
if (a.dienstwagenArt) q = q.where("dienstwagen_art", "=", a.dienstwagenArt as DienstwagenArt);
|
||||
if (a.teilzeitArt) q = q.where("teilzeit_art", "=", a.teilzeitArt as TeilzeitArt);
|
||||
if (a.absence) q = q.where("absence_type", "=", a.absence);
|
||||
|
||||
const j = c.jaNein;
|
||||
@@ -71,6 +73,9 @@ export function applyCriteria<O>(
|
||||
if (j.lateral) q = q.where("is_laterale_fuehrung", "=", j.lateral === "ja");
|
||||
if (j.clevel) q = q.where("is_c_level", "=", j.clevel === "ja");
|
||||
if (j.kuendigungsschutz) q = q.where("has_kuendigungsschutz", "=", j.kuendigungsschutz === "ja");
|
||||
// „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);
|
||||
|
||||
// Ein Austrittszeitraum schliesst alle ohne Austritt aus — ein Vergleich
|
||||
// mit NULL ist nicht wahr, und das ist hier genau die richtige Bedeutung.
|
||||
@@ -82,6 +87,8 @@ export function applyCriteria<O>(
|
||||
if (c.zeitraum.birth?.bis) q = q.where("birth_date", "<=", c.zeitraum.birth.bis);
|
||||
if (c.zeitraum.schutz?.von) q = q.where("kuendigungsschutz_bis", ">=", c.zeitraum.schutz.von);
|
||||
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);
|
||||
|
||||
if (c.stundenVon !== undefined) q = q.where("weekly_hours", ">=", c.stundenVon);
|
||||
if (c.stundenBis !== undefined) q = q.where("weekly_hours", "<=", c.stundenBis);
|
||||
@@ -162,6 +169,7 @@ const SNAPSHOT_EMPLOYEE_COLUMNS = [
|
||||
"has_dienstwagen",
|
||||
"is_laterale_fuehrung",
|
||||
"is_c_level",
|
||||
"teilzeit_art",
|
||||
] as const;
|
||||
|
||||
// Anzahl der Angehörigen je Person. Nur der Fremdschlüssel wird gelesen —
|
||||
@@ -233,6 +241,7 @@ export async function loadSnapshotEmployees(tx: Tx, filters: SnapshotFilters): P
|
||||
has_dienstwagen: e.has_dienstwagen,
|
||||
is_laterale_fuehrung: e.is_laterale_fuehrung,
|
||||
is_c_level: e.is_c_level,
|
||||
teilzeit_art: e.teilzeit_art,
|
||||
dependents_count: dependentsCounts.get(e.id) ?? 0,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export type GroupDimension =
|
||||
| "laterale_fuehrung"
|
||||
| "c_level"
|
||||
| "has_dependents"
|
||||
| "teilzeit_art"
|
||||
| "weekday";
|
||||
|
||||
export const MEASURE_LABELS: Record<Measure, string> = {
|
||||
@@ -55,6 +56,7 @@ export const GROUP_LABELS: Record<GroupDimension, string> = {
|
||||
laterale_fuehrung: "Laterale Führung",
|
||||
c_level: "C-Level",
|
||||
has_dependents: "Hat Angehörige",
|
||||
teilzeit_art: "Teilzeitvariante",
|
||||
weekday: "Wochentag",
|
||||
};
|
||||
|
||||
@@ -101,6 +103,7 @@ export type ReportEmployee = {
|
||||
has_dienstwagen: boolean;
|
||||
is_laterale_fuehrung: boolean;
|
||||
is_c_level: boolean;
|
||||
teilzeit_art: string | null;
|
||||
dependents_count: number;
|
||||
};
|
||||
|
||||
@@ -182,6 +185,9 @@ export function groupKeyFor(e: ReportEmployee, dim: GroupDimension, lookups: Org
|
||||
return e.is_c_level ? "Ja" : "Nein";
|
||||
case "has_dependents":
|
||||
return e.dependents_count > 0 ? "Ja" : "Nein";
|
||||
case "teilzeit_art":
|
||||
// „Keine" statt „–": die Antwort ist hier eine Aussage, kein Fehlen.
|
||||
return e.teilzeit_art ?? "Keine";
|
||||
case "weekday":
|
||||
// Not a strict partition — see groupKeysFor, which aggregateReport
|
||||
// actually uses. This single-key fallback only covers a direct
|
||||
|
||||
@@ -22,6 +22,15 @@ export type Weekday = "Mo" | "Di" | "Mi" | "Do" | "Fr" | "Sa" | "So";
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -176,6 +185,10 @@ export type Database = {
|
||||
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;
|
||||
@@ -225,6 +238,8 @@ export type Database = {
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user