Files
alpenwerk-hr/components/hire/PersonalnummerField.tsx
Andrei Laas 9ad2954970 Anmerkungen vom 16.09.: Uebersicht gegliedert, Personalnummer prueft frueher
1) Die Kacheln stehen jetzt in drei Gruppen — Personalstand,
Personalbewegung, Recruiting & Vakanzen — in der Reihenfolge aus dem Entwurf
des Kunden. Acht Zahlen nebeneinander sind acht Zahlen; sie beantworten aber
drei verschiedene Fragen, und ohne Ueberschrift muss man jede Beschriftung
einzeln lesen, um das herauszufinden. "Aktives Dienstverhaeltnis" steht
vorn: es ist die Bezugsgroesse fast jeder Personalkennzahl.

2) Der Namensfilter in "Anstehend" ist jetzt immer da. Die Schwelle "erst ab
neun Eintraegen" war in der Bedienung falsch — das Feld erschien bei 180
Tagen und verschwand bei 30, und ein Bedienelement, das je nach Zeitraum da
ist oder nicht, wirkt wie ein Fehler.

3b) Der Filter heisst jetzt "Aktives Dienstverhaeltnis (Aktiv +
Langzeitabwesenheit)" — derselbe Name wie die Kachel, die dorthin verlinkt.

6) Die Personalnummer wird gegen die Datenbank geprueft, waehrend sie
eingetippt wird, und nennt bei einem Treffer die Person, die sie schon hat.
hire_employee weist sie weiterhin ab — das bleibt die verbindliche Pruefung,
denn zwischen Frage und Anlegen kann jemand anderes dieselbe Nummer
vergeben. Nur kam diese Abweisung bisher nach sechs Schritten Eingabe, und
das Feld steht im ersten Schritt.

Gemerkt wird dabei die gepruefte *Nummer* samt Ergebnis, nicht ein Ja/Nein:
so ist die Sperre eine Ableitung aus dem, was im Feld steht, und es gibt
keinen Zustand, dessen Zuruecksetzen man vergessen koennte.

Zu 4) geprueft, nichts geaendert: die FTE-Kachel rechnet bereits Summe der
Wochenstunden der heute Aktiven durch 38,5. Der Berichtemanager rechnet
dieselbe Formel, nur als Summe der Einzelquotienten geschrieben.
2026-09-16 22:23:04 +02:00

100 lines
3.9 KiB
TypeScript

"use client";
import { useEffect, useRef, useState } from "react";
import { personalnummerVergeben } from "@/actions/employees";
import { TextField } from "@/components/ui/Field";
// Die Personalnummer wird eingegeben, nicht vergeben — sie muss mit Loga und
// Interflex übereinstimmen. Genau deshalb kommt sie von aussen, und genau
// deshalb kann sie schon vergeben sein: an dieselbe Person, die jemand ein
// zweites Mal anlegt, oder an eine ganz andere nach einem Zahlendreher.
//
// Die Datenbank weist das ab (hire_employee prüft es eigens), aber erst beim
// Anlegen — nach sechs Schritten Eingabe. Bis dahin ist die falsche Nummer
// schon durch alle Formulare gereist.
//
// ── Wann gefragt wird ───────────────────────────────────────────────
//
// Nicht bei jedem Tastendruck: „3", „34", „347" wären drei Fragen an die
// Datenbank für eine Eingabe, und die ersten beiden hätten eine Antwort auf
// eine Nummer, die niemand meint. Stattdessen eine halbe Sekunde Ruhe
// abwarten — lange genug, dass eine vierstellige Nummer am Stück eingetippt
// eine einzige Frage ergibt, kurz genug, dass die Antwort da ist, bevor der
// Blick zum nächsten Feld wandert.
//
// Die Antwort kann veralten, während sie unterwegs ist. Deshalb zählt jede
// Anfrage mit und nur die jüngste darf schreiben: sonst überschriebe die
// langsamere Antwort auf „347" die schnellere auf „3471".
//
// ── Warum der Befund an der Nummer hängt und nicht an einem Ja/Nein ──
//
// Gespeichert wird **die geprüfte Nummer** mitsamt ihrem Ergebnis, nicht
// bloss „frei" oder „vergeben". Damit ist jede Anzeige eine Ableitung aus
// dem, was gerade im Feld steht — es gibt keinen Zustand, der zurückgesetzt
// werden müsste, wenn die Eingabe sich ändert, und deshalb auch keine Stelle,
// an der das Zurücksetzen vergessen werden kann.
const WARTEZEIT_MS = 500;
type Befund = { nummer: number; vergeben: boolean; name?: string };
export function PersonalnummerField({
wert,
onChange,
onBefund,
}: {
wert: string;
onChange: (wert: string) => void;
/** Meldet dem Assistenten die geprüfte Nummer und ihr Ergebnis. */
onBefund: (befund: Befund) => void;
}) {
const [befund, setBefund] = useState<Befund | null>(null);
const laufendeNr = useRef(0);
const nummer = Number(wert);
const gueltig = Boolean(wert.trim()) && Number.isInteger(nummer) && nummer > 0;
// Nur ein Befund zu *dieser* Nummer zählt. Steht im Feld inzwischen etwas
// anderes, ist die alte Auskunft gegenstandslos.
const passend = gueltig && befund?.nummer === nummer ? befund : null;
const laeuft = gueltig && !passend;
useEffect(() => {
if (!gueltig) return;
const meine = ++laufendeNr.current;
const zeit = setTimeout(async () => {
let antwort: { vergeben: boolean; name?: string };
try {
antwort = await personalnummerVergeben(nummer);
} catch {
// Keine Auskunft ist kein Hindernis: die Datenbank entscheidet beim
// Anlegen ohnehin. Eine Meldung über eine gescheiterte *Vorab*-Prüfung
// wäre für die Eingebende nur Lärm.
antwort = { vergeben: false };
}
if (meine !== laufendeNr.current) return;
const neu: Befund = { nummer, ...antwort };
setBefund(neu);
onBefund(neu);
}, WARTEZEIT_MS);
return () => clearTimeout(zeit);
}, [gueltig, nummer, onBefund]);
return (
<TextField
label="Personalnummer"
required
inputMode="numeric"
value={wert}
onChange={(v) => onChange(v.replace(/\D/g, ""))}
error={
passend?.vergeben
? `Diese Personalnummer ist bereits vergeben${passend.name ? ` — an ${passend.name}` : ""}.`
: undefined
}
hint={laeuft ? "Wird geprüft…" : "Muss mit Loga und Interflex übereinstimmen. Wird nicht automatisch vergeben."}
/>
);
}