Commit graph

76 commits

Author SHA1 Message Date
3fdc1b1dd0 chore(bmad): BMAD v6.10 Setup (bmm, de) + PRD/Addendum/Projekt-Kontext (adversarial validiert, revidiert) 2026-07-07 21:58:55 +02:00
6d956c7672 docs: Design-Spec Mehrbenutzer/Magic-Link (Team-Release v1.0) 2026-07-07 20:49:56 +02:00
fc8d6dd065 fix(sw): skipWaiting + clients.claim -> neue Version aktiviert sofort (kein Stuck-auf-altem-Cache mehr); v6->v7 2026-07-07 19:29:32 +02:00
0001735207 fix(image-import): Zielmonat aus Live-Dropdown, Titel-Namen matchen, Importer-Instanziierung nach Sync-Boot
- Zielmonat/-jahr aus month-select/year-select statt veraltetem app.currentMonth (stand fix auf heute)
- normalizeName entfernt Anreden/Titel (Herr/Frau/Dr.) -> matcht titellose Mitarbeiter, kein Doppelanlegen
- Regression aus Sync-Boot behoben: app.js erzeugt imageImporter nach window.app (DOMContentLoaded-Listener lief zu frueh)
- totes app.currentMonth/currentYear entfernt; SW-Cache v5->v6
2026-07-07 19:23:45 +02:00
7d50e5d938 Merge remote-tracking branch 'github/main'
# Conflicts:
#	.github/ISSUE_TEMPLATE/config.yml
#	.gitignore
2026-07-07 17:18:04 +02:00
76569b94d6 Merge: Stufe 1 — Server-Persistenz (SQLite, Sync, Backup) 2026-07-07 17:08:14 +02:00
4f3539bb75 fix(sync): pending-Flag nur leeren wenn keine neuere Aenderung anliegt (Datenverlust-Race)
_flush() loeschte KEY_PENDING unconditionally nach erfolgreichem PUT. Lief
waehrend des PUT ein weiterer push() (neue Aenderung B), wurde deren pending-
Status vom ersten Flush geloescht, obwohl B noch nicht auf dem Server ist.
Schloss der Nutzer den Tab davor, sah der naechste boot() pending=false und
uebernahm den Server-Stand -> B ging verloren.

Fix: Generationszaehler _dirty. push() inkrementiert ihn, _flush() merkt sich
den Stand vor dem await und loescht pending nur, wenn kein neuerer push()
in der Zwischenzeit lief.

Neue Tests in sync.test.js decken Race (pending bleibt gesetzt) und
Normalfall (pending wird geleert) ab.
2026-07-07 16:18:45 +02:00
49a3803aa7 docs: Deployment mit persistentem Volume und Basic-Auth
Update-Prozedur in CLAUDE.md auf volume-aware docker run umgestellt
(-v dienstplan-data:/data, -e DATA_DIR=/data) und Dockerfile-Snippet
auf den neuen Express/SQLite-Server aktualisiert. Hinweis ergaenzt,
dass DB und Backups auf dem Volume liegen und die Domain hinter Caddy
Basic-Auth steht.

Zusaetzlich .dockerignore ergaenzt: node_modules aus dem Host-Kontext
wurde sonst per COPY . . ins Image kopiert und ueberschrieb das im
Image gebaute better-sqlite3-Binary (invalid ELF header beim Start).
Ohne diesen Fix startet der Container nicht.
2026-07-07 16:10:51 +02:00
67a3f4b546 feat(sync): Frontend pusht Aenderungen und laedt Server-Stand beim Start
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:59:48 +02:00
787bc1416b feat(sync): DataSync-Modul mit getesteter Reconcile-Logik 2026-07-07 15:54:32 +02:00
8459aaed1d chore: lokale data/ (Dev-DB + Backups) ignorieren 2026-07-07 15:52:49 +02:00
97e58903b8 feat(backend): automatisches taegliches SQLite-Online-Backup 2026-07-07 15:50:34 +02:00
45503f03ee feat(backend): GET/PUT /api/state mit Dokument-Persistenz 2026-07-07 15:46:48 +02:00
9714d845bb feat(backend): SQLite-Schicht mit WAL, Dokumenten und Historie 2026-07-07 15:43:30 +02:00
3b0632c4b9 feat(backend): Express-Server liefert statisch + /api/health 2026-07-07 15:40:06 +02:00
4eed9d0c4c refactor(variants): extract shared ineligible/pay/deduct helpers
V1/V2/V3 duplicated the empty-result shape, the paid-shares/bonus math
and the Friday-priority deduction loop. Pull them into ineligibleResult,
payResult and deductInOrder; each variant keeps only its distinct
threshold, eligibility test and deduction slots.

paidShares is now computed uniformly as max(0, classified - deduction);
never-deducted slots carry deduction 0, so the result is byte-identical
(verified against a 13122-case golden master over the full input grid).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:47:57 +02:00
88cc0553f6 refactor(variants): drop unused isWinner flag on variant results
Each variant emitted isWinner:false and the calculator set winner.isWinner
=true, but the UI marks the winner via variantId===winnerId, never the
flag. Only one test read it. Removed from the 6 variant returns, the empty
result, and the calculator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:45:15 +02:00
cb46e8fb04 refactor(calculator): drop unused dutyDetails/totalDuties result fields
No UI reads them - app.js computes its own labels via isQualifyingDay/
getDayTypeLabel when rendering the duty list. Only the test-suite asserted
them; those asserts are removed too. buildDutyDetails goes with them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:43:09 +02:00
bbc8690a0b refactor(image-import): extract resolveEntryName, dedupe resolution
groupEntriesByResolvedEmployee and resolveImports carried an identical
resolvedNames -> ignore/new/assign branch. One helper returning
{resolved, isNew}; callers keep their own null/newEmployee handling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:41:39 +02:00
de2befde7a refactor(app): extract getVacationMapForMonth, dedupe 4x vacation loop
The "loop employees -> getVacationMode -> calculateAllEmployees" block
was copy-pasted in calculateBonuses, generateEmailReport, exportCSV and
exportBonusReport. Collapse to one storage helper. Behaviour identical:
calculateAllEmployees only reads keys present in employeeDuties.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:40:06 +02:00
7efdcbe17b refactor(storage): drop unreachable try/catch in getDutiesForMonth
new Date() and the object spread never throw; the isNaN guard already
drops invalid dates via the .filter(). The inner catch could never fire.
Outer boundary try/catch is kept.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:38:12 +02:00
91565741a8 refactor(holidays): remove dead getHolidaysForYear
Zero callers across app, calculator, image-import and tests. Holiday
lookup goes through isHoliday/getHolidayName only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:37:36 +02:00
4d8437e508 docs: drop two more false-positive TODO entries
- Same-day duty overwrite: addDuty is keyed per employee+date, so a
  split Saturday duty between two employees creates two independent
  0.5 entries, not a conflict. Not a bug.
- Holiday cutoff 2030: not a current priority, dropped per user call.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05 21:08:04 +02:00
9276e8c64d docs: correct TODO.md - tie-breaker was already fixed, not a bug
The eligible-over-ineligible tie-break in calculateMonthlyBonus was a
deliberate fix (commit c733c3e) covered by an explicit test
(test-suite.js:191-203). Flagging it as an open bug in TODO.md was a
misreading of calculator.js against the design spec's simplified
pseudocode; the spec's own test plan (section 12.5) and the actual
test suite confirm the current behavior is correct.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05 20:23:13 +02:00
3598a4de9e docs: add TODO.md with known bonus-calculation gaps
Tie-breaker in calculateMonthlyBonus deviates from spec section 5,
same-day duties overwrite instead of summing, and holiday data only
covers 2025-2030.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05 19:58:42 +02:00
722f961387 chore: cleanup dead code and duplicated logic from audit
- Remove broken webpack.yml (no package.json/webpack config exists)
- Drop dead RATE_NORMAL/RATE_WEEKEND fields on BonusCalculator; share the
  real rate constants across all three variants in variants.js
- Simplify getEmptyResult() to build allResults via .map instead of
  three manual spreads
- Replace 5 duplicated German month-name arrays and 2 weekday-name
  arrays in app.js with a shared monthName()/WEEKDAY_NAMES helper;
  drop the now-redundant getMonthName() method
- Reuse existing .modal/.modal-backdrop/.modal-content CSS classes in
  generateEmailReport() instead of hand-rolled inline styles
- Delegate ImageImporter.classify() to the shared classify() in
  variants.js instead of a duplicated copy
- Unify ad hoc YYYY-MM-DD formatting in app.js to use the existing
  HolidayProvider.formatDate() helper

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05 19:23:14 +02:00
a6be57e964 chore: remove enterprise standards adoption boilerplate
Multi-tenant deploy policies, container/Quadlet hardening rules, and
ADR/SOP templates don't fit a single-maintainer static-site app with
one Dockerfile and no container orchestration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05 19:11:33 +02:00
5d8f7759d9 Merge pull request 'chore: adopt engineering standard v1.0.0' (#1) from chore/adopt-standards-v1.0.0 into main
Some checks failed
NodeJS with Webpack / build (22.x) (push) Has been cancelled
ci / standards-gate (push) Has been cancelled
NodeJS with Webpack / build (18.x) (push) Has been cancelled
NodeJS with Webpack / build (20.x) (push) Has been cancelled
2026-07-05 18:49:09 +02:00
Kenearos
cd6d1f7b57
chore: adopt engineering standard v1.0.0 (#3)
Adopt the org engineering standard (its-consulting/standards @ v1.0.0).

Adds baseline governance/CI/policy files rendered from the standard's
templates and pins .standards-version=1.0.0. Vendored OPA/Rego policies
under .standards/policies/ so CI runs the gate locally (no cross-repo dep).
Placeholders ({{ORG}}/{{REPO}}/{{OWNER_HANDLE}}/{{MAINTAINER_EMAIL}}) filled in.
Existing files that differ were left untouched by the adopter.

Automated rollout. Files created: 16.
2026-07-02 01:19:33 +02:00
9c0dc7b4e1 chore: adopt engineering standard v1.0.0
Some checks failed
ci / standards-gate (pull_request) Has been cancelled
NodeJS with Webpack / build (18.x) (pull_request) Has been cancelled
NodeJS with Webpack / build (20.x) (pull_request) Has been cancelled
NodeJS with Webpack / build (22.x) (pull_request) Has been cancelled
Adopt the org engineering standard (its-consulting/standards @ v1.0.0).

Adds baseline governance/CI/policy files rendered from the standard's
templates and pins .standards-version=1.0.0. Vendored OPA/Rego policies
under .standards/policies/ so CI runs the gate locally (no cross-repo dep).
Placeholders ({{ORG}}/{{REPO}}/{{OWNER_HANDLE}}/{{MAINTAINER_EMAIL}}) filled in.
Existing files that differ were left untouched by the adopter.

Automated rollout. Files created: 16.
2026-07-01 19:32:52 +02:00
262be954bb chore: remove implementation plans for shipped features
The bild-import and bonus-varianten plans were step-by-step build guides
consumed during implementation. Both features are merged and live.
Design specs in docs/specs/ stay for reference.
2026-05-12 19:36:22 +02:00
a92bf00d38 docs: replace Railway deployment section with Hetzner setup
Live URL, server details, container/network setup, Caddy block, and
update procedure for the self-hosted deployment.
2026-05-12 19:16:05 +02:00
9a26d8b9ef Merge feature/bonus-varianten: 3 variants + vacation + date-stepper
Conflicts resolved:
- sw.js: bumped CACHE_NAME to dienstplan-pro-v4 (was v3 + v2). Both
  variants.js and image-import.js are in ASSETS.
- storage.js: kept STORAGE_KEY_DUTIES + STORAGE_KEY_VACATION (Feature B)
  alongside STORAGE_KEY_OPENROUTER_KEY/MODEL + DEFAULT_MODEL (Feature A).
- styles.css: appended Feature B variants/vacation/date-stepper rules
  after Feature A modal/key rules; both blocks coexist.
2026-05-12 18:45:31 +02:00
408c30b4e3 Merge feature/bild-import: Bild->Dienste import via OpenRouter Vision 2026-05-12 18:42:48 +02:00
f69a36f2f0 fix(app): expose app instance on window so image-import.js init finds it
`let app` at top level does not create a window property, so the
DOMContentLoaded handler in image-import.js found window.app undefined
and never instantiated window.imageImporter. Clicking the Bild-Import
button then showed the 'Bild-Import nicht verfuegbar' toast.

Set window.app explicitly after construction.
2026-05-12 18:41:07 +02:00
c733c3ed02 fix(calculator): prefer eligible variant over ineligible at bonus-tie
Winner-picker used strict > with lowest-variantId tie-break, which made
V1 (ineligible) win over V3 (eligible) when all variants produced bonus=0
on Sa+So-only duties. Now ties prefer the eligible variant, keeping the
lowest-variantId rule as a sub-tie-breaker.
2026-05-12 18:36:24 +02:00
49b3b53878 chore(pwa): bump cache to v2, include variants.js, evict old caches 2026-05-12 18:27:24 +02:00
016ce93979 feat: add date-stepper buttons (Feature C) clamped to selected month 2026-05-12 18:27:06 +02:00
ba219ce0eb docs: settings info-box explains V1/V2/V3, auto-select, vacation mode 2026-05-12 18:25:39 +02:00
2e05067ce0 chore(pwa): bump cache to v3, precache image-import.js, evict old caches on activate 2026-05-12 18:25:16 +02:00
1407696adb refactor: CSV/HTML/email exports read winner.* from new variants shape 2026-05-12 18:25:07 +02:00
7839fd67d7 test(image-import): pin error-toast strings for HTTP/parse/network errors 2026-05-12 18:24:45 +02:00
e3a8ae2d7b feat(settings): add Bild-Import (KI) section with key management and model picker 2026-05-12 18:24:21 +02:00
3b6283ced1 feat(image-import): add resolveImports (pure) and commitImport persisting duties via DataStorage 2026-05-12 18:23:27 +02:00
15bf520bc1 feat: result card shows winner banner, all-variants details, vacation toggle 2026-05-12 18:23:11 +02:00
801757b92c feat(image-import): add renderPreview + Stage 3 grouping, unknowns, slot badges, remove-row 2026-05-12 18:22:37 +02:00
c43675478b feat(image-import): add runRecognition pipeline (compress, call, parse, dedupe, match) + Stage 2 cancel 2026-05-12 18:22:07 +02:00
34e9e67f6d feat: pass vacationMap from storage to calculateAllEmployees 2026-05-12 18:22:06 +02:00
6c536995b6 feat(image-import): add modal markup, CSS, and Stage 1 file/drop/camera wiring 2026-05-12 18:21:46 +02:00
68f35d3e3d feat: add dienstplan_vacation key + getVacationMode/setVacationMode + export/import 2026-05-12 18:21:31 +02:00