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:
co-authored by
Claude Opus 5
parent
b9d6041f3b
commit
7c4a051347
@@ -0,0 +1,197 @@
|
||||
'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';
|
||||
|
||||
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();
|
||||
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();
|
||||
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();
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
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();
|
||||
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();
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user