Files
AfiS-Aktensystem/packages/db/prisma/seed.ts
T
HarlekinandClaude Opus 5 5cbdabf337 Aktenzeichen-Muster je Vorlage, Startskript für die Entwicklung
Richtigstellung zuerst: der vorige Commit trägt die Nachricht "Nichts davon
gebaut", enthält aber bereits die Schema-Änderung an CaseTemplate und
CaseCounter. Ein abgebrochener Aufruf hatte die Datei schon geschrieben, und
das nachfolgende `git add -A` hat sie mitgenommen. Die Nachricht war damit
falsch. Hier ist die Änderung vollständig und geprüft.

Das Muster des Aktenzeichens gehört zur Vorlage: Präfix und Stellenzahl je
Fallart, ein Nummernkreis je Präfix und Jahr. Die Abteilung führt gewachsene
Präfixe wie AKT-PA-2026-62843 neben dem AfiS-2026-0042 aus dem Glossar.

Die Migration ist von Hand geschrieben, weil der Wechsel des Primärschlüssels
auf CaseCounter den bestehenden Zähler erhalten muss: eine zurückgesetzte
Nummer wäre ein doppeltes Aktenzeichen. Vorher und nachher geprüft.

parseCaseNumber zerlegt von hinten, damit Präfixe mit Bindestrich funktionieren.
Ein Test belegt, dass AKT-PA und AfiS getrennte Nummernkreise führen.

Dazu start.sh für die lokale Entwicklung, solange der Dockge-Stack nicht läuft:
Datenbank hochfahren, Migrationen, Grundzustand, Worker und Web. Mit
--ohne-worker für Arbeit ohne Discord und --nur-db für die Datenbank allein.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 12:10:34 +02:00

147 lines
5.2 KiB
TypeScript

import { createPrismaClient } from '../src/index.js'
/**
* Grundzustand der Anwendung. Alles hier ist Konfiguration, ohne die die App
* nicht sinnvoll läuft, kein Testinhalt.
*
* Die vier Startvorlagen stehen hier, weil ohne Vorlage keine Akte angelegt
* werden kann. Den Vorlagen-Editor und die übrigen Feldtypen baut Phase 3.
*/
const db = createPrismaClient()
/**
* Auslieferungszustand. Ab Stufe 3 ist die Existenz einer Akte für niedrigere
* Stufen verborgen, darunter ist nur der Inhalt gesperrt. Der Leiter kann das
* im Adminbereich ändern.
*
* Die Namen entsprechen den Zugangsrollen auf dem Discord und bleiben deshalb
* englisch, siehe docs/13-entscheidungen.md. Stufe 0 hat keine eigene
* Zugangsrolle: sie gilt für jedes AfiS-Mitglied ohne eine solche Rolle.
*/
const CLEARANCE_LEVELS = [
{ level: 0, name: 'Unclassified', hiddenBelow: false, colorToken: 'stamp-0' },
{ level: 1, name: 'Restricted Access', hiddenBelow: false, colorToken: 'stamp-1' },
{ level: 2, name: 'Confidential Access', hiddenBelow: false, colorToken: 'stamp-2' },
{ level: 3, name: 'Sensitive Access', hiddenBelow: true, colorToken: 'stamp-3' },
{ level: 4, name: 'Critical Operations', hiddenBelow: true, colorToken: 'stamp-4' },
{ level: 5, name: 'Executive Oversight', hiddenBelow: true, colorToken: 'stamp-5' },
]
/**
* Startvorlagen nach docs/05-akten-und-vorlagen.md. Die Feldschlüssel sind
* stabil und werden nie geändert — eine Akte merkt sich ihre Vorlage über
* `templateId`, und eine neue Vorlagenversion lässt bestehende Akten unberührt.
*
* Der Zuschnitt der Felder ist bewusst schmal gehalten: den Vorlagen-Editor
* mit allen Feldtypen baut Phase 3, hier steht nur, was eine Akte zum Laufen
* braucht.
*/
const TEMPLATES: {
key: string
name: string
description: string
numberPrefix?: string
numberPadding?: number
}[] = [
{
key: 'korruptionsverdacht',
name: 'Korruptionsverdacht',
description: 'Verdacht auf Vorteilsnahme, Bestechlichkeit oder Interessenkonflikt',
},
{
key: 'dienstvergehen',
name: 'Dienstvergehen',
description: 'Verstoß gegen Dienstpflichten oder interne Vorschriften',
},
{
key: 'sicherheitsvorfall',
name: 'Sicherheitsvorfall',
description: 'Containment-Bruch, Datenabfluss oder unbefugter Zugriff',
},
{
key: 'routinepruefung',
name: 'Routineprüfung',
description: 'Turnusmäßige Kontrolle ohne konkreten Anfangsverdacht',
},
]
const TEMPLATE_FIELDS = [
{ key: 'sachverhalt', label: 'Sachverhalt', type: 'longtext', required: true },
{ key: 'beteiligte', label: 'Beteiligte Personen', type: 'longtext' },
{ key: 'feststellungen', label: 'Feststellungen', type: 'longtext' },
{
key: 'bewertung',
label: 'Abschließende Bewertung',
type: 'longtext',
requiredToClose: true,
help: 'Muss gefüllt sein, bevor der Fall abgeschlossen werden kann.',
},
]
async function main(): Promise<void> {
for (const config of CLEARANCE_LEVELS) {
await db.clearanceLevelConfig.upsert({
where: { level: config.level },
create: config,
update: { name: config.name, colorToken: config.colorToken },
})
}
console.log(`[seed] ${CLEARANCE_LEVELS.length} Freigabestufen`)
// Änderungsrichtlinie im Auslieferungszustand: eigene Einträge sind 15 Minuten
// frei änderbar, danach nur mit Recht. Eine Änderung in dieser Karenzzeit wird
// versioniert und protokolliert, aber nicht sichtbar markiert.
// Siehe docs/05-akten-und-vorlagen.md.
await db.appSetting.upsert({
where: { id: 1 },
create: { id: 1 },
update: {},
})
console.log('[seed] Änderungsrichtlinie GRACE_WINDOW, 15 Minuten')
for (const template of TEMPLATES) {
await db.caseTemplate.upsert({
where: { key_version: { key: template.key, version: 1 } },
create: {
...template,
numberPrefix: template.numberPrefix ?? 'AfiS',
numberPadding: template.numberPadding ?? 4,
version: 1,
fields: TEMPLATE_FIELDS,
isActive: true,
},
// Muster und Felder bleiben unangetastet: was der Leiter im
// Vorlagen-Editor eingestellt hat, überschreibt der Seed nicht.
update: { name: template.name, description: template.description },
})
}
console.log(`[seed] ${TEMPLATES.length} Startvorlagen`)
// Ein Nummernkreis je Präfix und Jahr. Angelegt wird er beim ersten Fall
// von selbst; hier steht er nur, damit ein frischer Bestand nachvollziehbar
// bei null beginnt.
const year = new Date().getFullYear()
for (const prefix of [...new Set(TEMPLATES.map((t) => t.numberPrefix ?? 'AfiS'))]) {
await db.caseCounter.upsert({
where: { prefix_year: { prefix, year } },
create: { prefix, year, lastNumber: 0 },
update: {},
})
}
console.log(`[seed] Aktenzeichen-Zähler für ${year}`)
// entry.edit_own, entry.edit_any und entry.retract sind bewusst keiner Rolle
// zugeordnet. Der Leiter schaltet sie frei, wenn er sie will.
// Siehe docs/03-auth-und-rechte.md.
const mappings = await db.roleMapping.count()
console.log(`[seed] ${mappings} Rollen-Zuordnungen vorhanden, keine angelegt`)
}
main()
.then(() => db.$disconnect())
.catch(async (error: unknown) => {
console.error('[seed] fehlgeschlagen', error)
await db.$disconnect()
process.exit(1)
})