Baustein 6: Checklisten mit Durchlaeufen und PDF-Export

- Durchlauf aus einer Vorlage, mit Datum, Ticket und Kunde
- Schritt fuer Schritt abhaken, Bemerkung je Schritt
- Fortschritt gesamt und je Gruppe (Vorbereiten/Durchfuehren/Pruefen/Abschliessen)
- Abschliessen erst, wenn alles abgehakt ist; Meldung nennt die offenen Schritte
- Abgeschlossene Durchlaeufe sind gesperrt und bleiben als Nachweis
- Titel und Schritttexte werden beim Start kopiert, nicht verwiesen
- PDF-Export mit gezeichneten Ankreuzkaestchen und Unterschriftenzeilen
- Gemeinsame PDF-Grundlage fuer alle kuenftigen Ausgaben
- Ausbilder liest mit und darf das PDF abrufen, hakt aber nicht ab
- Abgeschlossene Durchlaeufe zaehlen im Wochenfortschritt mit

Behoben beim Durchstich: Die Fusszeile wurde unterhalb des Satzspiegels
gesetzt, wodurch PDFKit je Fusszeile eine leere Seite anhaengte - aus zwei
Seiten wurden sechs. Ein Test zaehlt jetzt die Seitenobjekte im PDF.

29 neue Tests, insgesamt 186.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-10 21:00:30 +02:00
co-authored by Claude Opus 5
parent ffbca01bdc
commit b9d6041f3b
41 changed files with 2074 additions and 5 deletions
+47
View File
@@ -174,6 +174,49 @@ export const anhang = sqliteTable(
(t) => [index('anhang_bezug_idx').on(t.bezugTyp, t.bezugId), index('anhang_benutzer_idx').on(t.benutzerId)],
);
export const checklisteLauf = sqliteTable(
'checkliste_lauf',
{
id: text('id').primaryKey(),
benutzerId: text('benutzer_id')
.notNull()
.references(() => benutzer.id, { onDelete: 'cascade' }),
checklisteId: text('checkliste_id').notNull(),
titel: text('titel').notNull(),
datum: text('datum').notNull(),
ticket: text('ticket'),
kunde: text('kunde'),
bemerkung: text('bemerkung'),
gestartetAm: text('gestartet_am').notNull(),
abgeschlossenAm: text('abgeschlossen_am'),
},
(t) => [
index('checkliste_lauf_benutzer_idx').on(t.benutzerId, t.datum),
index('checkliste_lauf_vorlage_idx').on(t.checklisteId),
],
);
export const checklisteSchritt = sqliteTable(
'checkliste_schritt',
{
id: text('id').primaryKey(),
laufId: text('lauf_id')
.notNull()
.references(() => checklisteLauf.id, { onDelete: 'cascade' }),
gruppeId: text('gruppe_id').notNull(),
schrittId: text('schritt_id').notNull(),
text: text('text').notNull(),
reihenfolge: integer('reihenfolge').notNull(),
erledigt: integer('erledigt').notNull().default(0),
bemerkung: text('bemerkung'),
erledigtAm: text('erledigt_am'),
},
(t) => [
uniqueIndex('checkliste_schritt_idx').on(t.laufId, t.schrittId),
index('checkliste_schritt_lauf_idx').on(t.laufId, t.reihenfolge),
],
);
export type Benutzer = typeof benutzer.$inferSelect;
export type Sitzung = typeof sitzung.$inferSelect;
export type Ausbildung = typeof ausbildung.$inferSelect;
@@ -183,6 +226,8 @@ export type Karte = typeof karte.$inferSelect;
export type KartenVerlauf = typeof kartenVerlauf.$inferSelect;
export type AufgabeStatus = typeof aufgabeStatus.$inferSelect;
export type Anhang = typeof anhang.$inferSelect;
export type ChecklisteLauf = typeof checklisteLauf.$inferSelect;
export type ChecklisteSchritt = typeof checklisteSchritt.$inferSelect;
/** Wird von der Schema-Abgleichprüfung im Test verwendet. */
export const alleTabellen = {
@@ -195,4 +240,6 @@ export const alleTabellen = {
kartenVerlauf,
aufgabeStatus,
anhang,
checklisteLauf,
checklisteSchritt,
};