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:
@@ -11,6 +11,7 @@ import { fmtFullName, tenure } from "@/lib/format";
|
||||
import { fortschritt as fortschrittOffboarding, gehoertOffboarding } from "@/lib/offboarding";
|
||||
import { fortschritt as fortschrittOnboarding } from "@/lib/onboarding";
|
||||
import type { OpenPositionResolved } from "@/lib/positions";
|
||||
import { deriveStatusAsOf } from "@/lib/reports";
|
||||
import type { Database } from "@/lib/types";
|
||||
import { DatenAendernPanel } from "./panels/DatenAendernPanel";
|
||||
import { KarenzPanel } from "./panels/KarenzPanel";
|
||||
@@ -54,6 +55,8 @@ type EmployeeDetailProps = {
|
||||
notes: NoteRow[];
|
||||
locations: Location[];
|
||||
openPositions: OpenPositionResolved[];
|
||||
/** Der Tag, zu dem die Akte gelesen wurde — derselbe wie in loadEmployeeDetail. */
|
||||
today: string;
|
||||
};
|
||||
|
||||
type PanelType = "transfer" | "promote" | "karenz" | "daten" | "terminate" | "rehire" | null;
|
||||
@@ -61,7 +64,7 @@ const ALLE_TABS = ["Stammdaten", "Vertrag", "Organisation", "Onboarding", "Offbo
|
||||
type Tab = (typeof ALLE_TABS)[number];
|
||||
|
||||
export function EmployeeDetail(props: EmployeeDetailProps) {
|
||||
const { employee, placement, breadcrumb, kostenstelle, onboarding, offboarding, manager, formalManager, directReports, history, dependents, notes, locations, openPositions } = props;
|
||||
const { employee, placement, breadcrumb, kostenstelle, onboarding, offboarding, manager, formalManager, directReports, history, dependents, notes, locations, openPositions, today } = props;
|
||||
const [tab, setTab] = useState<Tab>("Stammdaten");
|
||||
const [panel, setPanel] = useState<PanelType>(null);
|
||||
|
||||
@@ -74,8 +77,15 @@ export function EmployeeDetail(props: EmployeeDetailProps) {
|
||||
const zeigtOffboarding = gehoertOffboarding(employee);
|
||||
const tabs = ALLE_TABS.filter((t) => t !== "Offboarding" || zeigtOffboarding);
|
||||
|
||||
const isActive = employee.status === "Aktiv" || employee.status === "Karenz";
|
||||
const canEditData = employee.status !== "Ausgetreten";
|
||||
// Abgeleitet, nicht aus `employee.status` gelesen. Die Spalte hängt nach,
|
||||
// wenn ein Austritt mit einem damals künftigen Datum erfasst wurde — es gibt
|
||||
// keinen Lauf, der sie später nachzieht (Migration 20260814100000). Hier
|
||||
// entschied sie nicht nur über eine Beschriftung, sondern über die Knöpfe:
|
||||
// an einer Person, die seit zwei Wochen ausgetreten ist, stand „Austritt"
|
||||
// weiter zur Verfügung, und „Daten ändern" ebenfalls.
|
||||
const status = deriveStatusAsOf(employee, today);
|
||||
const isActive = status === "Aktiv" || status === "Karenz";
|
||||
const canEditData = status !== "Ausgetreten";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
@@ -92,7 +102,7 @@ export function EmployeeDetail(props: EmployeeDetailProps) {
|
||||
<h2 className="text-xl font-extrabold text-ink">
|
||||
{fmtFullName(employee.first_name, employee.last_name, employee.title_prefix, employee.title_suffix)}
|
||||
</h2>
|
||||
<StatusChip status={employee.status} entryDate={employee.entry_date} absenceType={employee.absence_type} />
|
||||
<StatusChip employee={employee} asOf={today} />
|
||||
</div>
|
||||
<p className="text-sm text-ink-body">{placement?.jobTitle ?? employee.job_title}</p>
|
||||
<p className="text-xs text-ink-muted">
|
||||
|
||||
@@ -1,19 +1,46 @@
|
||||
import { absenceLabel } from "@/lib/absence";
|
||||
import { STATUS_STYLES } from "@/lib/colors";
|
||||
import { fmtDate } from "@/lib/format";
|
||||
import type { EmploymentStatus } from "@/lib/types";
|
||||
import { deriveStatusAsOf } from "@/lib/reports";
|
||||
|
||||
type StatusChipProps = {
|
||||
status: EmploymentStatus;
|
||||
entryDate?: string | null; // shown as "Eintritt {date}" when status is Geplant
|
||||
// ── Warum der Status hier abgeleitet und nicht übergeben wird ────────
|
||||
//
|
||||
// Vorher nahm dieses Bauteil einen fertigen Status entgegen, und beide
|
||||
// Aufrufer reichten `employees.status` durch — die gespeicherte Spalte. Die
|
||||
// Liste daneben filtert aber über die Datumsspalten (lib/employee-status-
|
||||
// filter.ts), und sobald die Spalte nachhängt, widersprechen sich Filter und
|
||||
// Beschriftung: der Filter „Ausgetreten" fand 48 Personen, von denen mehrere
|
||||
// als „Aktiv" beschriftet waren.
|
||||
//
|
||||
// Nachhängen kann sie regelmässig. terminate_employee (Migration
|
||||
// 20260814100000) setzt die Spalte nur, wenn das Austrittsdatum nicht in der
|
||||
// Zukunft liegt — „es gibt keinen Lauf, der das später nachzieht" steht dort
|
||||
// als Kommentar. Ein Austritt, der zum Zeitpunkt der Erfassung noch bevorstand,
|
||||
// lässt die Spalte also für immer auf „Aktiv" stehen.
|
||||
//
|
||||
// Deshalb nimmt das Bauteil jetzt die Zeile und den Stichtag und leitet
|
||||
// selbst ab. Die Spalte lässt sich nicht mehr hineinreichen: die zweite
|
||||
// Quelle ist nicht bloss ungenutzt, es gibt sie hier nicht mehr.
|
||||
export type StatusChipEmployee = {
|
||||
entry_date: string;
|
||||
exit_date: string | null;
|
||||
karenz_start_date: string | null;
|
||||
karenz_return_date: string | null;
|
||||
/** Shown instead of the generic label when the kind of absence is known. */
|
||||
absenceType?: string | null;
|
||||
absence_type?: string | null;
|
||||
};
|
||||
|
||||
export function StatusChip({ status, entryDate, absenceType }: StatusChipProps) {
|
||||
// The stored status is still 'Karenz'; absenceLabel maps it to
|
||||
// "Langzeitabwesenheit", or to the specific kind when one is recorded.
|
||||
const label = status === "Geplant" && entryDate ? `Eintritt ${fmtDate(entryDate)}` : absenceLabel(status, absenceType);
|
||||
type StatusChipProps = {
|
||||
employee: StatusChipEmployee;
|
||||
/** Der Tag, zu dem der Status gilt — derselbe, nach dem die Seite filtert. */
|
||||
asOf: string;
|
||||
};
|
||||
|
||||
export function StatusChip({ employee, asOf }: StatusChipProps) {
|
||||
const status = deriveStatusAsOf(employee, asOf);
|
||||
// Der abgeleitete Status ist 'Karenz'; absenceLabel macht daraus
|
||||
// "Langzeitabwesenheit", oder die konkrete Art, wenn eine erfasst ist.
|
||||
const label = status === "Geplant" ? `Eintritt ${fmtDate(employee.entry_date)}` : absenceLabel(status, employee.absence_type ?? null);
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex items-center whitespace-nowrap rounded-full px-2.5 py-0.5 text-xs font-semibold ${STATUS_STYLES[status]}`}
|
||||
|
||||
Reference in New Issue
Block a user