// @vitest-environment node import { afterAll, beforeAll, describe, expect, it } from 'vitest' import { db } from '@/lib/db' import { createPersonnelFile } from '@/lib/personnel/service' import { createDocument, finalizeDocument, listTemplatesFor, loadDocumentSections, loadVisibleDocument, retractDocument, saveDocumentSection, } from '@/lib/documents/service' import { AccessError } from '@/lib/errors' import type { EffectivePermissions, Permission } from '@/lib/auth/permissions' /** * Der Weg, den ein Sonderarbeitsvertrag wirklich nimmt: Personalakte anlegen, * Dokument aus der Vorlage erzeugen, Felder füllen, ausfertigen, zurückziehen. * * Läuft gegen die Startvorlagen aus dem Seed. Wenn diese Prüfung bricht, ist * entweder die Vorlage kaputt oder die Dokumentebene. */ const url = process.env.DIRECT_DATABASE_URL ?? process.env.DATABASE_URL const suite = url ? describe : describe.skip const marke = `ablauf-${Date.now()}` function perms( userId: string, clearanceLevel: number, permissions: Permission[] = [], ): EffectivePermissions { return { userId, clearanceLevel, permissions: new Set(permissions), isAdmin: false, hasMembership: true, } } suite('Ein Sonderarbeitsvertrag von Anfang bis Ende', () => { const leitung = perms(`${marke}-leitung`, 4, [ 'personnel.read', 'personnel.edit', 'document.create', 'document.edit', 'document.finalize', 'document.retract', ]) const neuling = perms(`${marke}-neuling`, 1, ['personnel.read']) let personalakteId = '' let documentNumber = '' beforeAll(async () => { for (const p of [leitung, neuling]) { await db.user.create({ data: { id: p.userId, username: p.userId, displayName: p.userId }, }) } const akte = await createPersonnelFile(leitung, { displayName: `${marke} Vertragspartner`, classification: 1, }) personalakteId = akte.id }) afterAll(async () => { await db.$disconnect() }) it('bietet an einer Person nur die dafür vorgesehenen Vorlagen an', async () => { const schluessel = (await listTemplatesFor('PERSON_DOCUMENT')).map((t) => t.key) expect(schluessel).toContain('sonderarbeitsvertrag') expect(schluessel).toContain('vermerk') // Eine Fallart legt kein Dokument an. expect(schluessel).not.toContain('korruptionsverdacht') }) it('lehnt eine Vorlage ab, die an dieser Stelle nichts zu suchen hat', async () => { await expect( createDocument(leitung, { templateKey: 'korruptionsverdacht', classification: 1, owner: { kind: 'personnel', personnelFileId: personalakteId }, }), ).rejects.toThrow(AccessError) }) it('legt den Vertrag mit eigener Nummer aus dem Präfix der Vorlage an', async () => { const created = await createDocument(leitung, { templateKey: 'sonderarbeitsvertrag', title: 'Sonderarbeitsvertrag Testlauf', classification: 1, owner: { kind: 'personnel', personnelFileId: personalakteId }, }) documentNumber = created.documentNumber // Das Präfix kommt aus der Vorlage, nicht aus dem Programm. expect(documentNumber).toMatch(/^SAV-\d{4}-\d{4}$/) }) it('ersetzt Platzhalter im festen Vertragstext aus den Eingabefeldern', async () => { await saveDocumentSection(leitung, { documentNumber, fieldKey: 'vertragspartner', value: 'Dr. Vogt', baseVersion: 0, }) const doc = await loadVisibleDocument(leitung, documentNumber) const sections = await loadDocumentSections(leitung, doc!) const eingabe = sections.find((s) => s.fieldKey === 'vertragspartner') expect(eingabe?.visible && eingabe.value).toBe('Dr. Vogt') // Der Textbaustein selbst bleibt roh; ersetzt wird beim Rendern, damit // eine spätere Korrektur des Namens überall durchschlägt. const praeambel = sections.find((s) => s.fieldKey === 'praeambel') expect(praeambel?.field.type).toBe('paragraph') expect((praeambel?.field as { text?: string }).text).toContain('{{vertragspartner}}') }) it('lässt einen Nutzer ohne Recht nichts daran ändern', async () => { await expect( saveDocumentSection(neuling, { documentNumber, fieldKey: 'taetigkeit', value: 'Fremdeingriff', baseVersion: 0, }), ).rejects.toThrow(AccessError) }) it('macht aus dem Entwurf eine Ausfertigung und dann einen Grabstein', async () => { await finalizeDocument(leitung, documentNumber) const ausgefertigt = await loadVisibleDocument(leitung, documentNumber) expect(ausgefertigt?.status).toBe('FINAL') expect(ausgefertigt?.finalizedById).toBe(leitung.userId) await retractDocument(leitung, documentNumber, 'Vertrag wurde nie unterzeichnet') const zurueckgezogen = await loadVisibleDocument(leitung, documentNumber) expect(zurueckgezogen?.status).toBe('RETRACTED') expect(zurueckgezogen?.retractionReason).toBe('Vertrag wurde nie unterzeichnet') // Der Grabstein steht, das Dokument ist nicht verschwunden. expect(zurueckgezogen?.title).toBe('Sonderarbeitsvertrag Testlauf') // Und er ist zu: auch die Leitung schreibt nicht mehr hinein. await expect( saveDocumentSection(leitung, { documentNumber, fieldKey: 'taetigkeit', value: 'Nachtrag', baseVersion: 0, }), ).rejects.toThrow(AccessError) }) it('verlangt für die Zurücknahme eine Begründung', async () => { const zweiter = await createDocument(leitung, { templateKey: 'vermerk', classification: 1, owner: { kind: 'personnel', personnelFileId: personalakteId }, }) await expect(retractDocument(leitung, zweiter.documentNumber, 'kurz')).rejects.toThrow( AccessError, ) }) })