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
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
116 lines
3.9 KiB
TypeScript
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
|
|
}
|