import type { Kommentar } from "@/generated/prisma/client"; import { prisma } from "@/lib/db"; import type { HandelnderBenutzer } from "@/server/mandant"; import { LaufFehler, laufZumSchreibenHolen } from "@/server/services/laeufe"; /** * Kommentare an Laeufen und Schrittergebnissen (#013). * * Der Ort fuer das, was in kein Feld passt: „Kunde war nicht erreichbar“, * „Lizenz kommt am Montag“, „Netzteil defekt, Ersatz bestellt“. Ein Kommentar * traegt seinen Urheber und seinen Zeitpunkt selbst, und er wird nie geaendert — * deshalb gibt es hier nur einen Weg hinein und keinen hinaus. Das ist auch der * Grund, warum ein Kommentar **keinen** Protokolleintrag bekommt: er ist schon * einer. Ein zweiter Eintrag, der dasselbe noch einmal sagt, wuerde das Protokoll * nur laenger machen. * * Der Schreibschutz gilt trotzdem: an einem archivierten Lauf kommt kein * Kommentar mehr dazu. Der Weg dorthin ist derselbe wie bei den * Schrittergebnissen — `laufZumSchreibenHolen`, und darin * `laufMussVeraenderbarSein`. */ /** * Obergrenze je Kommentar. * * Ohne Grenze landet ein versehentlich eingefuegtes Ereignisprotokoll in der * Datenbank und in jeder Druckansicht. Viertausend Zeichen sind ungefaehr zwei * DIN-A4-Seiten — mehr als genug fuer eine Notiz, und die Meldung nennt die Zahl. */ export const KOMMENTAR_HOECHSTLAENGE = 4000; export type NeuerKommentar = { readonly laufId: string; /** Leer: der Kommentar haengt am Lauf und nicht an einem einzelnen Schritt. */ readonly schrittergebnisId?: string | null; readonly rumpf: string; }; /** * Schreibt einen Kommentar. * * Ein Kommentar **startet den Lauf nicht**: eine Rueckfrage zu notieren ist keine * Abarbeitung, und ein zugewiesener Lauf, der von einer Notiz auf `IN_ARBEIT` * springt, saehe in der Liste aus wie begonnene Arbeit. */ export async function kommentarHinzufuegen( eingabe: NeuerKommentar, benutzer: HandelnderBenutzer, ): Promise { const rumpf = eingabe.rumpf.trim(); if (!rumpf) throw new LaufFehler("Ein leerer Kommentar hilft niemandem."); if (rumpf.length > KOMMENTAR_HOECHSTLAENGE) { throw new LaufFehler( `Der Kommentar ist ${rumpf.length} Zeichen lang; erlaubt sind ${KOMMENTAR_HOECHSTLAENGE}. ` + "Längeres gehört in die Notiz am Schritt oder ins Ticket.", ); } return prisma.$transaction(async (tx) => { const lauf = await laufZumSchreibenHolen(tx, eingabe.laufId, benutzer); const schrittergebnisId = eingabe.schrittergebnisId?.trim() || null; if (schrittergebnisId) { // Die Kennung kommt von aussen. Ohne diese Pruefung hinge der Kommentar an // einem Schritt eines fremden Laufs – und damit moeglicherweise eines // fremden Kunden, dessen Schreibschutz nie geprueft wurde. const ergebnis = await tx.schrittergebnis.findUnique({ where: { id: schrittergebnisId }, select: { laufId: true }, }); if (!ergebnis || ergebnis.laufId !== lauf.id) { throw new LaufFehler("Dieser Schritt gehört nicht zu diesem Lauf."); } } return tx.kommentar.create({ data: { laufId: lauf.id, schrittergebnisId, benutzerId: benutzer.id, rumpf }, }); }); }