import { sql } from "kysely"; import { zeitstempel } from "./db/json"; import { istHinzugewaehlt } from "./kollegen"; import type { OrgEb } from "./org"; // Die angefangenen Neueinstellungen auf der Übersicht. // // Bis September 2026 sah jede Person nur die eigenen. Seit die Auswahl in der // Glocke auch hierfür gilt (lib/kollegen.ts), kommen die Entwürfe der // hinzugewählten Kolleg:innen dazu — damit eine angefangene Einstellung nicht // liegen bleibt, weil die eine Person im Urlaub ist und die andere nicht weiss, // dass es sie gibt. // // **Nur ansehen.** Fortsetzen und Löschen bleiben bei der Person, von der der // Entwurf stammt; die Regeln hire_drafts_update und hire_drafts_delete lassen // nichts anderes zu. Ein Entwurf ist unfertige Arbeit ohne Sperre und ohne // Historie — zwei Personen, die abwechselnd hineinschreiben, überschreiben // einander lautlos. export type EntwurfZeile = { id: string; step: number; payload: Record; updated_at: string; created_by: string | null; author_name: string | null; author_email: string | null; /** Wahr, solange jemand **anderes** den Entwurf offen hat. Aus der Datenbank, nicht gerechnet. */ gesperrt: boolean; sperrer_name: string | null; sperrer_email: string | null; }; export type Entwurf = { id: string; step: number; payload: Record; updated_at: string; /** Ob der Entwurf von der angemeldeten Person stammt — nur dann darf sie ihn anfassen. */ vonMir: boolean; /** Wer ihn angefangen hat — steht an jeder Zeile, auch an den eigenen. */ autor: string; /** * Wer ihn gerade offen hat, wenn es jemand anderes ist — sonst leer. * * Nicht dasselbe wie „nicht meiner": ein fremder Entwurf ist bearbeitbar, * ein gesperrter nicht. Die Karte hängt daran, ob „Fortsetzen" wählbar ist. */ gesperrtVon: string | null; }; /** * Die Entwürfe als *Teilabfrage* — zum Einhängen in die eine Abfrage, die die * Übersicht ohnehin stellt (lib/db/json.ts). * * Die Bedingung wiederholt, was `hire_drafts_select` ohnehin durchlässt. Das * ist Absicht: die Regel in der Datenbank ist die Grenze, aber sie steht in * einer Migration und lässt sich von hier aus nicht lesen. Steht sie auch in * der Abfrage, sagt der Quelltext, was die Liste zeigt — und ein Test kann es * festhalten. Fällt eine der beiden aus, bleibt die andere. * * `created_by` kann leer sein (die Person wurde gelöscht). Solche Zeilen zeigt * niemand: anders als bei einer Notiz steht in einem Entwurf nichts, was ohne * die Person, die ihn angefangen hat, noch weiterginge. */ export function entwuerfeAbfrage(eb: OrgEb, userId: string) { return eb .selectFrom("hire_drafts as d") .leftJoin("profiles as p", "p.id", "d.created_by") .leftJoin("profiles as sp", "sp.id", "d.locked_by") .select(["d.id", "d.step", "d.payload", "d.created_by"]) .select(["p.full_name as author_name", "p.email as author_email"]) .select(["sp.full_name as sperrer_name", "sp.email as sperrer_email"]) .select((x) => zeitstempel(x.ref("d.updated_at")).as("updated_at")) // Ob die Sperre noch gilt, rechnet die Datenbank — mit **ihrer** Uhr und // derselben Frist, die auch die Schreibregeln anwenden. Hier gerechnet // wäre es die Uhr des Servers, der gerade rendert, und eine zweite // Fassung der Frist. .select(sql`not app_entwurf_frei("d"."locked_by", "d"."locked_at")`.as("gesperrt")) .where((e) => e.or([e("d.created_by", "=", userId), istHinzugewaehlt(userId, "d.created_by")])) // Die eigenen zuerst. Vor der Freigabe standen dort nur sie; wer seinen // halbfertigen Entwurf sucht, soll ihn nicht zwischen fremden suchen. .orderBy(sql`case when "d"."created_by" = ${userId} then 0 else 1 end`) .orderBy("d.updated_at", "desc"); } /** Der reine Teil: aus den Zeilen die Entwürfe mit lesbarem Verfasser. */ export function baueEntwuerfe(rows: EntwurfZeile[], userId: string | null): Entwurf[] { return rows.map((row) => { return { id: row.id, step: row.step, payload: row.payload, updated_at: row.updated_at, vonMir: userId !== null && row.created_by === userId, // Ohne Namen die E-Mail. „Jemand" als letzter Ausweg: dass der Entwurf // belegt ist, muss auch dann herauskommen, wenn nicht zu sagen ist, // von wem — sonst wirkte „Fortsetzen" grundlos abgeschaltet. gesperrtVon: row.gesperrt ? row.sperrer_name?.trim() || row.sperrer_email || "jemandem" : null, // Ohne Namen die E-Mail — sonst stünde an einem Entwurf nichts ausser // dem Hinweis, dass er von jemandem ist. autor: row.author_name?.trim() || row.author_email || "Unbekannt", }; }); }