Files
alpenwerk-hr/actions/positions.ts
Andrei Laas b9da3f3411 Planstellen klonen — ausser den leitenden
Anforderung 11 aus dem Workshop. Migration 20260915160000.

In der Fertigung sind Planstellen reihenweise gleich: zwoelf
"Maschinenbediener:in" in derselben Abteilung auf derselben Kostenstelle.
Von Hand angelegt sind das zwoelf Gelegenheiten, die Taetigkeit
unterschiedlich zu schreiben — und ab der zweiten Schreibweise steht sie
zweimal im Katalog und jede Auswertung nach Taetigkeit ist falsch.

Der Klon nimmt Einheit, Taetigkeit (denselben Katalogeintrag) und die zum
Stichtag geltende Kontierung. Nicht mit kommt die Besetzung: eine
Planstelle ist ein Platz, keine Person, der Klon ist frei.

Leitungsplanstellen sind ausgenommen, und zwar mit einer eigenen Meldung.
Je Einheit gibt es genau eine, und ein Unique-Index sichert das ab — ohne
die Pruefung waere ein Klonversuch entweder "duplicate key value violates
unique constraint" oder, mit stillschweigend fallengelassenem is_chief,
eine Planstelle, die anders ist als ihre Vorlage, ohne dass es jemand
angefordert hat. In der Liste fehlt der Knopf dort; das ist Bequemlichkeit,
die Regel steht in der Funktion.
2026-09-15 22:55:33 +02:00

85 lines
2.9 KiB
TypeScript

"use server";
import { revalidatePath } from "next/cache";
import { currentUserId } from "@/lib/auth/session";
import { runMutation, type ActionResult } from "@/lib/db/rpc";
const POSITION_PATHS = ["/positions", "/orgchart", "/"];
async function callRpc(
fn: "create_position" | "clone_position" | "update_position" | "delete_position" | "set_position_cost_center",
payload: Record<string, unknown>,
revalidate: string[]
): Promise<ActionResult> {
const result = await runMutation(await currentUserId(), fn, payload);
if (!result.success) return result;
for (const path of revalidate) revalidatePath(path);
return result;
}
export async function createPosition(payload: {
org_unit_id: string;
job_title: string;
is_chief: boolean;
valid_from: string;
}): Promise<ActionResult> {
return callRpc("create_position", payload, POSITION_PATHS);
}
/**
* Eine Planstelle als Kopie einer bestehenden anlegen.
*
* Kein eigenes Formular: Einheit, Tätigkeit und Kontierung kommen von der
* Vorlage, getippt wird nichts. Genau das ist der Zweck — zwölf gleiche
* Planstellen von Hand anzulegen erzeugt zwölf Gelegenheiten, die Tätigkeit
* unterschiedlich zu schreiben, und ab der zweiten Schreibweise ist jede
* Auswertung nach Tätigkeit falsch.
*
* Dass Leitungsplanstellen ausgenommen sind, entscheidet die SQL-Funktion und
* nicht diese Stelle. Die Oberfläche blendet den Knopf dort aus, aber das ist
* eine Bequemlichkeit, keine Regel.
*/
export async function clonePosition(payload: { position_id: string; valid_from?: string }): Promise<ActionResult> {
return callRpc("clone_position", payload, POSITION_PATHS);
}
/**
* Ändert eine Planstelle.
*
* Weggelassene Felder bleiben, wie sie sind. `valid_to: null` beendet die
* Befristung ausdrücklich — deshalb ist es hier `string | null` und nicht
* optional: „nicht mitgeschickt" und „auf leer setzen" müssen unterscheidbar
* bleiben.
*/
export async function updatePosition(payload: {
position_id: string;
org_unit_id?: string;
job_title?: string;
is_chief?: boolean;
valid_from?: string;
valid_to?: string | null;
}): Promise<ActionResult> {
return callRpc("update_position", payload, POSITION_PATHS);
}
export async function deletePosition(positionId: string): Promise<ActionResult> {
return callRpc("delete_position", { position_id: positionId }, POSITION_PATHS);
}
/**
* Kontiert eine Planstelle auf eine andere Kostenstelle — zum Stichtag.
*
* Kein Feld im Änderungsdialog, sondern ein eigener Vorgang, weil es einer
* ist: die laufende Kontierung wird beendet, die neue beginnt. Die alte
* einfach zu überschreiben hiesse, die Kosten der Vergangenheit nachträglich
* anderswohin zu buchen.
*/
export async function setPositionCostCenter(payload: {
position_id: string;
cost_center_id: string;
valid_from: string;
}): Promise<ActionResult> {
return callRpc("set_position_cost_center", payload, POSITION_PATHS);
}