Show positions that do not exist yet, and let them be corrected
Two gaps in the positions view, both reported from use.
A position dated into the future was invisible. loadOpenPositions required
valid_from <= today, so a position decided now and effective at the quarter
boundary appeared nowhere until the day it began. The database already held
one — 60000824 "Neue Position", effective 01.09. — created through the
application and shown on no screen since.
Future positions now have their own section rather than joining the vacancy
list. They are a different statement: "nobody is here" and "this does not
exist yet" should not be counted together, and a position starting 01.10.
read as a vacancy nobody was filling.
Positions could only be created and deleted. Fixing a typo in the job title
meant deleting and recreating — with a new position number, which appears in
job postings, budgets and audit entries, and whose trail then breaks.
update_position keeps the number and records old and new values per field,
using the audit detail added earlier today.
Three things it refuses, as guards rather than remarks:
- Moving an occupied position to another unit. That is a transfer, with
history and reporting line, and belongs to the person — otherwise
someone changes department silently.
- Ending an occupied position, which would leave an assignment without
one.
- A second chief position in a unit, or an end before the start.
Verified against the live database, all rolled back: each guard fires with
its own message, the permitted edits go through, the audit entry carries the
changed fields. Open positions stay at 9 and the future one now appears in
its own section.
ESLint caught me priming the dialog's fields from an effect. Replaced by a
key on the component, so React rebuilds it per position and the fields
initialise from props — which also removes the flash of the previous
position's values on second open.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,17 @@ export type OpenPositionResolved = {
|
||||
org_unit_id: string;
|
||||
is_chief: boolean;
|
||||
valid_from: string;
|
||||
valid_to: string | null;
|
||||
/** Die Einheit selbst — für den Änderungsdialog, der sie vorbelegt. */
|
||||
org_unit_name: string;
|
||||
/**
|
||||
* Ob die Planstelle heute schon gilt.
|
||||
*
|
||||
* Eine, die erst zum 01.10. entsteht, ist nicht „seit 2 Tagen unbesetzt" —
|
||||
* sie ist geplant. Beides in einer Liste zu zeigen ist richtig, beides
|
||||
* gleich zu benennen wäre falsch.
|
||||
*/
|
||||
future: boolean;
|
||||
/** Wer die Stelle nach der Berichtslinie führen wird. */
|
||||
managerName: string | null;
|
||||
orgLabel: string;
|
||||
@@ -56,8 +67,11 @@ export async function loadOpenPositions(tx: Tx): Promise<OpenPositionResolved[]>
|
||||
tx
|
||||
.selectFrom("om_positions as p")
|
||||
.innerJoin("jobs as j", "j.id", "p.job_id")
|
||||
.select(["p.id", "p.position_number", "p.org_unit_id", "p.is_chief", "p.valid_from", "j.title"])
|
||||
.where("p.valid_from", "<=", asOf)
|
||||
.select(["p.id", "p.position_number", "p.org_unit_id", "p.is_chief", "p.valid_from", "p.valid_to", "j.title"])
|
||||
// Künftige Planstellen bleiben drin. Sie sind der Grund, warum diese
|
||||
// Ansicht existiert: eine Stelle, die zum Quartalswechsel entsteht,
|
||||
// muss vorher sichtbar und planbar sein. Vorher fielen sie durch das
|
||||
// Raster und tauchten am Stichtag unangekündigt auf.
|
||||
.where((eb) => eb.or([eb("p.valid_to", "is", null), eb("p.valid_to", ">", asOf)]))
|
||||
.where((eb) =>
|
||||
eb.not(
|
||||
@@ -125,6 +139,9 @@ export async function loadOpenPositions(tx: Tx): Promise<OpenPositionResolved[]>
|
||||
org_unit_id: p.org_unit_id,
|
||||
is_chief: p.is_chief,
|
||||
valid_from: p.valid_from,
|
||||
valid_to: p.valid_to,
|
||||
org_unit_name: orgMaps.units.get(p.org_unit_id)?.name ?? "",
|
||||
future: p.valid_from > asOf,
|
||||
managerName: managerUnit ? (chiefNameByUnit.get(managerUnit) ?? null) : null,
|
||||
orgLabel: breadcrumbLabel(orgMaps, p.org_unit_id),
|
||||
vacantSince: lastEndByPosition.get(p.id) ?? p.valid_from,
|
||||
|
||||
Reference in New Issue
Block a user