Files
Harlekin e0b03ea336
Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
Gedankenstriche durch Bindestriche ersetzt
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die
README; an keiner Stelle Bedeutung, nur Zeichensetzung.
2026-09-04 08:54:52 +02:00

116 lines
3.9 KiB
TypeScript

/**
* 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:<id>|ROLLE` oder
* `file:<id>|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<string>()
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
}