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>
196 lines
6.9 KiB
TypeScript
196 lines
6.9 KiB
TypeScript
'use server';
|
||
|
||
import { revalidatePath } from 'next/cache';
|
||
import { erfordereBenutzer, erfordereBewertungsrecht, erfordereZugriffAufAzubi } from '@/lib/auth/rechte';
|
||
import { inhalte } from '@/lib/inhalte';
|
||
import { anhangAblegen, anhangLoeschen, anhangNachId } from '@/lib/repos/anhaenge';
|
||
import {
|
||
abnehmen,
|
||
bewertungSetzen,
|
||
notizSetzen,
|
||
statusSetzen,
|
||
standVon,
|
||
zurueckgeben,
|
||
} from '@/lib/repos/aufgaben';
|
||
import { aufgabenStatus, type AufgabenStatus } from '@/lib/db/schema';
|
||
import { pruefeModul } from '@/lib/module/pruefen';
|
||
|
||
export type Rueckmeldung = { fehler?: string; erfolg?: string };
|
||
|
||
function aktualisiere(aufgabeId: string) {
|
||
revalidatePath('/aufgaben');
|
||
revalidatePath(`/aufgaben/${aufgabeId}`);
|
||
revalidatePath('/');
|
||
}
|
||
|
||
function aufgabeGibtEs(aufgabeId: string): boolean {
|
||
return inhalte().aufgabeNach.has(aufgabeId);
|
||
}
|
||
|
||
// --- Der Azubi an seiner eigenen Aufgabe -----------------------------------
|
||
|
||
export async function eigenenStatusSetzen(formular: FormData): Promise<void> {
|
||
const benutzer = await erfordereBenutzer();
|
||
pruefeModul('aufgaben');
|
||
if (benutzer.rolle !== 'azubi') return;
|
||
|
||
const aufgabeId = String(formular.get('aufgabeId') ?? '');
|
||
const status = String(formular.get('status') ?? '') as AufgabenStatus;
|
||
|
||
if (!aufgabeGibtEs(aufgabeId)) return;
|
||
if (!aufgabenStatus.includes(status)) return;
|
||
// Abnehmen darf nur der Ausbilder.
|
||
if (status === 'abgenommen') return;
|
||
|
||
statusSetzen(benutzer.id, aufgabeId, status);
|
||
aktualisiere(aufgabeId);
|
||
}
|
||
|
||
export async function eigeneNotizSpeichern(_vorher: Rueckmeldung, formular: FormData): Promise<Rueckmeldung> {
|
||
const benutzer = await erfordereBenutzer();
|
||
pruefeModul('aufgaben');
|
||
if (benutzer.rolle !== 'azubi') return { fehler: 'Nur der Azubi schreibt seine eigenen Notizen.' };
|
||
|
||
const aufgabeId = String(formular.get('aufgabeId') ?? '');
|
||
const notiz = String(formular.get('notiz') ?? '');
|
||
|
||
if (!aufgabeGibtEs(aufgabeId)) return { fehler: 'Diese Aufgabe gibt es nicht.' };
|
||
if (notiz.length > 8000) return { fehler: 'Die Notiz ist zu lang – höchstens 8000 Zeichen.' };
|
||
|
||
notizSetzen(benutzer.id, aufgabeId, notiz);
|
||
aktualisiere(aufgabeId);
|
||
return { erfolg: 'Notiz gespeichert.' };
|
||
}
|
||
|
||
export async function anhangHochladen(_vorher: Rueckmeldung, formular: FormData): Promise<Rueckmeldung> {
|
||
const benutzer = await erfordereBenutzer();
|
||
pruefeModul('aufgaben');
|
||
if (benutzer.rolle !== 'azubi') return { fehler: 'Anhänge lädt der Azubi hoch.' };
|
||
|
||
const aufgabeId = String(formular.get('aufgabeId') ?? '');
|
||
if (!aufgabeGibtEs(aufgabeId)) return { fehler: 'Diese Aufgabe gibt es nicht.' };
|
||
|
||
const stand = standVon(benutzer.id, aufgabeId);
|
||
if (stand?.status === 'abgenommen') {
|
||
return { fehler: 'Die Aufgabe ist abgenommen – sie lässt sich nicht mehr ändern.' };
|
||
}
|
||
|
||
const dateien = formular.getAll('datei').filter((d): d is File => d instanceof File && d.size > 0);
|
||
if (dateien.length === 0) return { fehler: 'Keine Datei ausgewählt.' };
|
||
|
||
const beschriftung = String(formular.get('beschriftung') ?? '');
|
||
const fehler: string[] = [];
|
||
let angelegt = 0;
|
||
|
||
for (const datei of dateien) {
|
||
const ergebnis = await anhangAblegen({
|
||
bezugTyp: 'aufgabe',
|
||
// Der Bezug ist Aufgabe **und** Azubi – sonst sähe ein zweiter Azubi
|
||
// die Anhänge des ersten an derselben Aufgabe.
|
||
bezugId: `${aufgabeId}::${benutzer.id}`,
|
||
benutzerId: benutzer.id,
|
||
datei,
|
||
beschriftung: dateien.length === 1 ? beschriftung : undefined,
|
||
});
|
||
|
||
if ('fehler' in ergebnis) {
|
||
fehler.push(`${datei.name}: ${ergebnis.fehler}`);
|
||
} else {
|
||
angelegt += 1;
|
||
}
|
||
}
|
||
|
||
aktualisiere(aufgabeId);
|
||
|
||
if (angelegt === 0) return { fehler: fehler.join(' · ') };
|
||
if (fehler.length > 0) {
|
||
return { fehler: `${angelegt} übernommen, aber: ${fehler.join(' · ')}` };
|
||
}
|
||
return { erfolg: `${angelegt} ${angelegt === 1 ? 'Anhang' : 'Anhänge'} hinzugefügt.` };
|
||
}
|
||
|
||
export async function anhangEntfernen(formular: FormData): Promise<void> {
|
||
const benutzer = await erfordereBenutzer();
|
||
pruefeModul('aufgaben');
|
||
const anhangId = String(formular.get('anhangId') ?? '');
|
||
const aufgabeId = String(formular.get('aufgabeId') ?? '');
|
||
|
||
const eintrag = anhangNachId(anhangId);
|
||
if (!eintrag) return;
|
||
|
||
// Nur der eigene Anhang, und nur solange die Aufgabe nicht abgenommen ist.
|
||
if (eintrag.benutzerId !== benutzer.id) return;
|
||
const stand = standVon(benutzer.id, aufgabeId);
|
||
if (stand?.status === 'abgenommen') return;
|
||
|
||
await anhangLoeschen(anhangId);
|
||
aktualisiere(aufgabeId);
|
||
}
|
||
|
||
// --- Der Ausbilder an der Aufgabe seines Azubis ----------------------------
|
||
|
||
export async function bewerten(_vorher: Rueckmeldung, formular: FormData): Promise<Rueckmeldung> {
|
||
const ausbilder = await erfordereBenutzer();
|
||
pruefeModul('aufgaben');
|
||
const azubiId = String(formular.get('azubiId') ?? '');
|
||
const aufgabeId = String(formular.get('aufgabeId') ?? '');
|
||
|
||
erfordereBewertungsrecht(ausbilder, azubiId);
|
||
|
||
const aufgabe = inhalte().aufgabeNach.get(aufgabeId);
|
||
if (!aufgabe) return { fehler: 'Diese Aufgabe gibt es nicht.' };
|
||
|
||
const kriterien = formular
|
||
.getAll('kriterium')
|
||
.map((wert) => Number(wert))
|
||
.filter((n) => Number.isInteger(n) && n >= 0 && n < aufgabe.kriterien.length);
|
||
|
||
const bewertung = String(formular.get('bewertung') ?? '').trim();
|
||
const rueckmeldung = String(formular.get('rueckmeldung') ?? '').trim();
|
||
|
||
if (bewertung.length > 60) return { fehler: 'Die Bewertung ist zu lang – höchstens 60 Zeichen.' };
|
||
if (rueckmeldung.length > 4000) return { fehler: 'Die Rückmeldung ist zu lang – höchstens 4000 Zeichen.' };
|
||
|
||
bewertungSetzen(azubiId, aufgabeId, {
|
||
kriterien,
|
||
bewertung: bewertung || null,
|
||
rueckmeldung: rueckmeldung || null,
|
||
});
|
||
|
||
aktualisiere(aufgabeId);
|
||
return { erfolg: `Bewertung gespeichert – ${kriterien.length} von ${aufgabe.kriterien.length} Kriterien erfüllt.` };
|
||
}
|
||
|
||
export async function aufgabeAbnehmen(formular: FormData): Promise<void> {
|
||
const ausbilder = await erfordereBenutzer();
|
||
pruefeModul('aufgaben');
|
||
const azubiId = String(formular.get('azubiId') ?? '');
|
||
const aufgabeId = String(formular.get('aufgabeId') ?? '');
|
||
|
||
erfordereBewertungsrecht(ausbilder, azubiId);
|
||
if (!aufgabeGibtEs(aufgabeId)) return;
|
||
|
||
abnehmen(azubiId, aufgabeId, ausbilder.id);
|
||
aktualisiere(aufgabeId);
|
||
}
|
||
|
||
export async function aufgabeZurueckgeben(formular: FormData): Promise<void> {
|
||
const ausbilder = await erfordereBenutzer();
|
||
pruefeModul('aufgaben');
|
||
const azubiId = String(formular.get('azubiId') ?? '');
|
||
const aufgabeId = String(formular.get('aufgabeId') ?? '');
|
||
|
||
erfordereBewertungsrecht(ausbilder, azubiId);
|
||
if (!aufgabeGibtEs(aufgabeId)) return;
|
||
|
||
zurueckgeben(azubiId, aufgabeId);
|
||
aktualisiere(aufgabeId);
|
||
}
|
||
|
||
/** Wird von der Aufgabenseite gebraucht, um fremde Daten überhaupt lesen zu dürfen. */
|
||
export async function pruefeLesezugriff(azubiId: string): Promise<void> {
|
||
const benutzer = await erfordereBenutzer();
|
||
pruefeModul('aufgaben');
|
||
erfordereZugriffAufAzubi(benutzer, azubiId);
|
||
}
|