Eine Organisationseinheit aus dem Organigramm heraus anlegen
Bisher gab es dafuer nur den Import. Jetzt sitzt auf jeder Einheit im Organigramm -- in der Grafik wie in der Liste -- ein Plus, das eine untergeordnete Einheit anlegt, wahlweise gleich mit Leitungsplanstelle. Bewusst nur dieser eine Fall. Umbenennen, verschieben und schliessen fehlen nicht aus Zeitmangel: org_units.parent_id traegt kein Datum, ein Verschieben aenderte damit auch jede Auswertung auf einen vergangenen Stichtag, und der fruehere Stand waere danach nirgends mehr ablesbar. Anlegen stellt diese Frage nicht, weil vorher nichts da war -- und es kann dabei auch kein Kreis entstehen, was hier mehr wiegt als es klingt: die rekursive Abfrage in om_reporting_lines hat weder Tiefenbegrenzung noch Kreiserkennung. Die Leitungsplanstelle entsteht ueber create_position statt durch eine zweite Fassung derselben Logik -- dort haengen Jobkatalog, Nummernvergabe und die Pruefung "je Einheit genau eine Leitung". Die Orgnummer wird eingetragen, nicht vergeben. Vorgeschlagen wird die naechste freie Nummer der bestehenden Reihe, und nur dann, wenn sich im Bestand genau eine Systematik ablesen laesst -- eine plausibel aussehende, aber erfundene Nummer prueft niemand nach. Das Datum kommt nicht aus dem Stichtag der Ansicht. Wer sich die Struktur zum letzten Jahresende ansieht und auf Plus drueckt, will in aller Regel eine Einheit von heute anlegen.
This commit is contained in:
126
components/orgchart/CreateOrgUnitModal.tsx
Normal file
126
components/orgchart/CreateOrgUnitModal.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { createOrgUnit } from "@/actions/org";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { SelectField, TextField } from "@/components/ui/Field";
|
||||
import { Modal } from "@/components/ui/Modal";
|
||||
import { useToast } from "@/components/ui/Toast";
|
||||
import { todayIso } from "@/lib/format";
|
||||
import { naechsteOrgnummer } from "@/lib/org-nummer";
|
||||
import type { OrgUnitNode } from "./types";
|
||||
|
||||
type Art = "Bereich" | "Abteilung" | "Team";
|
||||
|
||||
// „Gesellschaft“ fehlt in der Auswahl mit Absicht: es gibt genau eine, sie ist
|
||||
// die Wurzel, und unter einer anderen Einheit wäre sie ein Etikett, das seiner
|
||||
// Stelle im Baum widerspricht. Die Datenbankfunktion weist es ohnehin ab — die
|
||||
// fehlende Zeile erspart den Weg dorthin.
|
||||
const ARTEN: { value: Art; label: string }[] = [
|
||||
{ value: "Bereich", label: "Bereich" },
|
||||
{ value: "Abteilung", label: "Abteilung" },
|
||||
{ value: "Team", label: "Team" },
|
||||
];
|
||||
|
||||
/**
|
||||
* Die Art, die unter einer Einheit am wahrscheinlichsten ist: eine Stufe
|
||||
* feiner als die darüber. Nur eine Vorbelegung — die Tiefe des Baums ist frei,
|
||||
* und eine Abteilung unter einer Abteilung ist erlaubt.
|
||||
*/
|
||||
function vorschlagArt(eltern: OrgUnitNode["unit_type"]): Art {
|
||||
if (eltern === "Gesellschaft") return "Bereich";
|
||||
if (eltern === "Bereich") return "Abteilung";
|
||||
return "Team";
|
||||
}
|
||||
|
||||
export function CreateOrgUnitModal({
|
||||
parent,
|
||||
units,
|
||||
onClose,
|
||||
}: {
|
||||
parent: OrgUnitNode;
|
||||
units: OrgUnitNode[];
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { showToast } = useToast();
|
||||
const router = useRouter();
|
||||
const [orgNumber, setOrgNumber] = useState(() => naechsteOrgnummer(units.map((u) => u.org_number)) ?? "");
|
||||
const [name, setName] = useState("");
|
||||
const [art, setArt] = useState<Art>(() => vorschlagArt(parent.unit_type));
|
||||
// **Nicht** der Stichtag der Ansicht. Wer sich das Organigramm zum letzten
|
||||
// Jahresende ansieht und hier auf Plus drückt, will in aller Regel eine
|
||||
// Einheit von heute anlegen und keine rückdatierte. Ein stillschweigend
|
||||
// übernommenes Datum aus der Ansicht wäre die Art von Vorbelegung, die
|
||||
// niemand liest und die hinterher niemand erklären kann.
|
||||
const [validFrom, setValidFrom] = useState(todayIso);
|
||||
const [leitung, setLeitung] = useState("");
|
||||
const [pending, setPending] = useState(false);
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!orgNumber.trim() || !name.trim() || !validFrom) {
|
||||
showToast("Orgnummer, Bezeichnung und Gültigkeitsbeginn sind Pflicht.", "error");
|
||||
return;
|
||||
}
|
||||
setPending(true);
|
||||
const result = await createOrgUnit({
|
||||
parent_id: parent.id,
|
||||
org_number: orgNumber.trim(),
|
||||
name: name.trim(),
|
||||
unit_type: art,
|
||||
valid_from: validFrom,
|
||||
leitung_taetigkeit: leitung.trim() || undefined,
|
||||
});
|
||||
setPending(false);
|
||||
if (result.success) {
|
||||
showToast(leitung.trim() ? "Einheit samt Leitungsplanstelle angelegt." : "Einheit angelegt.");
|
||||
router.refresh();
|
||||
onClose();
|
||||
} else {
|
||||
showToast(result.error ?? "Fehler beim Anlegen.", "error");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open
|
||||
onClose={onClose}
|
||||
title="Einheit anlegen"
|
||||
footer={
|
||||
<>
|
||||
<Button variant="ghost" onClick={onClose}>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button onClick={handleSubmit} pending={pending}>
|
||||
Anlegen
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="rounded-md bg-surface px-3 py-2 text-xs leading-relaxed text-ink-muted">
|
||||
Unter <span className="font-semibold text-ink-body">{parent.org_number} · {parent.name}</span>.
|
||||
Bestehende Einheiten lassen sich nicht hierher verschieben — die neue entsteht leer.
|
||||
</p>
|
||||
|
||||
<TextField
|
||||
label="Orgnummer"
|
||||
required
|
||||
value={orgNumber}
|
||||
onChange={setOrgNumber}
|
||||
hint="Vorgeschlagen wird die nächste freie Nummer der bestehenden Reihe. Sie muss zum führenden System passen."
|
||||
/>
|
||||
<TextField label="Bezeichnung" required value={name} onChange={setName} />
|
||||
<SelectField label="Art" required value={art} onChange={(v) => setArt(v as Art)} options={ARTEN} />
|
||||
<TextField label="Gültig ab" required type="date" value={validFrom} onChange={setValidFrom} />
|
||||
|
||||
<TextField
|
||||
label="Leitungsplanstelle (optional)"
|
||||
value={leitung}
|
||||
onChange={setLeitung}
|
||||
hint="Tätigkeit der Leitung, z. B. „Abteilungsleitung Einkauf“. Leer lassen, dann entsteht die Einheit ohne Leitung und ist im Organigramm als vakant zu sehen."
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user