import type { Tx } from "./db"; import type { Database } from "./supabase/types"; // Die Organisation ist ein Baum, keine drei Tabellen mehr. Alles, was früher // aus divisions/departments/teams zusammengesteckt wurde, ergibt sich jetzt // aus org_units.parent_id — und damit funktioniert es auch für eine fünfte // Ebene, ohne dass hier etwas zu ändern wäre. export type OrgUnitType = "Gesellschaft" | "Bereich" | "Abteilung" | "Team"; export type OrgUnit = { id: string; org_number: string; name: string; parent_id: string | null; unit_type: OrgUnitType; }; type Location = Database["public"]["Tables"]["locations"]["Row"]; export type OrgMaps = { units: Map; /** Tiefensuche ab der Wurzel: eine Einheit steht immer hinter ihrem Elternteil. */ unitList: OrgUnit[]; /** Abstand zur Wurzel; die Wurzel selbst hat 0. */ depthOf: Map; childrenOf: Map; locations: Map; locationList: Location[]; }; // Die Referenzdaten sind winzig (60 Einheiten, 5 Standorte) — sie werden // ganz geladen und im Speicher verknüpft, statt je Zeile nachzuschlagen. export async function loadOrgMaps(tx: Tx): Promise { const [units, locations] = await Promise.all([ tx .selectFrom("org_units") .select(["id", "org_number", "name", "parent_id", "unit_type"]) .orderBy("org_number") .execute(), tx.selectFrom("locations").selectAll().orderBy("name").execute(), ]); return buildOrgMaps(units as OrgUnit[], locations as Location[]); } /** Der reine Teil: aus den Zeilen den Baum bauen, ohne Datenbank. */ export function buildOrgMaps(units: OrgUnit[], locations: Location[]): OrgMaps { const childrenOf = new Map(); for (const u of units) { const list = childrenOf.get(u.parent_id) ?? []; list.push(u); childrenOf.set(u.parent_id, list); } for (const list of childrenOf.values()) list.sort((a, b) => a.name.localeCompare(b.name, "de")); const unitList: OrgUnit[] = []; const depthOf = new Map(); const walk = (parentId: string | null, depth: number) => { for (const u of childrenOf.get(parentId) ?? []) { unitList.push(u); depthOf.set(u.id, depth); walk(u.id, depth + 1); } }; walk(null, 0); return { units: new Map(units.map((u) => [u.id, u])), unitList, depthOf, childrenOf, locations: new Map(locations.map((l) => [l.id, l])), locationList: locations, }; } /** Wurzel zuerst, die Einheit selbst zuletzt. */ export function ancestorsOf(maps: OrgMaps, unitId: string | null | undefined): OrgUnit[] { const chain: OrgUnit[] = []; const seen = new Set(); let current = unitId ? maps.units.get(unitId) : undefined; while (current && !seen.has(current.id)) { seen.add(current.id); chain.unshift(current); current = current.parent_id ? maps.units.get(current.parent_id) : undefined; } return chain; } /** Die Einheit und alles darunter — die Menge, die ein Filter „Bereich X" meint. */ export function subtreeOf(maps: OrgMaps, unitId: string): string[] { const out: string[] = []; const queue = [unitId]; const seen = new Set(); while (queue.length > 0) { const id = queue.shift()!; if (seen.has(id)) continue; seen.add(id); out.push(id); for (const child of maps.childrenOf.get(id) ?? []) queue.push(child.id); } return out; } /** * „Produktion › Fertigung › Montage". Die Gesellschaft bleibt weg: sie steht * über allem und trägt in einer Zeile nichts bei. */ export function breadcrumbLabel(maps: OrgMaps, unitId: string | null | undefined): string { const chain = ancestorsOf(maps, unitId).filter((u) => u.unit_type !== "Gesellschaft"); return chain.map((u) => u.name).join(" › ") || "–"; } /** Die oberste Einheit unterhalb der Gesellschaft — das, was früher „Bereich" hiess. */ export function divisionOf(maps: OrgMaps, unitId: string | null | undefined): OrgUnit | undefined { return ancestorsOf(maps, unitId).find((u) => u.unit_type !== "Gesellschaft"); } /** Die Einheit selbst, wenn sie nicht die Gesellschaft ist. */ export function unitOf(maps: OrgMaps, unitId: string | null | undefined): OrgUnit | undefined { return unitId ? maps.units.get(unitId) : undefined; }