/** * Feste Wertelisten der Akte - Rollen der Beteiligten und Arten der * Verknüpfung. * * **Ohne Abhängigkeiten.** Diese Datei wird auch im Client geladen, und die * Dienstmodule daneben ziehen den Datenbankclient nach sich. Denselben Schnitt * hat `lib/templates/fields.ts` gegenüber `schema.ts`, aus demselben Grund: * das Bündelbudget aus docs/09-performance-und-ux.md. * * Die Übersetzungen stehen in `lib/i18n/de.ts`, nicht hier: diese Namen sind * Datenbankwerte. */ export const PARTICIPANT_ROLES = [ 'LEAD', 'INVESTIGATOR', 'OBSERVER', 'WITNESS', 'SUBJECT', 'SUSPECT', 'ACCOMPLICE', ] as const export type ParticipantRoleName = (typeof PARTICIPANT_ROLES)[number] export function isParticipantRole(value: string): value is ParticipantRoleName { return (PARTICIPANT_ROLES as readonly string[]).includes(value) } /** * Wer an der Akte **arbeitet** - und nur diese drei Rollen öffnen sie auch. * * Die Beteiligtenliste beantwortet zwei ganz verschiedene Fragen: *wer bearbeitet * diesen Fall* und *um wen geht es darin*. Sie standen bisher gleichberechtigt * nebeneinander, und weil ein Beteiligter die Akte lesen darf, hätte ein * Betroffener mit Discord-Konto die Ermittlung gegen sich selbst mitlesen * können. Mit „Täter" und „Mittäter" wäre das offensichtlich falsch geworden, * es war es vorher schon. * * Deshalb: **nur Arbeitsrollen öffnen die Akte.** Zeuge, Betroffener, Täter und * Mittäter sind Angaben über den Fall, keine Berechtigung an ihm. * Siehe docs/03-auth-und-rechte.md. */ export const WORKING_ROLES = ['LEAD', 'INVESTIGATOR', 'OBSERVER'] as const export type WorkingRoleName = (typeof WORKING_ROLES)[number] /** Ob diese Rolle Zugang zur Akte verschafft. */ export function grantsCaseAccess(role: string): boolean { return (WORKING_ROLES as readonly string[]).includes(role) } /** * Die Zustände einer Fallakte in der Reihenfolge des Lebenszyklus aus * docs/05-akten-und-vorlagen.md. `SEALED` steht am Ende, weil es aus jedem * Zustand erreichbar ist und keine Stufe im Ablauf bildet. */ export const CASE_STATUSES = [ 'DRAFT', 'OPEN', 'REVIEW', 'DORMANT', 'CLOSED', 'ARCHIVED', 'SEALED', ] as const export const LINK_KINDS = ['RELATED', 'SUCCESSOR', 'MERGED_INTO'] as const export type LinkKindName = (typeof LINK_KINDS)[number] export function isLinkKind(value: string): value is LinkKindName { return (LINK_KINDS as readonly string[]).includes(value) } /** * Ein Beteiligter, wie ihn das Formular „Neue Fallakte" mitschickt. * * Je Zeile ein verstecktes Feld der Form `user:|ROLLE` oder * `file:|ROLLE`. Die Zerlegung steht hier und nicht in der Server Action, * damit sie prüfbar ist: sie entscheidet, wer mit welcher Rolle in eine Akte * kommt, und eine unbekannte Rolle darf nicht durchrutschen. */ export type ParticipantDraft = | { kind: 'user'; id: string; role: ParticipantRoleName } | { kind: 'file'; id: string; role: ParticipantRoleName } /** So viele Beteiligte nimmt das Formular höchstens an. */ export const MAX_PARTICIPANT_DRAFTS = 50 export function parseParticipantDrafts(werte: readonly unknown[]): ParticipantDraft[] { const gesehen = new Set() const ergebnis: ParticipantDraft[] = [] for (const wert of werte) { if (typeof wert !== 'string') continue const [ziel, rolle] = wert.split('|') const [art, id] = (ziel ?? '').split(':') if (art !== 'user' && art !== 'file') continue if (!id) continue if (!rolle || !isParticipantRole(rolle)) continue // Dieselbe Person in derselben Rolle nur einmal. In einer anderen Rolle // darf sie durchaus zweimal stehen: Zeuge und Betroffener zugleich kommt vor. const schluessel = `${art}:${id}|${rolle}` if (gesehen.has(schluessel)) continue gesehen.add(schluessel) ergebnis.push({ kind: art, id, role: rolle }) if (ergebnis.length >= MAX_PARTICIPANT_DRAFTS) break } return ergebnis }