feat(frontend): Rechnungsempfaenger im Haushalt konfigurierbar #253

Merged
niboer merged 9 commits from issue-216 into main 2026-07-06 17:50:53 +02:00
Owner

Allgemein

Altes Verhalten

  • Rechnungsempfaenger war immer der Haushaltsgruender (group.createdBy)
  • Es gab keine Moeglichkeit, einen anderen Empfaenger zu bestimmen
  • Keine Sichtbarkeit im Frontend, wer aktuell Rechnungsempfaenger ist

Neues Verhalten

Backend (#216):

  • Neues Prisma-Model GroupInvoiceRecipient (Tabelle groupInvoiceRecipients)
  • GET /api/v1/groups/{groupId}/invoice-recipients — Liste der Empfaenger abrufen
  • PUT /api/v1/groups/{groupId}/invoice-recipients — Empfaenger setzen
    • Validierung: nur aktive Gruppenmitglieder duerfen gesetzt werden
    • Leeres Array = Fallback auf group.createdBy
    • Auth: requireGroupMember fuer beide Endpunkte
  • OpenAPI-Spec + Migration + 9 Unit-Tests

Frontend (#251):

  • DataTable: Badge "Rechnung" (variant="outline") im name-Slot bei Rechnungsempfaenger
  • ActionMenu-Toggle: "Rechnungsempfaenger setzen / entfernen" pro Mitglied
  • Fallback: Bei leerer Empfaenger-Liste zeigt effectiveRecipientIds den Gruender an
  • Frontend limitiert auf max. 1 Empfaenger (Backend unterstuetzt bereits Array fuer Zukunft)
  • i18n DE/EN: invoiceRecipient, setRecipient, removeRecipient
  • Orval-Types regeneriert

Fix: ActionMenu schliesst nach Form-Submit

  • use:enhance={() => { close(); }} verhindert dass der Menu-Backdrop offen bleibt
  • Bug: toBeVisible() schlug fehl weil Badge vom Backdrop ueberdeckt wurde

Begruendung

ZUGFeRD-Rechnungsmodul (#16) benoetigt pro Haushalt einen konfigurierbaren Rechnungsempfaenger. Viele Haushalte wollen nicht den Gruender, sondern eine andere Person (z. B. den Partner) als Rechnungsempfaenger.

Test-Hinweise

Backend:

  • cd backend && npm test — 536 Tests, alle gruen

Frontend:

  • cd frontend && npm test — 921 Tests, alle gruen

E2E (Integration):

  • groups.spec.ts (test-2-independent) — Max setzt Petra als Rechnungsempfaenger, verifiziert Badge
  • group-permissions.spec.ts (test-3-workflows) — Petra entfernt sich als Empfaenger, verifiziert Fallback-Badge bei Max

Checkliste

  • use:testid an neuen <button>, <a>, <form>-Elementen gesetzt?
  • Korrespondierender E2E-Test in e2e/specs/ angelegt?
### Allgemein - closes #216 - closes #251 ### Altes Verhalten - Rechnungsempfaenger war immer der Haushaltsgruender (`group.createdBy`) - Es gab keine Moeglichkeit, einen anderen Empfaenger zu bestimmen - Keine Sichtbarkeit im Frontend, wer aktuell Rechnungsempfaenger ist ### Neues Verhalten **Backend (#216):** - Neues Prisma-Model `GroupInvoiceRecipient` (Tabelle `groupInvoiceRecipients`) - `GET /api/v1/groups/{groupId}/invoice-recipients` — Liste der Empfaenger abrufen - `PUT /api/v1/groups/{groupId}/invoice-recipients` — Empfaenger setzen - Validierung: nur aktive Gruppenmitglieder duerfen gesetzt werden - Leeres Array = Fallback auf `group.createdBy` - Auth: `requireGroupMember` fuer beide Endpunkte - OpenAPI-Spec + Migration + 9 Unit-Tests **Frontend (#251):** - DataTable: Badge "Rechnung" (`variant="outline"`) im name-Slot bei Rechnungsempfaenger - ActionMenu-Toggle: "Rechnungsempfaenger setzen / entfernen" pro Mitglied - Fallback: Bei leerer Empfaenger-Liste zeigt `effectiveRecipientIds` den Gruender an - Frontend limitiert auf max. 1 Empfaenger (Backend unterstuetzt bereits Array fuer Zukunft) - i18n DE/EN: `invoiceRecipient`, `setRecipient`, `removeRecipient` - Orval-Types regeneriert **Fix: ActionMenu schliesst nach Form-Submit** - `use:enhance={() => { close(); }}` verhindert dass der Menu-Backdrop offen bleibt - Bug: `toBeVisible()` schlug fehl weil Badge vom Backdrop ueberdeckt wurde ### Begruendung ZUGFeRD-Rechnungsmodul (#16) benoetigt pro Haushalt einen konfigurierbaren Rechnungsempfaenger. Viele Haushalte wollen nicht den Gruender, sondern eine andere Person (z. B. den Partner) als Rechnungsempfaenger. ### Test-Hinweise **Backend:** - `cd backend && npm test` — 536 Tests, alle gruen **Frontend:** - `cd frontend && npm test` — 921 Tests, alle gruen **E2E (Integration):** - `groups.spec.ts` (test-2-independent) — Max setzt Petra als Rechnungsempfaenger, verifiziert Badge - `group-permissions.spec.ts` (test-3-workflows) — Petra entfernt sich als Empfaenger, verifiziert Fallback-Badge bei Max ### Checkliste - [x] `use:testid` an neuen `<button>`, `<a>`, `<form>`-Elementen gesetzt? - [x] Korrespondierender E2E-Test in `e2e/specs/` angelegt?
closes #216

Aktuelles: Rechnungsempfaenger war immer der Haushaltsgruender
(group.createdBy). Fuer ZUGFeRD-Rechnungen (#16) soll aber eine
andere Person im Haushalt Rechnungsempfaenger sein koennen.

Neu:
- Neues Prisma-Model GroupInvoiceRecipient (groupInvoiceRecipients)
- GET /api/v1/groups/{groupId}/invoice-recipients
- PUT /api/v1/groups/{groupId}/invoice-recipients
  mit Validierung: nur aktive Gruppenmitglieder
- Leeres Array = Fallback auf group.createdBy
- Auth: requireGroupMember fuer beide Endpunkte
- 9 Unit-Tests (GET/PUT + Auth + Validierung)
- OpenAPI-Spec ergaenzt
- Design-Docs aktualisiert (datenmodell, api-design, autorisierungsmatrix)
- PROJECT_STATE.md aktualisiert

Kein Konflikt mit issue-249 (unterschiedliche Dateien)
closes #251

Aktuelles: Rechnungsempfaenger kann im Backend gesetzt werden (#216),
aber im Frontend gibt es keine Sichtbarkeit oder Konfiguration.

Neu:
- +page.server.ts: invoiceRecipientIds aus API laden + toggle-Aktion
  (PUT mit immer 0 oder 1 Eintrag = immer max ein Empfaenger)
- +page.svelte: Badge 'Rechnung' (variant=outline) im name-Slot
  + ActionMenu-Toggle 'Rechnungsempfaenger setzen/entfernen'
- i18n: 3 Keys in DE/EN (invoiceRecipient, setRecipient, removeRecipient)
- Orval-Types regeneriert
- Frontend-Unit-Tests: 4 neue Tests
- E2E-Tests: 2 Subtests (Max setzt Petra, Petra entfernt sich selbst)
- PROJECT_STATE.md aktualisiert
Bezieht sich auf #251

Aktuelles: Bei leerer invoiceRecipientIds wird kein Badge angezeigt,
obwohl der Gruender (group.createdBy) impliziter Fallback ist.

Neu:
- effectiveRecipientIds-Derived: fallbackt auf group.createdBy bei
  leerer Liste
- Badge + ActionMenu nutzen effectiveRecipientIds statt
  invoiceRecipientIds
- E2E-Test aktualisiert: prueft dass Max-Badge nach Petras
  Entfernung erscheint
Bezieht sich auf #251

Aktuelles: .locator('..') trifft nur <td> statt <tr>;
waitForTimeout(500) ist unzuverlaessig.

Neu:
- getByRole('row').filter({ hasText }) statt .locator('..')
- expect().toBeVisible() mit auto-retry statt waitForTimeout
In #251 wurde beobachtet, dass der Menu-Backdrop nach dem
ActionMenu-Submit offen bleibt und darunter liegende Elemente
(z.B. Badge) verdeckt. toBeVisible() schlaegt dann fehl.

Neu:
- use:enhance={() => { close(); }} bei Haupt- und Submenu-Submit-Items
- Menu schliesst sofort bei Submit, der Request laeuft weiter
- Default-Verhalten (applyAction + update) bleibt erhalten
test.info().fixture('browser') gibt es nicht in Playwright.
Stattdessen 'browser' aus dem Test-Funktionsparameter.
Petra-Login scheitert in CI (Keycloak-Timeout). Max ist ebenfalls
Gruppenmitglied und kann den Rechnungsempfaenger entfernen.
Funktionspruefung bleibt identisch.
This reverts commit c4791c546e.
fix(e2e): Petra-Test in group-permissions.spec.ts verschoben
All checks were successful
CI Backend / lint-backend (pull_request) Successful in 51s
CI Frontend / lint-frontend (pull_request) Successful in 1m10s
CI Meta / conventional-commit (pull_request) Successful in 1s
CI Backend / test-backend (pull_request) Successful in 1m47s
CI Frontend / test-frontend (pull_request) Successful in 42s
E2E / e2e (pull_request) Successful in 2m41s
c179671320
group-permissions.spec.ts logged bereits als Petra ein (mit
setPassword + Consent). groups.spec.ts logged als Max ein.

closes #251
niboer changed title from feat(backend+frontend+e2e): Rechnungsempfaenger im Haushalt konfigurierbar to feat(frontend): Rechnungsempfaenger im Haushalt konfigurierbar 2026-07-06 17:38:46 +02:00
niboer merged commit 7a4205b2c3 into main 2026-07-06 17:50:53 +02:00
niboer deleted branch issue-216 2026-07-06 17:50:53 +02:00
Sign in to join this conversation.
No description provided.