Baustein 7: Berichtsheft mit Vorschlaegen, Pruefung, Abzeichnen und PDF

- Taegliche Stichworte, je Tag ein eigenes Formular
- Wochenblatt mit Betrieb, Berufsschule und Sonstiges, je mit Stunden
- Vorschlaege aus Tagesnotizen, Aufgaben, Checklisten und Handouts
- Pruefung mitlaufend beim Tippen: fehlende Stunden, Floskeln,
  IP- und MAC-Adressen, E-Mail, Zugangsdaten, Kundennamen
- Warnung, keine Sperre - Einreichen ist immer moeglich
- Formulierungshilfe mit Taetigkeitsverben und Gegenueberstellungen
- Einreichen, zurueckgeben mit Anmerkung, abzeichnen; danach gesperrt
- Fehlende Wochen werden benannt und lassen sich nachtragen; die laufende
  Nummer richtet sich nach dem Zeitraum, nicht nach dem Anlegen
- PDF im IHK-Layout, einzeln oder als ganzer Zeitraum

Behoben: Im PDF-Kopffeld liefen die Spalten auseinander (dokument.y als
Bezugspunkt in der Schleife). Das Abzeichnen-Formular hatte keine action -
gefunden ueber die ESLint-Meldung zur ungenutzten Serveraktion.

55 neue Tests, insgesamt 241.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-10 23:09:54 +02:00
co-authored by Claude Opus 5
parent b9d6041f3b
commit 7c4a051347
47 changed files with 3679 additions and 8 deletions
@@ -0,0 +1,52 @@
import { benutzerNachId } from '@/lib/repos/benutzer';
import { ausbildungNachAzubi } from '@/lib/repos/ausbildung';
import { alsWochenblatt } from '@/lib/repos/berichtsheft';
import { berichtsheftPdf } from '@/lib/pdf/berichtsheft';
import { alsDatumText, alsZeitpunktText } from '@/lib/zeit';
import { ausbildungsjahr } from '@/lib/berichtsheft/jahr';
import { blattZumLesen } from '../../aktionen';
export const dynamic = 'force-dynamic';
/** Ein einzelnes Wochenblatt als PDF. Rechteprüfung über `blattZumLesen`. */
export async function GET(_anfrage: Request, kontext: { params: Promise<{ berichtId: string }> }) {
const { berichtId } = await kontext.params;
const gelesen = await blattZumLesen(berichtId);
if (!gelesen) return new Response('Nicht gefunden.', { status: 404 });
const { bericht } = gelesen;
const azubi = benutzerNachId(bericht.benutzerId);
const ausbildung = ausbildungNachAzubi(bericht.benutzerId);
const ausbilder = ausbildung ? benutzerNachId(ausbildung.ausbilderId) : null;
const abzeichner = bericht.abgezeichnetVon ? benutzerNachId(bericht.abgezeichnetVon) : null;
const pdf = await berichtsheftPdf([
{
nr: bericht.nr,
von: alsDatumText(bericht.von),
bis: alsDatumText(bericht.bis),
azubi: azubi?.name ?? 'unbekannt',
ausbilder: ausbilder?.name ?? '',
beruf: ausbildung?.beruf ?? 'Fachinformatiker/-in für Systemintegration',
ausbildungsjahr: ausbildung ? ausbildungsjahr(ausbildung.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 dateiname = `Ausbildungsnachweis_${String(bericht.nr).padStart(3, '0')}_${bericht.von}.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',
},
});
}