'use server'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; import { erfordereBenutzer, erfordereBewertungsrecht, ausbildungVonAzubi } from '@/lib/auth/rechte'; import { darfAzubiDatenSehen } from '@/lib/auth/regeln'; import { abzeichnen, berichtNachId, einreichen, inhaltSpeichern, notizSetzen, sorgeFuerBlatt, zurueckgeben, } from '@/lib/repos/berichtsheft'; import type { Wochenblatt } from '@/lib/berichtsheft/pruefung'; import { pruefeModul } from '@/lib/module/pruefen'; export type Rueckmeldung = { fehler?: string; erfolg?: string }; const datumMuster = /^\d{4}-\d{2}-\d{2}$/; /** Zum Bearbeiten: nur das eigene Blatt, und nur solange es nicht abgezeichnet ist. */ async function blattZumBearbeiten(berichtId: string) { const benutzer = await erfordereBenutzer(); const bericht = berichtNachId(berichtId); if (!bericht) throw new Error('Dieses Wochenblatt gibt es nicht.'); if (bericht.benutzerId !== benutzer.id) throw new Error('Kein Zugriff auf dieses Wochenblatt.'); return { benutzer, bericht }; } /** Zum Lesen: das eigene Blatt oder das eines betreuten Azubis. */ export async function blattZumLesen(berichtId: string) { const benutzer = await erfordereBenutzer(); pruefeModul('berichtsheft'); const bericht = berichtNachId(berichtId); if (!bericht) return null; const bezug = ausbildungVonAzubi(bericht.benutzerId); if (!darfAzubiDatenSehen(benutzer, bericht.benutzerId, bezug)) return null; return { benutzer, bericht }; } function aktualisiere(berichtId?: string) { revalidatePath('/berichtsheft'); if (berichtId) revalidatePath(`/berichtsheft/${berichtId}`); revalidatePath('/'); } // --- Tagesnotizen ---------------------------------------------------------- export async function tagesnotizSpeichern(_vorher: Rueckmeldung, formular: FormData): Promise { const benutzer = await erfordereBenutzer(); pruefeModul('berichtsheft'); if (benutzer.rolle !== 'azubi') return { fehler: 'Tagesnotizen schreibt der Azubi.' }; const datum = String(formular.get('datum') ?? '').trim(); if (!datumMuster.test(datum)) return { fehler: 'Ungültiges Datum.' }; const text = String(formular.get('text') ?? ''); if (text.length > 2000) return { fehler: 'Die Notiz ist zu lang – höchstens 2000 Zeichen.' }; const stunden = zahl(formular.get('stunden')); if (stunden > 24) return { fehler: 'Mehr als 24 Stunden an einem Tag sind nicht plausibel.' }; notizSetzen(benutzer.id, datum, text, stunden); aktualisiere(); return { erfolg: text.trim() ? 'Notiz gespeichert.' : 'Notiz entfernt.' }; } // --- Wochenblatt ----------------------------------------------------------- export async function blattAnlegen(formular: FormData): Promise { const benutzer = await erfordereBenutzer(); pruefeModul('berichtsheft'); if (benutzer.rolle !== 'azubi') return; const datum = String(formular.get('datum') ?? '').trim(); if (!datumMuster.test(datum)) return; const bericht = sorgeFuerBlatt(benutzer.id, datum); aktualisiere(bericht.id); redirect(`/berichtsheft/${bericht.id}`); } /** Liest das Blatt aus dem Formular. Leere Zeilen fliegen raus. */ function leseBlatt(formular: FormData): Wochenblatt { const betriebTexte = formular.getAll('betriebText').map(String); const betriebStunden = formular.getAll('betriebStunden'); const schuleLernfelder = formular.getAll('schuleLernfeld').map(String); const schuleThemen = formular.getAll('schuleThemen').map(String); const schuleStunden = formular.getAll('schuleStunden'); return { betrieb: betriebTexte .map((text, stelle) => ({ text: text.trim(), stunden: zahl(betriebStunden[stelle]) })) .filter((e) => e.text !== '' || e.stunden > 0), schule: schuleLernfelder .map((lernfeld, stelle) => ({ lernfeld: lernfeld.trim(), themen: (schuleThemen[stelle] ?? '').trim(), stunden: zahl(schuleStunden[stelle]), })) .filter((e) => e.lernfeld !== '' || e.themen !== '' || e.stunden > 0), sonstiges: String(formular.get('sonstiges') ?? '').trim(), }; } export async function blattSpeichern(_vorher: Rueckmeldung, formular: FormData): Promise { pruefeModul('berichtsheft'); const berichtId = String(formular.get('berichtId') ?? ''); const { bericht } = await blattZumBearbeiten(berichtId); if (bericht.status === 'abgezeichnet') { return { fehler: 'Das Blatt ist abgezeichnet und damit gesperrt.' }; } const blatt = leseBlatt(formular); if (blatt.betrieb.length > 30) return { fehler: 'Mehr als 30 Tätigkeiten in einer Woche sind zu viel.' }; if (blatt.sonstiges.length > 2000) return { fehler: 'Der Text unter „Sonstiges" ist zu lang.' }; inhaltSpeichern(berichtId, blatt); aktualisiere(berichtId); return { erfolg: 'Gespeichert.' }; } /** * Einreichen. * * Die Prüfung ist eine **Warnung, keine Sperre** – deshalb wird hier nichts * geprüft, was das Einreichen verhindern könnte. Der Azubi hat die Warnungen * auf der Seite gesehen; ob er sie beherzigt, entscheidet er. */ export async function blattEinreichen(_vorher: Rueckmeldung, formular: FormData): Promise { pruefeModul('berichtsheft'); const berichtId = String(formular.get('berichtId') ?? ''); const { bericht } = await blattZumBearbeiten(berichtId); if (bericht.status === 'abgezeichnet') return { fehler: 'Das Blatt ist schon abgezeichnet.' }; if (bericht.status === 'eingereicht') return { fehler: 'Das Blatt liegt schon beim Ausbilder.' }; // Vor dem Einreichen den letzten Stand übernehmen, damit nichts verlorengeht. inhaltSpeichern(berichtId, leseBlatt(formular)); einreichen(berichtId); aktualisiere(berichtId); return { erfolg: 'Eingereicht. Dein Ausbilder sieht das Blatt jetzt unter „Was auf dich wartet".' }; } export async function blattZurueckziehen(formular: FormData): Promise { pruefeModul('berichtsheft'); const berichtId = String(formular.get('berichtId') ?? ''); const { bericht } = await blattZumBearbeiten(berichtId); if (bericht.status !== 'eingereicht') return; zurueckgeben(berichtId, bericht.anmerkung ?? ''); aktualisiere(berichtId); } // --- Abzeichnen durch den Ausbilder ---------------------------------------- export async function blattAbzeichnen(_vorher: Rueckmeldung, formular: FormData): Promise { const ausbilder = await erfordereBenutzer(); pruefeModul('berichtsheft'); const berichtId = String(formular.get('berichtId') ?? ''); const bericht = berichtNachId(berichtId); if (!bericht) return { fehler: 'Dieses Wochenblatt gibt es nicht.' }; erfordereBewertungsrecht(ausbilder, bericht.benutzerId); if (bericht.status === 'abgezeichnet') return { fehler: 'Das Blatt ist schon abgezeichnet.' }; const anmerkung = String(formular.get('anmerkung') ?? '').trim(); if (anmerkung.length > 2000) return { fehler: 'Die Anmerkung ist zu lang.' }; abzeichnen(berichtId, ausbilder.id, anmerkung || null); aktualisiere(berichtId); return { erfolg: 'Abgezeichnet. Das Blatt ist damit gesperrt.' }; } export async function blattZurueckgeben(_vorher: Rueckmeldung, formular: FormData): Promise { const ausbilder = await erfordereBenutzer(); pruefeModul('berichtsheft'); const berichtId = String(formular.get('berichtId') ?? ''); const bericht = berichtNachId(berichtId); if (!bericht) return { fehler: 'Dieses Wochenblatt gibt es nicht.' }; erfordereBewertungsrecht(ausbilder, bericht.benutzerId); const anmerkung = String(formular.get('anmerkung') ?? '').trim(); if (!anmerkung) { return { fehler: 'Ohne Anmerkung weiß der Azubi nicht, was er ändern soll. Schreib bitte einen Satz dazu.' }; } if (anmerkung.length > 2000) return { fehler: 'Die Anmerkung ist zu lang.' }; zurueckgeben(berichtId, anmerkung); aktualisiere(berichtId); return { erfolg: 'Zurückgegeben. Der Azubi kann das Blatt jetzt wieder bearbeiten.' }; } function zahl(wert: FormDataEntryValue | null | undefined): number { const roh = String(wert ?? '').replace(',', '.').trim(); const gelesen = Number(roh); return Number.isFinite(gelesen) && gelesen > 0 ? Math.round(gelesen * 100) / 100 : 0; }