Merkkarten, Praxisaufgaben, Checklisten, Berichtsheft, Lernzielkontrollen und Feedbackgespraech lassen sich unter Verwaltung -> Module fuer die ganze Lern-Bar ab- und einschalten. Wochenplan, Handouts, Glossar, Auswertung und Verwaltung bleiben immer an. Gesperrt wird an drei Stellen, nicht im Layout (Next rendert Layouts beim Seitenwechsel darunter nicht neu): Seiten mit erfordereModul, Routen fuer PDF, Anhaenge und Lernstand mit modulAn, alle 37 Serveraktionen der Module mit pruefeModul. Die Anmeldepruefung kommt jeweils zuerst, damit ein Unangemeldeter nicht am 404 abliest, was abgeschaltet ist. Ein abgeschaltetes Modul traegt nirgends bei: Wochenplan, Fortschritt, Was-wartet, Mein Stand, Ergebnisverlauf, Rahmenplanabdeckung, Vorschlaege fuers Berichtsheft, Karten im Handout, Kurzbefehle im Manifest. Die Lernstand-Schnittstelle antwortet mit 410, das Geraet verwirft daraufhin seine Offline-Warteschlange. Abschalten loescht nichts. Gespeichert in modul_einstellung (Migration 0010); fehlt die Zeile, ist das Modul an, eine bestehende Datenbank verhaelt sich also wie vorher. 12 Tests fuer die Regeln, Abschnitt A8 im Abnahmelauf, Anleitung Kapitel 9 mit Bildschirmfoto, README, E43. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
207 lines
8.0 KiB
TypeScript
207 lines
8.0 KiB
TypeScript
'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<Rueckmeldung> {
|
||
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<void> {
|
||
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<Rueckmeldung> {
|
||
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<Rueckmeldung> {
|
||
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<void> {
|
||
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<Rueckmeldung> {
|
||
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<Rueckmeldung> {
|
||
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;
|
||
}
|