import type { SupabaseClient } from "@supabase/supabase-js"; import { todayIso } from "./format"; import { breadcrumbLabel, loadOrgMaps, type OrgMaps } from "./org"; import { fetchAllRows } from "./supabase/query"; import type { Database } from "./supabase/types"; // Eine offene Stelle ist keine eigene Sache mehr. Sie ist eine Planstelle // ohne laufende Besetzung — Vakanz ist eine Eigenschaft der Planstelle, kein // zweites Objekt daneben, das mit der Organisation synchron gehalten werden // müsste. export type OpenPositionResolved = { id: string; position_number: string; title: string; org_unit_id: string; is_chief: boolean; valid_from: string; /** Wer die Stelle nach der Berichtslinie führen wird. */ managerName: string | null; orgLabel: string; /** Seit wann die Stelle unbesetzt ist: Ende der letzten Besetzung, sonst ihr Beginn. */ vacantSince: string; }; type PositionRow = { id: string; position_number: string; org_unit_id: string; is_chief: boolean; valid_from: string; jobs: { title: string }; position_assignments: { employee_id: string; valid_from: string; valid_to: string | null }[]; }; /** * Wer eine unbesetzte Planstelle führen würde: die Leitung der eigenen * Einheit, für eine Leitungsplanstelle die der übergeordneten — dieselbe * Regel wie in om_reporting_lines(), nur ohne Inhaber:in, für die sie gälte. */ function managerUnitFor(maps: OrgMaps, orgUnitId: string, isChief: boolean): string | null { if (!isChief) return orgUnitId; return maps.units.get(orgUnitId)?.parent_id ?? null; } export async function loadOpenPositions(supabase: SupabaseClient): Promise { const asOf = todayIso(); const [orgMaps, positions] = await Promise.all([ loadOrgMaps(supabase), fetchAllRows(() => supabase .from("om_positions") .select( "id, position_number, org_unit_id, is_chief, valid_from, jobs!inner(title), position_assignments(employee_id, valid_from, valid_to)" ) .lte("valid_from", asOf) .or(`valid_to.is.null,valid_to.gt.${asOf}`) .order("position_number") ), ]); const open = (positions as unknown as PositionRow[]).filter( (p) => !p.position_assignments.some((a) => a.valid_from <= asOf && (a.valid_to === null || a.valid_to > asOf)) ); if (open.length === 0) return []; // Die Leitung der zuständigen Einheit — genau die Planstellen, die als // Leitung markiert und laufend besetzt sind. const chiefUnitIds = Array.from( new Set(open.map((p) => managerUnitFor(orgMaps, p.org_unit_id, p.is_chief)).filter((id): id is string => Boolean(id))) ); const chiefs = chiefUnitIds.length ? ((await fetchAllRows(() => supabase .from("om_positions") .select("org_unit_id, position_assignments!inner(employees!inner(first_name, last_name), valid_to)") .eq("is_chief", true) .in("org_unit_id", chiefUnitIds) .is("position_assignments.valid_to", null) )) as unknown as { org_unit_id: string; position_assignments: { employees: { first_name: string; last_name: string } }[]; }[]) : []; const chiefNameByUnit = new Map( chiefs.flatMap((c) => { const holder = c.position_assignments[0]?.employees; return holder ? [[c.org_unit_id, `${holder.first_name} ${holder.last_name}`] as const] : []; }) ); return open.map((p) => { const ended = p.position_assignments .map((a) => a.valid_to) .filter((d): d is string => d !== null) .sort(); const managerUnit = managerUnitFor(orgMaps, p.org_unit_id, p.is_chief); return { id: p.id, position_number: p.position_number, title: p.jobs.title, org_unit_id: p.org_unit_id, is_chief: p.is_chief, valid_from: p.valid_from, managerName: managerUnit ? (chiefNameByUnit.get(managerUnit) ?? null) : null, orgLabel: breadcrumbLabel(orgMaps, p.org_unit_id), vacantSince: ended.at(-1) ?? p.valid_from, }; }); }