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>
80 lines
3.2 KiB
TypeScript
80 lines
3.2 KiB
TypeScript
import { erfordereBenutzer, ausbildungVonAzubi } from '@/lib/auth/rechte';
|
||
import { darfAzubiDatenSehen } from '@/lib/auth/regeln';
|
||
import { benutzerNachId } from '@/lib/repos/benutzer';
|
||
import { alsWochenblatt, berichteVon } from '@/lib/repos/berichtsheft';
|
||
import { berichtsheftPdf, type Blattangaben } from '@/lib/pdf/berichtsheft';
|
||
import { ausbildungsjahr } from '@/lib/berichtsheft/jahr';
|
||
import { alsDatumText, alsZeitpunktText } from '@/lib/zeit';
|
||
import { modulAn, modulGesperrt } from '@/lib/module/pruefen';
|
||
|
||
export const dynamic = 'force-dynamic';
|
||
|
||
const datumMuster = /^\d{4}-\d{2}-\d{2}$/;
|
||
|
||
/**
|
||
* Ein ganzer Zeitraum als PDF, ein Blatt je Seite.
|
||
*
|
||
* /berichtsheft/pdf?von=2026-08-01&bis=2026-12-31[&azubi=…]
|
||
*
|
||
* Ohne `azubi` das eigene Berichtsheft. Der Ausbilder gibt die Kennung an –
|
||
* geprüft wird sie serverseitig gegen das Ausbildungsverhältnis.
|
||
*/
|
||
export async function GET(anfrage: Request) {
|
||
const benutzer = await erfordereBenutzer();
|
||
if (!modulAn('berichtsheft')) return modulGesperrt();
|
||
const adresse = new URL(anfrage.url);
|
||
|
||
const azubiId = benutzer.rolle === 'azubi' ? benutzer.id : (adresse.searchParams.get('azubi') ?? '');
|
||
if (!azubiId) return new Response('Nicht gefunden.', { status: 404 });
|
||
|
||
const bezug = ausbildungVonAzubi(azubiId);
|
||
if (!darfAzubiDatenSehen(benutzer, azubiId, bezug)) {
|
||
return new Response('Nicht gefunden.', { status: 404 });
|
||
}
|
||
|
||
const von = adresse.searchParams.get('von') ?? '';
|
||
const bis = adresse.searchParams.get('bis') ?? '';
|
||
if (!datumMuster.test(von) || !datumMuster.test(bis) || von > bis) {
|
||
return new Response('Ungültiger Zeitraum.', { status: 400 });
|
||
}
|
||
|
||
const azubi = benutzerNachId(azubiId);
|
||
const ausbilder = bezug ? benutzerNachId(bezug.ausbilderId) : null;
|
||
|
||
// Aufsteigend, damit das Heft in der richtigen Reihenfolge ausgedruckt wird.
|
||
const berichte = berichteVon(azubiId)
|
||
.filter((b) => b.bis >= von && b.von <= bis)
|
||
.sort((a, b) => a.nr - b.nr);
|
||
|
||
const blaetter: Blattangaben[] = berichte.map((bericht) => {
|
||
const abzeichner = bericht.abgezeichnetVon ? benutzerNachId(bericht.abgezeichnetVon) : null;
|
||
return {
|
||
nr: bericht.nr,
|
||
von: alsDatumText(bericht.von),
|
||
bis: alsDatumText(bericht.bis),
|
||
azubi: azubi?.name ?? 'unbekannt',
|
||
ausbilder: ausbilder?.name ?? '',
|
||
beruf: bezug?.beruf ?? 'Fachinformatiker/-in für Systemintegration',
|
||
ausbildungsjahr: bezug ? ausbildungsjahr(bezug.beginn, bericht.von) : null,
|
||
status: bericht.status,
|
||
anmerkung: bericht.anmerkung,
|
||
abgezeichnetVon: abzeichner?.name ?? null,
|
||
abgezeichnetAm: bericht.abgezeichnetAm ? alsZeitpunktText(bericht.abgezeichnetAm) : null,
|
||
blatt: alsWochenblatt(bericht),
|
||
};
|
||
});
|
||
|
||
const pdf = await berichtsheftPdf(blaetter, `${alsDatumText(von)} bis ${alsDatumText(bis)}`);
|
||
const dateiname = `Ausbildungsnachweis_${von}_bis_${bis}.pdf`;
|
||
|
||
return new Response(new Uint8Array(pdf), {
|
||
headers: {
|
||
'Content-Type': 'application/pdf',
|
||
'Content-Length': String(pdf.byteLength),
|
||
'Content-Disposition': `inline; filename="${dateiname}"`,
|
||
'Cache-Control': 'private, no-store',
|
||
'X-Content-Type-Options': 'nosniff',
|
||
},
|
||
});
|
||
}
|