Der Status kommt aus den Daten, nicht aus der Spalte

Die Liste filterte ueber die Datumsspalten, beschriftete die Zeilen aber mit
employees.status. Sobald die Spalte nachhaengt, widersprechen sich die
beiden — und sie haengt regelmaessig nach: terminate_employee setzt sie nur,
wenn das Austrittsdatum nicht in der Zukunft liegt, und es gibt keinen Lauf,
der das spaeter nachzieht (Migration 20260814100000 sagt das selbst).

Beim Kunden waren beide Richtungen zu sehen. Der Filter "Ausgetreten" fand
48 Personen, von denen mehrere als "Aktiv" beschriftet waren; der Filter
"Geplant" zeigte Nichtantritte, deren Spalte laengst "Ausgetreten" trug.

StatusChip nimmt deshalb jetzt die Zeile und den Stichtag und leitet selbst
ab. Die Spalte laesst sich nicht mehr hineinreichen — die zweite Quelle ist
nicht bloss ungenutzt, es gibt sie an dieser Stelle nicht mehr.

Dazu drei Stellen, die an derselben Spalte hingen:

  * Die Akte entschied mit ihr ueber die Knoepfe. An einer Person, die seit
    zwei Wochen ausgetreten ist, stand "Austritt" weiter zur Verfuegung.
  * Die Sortierung nach Status ordnete nach einem Wert, der nirgends auf der
    Seite steht.
  * Die Karte "Anstehend" zaehlte kuenftige Eintritte und Rueckkehren ueber
    die Spalte und damit anders als die Liste, auf die sie verlinkt.

Und eine Klausel, die in der Ableitung fehlte: ein Nichtantritt traegt als
Austrittsdatum den Eintrittstag. Liegt der in der Zukunft, ist auch der
Austritt groesser als der Stichtag — die vorige Korrektur verglich nur gegen
den Stichtag und blieb damit wirkungslos. Endet ein Verhaeltnis nicht
spaeter, als es beginnt, gab es keinen Tag Beschaeftigung, zu keinem
Stichtag.
This commit is contained in:
2026-09-15 22:16:30 +02:00
parent 7a33e493b5
commit 1cbed1a8f5
10 changed files with 240 additions and 44 deletions

View File

@@ -94,6 +94,29 @@ const bereichAusdruck = sql<string>`(
const standortAusdruck = sql<string>`(select name from locations where id = employees.location_id)`;
/**
* Der Status zum Stichtag als Rang — nicht die Spalte `employees.status`.
*
* Die Liste beschriftet jede Zeile mit dem **abgeleiteten** Status
* (components/ui/StatusChip.tsx) und filtert danach
* (lib/employee-status-filter.ts). Nach der gespeicherten Spalte zu sortieren
* hiesse, die Zeilen nach einem Wert zu ordnen, der nirgends auf der Seite
* steht: eine als „Ausgetreten" beschriftete Person landete mitten unter den
* aktiven, weil in ihrer Spalte noch „Aktiv" steht.
*
* Die Reihenfolge ist die des Aufzählungstyps — Aktiv, Karenz, Geplant,
* Ausgetreten. Das ist der Verlauf eines Dienstverhältnisses und sagt mehr
* als alphabetisch. Die Klauseln stehen in derselben Reihenfolge wie in
* deriveStatusAsOf; wer dort etwas ändert, ändert es auch hier.
*/
const statusRang = (asOf: string) => sql<number>`case
when exit_date is not null and (exit_date <= ${asOf}::date or exit_date <= entry_date) then 4
when entry_date > ${asOf}::date then 3
when karenz_start_date is not null and karenz_start_date <= ${asOf}::date
and (karenz_return_date is null or karenz_return_date > ${asOf}::date) then 2
else 1
end`;
/**
* Hängt die Reihenfolge an eine Abfrage über `employees`.
*
@@ -114,7 +137,8 @@ const standortAusdruck = sql<string>`(select name from locations where id = empl
export function sortiere<O>(
q: SelectQueryBuilder<Schema, "employees", O>,
feld: Sortierfeld,
richtung: Richtung
richtung: Richtung,
asOf: string
): SelectQueryBuilder<Schema, "employees", O> {
// Zwei ausgeschriebene Zweige statt einer eingesetzten Richtung: so gerät
// nichts aus der Adresse in die Abfrage, auch nicht als geprüfter Wert.
@@ -140,10 +164,7 @@ export function sortiere<O>(
case "beschaeftigung":
return nachName(q.orderBy(ordne(sql.ref("employment_type"))).orderBy(ordne(sql.ref("weekly_hours"))));
case "status":
// Aufzählungstyp: Postgres ordnet nach der Reihenfolge der Werte —
// Aktiv, Karenz, Geplant, Ausgetreten. Das ist der Verlauf eines
// Dienstverhältnisses und sagt mehr als alphabetisch.
return nachName(q.orderBy(ordne(sql.ref("status"))));
return nachName(q.orderBy(ordne(statusRang(asOf))));
case "name":
return nachName(q);
}