#101 — Vorratsschrank — Labels, Kommentare, Aktivitäts-Feed, CSV-Import/-Export #100

Closed
opened 2026-08-18 13:14:01 +02:00 by lena · 1 comment
lena commented 2026-08-18 13:14:01 +02:00 (Migrated from git.butzei.de)

Story: Vorratsschrank — Labels, Kommentare, Aktivitäts-Feed, CSV-Import/-Export

Depends on: #94 (Vorratsschrank Phase 1 — liefert den Listentyp selbst).

As a Haushaltsmitglied,
I want to Pantry-Artikel mit Labels versehen, kommentieren können, nachvollziehen können, wer wann
was ein-/ausgecheckt hat, und den Bestand als CSV sichern/importieren können,
so that der Vorratsschrank dieselbe Feature-Tiefe wie Todos/Einkaufsliste erreicht.

Hintergrund: #94's Architect-Entscheidung hat diese Punkte bewusst aus Phase 1 herausgenommen, da
LabelEntity/CommentEntity/ActivityEventEntity in diesem Repo aktuell hart an TodoListId/TodoNr
gebunden sind (keine polymorphe Struktur) — für Pantry braucht es strukturelle Kopien nach demselben Muster,
das #89 für Shopping Lists' Sharing-Entities verwendet hat, kein gemeinsames Tabellenschema. CSV-Import
braucht ebenfalls einen komplett neuen Backend-Handler (Export ist client-seitig quasi kostenlos, sobald die
Get-Queries existieren — siehe #83's Architektur). Siehe
docs/features/done/94_pantry_inventory_phase1_design.md's "Scope-Entscheidung".

Acceptance criteria:

  • Labels: neue PantryLabelEntity/Join-Tabelle, wiederverwendet dieselbe LabelColor-Palette wie Todo/
    Shopping-Kategorien, gleiche UI-Konvention (LabelPicker). — geliefert als #101b.
  • Kommentare: neue PantryCommentEntity, gleiche UI-Konvention wie Todo-Kommentare. — geliefert als #101c.
  • Aktivitäts-Feed: neue PantryActivityEventEntity, Ereignistypen mindestens: Artikel angelegt, ein-/
    ausgecheckt (inkl. Menge-Delta), Soll-Menge geändert, Mitglied beigetreten/entfernt (über die
    gekoppelte Ziel-Einkaufsliste sichtbar, nicht separat verwaltet). — geliefert als #101d.
  • CSV-Import/-Export: neuer ImportPantryProductsFromCsvCommand (analog #90's
    ImportShoppingProductsFromCsvCommand); Export braucht keinen neuen Endpunkt (reine Erweiterung von
    CsvExportDialogs kind-Union um 'pantry', sobald GetPantryProductsQuery/GetPantryCategoriesQuery
    aus #94 existieren). — geliefert als #101e.

Status: Alle vier Teilbereiche über die Folge-Stories #101b#101e geliefert. Siehe
docs/features/done/101b_pantry_labels_ui.md, 101c_pantry_comments_ui.md, 101d_pantry_activity_feed_ui.md,
101e_pantry_csv_export_import_ui.md.

Out of scope: keine neue Sharing-Infrastruktur — bleibt an die Ziel-Einkaufsliste gekoppelt (#94's
Architect-Entscheidung).

# Story: Vorratsschrank — Labels, Kommentare, Aktivitäts-Feed, CSV-Import/-Export **Depends on:** `#94` (Vorratsschrank Phase 1 — liefert den Listentyp selbst). **As a** Haushaltsmitglied, **I want to** Pantry-Artikel mit Labels versehen, kommentieren können, nachvollziehen können, wer wann was ein-/ausgecheckt hat, und den Bestand als CSV sichern/importieren können, **so that** der Vorratsschrank dieselbe Feature-Tiefe wie Todos/Einkaufsliste erreicht. **Hintergrund:** `#94`'s Architect-Entscheidung hat diese Punkte bewusst aus Phase 1 herausgenommen, da `LabelEntity`/`CommentEntity`/`ActivityEventEntity` in diesem Repo aktuell hart an `TodoListId`/`TodoNr` gebunden sind (keine polymorphe Struktur) — für Pantry braucht es strukturelle Kopien nach demselben Muster, das `#89` für Shopping Lists' Sharing-Entities verwendet hat, kein gemeinsames Tabellenschema. CSV-Import braucht ebenfalls einen komplett neuen Backend-Handler (Export ist client-seitig quasi kostenlos, sobald die Get-Queries existieren — siehe `#83`'s Architektur). Siehe `docs/features/done/94_pantry_inventory_phase1_design.md`'s "Scope-Entscheidung". **Acceptance criteria:** - [x] Labels: neue `PantryLabelEntity`/Join-Tabelle, wiederverwendet dieselbe `LabelColor`-Palette wie Todo/ Shopping-Kategorien, gleiche UI-Konvention (`LabelPicker`). — geliefert als `#101b`. - [x] Kommentare: neue `PantryCommentEntity`, gleiche UI-Konvention wie Todo-Kommentare. — geliefert als `#101c`. - [x] Aktivitäts-Feed: neue `PantryActivityEventEntity`, Ereignistypen mindestens: Artikel angelegt, ein-/ ausgecheckt (inkl. Menge-Delta), Soll-Menge geändert, Mitglied beigetreten/entfernt (über die gekoppelte Ziel-Einkaufsliste sichtbar, nicht separat verwaltet). — geliefert als `#101d`. - [x] CSV-Import/-Export: neuer `ImportPantryProductsFromCsvCommand` (analog `#90`'s `ImportShoppingProductsFromCsvCommand`); Export braucht keinen neuen Endpunkt (reine Erweiterung von `CsvExportDialog`s `kind`-Union um `'pantry'`, sobald `GetPantryProductsQuery`/`GetPantryCategoriesQuery` aus `#94` existieren). — geliefert als `#101e`. **Status:** Alle vier Teilbereiche über die Folge-Stories `#101b`–`#101e` geliefert. Siehe `docs/features/done/101b_pantry_labels_ui.md`, `101c_pantry_comments_ui.md`, `101d_pantry_activity_feed_ui.md`, `101e_pantry_csv_export_import_ui.md`. **Out of scope:** keine neue Sharing-Infrastruktur — bleibt an die Ziel-Einkaufsliste gekoppelt (`#94`'s Architect-Entscheidung).
lena commented 2026-08-18 13:14:02 +02:00 (Migrated from git.butzei.de)

design (101_pantry_labels_comments_activity_feed_design.md)

Design: #101 — Vorratsschrank: Labels, Kommentare, Aktivitäts-Feed, CSV-Import/-Export

Kernprinzip: Sibling-Entities, keine polymorphe Struktur

Per #94's Architect-Notiz und #89/#93-Präzedenzfall: LabelEntity/CommentEntity/
ActivityEventEntity sind aktuell hart an TodoListId/TodoNr gebunden. Statt sie polymorph zu
machen (ein TodoListId? und PantryId? Feld an derselben Tabelle, mit XOR-Constraint), werden
strukturelle Kopien angelegt:

  • PantryLabelEntity / PantryProductLabelEntity (Join) — spiegelt LabelEntity /
    TodoLabelEntity.
  • PantryProductCommentEntity — spiegelt CommentEntity, keyed auf PantryProductId statt
    (TodoListId, TodoNr).
  • PantryActivityEventEntity — spiegelt ActivityEventEntity, keyed auf PantryId statt
    TodoListId.

Wiederverwendet werden die vorhandenen Value-Objects LabelName, LabelColor, CommentText.
Neu sind nur ID-VOs: PantryLabelId, PantryProductCommentId, PantryActivityEventId, plus
das Enum PantryActivityEventKind.

Diese Entscheidung matcht exakt das, was #93 für Masterpackliste getroffen hat
(MasterPackLabelEntity + MasterPackItemLabelEntity, MasterPackItemCommentEntity) — keine neue
Bauweise, sondern die im Repo bereits etablierte.

Etappen (jede eigenständig commit-fähig, jede lässt Build grün)

  1. Backend Labels: neue Entities + Vogen-ID + Create/Rename/Delete-Label-Commands +
    Tag/Untag-PantryProduct-Commands + GetLabelsForPantryQuery + Migration + Handler-Tests.
    Bestehendes DTO-Feld LabelDto wird für Response wiederverwendet (identische Shape).
  2. Backend Kommentare: neue Entity + Vogen-ID + Create/DeletePantryProductComment-Commands +
    GetCommentsForPantryProductQuery + Migration + Tests. Bestehendes DTO-Feld CommentDto wird
    wiederverwendet (identische Shape).
  3. Backend Activity Feed: neue Entity + neuer PantryActivityEventKind-Enum
    (ProductCreated, ProductRenamed, ProductCheckedIn, ProductCheckedOut,
    ProductTargetQuantityChanged) + CreatePantryActivityEventCommand + emit-Aufrufe in den
    bestehenden Pantry-Mutations-Handlern + GetPantryActivityFeedQuery (Pagination wie beim Todo-
    Pendant) + Migration + Tests. Analog zu TodoCreated/TodoChecked/TodoAssigned etc.
  4. Backend CSV-Import: ImportPantryProductsFromCsvCommand nach dem
    ImportShoppingProductsFromCsvCommand-Vorbild — kein neuer CSV-Parser, sondern die bestehende
    Zeilen-Schleife + Kategorie-Auto-Anlage. Spalten: Name, Category, Quantity, TargetQuantity, Barcode. Tests inkl. Duplikat-Merge (bestehendes Produkt mit gleichem Namen wird auf die
    importierte Menge gesetzt) und leere/ungültige Zeilen.
  5. Frontend Labels: PantryLabelPicker-Komponente (nach dem LabelPicker-Vorbild, aber mit
    pantryProductId statt todoListId/todoNr) + Anzeige auf PantryProductItem + Label-Filter
    in PantryPage.
  6. Frontend Kommentare: wie Todo — Komponente PantryCommentList analog CommentList; UI-
    Zugriff über einen Klick/Icon auf PantryProductItem.
  7. Frontend Activity-Feed: PantryActivityFeedPanel analog zu ActivityFeedPanel, geöffnet
    via einen Header-Button auf PantryPage. Pagination-Verhalten wie beim Todo-Pendant.
  8. Frontend CSV: CsvExportDialogs kind-Union um 'pantry' erweitert; Import über einen
    Menü-Eintrag auf PantryPage (analog Shopping/Todo-CSV-Import). Nutzt die bereits vorhandenen
    getPantryProductsForList/getPantryCategoriesForList-Queries.

Security-Vorprüfung (überall gleiche Regel)

Alle neuen Handler nutzen AuthorizePantryAccessForCurrentUserQuery(pantryId) — dieselbe
Membership-basierte Autorisierung, die #94 für alle bestehenden Pantry-Handler eingeführt hat.
Kein Owner-only-Split nötig, da Pantry-Sharing rein Mitgliedschafts-basiert ist (Owner-only wäre
ein neues Konzept und ist nicht von der AC gefordert). Label/Kommentar-Löschung: jeder Mitglied
darf Labels/Kommentare löschen (matcht Todo-Regel — vgl. DeleteLabelCommand/DeleteCommentCommand,
beide member-level).

Aktivitäts-Feed-Zugriff: GetPantryActivityFeedQuery prüft
AuthorizePantryAccessForCurrentUserQuery — Members sehen nur die Feeds ihrer Pantries.

CSV-Import: analog ImportShoppingProductsFromCsvCommand — Mitgliedschafts-Check +
ImportSizeLimit (Zeilenanzahl-Obergrenze zur DoS-Absicherung, vom Shopping-Import geerbt).

Keine neue Attack Surface: alle Endpunkte spiegeln existierende, bereits geprüfte Muster.

Test-Strategie

Backend: Handler-Tests pro Command wie im Rest des Repos (CqsTodo.Tests/Features/Pantry/
bekommt jeweils einen *Tests.cs), inkl. Auth-Ablehnung und typischer Fehlerpfade.

Frontend: Unit-Tests pro neuer/erweiterter Komponente (Vitest), plus 1 E2E
(pantry-collaboration.spec.ts) das die vier Sub-Features im Zusammenspiel prüft: Label anlegen +
taggen + filtern; Kommentar schreiben; Check-in/-out landet im Activity-Feed; CSV-Export +
Re-Import erzeugt identischen Bestand.

Explizit Nicht-Scope

  • Keine Sharing-Änderungen — Pantry bleibt an die Ziel-Einkaufsliste gekoppelt.
  • Keine WS-Live-Sync für die neuen Sub-Features in V1 (analog #94 selbst — Pantry hat keinen WS-
    Kanal, refetch-nach-Mutation reicht für die typische Häufigkeit). Falls sich im Betrieb
    Bedarf ergibt, wäre das eine separate Folge-Story analog #99.
  • Keine "Group"-Erweiterung an PantryLabelEntity (das war #93-spezifisch für die
    Climate/Travel-Type-Facets — Pantry-Labels sind gruppenlos wie Todo-Labels).
  • Kein PantryActivityEventEntity-Broadcast über einen WS-Change-Subject; Refetch-on-Open reicht
    wie beim Todo-Pendant, das keinen eigenen WS-Kanal hat.
**design** (`101_pantry_labels_comments_activity_feed_design.md`) # Design: `#101` — Vorratsschrank: Labels, Kommentare, Aktivitäts-Feed, CSV-Import/-Export ## Kernprinzip: Sibling-Entities, keine polymorphe Struktur Per `#94`'s Architect-Notiz und `#89`/`#93`-Präzedenzfall: `LabelEntity`/`CommentEntity`/ `ActivityEventEntity` sind aktuell hart an `TodoListId`/`TodoNr` gebunden. Statt sie polymorph zu machen (ein `TodoListId?` **und** `PantryId?` Feld an derselben Tabelle, mit XOR-Constraint), werden **strukturelle Kopien** angelegt: - `PantryLabelEntity` / `PantryProductLabelEntity` (Join) — spiegelt `LabelEntity` / `TodoLabelEntity`. - `PantryProductCommentEntity` — spiegelt `CommentEntity`, keyed auf `PantryProductId` statt `(TodoListId, TodoNr)`. - `PantryActivityEventEntity` — spiegelt `ActivityEventEntity`, keyed auf `PantryId` statt `TodoListId`. **Wiederverwendet** werden die vorhandenen Value-Objects `LabelName`, `LabelColor`, `CommentText`. **Neu** sind nur ID-VOs: `PantryLabelId`, `PantryProductCommentId`, `PantryActivityEventId`, plus das Enum `PantryActivityEventKind`. Diese Entscheidung matcht exakt das, was `#93` für Masterpackliste getroffen hat (`MasterPackLabelEntity` + `MasterPackItemLabelEntity`, `MasterPackItemCommentEntity`) — keine neue Bauweise, sondern die im Repo bereits etablierte. ## Etappen (jede eigenständig commit-fähig, jede lässt Build grün) 1. **Backend Labels:** neue Entities + Vogen-ID + Create/Rename/Delete-Label-Commands + Tag/Untag-PantryProduct-Commands + GetLabelsForPantryQuery + Migration + Handler-Tests. Bestehendes DTO-Feld `LabelDto` wird für Response wiederverwendet (identische Shape). 2. **Backend Kommentare:** neue Entity + Vogen-ID + Create/DeletePantryProductComment-Commands + GetCommentsForPantryProductQuery + Migration + Tests. Bestehendes DTO-Feld `CommentDto` wird wiederverwendet (identische Shape). 3. **Backend Activity Feed:** neue Entity + neuer `PantryActivityEventKind`-Enum (`ProductCreated`, `ProductRenamed`, `ProductCheckedIn`, `ProductCheckedOut`, `ProductTargetQuantityChanged`) + `CreatePantryActivityEventCommand` + emit-Aufrufe in den bestehenden Pantry-Mutations-Handlern + `GetPantryActivityFeedQuery` (Pagination wie beim Todo- Pendant) + Migration + Tests. Analog zu `TodoCreated`/`TodoChecked`/`TodoAssigned` etc. 4. **Backend CSV-Import:** `ImportPantryProductsFromCsvCommand` nach dem `ImportShoppingProductsFromCsvCommand`-Vorbild — kein neuer CSV-Parser, sondern die bestehende Zeilen-Schleife + Kategorie-Auto-Anlage. Spalten: `Name, Category, Quantity, TargetQuantity, Barcode`. Tests inkl. Duplikat-Merge (bestehendes Produkt mit gleichem Namen wird auf die importierte Menge gesetzt) und leere/ungültige Zeilen. 5. **Frontend Labels:** `PantryLabelPicker`-Komponente (nach dem `LabelPicker`-Vorbild, aber mit `pantryProductId` statt `todoListId`/`todoNr`) + Anzeige auf `PantryProductItem` + Label-Filter in `PantryPage`. 6. **Frontend Kommentare:** wie Todo — Komponente `PantryCommentList` analog `CommentList`; UI- Zugriff über einen Klick/Icon auf `PantryProductItem`. 7. **Frontend Activity-Feed:** `PantryActivityFeedPanel` analog zu `ActivityFeedPanel`, geöffnet via einen Header-Button auf `PantryPage`. Pagination-Verhalten wie beim Todo-Pendant. 8. **Frontend CSV:** `CsvExportDialog`s `kind`-Union um `'pantry'` erweitert; Import über einen Menü-Eintrag auf `PantryPage` (analog Shopping/Todo-CSV-Import). Nutzt die bereits vorhandenen `getPantryProductsForList`/`getPantryCategoriesForList`-Queries. ## Security-Vorprüfung (überall gleiche Regel) Alle neuen Handler nutzen `AuthorizePantryAccessForCurrentUserQuery(pantryId)` — dieselbe Membership-basierte Autorisierung, die `#94` für alle bestehenden Pantry-Handler eingeführt hat. Kein Owner-only-Split nötig, da Pantry-Sharing rein Mitgliedschafts-basiert ist (Owner-only wäre ein neues Konzept und ist nicht von der AC gefordert). Label/Kommentar-Löschung: jeder Mitglied darf Labels/Kommentare löschen (matcht Todo-Regel — vgl. `DeleteLabelCommand`/`DeleteCommentCommand`, beide member-level). Aktivitäts-Feed-Zugriff: `GetPantryActivityFeedQuery` prüft `AuthorizePantryAccessForCurrentUserQuery` — Members sehen nur die Feeds ihrer Pantries. CSV-Import: analog `ImportShoppingProductsFromCsvCommand` — Mitgliedschafts-Check + `ImportSizeLimit` (Zeilenanzahl-Obergrenze zur DoS-Absicherung, vom Shopping-Import geerbt). Keine neue Attack Surface: alle Endpunkte spiegeln existierende, bereits geprüfte Muster. ## Test-Strategie Backend: Handler-Tests pro Command wie im Rest des Repos (`CqsTodo.Tests/Features/Pantry/` bekommt jeweils einen `*Tests.cs`), inkl. Auth-Ablehnung und typischer Fehlerpfade. Frontend: Unit-Tests pro neuer/erweiterter Komponente (Vitest), plus **1 E2E** (`pantry-collaboration.spec.ts`) das die vier Sub-Features im Zusammenspiel prüft: Label anlegen + taggen + filtern; Kommentar schreiben; Check-in/-out landet im Activity-Feed; CSV-Export + Re-Import erzeugt identischen Bestand. ## Explizit Nicht-Scope - Keine Sharing-Änderungen — Pantry bleibt an die Ziel-Einkaufsliste gekoppelt. - Keine WS-Live-Sync für die neuen Sub-Features in V1 (analog `#94` selbst — Pantry hat keinen WS- Kanal, refetch-nach-Mutation reicht für die typische Häufigkeit). Falls sich im Betrieb Bedarf ergibt, wäre das eine separate Folge-Story analog `#99`. - Keine "Group"-Erweiterung an `PantryLabelEntity` (das war `#93`-spezifisch für die Climate/Travel-Type-Facets — Pantry-Labels sind gruppenlos wie Todo-Labels). - Kein `PantryActivityEventEntity`-Broadcast über einen WS-Change-Subject; Refetch-on-Open reicht wie beim Todo-Pendant, das keinen eigenen WS-Kanal hat.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
robert/todo#100
No description provided.