import type { OrgEmployee, OrgUnitNode, OrgVacancy } from "@/components/orgchart/types"; // Das Modell für den Druck. // // Der Bildschirm zeigt das Organigramm als Fläche, in die man hineinzoomt. // Papier kann das nicht: es hat eine feste Grösse, und was darauf nicht // lesbar ist, ist verloren. Ein Ausdruck der Leinwand — 850 Personen auf eine // Seite skaliert — ergibt Kästchen von zwei Millimetern. // // Deshalb wird für den Druck **umgebaut statt verkleinert**: eine Übersicht // über die obersten Ebenen, und danach ein Bereich je Seite als eigener, // vollständiger Baum. Jede Seite ist für sich verständlich und trägt ihre // Überschrift — auch wenn jemand nur Blatt sieben aus dem Drucker nimmt. // // Der Baum bleibt hier ein Baum: `children` und `totalPeople` sind das, woraus // die Auswahl („welche Bereiche, wie tief") und die gezeichnete Hierarchie // gleichermassen entstehen. export type PrintPerson = { id: string; name: string; jobTitle: string; personnelNumber: number; isChief: boolean; absent: boolean; absenceType: string | null; }; export type PrintUnit = { id: string; name: string; unitType: OrgUnitNode["unit_type"]; orgNumber: string; /** Die Leitung dieser Einheit, falls besetzt. */ chief: PrintPerson | null; /** Alle übrigen Personen, die unmittelbar in dieser Einheit sitzen. */ members: PrintPerson[]; /** Unbesetzte Planstellen dieser Einheit. */ vacancies: { positionNumber: string; jobTitle: string; isChief: boolean }[]; children: PrintUnit[]; /** Personen in dieser Einheit und allem darunter — für die Übersicht. */ totalPeople: number; }; export type PrintModel = { root: PrintUnit | null; /** Je Bereich eine Seite. Bei nur einer Ebene bleibt es bei der Übersicht. */ divisions: PrintUnit[]; totals: { people: number; vacancies: number; units: number }; }; function personFrom(e: OrgEmployee): PrintPerson { return { id: e.id, name: `${e.first_name} ${e.last_name}`, jobTitle: e.job_title, personnelNumber: e.personnel_number, isChief: e.is_chief, absent: e.absent, absenceType: e.absence_type, }; } /** Nachname, dann Vorname — wie in jeder Liste, die jemand durchsucht. */ function nachName(a: PrintPerson, b: PrintPerson): number { return a.name.split(" ").slice(-1)[0].localeCompare(b.name.split(" ").slice(-1)[0], "de") || a.name.localeCompare(b.name, "de"); } export function buildPrintModel( units: OrgUnitNode[], employees: OrgEmployee[], vacancies: OrgVacancy[] ): PrintModel { const byUnit = new Map(); for (const u of units) { byUnit.set(u.id, { id: u.id, name: u.name, unitType: u.unit_type, orgNumber: u.org_number, chief: null, members: [], vacancies: [], children: [], totalPeople: 0, }); } for (const e of employees) { const unit = byUnit.get(e.org_unit_id); if (!unit) continue; const p = personFrom(e); // Die Leitung steht oben und nicht in der Namensliste — sie ist die // Antwort auf „wer führt das hier", und die soll man nicht suchen. if (p.isChief && !unit.chief) unit.chief = p; else unit.members.push(p); } for (const v of vacancies) { const unit = byUnit.get(v.org_unit_id); if (!unit) continue; unit.vacancies.push({ positionNumber: v.position_number, jobTitle: v.job_title, isChief: v.is_chief }); } // Bäume verknüpfen, in der Reihenfolge der Orgnummern — dieselbe wie // überall sonst in der Anwendung. const sortiert = [...units].sort((a, b) => a.org_number.localeCompare(b.org_number, "de")); let root: PrintUnit | null = null; for (const u of sortiert) { const node = byUnit.get(u.id)!; if (u.parent_id && byUnit.has(u.parent_id)) byUnit.get(u.parent_id)!.children.push(node); else if (u.unit_type === "Gesellschaft") root = node; } for (const unit of byUnit.values()) { unit.members.sort(nachName); unit.vacancies.sort((a, b) => a.jobTitle.localeCompare(b.jobTitle, "de")); } // Summen von unten nach oben. Rekursiv über höchstens vier Ebenen — die // Tiefe ist durch das Modell begrenzt (Gesellschaft/Bereich/Abteilung/Team). function zaehle(unit: PrintUnit): number { const eigene = (unit.chief ? 1 : 0) + unit.members.length; unit.totalPeople = eigene + unit.children.reduce((s, c) => s + zaehle(c), 0); return unit.totalPeople; } if (root) zaehle(root); else for (const u of byUnit.values()) if (!units.find((x) => x.id === u.id)?.parent_id) zaehle(u); const divisions = root ? root.children : [...byUnit.values()].filter((u) => u.unitType === "Bereich"); return { root, divisions, totals: { people: employees.length, vacancies: vacancies.length, units: units.length, }, }; }