feat(seed): sieben Vorlagen, Beispielbestand und Demo-Kunden
Prüfung / pruefung (push) Canceled after 0s
Prüfung / pruefung (pull_request) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
Prüfung / e2e (pull_request) Canceled after 0s

Die sieben Ablaeufe des Hauses liegen nach dem Seed als veroeffentlichte
Version 1 bereit: 36 Abschnitte, 155 Schritte, 28 Variablen, jeder
Schritt mit Hilfetext. Sie stehen als JSON im Austauschformat unter
prisma/seed-daten/vorlagen/ und werden beim Einrichten gegen dasselbe
Schema geprueft wie ein Import.

Der Seed richtet ein, er raeumt nicht auf: jede Zeile hat einen
sprechenden Schluessel, und was es schon gibt, bleibt unberuehrt. Erst
Entwurf, dann veroeffentlichen - die Trigger aus #010 lassen keinen
anderen Weg zu. Im Modus DIENSTLEISTUNG haengt der Beispielbestand an
der Musterfirma, LZ-7K3F9Q fuehrt also auf MUS-PC-001.

Dazu zwei Zusagen eingeloest, die niemand deckte: {{ou_gesperrt}} kommt
jetzt wirklich aus den Einstellungen, und Titel und Hilfetext eines
Laufs bekommen dasselbe generierte Passwort wie der Befehl daneben -
vorher stand im Befehl A und im Hilfetext B.

Closes #17

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-26 05:26:04 +02:00
co-authored by Claude Opus 5
parent ae290345b7
commit 5c81a62cfd
33 changed files with 3400 additions and 119 deletions
+31 -1
View File
@@ -27,10 +27,33 @@ async function anmelden(seite: Page) {
await Promise.all([seite.waitForURL("**/"), seite.click('button[type="submit"]')]);
}
/** Die Vorlage aus `pnpm beispiel:vorlage` – diese Ablaeufe arbeiten mit ihr. */
const BEISPIEL_VORLAGE = "PC-Neuinstallation (Beispiel)";
/** Legt einen Lauf an und gibt seine Adresse zurueck. */
async function laufStarten(seite: Page, titel: string): Promise<string> {
await seite.goto("/laeufe/starten");
await seite.selectOption('select[name="vorlagenVersionId"]', { index: 1 });
/*
* Die Vorlage wird **am Namen** gewaehlt und nicht ueber `index: 1`. Seit dem
* Seed der sieben Vorlagen (#017) steht dort an erster Stelle, was die
* Sortierung gerade nach vorn stellt — bei einer frisch eingerichteten
* Installation etwa „Mitarbeiter-Offboarding“ mit Pflichtangaben, die dieser
* Helfer nicht kennt. Der Ablauf lief dann in den Zeitablauf, und die Meldung
* sagte nichts ueber die Ursache.
*/
const vorlagen = await seite
.locator('select[name="vorlagenVersionId"] option')
.evaluateAll((liste) =>
liste
.filter((option): option is HTMLOptionElement => option instanceof HTMLOptionElement)
.map((option) => ({ wert: option.value, text: option.textContent })),
);
const beispiel = vorlagen.find((eintrag) => (eintrag.text ?? "").includes(BEISPIEL_VORLAGE));
if (!beispiel) {
throw new Error(`Die Vorlage „${BEISPIEL_VORLAGE}“ fehlt. Erst „pnpm beispiel:vorlage“ laufen lassen.`);
}
await seite.selectOption('select[name="vorlagenVersionId"]', beispiel.wert);
await seite.selectOption('select[name="geraetId"]', { index: 1 });
await seite.locator("details summary", { hasText: "Titel, Ticket" }).click();
await seite.getByLabel("Titel", { exact: true }).fill(titel);
@@ -42,6 +65,13 @@ async function laufStarten(seite: Page, titel: string): Promise<string> {
for (const feld of await seite.locator('fieldset input[type="number"]').all()) {
await feld.fill("16");
}
for (const feld of await seite.locator('fieldset input[type="date"]').all()) {
await feld.fill("2026-10-01");
}
for (const feld of await seite.locator('fieldset input[type="text"]').all()) {
// Nur leere Felder: die Namensvorschlaege sollen stehen bleiben.
if ((await feld.inputValue()) === "") await feld.fill("E2E");
}
await seite.getByRole("button", { name: "Lauf anlegen" }).click();
await seite.waitForURL(/\/laeufe\/(?!starten)[^/]+$/, { timeout: 30_000 });