30 KiB
Unterstützung bei Arbeitsblättern + 15-Minuten-Zeiten – Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Mitarbeiter können auf Arbeitsblättern als Unterstützung (Person + Tätigkeit) eingetragen werden und bekommen 50 % der Arbeitszeit (aufgerundet auf 15 min) angerechnet; Zeiten gibt es überall nur noch in 15-Minuten-Schritten; Kundenportal, Stundenkonto und Verträge ziehen mit.
Architecture: Neues JSON-Attribut supporters auf worksheets; Minuten werden nie gespeichert, sondern mit derselben Rechenregel in WOMS (src/lib/support.js) und Portal (server/services/worksheetMinutes.js) aus totalTime berechnet. WOMS bekommt eine gemeinsame Zeitauswahl (DurationSelect) und zwei Wege, Unterstützung einzutragen (Ersteller im Zeiten-Formular, Helfer über „Ich habe unterstützt“). Das Portal zeigt Unterstützung als eigene Zeilen, eine Gesamtzeit oben und zieht sie im Abo-Stundenkonto ab. Abo-Bedingungen Fassung 5 und AGB § 9 Abs. 2 halten die Regeln fest.
Tech Stack: WOMS React/Vite + Appwrite Web SDK 13, Tests node --test (+ esbuild tests/unit/load.mjs), Playwright-Harness; Portal Node/Express ESM, node --test test/*.test.js, Vanilla-JS public/app.js; webklar.com React/TS + vitest.
Spec: docs/superpowers/specs/2026-09-30-unterstuetzung-design.md (WOMS-Repo, Commit a336dc0)
Repos und Arbeitsverzeichnisse
| Teil | Repo / Zweig | Worktree | Livegang |
|---|---|---|---|
| WOMS (Tasks 1–6) | JUSN/tickte-system, feature/unterstuetzung von origin/test |
/home/webklar/apps/ticket.webklar.com/dev-unterstuetzung (existiert) |
git push origin feature/unterstuetzung:test |
| Portal (Tasks 7–10) | Portal-Repo, feature/unterstuetzung von origin/main |
/home/webklar/apps/kundenbereich.webklar.com/dev-unterstuetzung (in Task 7 anlegen) |
git push origin feature/unterstuetzung:main |
| webklar.com (Task 11) | JUSN/Webklar, fix/agb-haftung (f40e889) |
/home/webklar/apps/webklar.com/dev-agb-haftung (existiert, erst auf origin/main rebasen) |
git push origin fix/agb-haftung:main |
Worktree-Regeln (alle drei Repos): node_modules ist ein Symlink auf ../build/node_modules; nach Rebase/Checkout rm -rf node_modules && ln -s ../build/node_modules node_modules. In WOMS ist node_modules getrackt → nur git add <dateien>, nie -A. Nichts pushen ohne OK des Nutzers; der WOMS-Webhook deployt JEDEN gepushten Zweig.
Global Constraints
- Zeit-Raster:
TIME_STEP = 15Minuten; Auswahl 15 min … 12 h (MAX_STEP_MINUTES = 720, 48 Werte; 720 = bestehendesMAX_MINUTESder Push-Zeitmail). Abweichung von der Spec (dort „bis 10 h“) zugunsten eines Maximums in beiden Systemen – Spec-Zeile wird in Task 1 angepasst. - Berechnete Dauer wird aufgerundet auf das nächste Vielfache von 15; Dauer 0/fehlend → 15 vorausgewählt.
supportMinutes(t) = t > 0 ? ceil(t / 2 / 15) * 15 : 0; Testvektoren überall: 15→15, 30→15, 45→30, 60→30, 75→45, 70→45, 0→0, -5→0, '60'→30, null→0.worksheetMinutes(ws) = base + supporters.length * supportMinutes(base),base = Number(ws.totalTime) || 0; Kommentar-Filter bleibt beim Aufrufer.supporters-Eintrag:{ employeeId: string, name: string, task: string };taskgetrimmt 1–200 Zeichen;employeeIdeindeutig; gespeichert als JSON-String, AttributsupportersString 4000, nicht Pflicht.- Alte Arbeitsblätter werden nicht umgerechnet.
- Anzeige-Texte (exakt): Haken „Unterstützung“, Feld „Wobei?“, Knopf „+ weitere Person“, Knopf „Ich habe unterstützt“ / „Meine Unterstützung bearbeiten“, Listenzeile WOMS „↳ Unterstützung {Vorname} · {task} · {Zeit}“, Portal „↳ Unterstützung {Vorname}: {task}“ + Zeit, Portal-Kopf „Arbeitszeit gesamt: {Zeit}“, Stundenkonto-Zusatz „inkl. {Zeit} Unterstützung“.
- Vorname = erstes Wort von
name. - Zeitformat WOMS wie bisher
formatTime(„1h 30min“); Portal wie bisherfmtMinutes/fmtHours. - Berechtigungen nur in der Oberfläche; Appwrite-Rechte bleiben unverändert.
- Claude darf keinen Appwrite-API-Key benutzen: Skripte mit Key führt der Nutzer aus.
- Commits enden mit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>.
Review Focus
- Ersteller speichert das Zeiten-Formular, während ein Kollege sich gerade selbst als Unterstützung eingetragen hat → dessen Eintrag darf nicht still verloren gehen (Task 4: Konflikt-Prüfung beim Speichern).
- Altes Arbeitsblatt mit 70 min wird zum Bearbeiten geöffnet → 75 ist vorausgewählt, und Unterstützung rechnet dann mit 75 (Task 3 + Task 1-Vektoren).
- Git-Push: der zugeordnete Pusher sieht „Zeiten bearbeiten“ statt „Ich habe unterstützt“; ein Push ohne nachgetragene Zeit bietet keinem „Ich habe unterstützt“ an (Task 1
canAddOwnSupport, Task 5). supporterskaputt (kein JSON, kein Array, Eintrag ohnetask, doppelte Person) → WOMS-Liste, Portal-Projektansicht und Stundenkonto laufen weiter und ignorieren nur den kaputten Teil (Task 1, Task 7).- Mitarbeiterliste lädt nicht oder ein Mitarbeiter hat kein
userId→ Auswahl zeigt Hinweis statt leerer Zeilen, „Ich habe unterstützt“ funktioniert trotzdem mituser.$id/user.name(Task 4, Task 5).
WOMS
Task 1: Rechenregeln (Zeit-Raster + Unterstützung)
Files:
- Create:
src/lib/timeSteps.js,src/lib/support.js - Test:
tests/unit/support.test.mjs(lädt Module übertests/unit/load.mjs) - Modify:
docs/superpowers/specs/2026-09-30-unterstuetzung-design.md(„bis 10 h (40 Werte)“ → „bis 12 h (48 Werte)“)
Interfaces:
-
Produces (
timeSteps.js):TIME_STEP = 15,MAX_STEP_MINUTES = 720,roundUpToStep(minutes: number|null): number(≤0/NaN/null → 15, sonstceil(m/15)*15),isValidStep(minutes: any): boolean(ganze Zahl, >0, %15===0, ≤720),stepOptions(current?: number): {value:number,label:string}[](15…720; liegtcurrentgültig über 720, wird er angehängt),stepLabel(minutes): string(„15 min“, „1 h“, „1 h 15 min“). -
Produces (
support.js):supportMinutes(total): number,parseSupporters(raw: string|array|null): Supporter[](nie Exception; verwirft Einträge ohneemployeeId/task, trimmt, kürzttaskauf 200, erster Eintrag jeemployeeIdgewinnt),serializeSupporters(list): string,worksheetMinutes(ws): number,firstName(name): string,canAddOwnSupport(ws, user, employee): boolean(user.$idvorhanden,!ws.isComment,Number(ws.totalTime) > 0,!isWorksheetCreator(ws, user, employee)),upsertSupporter(list, entry): Supporter[],removeSupporter(list, employeeId): Supporter[],validateSupporters(list, creatorUserId): string|null(Fehlertext: „Bitte bei jeder Person angeben, wobei sie unterstützt hat.“, „Jede Person nur einmal eintragen.“, „Du kannst dich nicht selbst als Unterstützung eintragen.“). -
Consumes:
isWorksheetCreatoraussrc/lib/ticketForm.js. -
Step 1: Failing tests in
tests/unit/support.test.mjs: Vektoren aus Global Constraints fürsupportMinutes;roundUpToStep: 0→15, 50→60, 60→60, 61→75, null→15;isValidStep: 15/720 true, 70/0/735/'x'/7.5 false;stepOptions()Länge 48, erstes{15,'15 min'},[3]={60,'1 h'},[4]={75,'1 h 15 min'};stepOptions(900)Länge 49;parseSupporters:'kaputt'→[],'{}'→[],'[{"employeeId":"a","name":"Andrej S","task":" Farbe "},{"employeeId":"a","name":"x","task":"y"},{"employeeId":"b","name":"Nico"}]'→[{employeeId:'a',name:'Andrej S',task:'Farbe'}];worksheetMinutes({totalTime:60, supporters:<2 Einträge>})= 120,{totalTime:70, supporters:<1>}= 115, ohnesupporters=totalTime;canAddOwnSupport: fremdes normales Blatt mit Zeit → true, eigenes → false, Kommentar → false, Git ohne Zeit (isComment:true,totalTime:0) → false, Git mit Zeit gepusht vonknsofüremployee.shortcode 'KNSO'→ false, für anderen Mitarbeiter → true;validateSupportersfür die drei Fehler undnullfür gültige Liste;firstName('Andrej Stevanovski')= 'Andrej'. -
Step 2:
npm test→ neue Tests FAIL (Modul fehlt). -
Step 3:
timeSteps.jsundsupport.jsmit den Signaturen oben implementieren (reine Funktionen, keine Imports außerticketForm.js). -
Step 4:
npm test→ alle grün. -
Step 5: Spec-Zeile anpassen; commit
git add src/lib/timeSteps.js src/lib/support.js tests/unit/support.test.mjs docs/superpowers/specs/2026-09-30-unterstuetzung-design.md && git commit -m "Unterstützung: Rechenregeln für 15-Minuten-Raster und Unterstützungszeit".
Task 2: Appwrite-Attribut supporters (Skript für den Nutzer)
Files:
- Create:
scripts/setup-supporters.mjs - Test:
tests/setup-supporters.test.mjs
Interfaces:
-
Produces:
export const ATTRIBUTE = { key: 'supporters', type: 'string', size: 4000, required: false };export function planAttribute(existing: {key,type,size}[]): 'create'|'exists'|'conflict'(conflict= Key vorhanden mit anderem Typ oder Größe < 4000). CLI wiescripts/setup-legal.mjs:--dry-run,APPWRITE_API_KEY, Endpoint/Projekt/DB-Defaults identisch, Collectionworksheets; beicreatePOST/databases/woms-database/collections/worksheets/attributes/string, danach Status pollen bisavailable(max. 30 s); beiconflictExit 1 mit Meldung. Hauptteil nur, wenn als Skript gestartet (import.meta.url === pathToFileURL(process.argv[1]).href). -
Step 1: Failing test:
planAttribute([])= 'create';[{key:'supporters',type:'string',size:4000}]= 'exists';[{key:'supporters',type:'string',size:255}]= 'conflict';[{key:'supporters',type:'integer'}]= 'conflict'. -
Step 2:
npm test→ FAIL. -
Step 3: Skript implementieren.
-
Step 4:
npm testgrün;node scripts/setup-supporters.mjs --dry-runohne Key → gibt geplanten Schritt aus, Exit 0, kein Netzwerkzugriff. -
Step 5: Commit „Unterstützung: Setup-Skript für das Attribut supporters“.
Task 3: Zeiten nur in 15-Minuten-Schritten
Files:
- Create:
src/components/ui/DurationSelect.jsx(+ Export insrc/components/ui/index.js, falls dort gebündelt) - Modify:
src/components/CreateWorksheetModal.jsx(Feld „Arbeitszeit (Minuten)“ Z. ~238–254, Auto-Berechnung Z. ~60–67, Submit Z. ~105),src/components/WorksheetTimesModal.jsx(Z. 21–31, 117–134),src/hooks/useWorksheets.js(createWorksheetZ. ~146–153,updateWorksheetTimesZ. ~222–231) - Test:
.superpowers/sdd/2026-09-30-unterstuetzung/ui.mjs(Playwright, siehe Test-Rezept unten),tests/unit/support.test.mjserweitern falls neue reine Hilfen
Interfaces:
- Consumes:
stepOptions,roundUpToStep,isValidStep,MAX_STEP_MINUTES(Task 1),minutesBetween(ticketForm). - Produces:
<DurationSelect id value onChange disabled />–<select>überstepOptions(value),onChange(minutes:number); Label „Arbeitszeit“.
Verhalten: Beide Modals rechnen automatisch roundUpToStep(minutesBetween(start,end)), solange nicht manuell gewählt; Hinweis darunter „✓ Aus Beginn und Ende berechnet, aufgerundet auf 15 Minuten“ bzw. „Von Hand gewählt“. WorksheetTimesModal startet mit isValidStep(stored) ? stored : roundUpToStep(stored || berechnet). „Nur Kommentar“ deaktiviert die Auswahl und speichert 0. createWorksheet rundet eine fehlende/ungültige Zeit mit roundUpToStep; createWorksheet (nicht Kommentar) und updateWorksheetTimes lehnen !isValidStep(totalTime) ab mit „Die Arbeitszeit muss in 15-Minuten-Schritten angegeben werden.“
- Step 1: Failing UI-Checks in
ui.mjs: (a) neues Arbeitsblatt 09:00–09:50 → Auswahl zeigt „1 h“, gespeicherttotalTime: 60; (b) 23:30–00:15 → 45; (c) Auswahl hat 48 Optionen, keine freie Zahleneingabe (input[type=number]im Formular fehlt); (d) Zeiten bearbeiten beitotalTime: 70→ „1 h 15 min“ vorausgewählt, Speichern schickt 75; (e) Git „Zeit nachtragen“ Beginn 14:00, Ende 14:37 → 45; (f) „Nur Kommentar“ → Auswahl disabled, gespeichert 0. - Step 2: Harness bauen und laufen lassen → Checks FAIL.
- Step 3:
DurationSelectbauen, in beiden Modals einsetzen,useWorksheetsanpassen. - Step 4:
npm testgrün;ui.mjs(a)–(f) grün. - Step 5: Commit „Arbeitszeit nur in 15-Minuten-Schritten, berechnete Zeit wird aufgerundet“.
Task 4: Unterstützung im Formular des Erstellers
Files:
- Create:
src/components/SupportersEditor.jsx - Modify:
src/components/WorksheetTimesModal.jsx,src/components/CreateWorksheetModal.jsx,src/hooks/useWorksheets.js,src/components/TicketCard.jsx(Mitarbeiterliste an die Modals geben) - Test:
ui.mjserweitern
Interfaces:
-
Consumes:
parseSupporters,serializeSupporters,validateSupporters,supportMinutes,stepLabel(Task 1);useEmployees()→employeesmituserId,displayName. -
Produces:
<SupportersEditor value={Supporter[]} onChange={(list)=>void} employees={[]} excludeUserId={string} minutes={number} />– Haken „Unterstützung“ (an, wennvalue.length), Zeilen Mitarbeiter (<select>, ohneexcludeUserId, ohne in anderen Zeilen gewählte, Mitarbeiter ohneuserIdweggelassen) + Wobei? (maxLength 200, required) + Entfernen; „+ weitere Person“; je Zeile Vorschau „= {stepLabel(supportMinutes(minutes))}“; Haken aus →onChange([]); keine Mitarbeiter → Hinweis „Mitarbeiterliste konnte nicht geladen werden.“ -
Produces (
useWorksheets):updateWorksheetTimes(worksheet, times, currentUser, employee)akzeptiert zusätzlichtimes.supporters: Supporter[]undtimes.supportersBefore: string(Rohwert beim Öffnen). Vor dem Schreiben:databases.getDocumentfrisch; istfresh.supporters ?? ''≠supportersBefore ?? '', Abbruch mit{ success:false, error:'Inzwischen hat jemand die Unterstützung geändert. Bitte das Formular schließen und neu öffnen.' }. Sonst patcht essupporters: serializeSupporters(list)mit.createWorksheet(data, currentUser)übernimmtdata.supporters(Array) serialisiert. -
Step 1: Failing UI-Checks: (g) Ersteller öffnet „Zeiten bearbeiten“ (60 min), setzt Haken, wählt Andrej „Farbkonzept“ und Nico „Texte“ → PATCH enthält
supportersmit beiden, Vorschau je „= 30 min“; (h) eigene Person fehlt in der Auswahl, schon gewählte Person fehlt in der zweiten Zeile; (i) leeres „Wobei?“ → Speichern blockiert mit Fehlertext aus Task 1; (j) Mock ändertsupporterszwischen Öffnen und Speichern → Konfliktmeldung, kein PATCH; (k) neues Arbeitsblatt mit Unterstützung →createDocumententhältsupporters; (l) Employees-Mock liefert 500 → Hinweistext sichtbar. -
Step 2: → FAIL.
-
Step 3: Implementieren.
-
Step 4:
npm test+ui.mjsgrün (a)–(l). -
Step 5: Commit „Unterstützung: Ersteller trägt helfende Personen im Arbeitsblatt ein“.
Task 5: „Ich habe unterstützt“
Files:
- Create:
src/components/OwnSupportModal.jsx - Modify:
src/components/WorksheetList.jsx(Kopf-Knöpfe Z. ~164–200),src/components/TicketCard.jsx,src/hooks/useWorksheets.js - Test:
ui.mjserweitern
Interfaces:
-
Consumes:
canAddOwnSupport,parseSupporters,upsertSupporter,removeSupporter,serializeSupporters(Task 1). -
Produces (
useWorksheets):saveOwnSupport(worksheet, task: string|null, currentUser, employee): Promise<{success, error?}>– prüftcanAddOwnSupport, liest das Dokument frisch (getDocument), setzt/ersetzt den Eintrag{ employeeId: user.$id, name: employee?.displayName || user.name, task }bzw. entfernt ihn beitask === null, patcht nur{ supporters }, aktualisiert den State. -
Produces (
WorksheetListProps):canAddSupport(ws): boolean,onOwnSupport(ws): void; Knopf-Text „Ich habe unterstützt“ bzw. „Meine Unterstützung bearbeiten“, wennparseSupporters(ws.supporters)die eigeneuser.$identhält (dafür neue PropcurrentUserId). -
OwnSupportModal: Titel „Unterstützung eintragen“, Feld „Wobei?“ (required, 200), Anzeige „Dir werden {stepLabel(supportMinutes(ws.totalTime))} angerechnet.“, Knöpfe „Speichern“ und – wenn schon eingetragen – „Eintrag entfernen“. -
Step 1: Failing UI-Checks: (m) Nicht-Ersteller sieht bei fremdem 60-min-Blatt „Ich habe unterstützt“, Ersteller nicht; (n) Speichern mit „Design geprüft“ → vor dem PATCH ein GET des Dokuments; PATCH-Body enthält nur
supportersmit dem bestehenden Eintrag (vom Mock) und dem neuen; (o) danach heißt der Knopf „Meine Unterstützung bearbeiten“, „Eintrag entfernen“ entfernt nur den eigenen; (p) Git-Push ohne Zeit und Kommentar: kein Knopf; Git-Push mit Zeit vonknso: Kenso sieht „Zeiten bearbeiten“, Andrej sieht „Ich habe unterstützt“. -
Step 2: → FAIL.
-
Step 3: Implementieren.
-
Step 4:
npm test+ui.mjsgrün. -
Step 5: Commit „Unterstützung: Mitarbeiter tragen sich selbst als Unterstützung ein“.
Task 6: Anzeige in WOMS
Files:
- Modify:
src/components/WorksheetList.jsx(eingeklappter Kopf Z. ~146–155 und aufgeklappter Körper Z. ~214–240),src/components/WorksheetSummary.jsx,src/hooks/useWorksheets.js(getTotalTimeZ. ~255–259) - Test:
ui.mjserweitern
Interfaces:
- Consumes:
parseSupporters,supportMinutes,worksheetMinutes,firstName(Task 1).
Verhalten: Aufgeklappt unter Mitarbeiter/Zeit je Eintrag eine Zeile „↳ Unterstützung Andrej · Farbkonzept · 30min“ (Zeit mit dem vorhandenen formatTime). Eingeklappt bei vorhandener Unterstützung zusätzlich kleines Badge „+{n} Unterstützung“. WorksheetSummary und getTotalTime summieren worksheetMinutes(ws) über !ws.isComment.
- Step 1: Failing UI-Checks: (q) Blatt 60 min mit 2 Unterstützern → zwei Zeilen „↳ Unterstützung Andrej · Farbkonzept · 30min“ / „↳ Unterstützung Nico · Texte · 30min“, Kopf-Badge „+2 Unterstützung“; (r) Summe „Arbeitszeit gesamt 2h 0min“ bei diesem einen Blatt; (s)
supporters: 'kaputt'→ Blatt rendert normal ohne Zeilen, keine Konsolen-Exception. - Step 2: → FAIL.
- Step 3: Implementieren.
- Step 4:
npm test+ui.mjsgrün; Deploy-naher Build:git archive HEAD | …indocker run --rm node:20-alpine(npm install && npm run build) Exit 0. - Step 5: Commit „Unterstützung: Anzeige in Arbeitsblatt-Liste und Gesamtzeit“.
WOMS-Test-Rezept (aus Memory, ui.mjs): esbuild-Harness mit JS-API (require('<worktree>/node_modules/esbuild').build), define für import.meta.env.*, Loader .js=jsx, .ttf/.woff2, Stub-Plugin für ProjectContextPanel/TicketProjects/InvoicePanel; Bau unter systemd-run --scope -p MemoryMax=450M -p MemorySwapMax=0, Playwright headless unter MemoryMax 700M, Bundle über page.route von https://ticket.test, Appwrite-REST (/v1/account, /v1/databases/woms-database/collections/{worksheets,employees}/documents[/:id]) gemockt, PATCH/POST-Bodies in window.__calls protokolliert. set -o pipefail, Exit-Codes prüfen.
Kundenportal
Task 7: Rechenregel + Stundenkonto
Files:
- Setup:
git -C /home/webklar/apps/kundenbereich.webklar.com/build fetch origin && git -C … worktree add -b feature/unterstuetzung ../dev-unterstuetzung origin/main, dann node_modules-Symlink. - Create:
server/services/worksheetMinutes.js - Modify:
server/services/hourLedger.js(listPlanUsageZ. 54–70),public/app.js(renderHoursViewZ. ~1218–1221) - Test:
test/worksheetMinutes.test.js,test/hourLedger.test.js(neu, falls nicht vorhanden; sonst erweitern)
Interfaces:
-
Produces:
supportMinutes(total),parseSupporters(raw),worksheetMinutes(ws),firstName(name)– gleiche Semantik und Testvektoren wie WOMS Task 1;parseSupportersloggt bei kaputtem Wert einmalconsole.warn('[unterstuetzung] supporters unlesbar', ws?.$id)über optionalen zweiten Parametercontext. -
Produces (
listPlanUsageEintrag): zusätzlichsupportMinutes: number(Summe der Unterstützung);rawMinutes = worksheetMinutes(ws),minutes = roundUsage(rawMinutes); FilterworksheetMinutes(ws) > 0. -
Step 1: Failing tests: Vektoren;
parseSupporterswie WOMS;listPlanUsagemit gemocktemlistDocuments(Muster austest/support/appwriteMock) – Blatt 60 min + 1 Unterstützer →minutes 90, supportMinutes 30; Blatt mitsupporters:'kaputt'→minutes 60, kein Throw;computeLedgermit 60 min Guthaben und diesem 90-min-Eintrag →overageMinutes 30. -
Step 2:
npm test→ FAIL. -
Step 3: Implementieren; in
renderHoursViewhinter−{fmtHours(u.minutes)}beiu.supportMinutesein<span class="muted">inkl. {fmtHours(u.supportMinutes)} Unterstützung</span>. -
Step 4:
npm testgrün (bekannt flaky/unabhängig:repoMove.test.js). -
Step 5: Commit „Unterstützung: Rechenregel und Abzug im Abo-Stundenkonto“.
Task 8: Projektansicht – Unterstützungs-Zeilen und Gesamtzeit
Files:
- Modify:
server/services/customerProjectView.js(customerWorkEntryZ. ~197–216,customerWorkEntriesZ. ~219–233),server/routes/projects.js(GET /:id/workZ. ~78–95),public/app.js(renderWorkEntryZ. ~319–336,renderOrderZ. ~252–273, Projektdetails Z. ~386–396),public/kundenbereich.css,server/services/customerActivity.js(Z. ~36–47),public/admin.js(Z. ~167) - Test:
test/customerProjectView.test.jserweitern
Interfaces:
-
Consumes:
parseSupporters,supportMinutes,worksheetMinutes,firstName(Task 7). -
Produces: Work-Eintrag bekommt
supports: { name: string /*Vorname*/, task: string, minutes: number }[](leer =[]); zusammengefasste Git-Tage hängensupportsaneinander;minutesdes Eintrags bleibt die eigene Arbeitszeit (ohne Unterstützung). Neue Export-FunktionworkTotalMinutes(entries): number= Σ (minutes+ Σsupports.minutes).GET /:id/workantwortet zusätzlichtotalMinutes.customerActivityliefert je Blatt zusätzlichsupportMinutes(Summe);admin.jszeigt dann „, +{n} min Unterstützung“. -
Anzeige:
renderWorkEntryhängt je Unterstützung<li class="pd-support">↳ Unterstützung {name}: {task} <span class="pd-time">{fmtMinutes}</span></li>in einer<ul class="pd-supports">unter den Eintrag. Kopf: beiorderinpd-order-headein<span class="pd-total">Arbeitszeit gesamt: {fmtMinutes(totalMinutes)}</span>; ohneorderals erste Zeile des Abschnitts „Arbeiten am Projekt“; bei 0 nichts. -
Step 1: Failing tests: normales Blatt 60 min + Andrej „Farbkonzept“ →
supports: [{name:'Andrej', task:'Farbkonzept', minutes:30}],minutes 60; zwei Git-Pushs am selben Tag je mit einem Unterstützer → ein Eintrag mit zweisupports;workTotalMinutesüber diese Einträge; Kommentar mitsupporters→ kein Eintrag;supporters:'kaputt'→supports: []. -
Step 2: → FAIL.
-
Step 3: Implementieren (Server +
app.js+ CSS:.pd-supportseingerückt, gleiche Schriftgröße wie.pd-entry-meta). -
Step 4:
npm testgrün; UI-Blick mit dem statischen Fixture-Server aus kundenportal-testing (Projektdetails mitwork-Fixture inkl.totalMinutesundsupports, Desktop + 390 px Breite, Screenshot in den SDD-Ordner). -
Step 5: Commit „Unterstützung: eigene Zeilen und Gesamtzeit in den Projektdetails“.
Task 9: Push-Zeitmail nur in 15-Minuten-Schritten
Files:
- Modify:
server/services/pushTimeEntry.js(parseMinutesZ. ~40–47),server/routes/pushTime.js(Formular Z. ~153–156) - Test:
test/pushTimeEntry.test.jsbzw.test/pushTimeRoute.test.jserweitern
Interfaces: parseMinutes(raw) akzeptiert nur ganze Vielfache von 15 in 15–720, sonst null. Das Formular wird ein <select name="minutes"> mit 15 … 720 in 15er-Schritten (Label wie formatMinutes), vorausgewählt minutes aus der Query bzw. 60. QUICK_MINUTES bleibt.
- Step 1: Failing tests:
parseMinutes('45')=45,'720'=720,'70'=null,'0'=null,'735'=null; Route-Test: GET/zeit/<token>enthält<select name="minutes"mit 48<option; POSTminutes=70→ Fehlerseite, kein Schreiben. - Step 2: → FAIL.
- Step 3: Implementieren; Fehlertext „Bitte eine Zeit in 15-Minuten-Schritten wählen.“
- Step 4:
npm testgrün. - Step 5: Commit „Push-Zeitmail: Zeit nur in 15-Minuten-Schritten“.
Task 10: Abo-Bedingungen Fassung 5
Gate: Vor Step 3 den endgültigen § 4-Text (Spec, Abschnitt Verträge) dem Nutzer im Chat vorlegen und auf sein OK warten.
Files:
- Create:
public/vertragsunterlagen/v4/abo-bedingungen.html+public/vertragsunterlagen/v4/WEBklar-Abo-Vertragsunterlagen.pdf(bytegleiche Kopien der heutigen Fassung 4) - Modify:
public/abo-bedingungen.html(§ 4 neuer Satz nach der Rundungsregel; Kopf „Version 5 · Stand {Datum}“ an beiden Stellen),server/config.js(termsVersion: '{yyyy-mm-dd}-v5'Z. ~158),server/services/products.js(isTermsV4/termsDocForZ. ~28–55),scripts/build-legal-pdfs.sh,public/WEBklar-Abo-Vertragsunterlagen.pdf(neu erzeugt),test/legalTexts.test.js,test/products.test.js, alle Fundstellen vonisTermsV4/-v4(server/routes/abo.js,test/mailTemplates.test.js,server/mail-templates/K01 Vertragsbestaetigung.html) {Datum}= Tag der Umsetzung; verschiebt sich der Livegang, im Livegang-Schritt anpassen.
Interfaces:
-
Produces (
products.js):isCurrentTerms(v): boolean(endet auf-v5),isTermsV4OrNewer(v): boolean(endet auf-v4oder-v5),termsDocFor(v):-v5→{ url:'/abo-bedingungen.html', pdfFile:'WEBklar-Abo-Vertragsunterlagen.pdf', endParagraph:'§ 11' };-v4→{ url:'/vertragsunterlagen/v4/abo-bedingungen.html', pdfFile:'vertragsunterlagen/v4/WEBklar-Abo-Vertragsunterlagen.pdf', endParagraph:'§ 11' }; sonst v3 wie bisher.contractAttachmentForliefert alsfilenameden Basisnamen. Alle bisherigen Aufrufer vonisTermsV4, die „aktuelle Fassung“ meinen, nutzenisCurrentTerms; wo „Fassung 4 oder neuer“ gemeint ist (Hosting/Zusatzspeicher/Wechsel),isTermsV4OrNewer.isTermsV4entfällt. -
Step 1: Failing tests (
legalTexts.test.js): Fassung 4 bytegleich archiviert (sha256 HTML =git show origin/main:public/abo-bedingungen.html, fest eingetragen wie V3_SHA256); Fassung 5 enthält „Version 5“, nicht „Version 4“, § 4 enthält „die Hälfte der Arbeitszeit dieses Arbeitsschritts“, „aufgerundet auf volle 15 Minuten“, „jede Unterstützung mit Person, Tätigkeit und angerechneter Zeit“; Widerrufsbelehrung + Formular wortgleich mit Fassung 4.products.test.js:termsDocForfür-v5,-v4,-v3,undefined. -
Step 2:
npm test→ FAIL. -
Step 3: Nach OK des Nutzers: Archiv anlegen, Text einfügen, Version hochziehen, Code anpassen, PDFs mit
scripts/build-legal-pdfs.shneu erzeugen. Weitere Fundstellen der Rundungs-/Stundenregel suchen (grep -rn "15 Minuten" public server/mail-templates), bei Abweichung angleichen. -
Step 4:
npm testgrün; PDF öffnen und § 4 prüfen (Seitenumbruch, Kopfzeile „Version 5“). -
Step 5: Commit „Abo-Bedingungen Fassung 5: Unterstützung in § 4, Fassung 4 archiviert“.
webklar.com
Task 11: AGB § 9 Absatz 2
Gate: Endgültigen Text dem Nutzer vorlegen (zusammen mit Task 10 möglich), OK abwarten.
Files:
- Vorher:
git -C /home/webklar/apps/webklar.com/dev-agb-haftung fetch origin && git … rebase origin/main(f40e889 liegt noch auf 1637a4a; main ist 3c9a879), node_modules-Symlink prüfen. - Modify:
src/pages/AGB.tsx(§ 9, Z. ~188–198: neuer zweiter Listenpunkt, bisherige 2 und 3 werden 3 und 4) - Test:
src/test/agb-verguetung.test.tsx
Text (vollständig ausgeschrieben, kein Verweis): „Wird nach Aufwand abgerechnet, erfasst WEBklar die Arbeitszeit je Arbeitsschritt in Schritten von 15 Minuten; angefangene 15 Minuten werden aufgerundet. Wirkt eine weitere Person unterstützend mit, rechnet WEBklar für sie die Hälfte der Arbeitszeit des Arbeitsschritts an, aufgerundet auf volle 15 Minuten; bei mehreren unterstützenden Personen gilt das für jede. Jeder Arbeitsschritt und jede Unterstützung ist im Kundenportal einsehbar.“ (AGB sprechen von „WEBklar“ in der dritten Person, daher angepasst.)
- Step 1: Failing test: § 9 hat 4 Absätze; Absatz 1 unverändert „Die Vergütung ergibt sich aus dem Angebot.“; Absatz 2 = Text oben; Absatz 4 „Für die Abos gelten die Vertragsbedingungen für die WEBklar-Abos.“
- Step 2:
npx vitest run src/test/agb-verguetung.test.tsx→ FAIL. - Step 3: Absatz einfügen; prüfen, ob irgendwo auf „§ 9 Abs. 2/3“ der AGB verwiesen wird (
grep -rn "9 Abs" srcin webklar.com undpublic/des Portals) und anpassen. - Step 4:
npx vitest rungrün (inkl.agb-haftung.test.tsx);npm run buildExit 0. - Step 5: Commit „AGB § 9: Zeiterfassung in 15-Minuten-Schritten und Unterstützung“.
Livegang (nur mit OK des Nutzers, in dieser Reihenfolge)
- 1. Nutzer:
cd /home/webklar/apps/ticket.webklar.com/dev-unterstuetzung && APPWRITE_API_KEY=… node scripts/setup-supporters.mjs→ „available“. - 2. Portal:
git -C …/kundenbereich.webklar.com/dev-unterstuetzung push origin feature/unterstuetzung:main; prüfen: Deploy-Marker/Commit im Container,/api/projects/:id/worklieferttotalMinutes, Stundenkonto lädt,/abo-bedingungen.htmlzeigt „Version 5“,/vertragsunterlagen/v4/abo-bedingungen.htmlerreichbar. - 3. WOMS:
git -C …/ticket.webklar.com/dev-unterstuetzung push origin feature/unterstuetzung:test; prüfen: Live-Bundle enthält „Ich habe unterstützt“, Arbeitszeit-Auswahl vorhanden. - 4. webklar.com:
git -C …/webklar.com/dev-agb-haftung push origin fix/agb-haftung:main;/agbzeigt § 9 Abs. 2 und § 11 neu. - 5. Nutzer lädt in WOMS „Rechtliches“ die neuen Versionen hoch (Abo-Bedingungen v5 PDF, AGB neue Version) mit Änderungsnotiz.
- 6. Lesender Check mit echten Daten im Chrome des Nutzers: ein Arbeitsblatt mit Unterstützung anlegen (Test-Ticket), Portal-Projektansicht zeigt die Zeile und Gesamtzeit.
- 7. Memory aktualisieren (woms-ticketsystem, webklar-kundenportal, webklar-website).