"use client"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { setPositionCostCenter, updatePosition } from "@/actions/positions"; 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 type { PlanstelleZumAendern } from "@/lib/positions"; import type { UnitOption } from "./CreatePositionModal"; // Ändern statt löschen und neu anlegen. // // Die Planstellennummer steht in Ausschreibungen, Budgets und // Protokolleinträgen. Wer wegen eines Tippfehlers in der Tätigkeit eine neue // Nummer vergibt, macht die alten Bezüge wertlos — deshalb gibt es diesen // Dialog. /** * Erwartet eine Planstelle, keine „vielleicht keine". * * Die aufrufende Seite hängt einen `key` mit der Kennung daran und rendert * ihn nur, solange etwas bearbeitet wird. Dadurch baut React den Dialog je * Planstelle neu auf, und die Felder lassen sich direkt aus den Eigenschaften * vorbelegen — statt sie in einem Effekt nachzuziehen, der beim zweiten * Öffnen kurz die Werte der vorigen Stelle zeigt. */ export function EditPositionModal({ position, units, kostenstellen, heute, onClose, }: { position: PlanstelleZumAendern; units: UnitOption[]; kostenstellen: { id: string; code: string; name: string }[]; /** Vom Server, nicht aus new Date(): sonst rechnet der Browser mit seiner Zone. */ heute: string; onClose: () => void; }) { const { showToast } = useToast(); const router = useRouter(); const [jobTitle, setJobTitle] = useState(position.title); const [orgUnitId, setOrgUnitId] = useState(position.org_unit_id); const [isChief, setIsChief] = useState(position.is_chief); const [validFrom, setValidFrom] = useState(position.valid_from); const [validTo, setValidTo] = useState(position.valid_to ?? ""); // Die Kontierung ist ein eigener Vorgang mit eigenem Stichtag, kein Feld // unter anderen: die laufende wird beendet, die neue beginnt. Deshalb steht // hier ein Datum daneben und kein stilles Überschreiben. const [kostenstelleId, setKostenstelleId] = useState( kostenstellen.find((k) => k.code === position.kostenstelle?.code)?.id ?? "" ); const [kostenstelleAb, setKostenstelleAb] = useState(heute); const [pending, setPending] = useState(false); const kostenstelleGeaendert = kostenstelleId !== "" && kostenstellen.find((k) => k.id === kostenstelleId)?.code !== position.kostenstelle?.code; const unit = units.find((u) => u.id === orgUnitId); // Die eigene Leitung zählt nicht als Hindernis für sich selbst. const chiefTaken = Boolean(unit?.hasChief) && !(position.is_chief && orgUnitId === position.org_unit_id); // Ob sich an den Stammangaben überhaupt etwas geändert hat. // // update_position weist einen Aufruf ohne Änderung mit „Es wurde nichts // geändert." ab — und weil die Umkontierung erst bei dessen Erfolg lief, // liess sich die Kostenstelle **allein** nicht setzen: die Meldung kam, und // die neue Kostenstelle blieb liegen. Wer es trotzdem wollte, musste // nebenbei ein anderes Feld verstellen. Gemeldet im Test vom 29.09. (E.07). const stammGeaendert = jobTitle.trim() !== position.title || orgUnitId !== position.org_unit_id || (isChief && !chiefTaken) !== position.is_chief || validFrom !== position.valid_from || (validTo || null) !== (position.valid_to ?? null); async function handleSubmit() { if (!jobTitle.trim() || !orgUnitId || !validFrom) { showToast("Tätigkeit, Einheit und Gültigkeitsbeginn sind Pflicht.", "error"); return; } if (!stammGeaendert && !kostenstelleGeaendert) { showToast("Es wurde nichts geändert.", "error"); return; } setPending(true); // Nur aufrufen, wenn es etwas zu ändern gibt. Sonst ist der Fehlschlag // dieses Schritts die einzige Antwort auf eine reine Umkontierung. const result = stammGeaendert ? await updatePosition({ position_id: position.id, org_unit_id: orgUnitId, job_title: jobTitle.trim(), is_chief: isChief && !chiefTaken, valid_from: validFrom, valid_to: validTo || null, }) : { success: true as const }; if (result.success && kostenstelleGeaendert) { // Nacheinander, nicht gemeinsam: die Umkontierung ist ein eigener // Vorgang mit eigenem Stichtag und eigenem Protokolleintrag. Schlägt sie // fehl, steht die übrige Änderung trotzdem — und die Meldung sagt, was // offen blieb, statt beides stillschweigend zu verwerfen. const kosten = await setPositionCostCenter({ position_id: position.id, cost_center_id: kostenstelleId, valid_from: kostenstelleAb, }); if (!kosten.success) { setPending(false); showToast(kosten.error ?? "Die Kostenstelle konnte nicht geändert werden.", "error"); router.refresh(); return; } } setPending(false); if (result.success) { showToast( stammGeaendert && kostenstelleGeaendert ? "Planstelle geändert und umkontiert." : kostenstelleGeaendert ? "Planstelle umkontiert." : "Planstelle geändert." ); router.refresh(); onClose(); } else { // Die Meldungen der Datenbankfunktion sind für die Oberfläche // geschrieben („Diese Planstelle ist vergeben …“) und werden gezeigt. showToast(result.error ?? "Die Änderung war nicht möglich.", "error"); } } return ( } >
({ value: u.id, label: `${"  ".repeat(u.depth)}${u.name} (${u.unit_type})`, }))} />
({ value: k.id, label: `${k.code} · ${k.name}` }))} /> {kostenstelleGeaendert && (

Die bisherige Kontierung endet an diesem Tag, die neue beginnt. Was vorher gebucht wurde, bleibt, wo es gebucht wurde.

)}

Die Planstellennummer bleibt. Ist die Stelle vergeben, lassen sich Einheit und Gültigkeitsende nicht ändern — ein Wechsel der Einheit ist eine Versetzung und gehört zur Person, nicht zur Stelle.

); }