Baustein 3: Wochenplan als Startseite, Fortschritt, Wochen verschieben
- Wochenplan ist die Startseite des Azubis: Thema, Leitfrage, fuenf Tage mit Theorie und Praxis, Fortschrittsbalken, was noch offen ist - Ausbilder sieht je Azubi die laufende Woche mit Stand und offenen Punkten - /woche/<nr> fuer jede Woche, Ausbilder waehlt ueber ?azubi= den Stand; Zugriff wird serverseitig bei jedem Aufruf geprueft - Tabelle wochenplanung mit geplantem und tatsaechlichem Datum, Zustand und Notiz fuer den Azubi - Verschieben per Datumsfeld, wahlweise mit allen folgenden Wochen - Reihenfolge per Ziehen und Ablegen sowie per Tastatur (Alt + Pfeil) - Warnung bei sich ueberlappenden Terminen - Plan entsteht automatisch beim Anlegen eines Azubikontos - Planungs- und Fortschrittslogik als reine Funktionen, 36 Tests dafuer - Insgesamt 104 Tests Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1af5613e9b
commit
103a603eb4
@@ -0,0 +1,151 @@
|
||||
import crypto from 'node:crypto';
|
||||
import { and, asc, eq } from 'drizzle-orm';
|
||||
import { datenbank, schema } from '../db';
|
||||
import { jetzt, plusTage } from '../zeit';
|
||||
import { erzeugePlan, sortiereUm, verschiebe, type Planzeile } from '../planung/wochen';
|
||||
import type { Wochenplanung, WochenStatus } from '../db/schema';
|
||||
|
||||
/** Zugriff auf die Wochenplanung. Die Rechnung steckt in ../planung/wochen. */
|
||||
|
||||
export function planVon(ausbildungId: string): Wochenplanung[] {
|
||||
return datenbank()
|
||||
.select()
|
||||
.from(schema.wochenplanung)
|
||||
.where(eq(schema.wochenplanung.ausbildungId, ausbildungId))
|
||||
.orderBy(asc(schema.wochenplanung.geplantVon), asc(schema.wochenplanung.wochenNummer))
|
||||
.all();
|
||||
}
|
||||
|
||||
export function zeileVon(ausbildungId: string, wochenNummer: number): Wochenplanung | null {
|
||||
return (
|
||||
datenbank()
|
||||
.select()
|
||||
.from(schema.wochenplanung)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.wochenplanung.ausbildungId, ausbildungId),
|
||||
eq(schema.wochenplanung.wochenNummer, wochenNummer),
|
||||
),
|
||||
)
|
||||
.get() ?? null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Legt fehlende Planzeilen an. Wird beim Anlegen einer Ausbildung aufgerufen
|
||||
* und noch einmal, wenn im Repository neue Wochen dazugekommen sind.
|
||||
*
|
||||
* Bestehende Zeilen werden nie angefasst – sonst wäre jede Terminverschiebung
|
||||
* beim nächsten Start wieder weg.
|
||||
*/
|
||||
export function planAuffrischen(ausbildungId: string, beginn: string, wochenNummern: number[]): number {
|
||||
const db = datenbank();
|
||||
const bestand = planVon(ausbildungId);
|
||||
const vorhanden = new Set(bestand.map((z) => z.wochenNummer));
|
||||
const fehlende = wochenNummern.filter((n) => !vorhanden.has(n));
|
||||
if (fehlende.length === 0) return 0;
|
||||
|
||||
// Neue Wochen hängen sich hinten an den bestehenden Plan an, statt ihn
|
||||
// umzuwerfen. Gibt es noch nichts, ist der Ausbildungsbeginn der Anker.
|
||||
const letzterMontag = bestand.at(-1)?.geplantVon;
|
||||
const anker = letzterMontag ? plusTage(letzterMontag, 7) : beginn;
|
||||
const zuSchreiben = erzeugePlan(anker, fehlende);
|
||||
|
||||
const wann = jetzt();
|
||||
db.insert(schema.wochenplanung)
|
||||
.values(
|
||||
zuSchreiben.map((zeile) => ({
|
||||
id: crypto.randomUUID(),
|
||||
ausbildungId,
|
||||
wochenNummer: zeile.wochenNummer,
|
||||
geplantVon: zeile.geplantVon,
|
||||
geplantBis: zeile.geplantBis,
|
||||
tatsaechlichVon: null,
|
||||
status: 'geplant' as const,
|
||||
notiz: null,
|
||||
geaendertAm: wann,
|
||||
})),
|
||||
)
|
||||
.run();
|
||||
|
||||
return zuSchreiben.length;
|
||||
}
|
||||
|
||||
/** Schreibt einen gerechneten Plan zurück – nur die Zeilen, die sich geändert haben. */
|
||||
function schreibeZurueck(ausbildungId: string, vorher: Wochenplanung[], nachher: Planzeile[]): number {
|
||||
const db = datenbank();
|
||||
const nachNummer = new Map(nachher.map((z) => [z.wochenNummer, z]));
|
||||
const wann = jetzt();
|
||||
let geaendert = 0;
|
||||
|
||||
for (const alt of vorher) {
|
||||
const neu = nachNummer.get(alt.wochenNummer);
|
||||
if (!neu) continue;
|
||||
if (neu.geplantVon === alt.geplantVon && neu.geplantBis === alt.geplantBis) continue;
|
||||
|
||||
db.update(schema.wochenplanung)
|
||||
.set({ geplantVon: neu.geplantVon, geplantBis: neu.geplantBis, geaendertAm: wann })
|
||||
.where(eq(schema.wochenplanung.id, alt.id))
|
||||
.run();
|
||||
geaendert += 1;
|
||||
}
|
||||
|
||||
return geaendert;
|
||||
}
|
||||
|
||||
export function wocheVerschieben(
|
||||
ausbildungId: string,
|
||||
wochenNummer: number,
|
||||
neuerMontag: string,
|
||||
folgendeMitverschieben: boolean,
|
||||
): number {
|
||||
const bestand = planVon(ausbildungId);
|
||||
const gerechnet = verschiebe(bestand, wochenNummer, neuerMontag, folgendeMitverschieben);
|
||||
return schreibeZurueck(ausbildungId, bestand, gerechnet);
|
||||
}
|
||||
|
||||
export function wocheUmsortieren(ausbildungId: string, wochenNummer: number, neueStelle: number): number {
|
||||
const bestand = planVon(ausbildungId);
|
||||
const gerechnet = sortiereUm(bestand, wochenNummer, neueStelle);
|
||||
return schreibeZurueck(ausbildungId, bestand, gerechnet);
|
||||
}
|
||||
|
||||
export function statusSetzen(
|
||||
ausbildungId: string,
|
||||
wochenNummer: number,
|
||||
status: WochenStatus,
|
||||
heuteDatum: string,
|
||||
): void {
|
||||
const zeile = zeileVon(ausbildungId, wochenNummer);
|
||||
if (!zeile) return;
|
||||
|
||||
const db = datenbank();
|
||||
|
||||
// Es läuft immer höchstens eine Woche.
|
||||
if (status === 'laufend') {
|
||||
db.update(schema.wochenplanung)
|
||||
.set({ status: 'geplant', geaendertAm: jetzt() })
|
||||
.where(and(eq(schema.wochenplanung.ausbildungId, ausbildungId), eq(schema.wochenplanung.status, 'laufend')))
|
||||
.run();
|
||||
}
|
||||
|
||||
db.update(schema.wochenplanung)
|
||||
.set({
|
||||
status,
|
||||
tatsaechlichVon: status === 'laufend' ? (zeile.tatsaechlichVon ?? heuteDatum) : zeile.tatsaechlichVon,
|
||||
geaendertAm: jetzt(),
|
||||
})
|
||||
.where(eq(schema.wochenplanung.id, zeile.id))
|
||||
.run();
|
||||
}
|
||||
|
||||
export function notizSetzen(ausbildungId: string, wochenNummer: number, notiz: string): void {
|
||||
const zeile = zeileVon(ausbildungId, wochenNummer);
|
||||
if (!zeile) return;
|
||||
|
||||
datenbank()
|
||||
.update(schema.wochenplanung)
|
||||
.set({ notiz: notiz.trim() || null, geaendertAm: jetzt() })
|
||||
.where(eq(schema.wochenplanung.id, zeile.id))
|
||||
.run();
|
||||
}
|
||||
Reference in New Issue
Block a user