Speisekammer — Kategorien mit der verknüpften Einkaufsliste teilen #174

Closed
opened 2026-09-07 10:06:27 +02:00 by lena · 3 comments
Collaborator

Story: Speisekammer — Kategorien mit der verknüpften Einkaufsliste teilen

As a Nutzer, der eine Speisekammer und ihre verknüpfte Einkaufsliste parallel pflegt,
I want to in beiden dieselben Kategorien mit derselben Zuordnung sehen,
so that ich nicht zwei unabhängige Kategorie-Systeme für dieselben Produkte pflegen muss.

Kontext (verifiziert im Code):

  • PantryCategoryEntity ist per FK an PantryId gebunden, ShoppingCategoryEntity an ShoppingListId — vollständig getrennte Tabellen und ID-Räume (PantryCategoryId vs ShoppingCategoryId), keinerlei Verknüpfung.
  • PantryEntity.TargetShoppingListId existiert bereits (FK zur verknüpften Einkaufsliste), wird aber ausschließlich zur Herleitung von Zugriffsrechten genutzt ("access is derived live from TargetShoppingList's own membership") — nicht zum Teilen von Kategorien.
  • GetPantryCategoriesQueryHandler.cs liest aus PantryCategoryEntity gefiltert auf PantryId; GetShoppingCategoriesForListQueryHandler.cs liest aus ShoppingCategoryEntity gefiltert auf ShoppingListId — komplett unabhängige Datensätze heute.
  • PantryProductEntity.CategoryId zeigt nur auf PantryCategoryEntity, nie auf ShoppingCategoryEntity.

Acceptance criteria:

  • Die Kategorien-Auswahl in der Speisekammer zeigt exakt dieselben Kategorien wie die verknüpfte Einkaufsliste (TargetShoppingListId) — gleiche Namen, gleiche Reihenfolge, gleiche Icons.
  • Ein Produkt in der Speisekammer einer Kategorie zuzuordnen, ordnet es derselben Kategorie zu, die auch auf der Einkaufsliste existiert (keine parallele, unabhängige Zuordnung mehr).
  • Eine neue Kategorie anlegen/umbenennen/löschen/umsortieren — egal ob von der Speisekammer- oder der Einkaufslisten-Seite aus angestoßen — wirkt sich sofort auf beide Ansichten aus.
  • Bestehende Speisekammer-Produkte behalten eine sinnvolle Kategorie-Zuordnung nach der Umstellung (siehe offene Frage zur Migration unten).

Out of scope for this story:

  • Verhalten für eine Speisekammer, deren verknüpfte Einkaufsliste zwischenzeitlich gelöscht wurde (falls das laut anderen Stories überhaupt möglich ist) — separat klären, falls relevant.

Open questions: (escalate to human if unanswered)

  • Migrationspfad: Was passiert mit den heute existierenden, eigenständigen PantryCategoryEntity-Zeilen? Werden sie anhand des Namens auf passende ShoppingCategoryEntity-Zeilen der verknüpften Liste gemappt, oder verlieren Produkte mit einer heute nur-Speisekammer-eigenen Kategorie (die es auf der Einkaufsliste nicht gibt) ihre Zuordnung? Braucht vermutlich eine Datenmigration, kein reiner Code-Change — Architect-Entscheidung.
  • Wird PantryCategoryEntity komplett entfernt, oder bleibt sie (leer/ungenutzt) aus Kompatibilitätsgründen bestehen?
## Story: Speisekammer — Kategorien mit der verknüpften Einkaufsliste teilen **As a** Nutzer, der eine Speisekammer und ihre verknüpfte Einkaufsliste parallel pflegt, **I want to** in beiden dieselben Kategorien mit derselben Zuordnung sehen, **so that** ich nicht zwei unabhängige Kategorie-Systeme für dieselben Produkte pflegen muss. **Kontext (verifiziert im Code):** - `PantryCategoryEntity` ist per FK an `PantryId` gebunden, `ShoppingCategoryEntity` an `ShoppingListId` — vollständig getrennte Tabellen und ID-Räume (`PantryCategoryId` vs `ShoppingCategoryId`), keinerlei Verknüpfung. - `PantryEntity.TargetShoppingListId` existiert bereits (FK zur verknüpften Einkaufsliste), wird aber ausschließlich zur Herleitung von Zugriffsrechten genutzt ("access is derived live from TargetShoppingList's own membership") — nicht zum Teilen von Kategorien. - `GetPantryCategoriesQueryHandler.cs` liest aus `PantryCategoryEntity` gefiltert auf `PantryId`; `GetShoppingCategoriesForListQueryHandler.cs` liest aus `ShoppingCategoryEntity` gefiltert auf `ShoppingListId` — komplett unabhängige Datensätze heute. - `PantryProductEntity.CategoryId` zeigt nur auf `PantryCategoryEntity`, nie auf `ShoppingCategoryEntity`. **Acceptance criteria:** - [ ] Die Kategorien-Auswahl in der Speisekammer zeigt exakt dieselben Kategorien wie die verknüpfte Einkaufsliste (`TargetShoppingListId`) — gleiche Namen, gleiche Reihenfolge, gleiche Icons. - [ ] Ein Produkt in der Speisekammer einer Kategorie zuzuordnen, ordnet es derselben Kategorie zu, die auch auf der Einkaufsliste existiert (keine parallele, unabhängige Zuordnung mehr). - [ ] Eine neue Kategorie anlegen/umbenennen/löschen/umsortieren — egal ob von der Speisekammer- oder der Einkaufslisten-Seite aus angestoßen — wirkt sich sofort auf beide Ansichten aus. - [ ] Bestehende Speisekammer-Produkte behalten eine sinnvolle Kategorie-Zuordnung nach der Umstellung (siehe offene Frage zur Migration unten). **Out of scope for this story:** - Verhalten für eine Speisekammer, deren verknüpfte Einkaufsliste zwischenzeitlich gelöscht wurde (falls das laut anderen Stories überhaupt möglich ist) — separat klären, falls relevant. **Open questions:** (escalate to human if unanswered) - Migrationspfad: Was passiert mit den heute existierenden, eigenständigen `PantryCategoryEntity`-Zeilen? Werden sie anhand des Namens auf passende `ShoppingCategoryEntity`-Zeilen der verknüpften Liste gemappt, oder verlieren Produkte mit einer heute nur-Speisekammer-eigenen Kategorie (die es auf der Einkaufsliste nicht gibt) ihre Zuordnung? Braucht vermutlich eine Datenmigration, kein reiner Code-Change — Architect-Entscheidung. - Wird `PantryCategoryEntity` komplett entfernt, oder bleibt sie (leer/ungenutzt) aus Kompatibilitätsgründen bestehen?
Author
Collaborator

Entscheidung (Mensch, 2026-09-08): Migrationspfad geklaert. Beim Umstieg werden bestehende PantryCategoryEntity-Zeilen per Name auf die passende ShoppingCategoryEntity der verknuepften Einkaufsliste gemappt; nicht matchende Kategorien verlieren ihre Zuordnung (Produkt wird unkategorisiert). PantryCategoryEntity wird danach vollstaendig entfernt, keine Kompatibilitaets-Altlast. Beide offenen Fragen der Story sind damit beantwortet - Umsetzung kann ohne weitere Eskalation starten.

**Entscheidung (Mensch, 2026-09-08):** Migrationspfad geklaert. Beim Umstieg werden bestehende `PantryCategoryEntity`-Zeilen per Name auf die passende `ShoppingCategoryEntity` der verknuepften Einkaufsliste gemappt; nicht matchende Kategorien verlieren ihre Zuordnung (Produkt wird unkategorisiert). `PantryCategoryEntity` wird danach vollstaendig entfernt, keine Kompatibilitaets-Altlast. Beide offenen Fragen der Story sind damit beantwortet - Umsetzung kann ohne weitere Eskalation starten.
lena self-assigned this 2026-09-08 09:00:53 +02:00
Author
Collaborator

Claimed for this go-cycle (2026-09-08). Migration path is already resolved per the human decision comment above - implementation plan: (1) EF Core migration to drop PantryCategoryEntity/PantryProductEntity.CategoryId's FK to it, repoint PantryProductEntity.CategoryId at ShoppingCategoryEntity (or add a new column and drop the old, whichever is the cleaner EF migration), (2) one-time data migration mapping existing PantryCategoryEntity rows to the linked ShoppingList's ShoppingCategoryEntity rows by name (unmatched -> product left uncategorized, per the human decision), (3) update GetPantryCategoriesQueryHandler/PantryCategoryPicker.tsx and all Pantry category CRUD handlers (Create/Rename/Delete/Reorder) to operate on ShoppingCategoryEntity scoped by the pantry's TargetShoppingListId instead of PantryCategoryEntity, (4) remove PantryCategoryEntity and PantryCategoryId entirely once nothing references them, (5) frontend: PantryProductItem's category picker/select and PantryPage's category filter switch from PantryCategoryDto/PantryCategoryId to the shopping-list's own CategoryDto/CategoryId types.

Claimed for this go-cycle (2026-09-08). Migration path is already resolved per the human decision comment above - implementation plan: (1) EF Core migration to drop PantryCategoryEntity/PantryProductEntity.CategoryId's FK to it, repoint PantryProductEntity.CategoryId at ShoppingCategoryEntity (or add a new column and drop the old, whichever is the cleaner EF migration), (2) one-time data migration mapping existing PantryCategoryEntity rows to the linked ShoppingList's ShoppingCategoryEntity rows by name (unmatched -> product left uncategorized, per the human decision), (3) update GetPantryCategoriesQueryHandler/PantryCategoryPicker.tsx and all Pantry category CRUD handlers (Create/Rename/Delete/Reorder) to operate on ShoppingCategoryEntity scoped by the pantry's TargetShoppingListId instead of PantryCategoryEntity, (4) remove PantryCategoryEntity and PantryCategoryId entirely once nothing references them, (5) frontend: PantryProductItem's category picker/select and PantryPage's category filter switch from PantryCategoryDto/PantryCategoryId to the shopping-list's own CategoryDto/CategoryId types.
Author
Collaborator

Done - merged to master as 96f9d545 (feature) + 838eccaf (coverage report).

Scope delivered (both AC bullets from the human's migration decision comment included):

  • PantryCategoryEntity (a fully separate table keyed by PantryId) removed entirely - a pantry's category selection now shows exactly the same categories as its linked shopping list (same names, order, icons), because they are literally the same ShoppingCategoryEntity rows, not a copy.
  • Assigning a category to a pantry product assigns the same category that exists on the shopping list - no more parallel, independent assignment.
  • Creating/renaming/deleting/reordering a category from either the Pantry or the Shopping List side takes effect on both immediately, since both read/write the identical rows.
  • Migration remaps existing PantryProductEntity.CategoryId values by matching the old category's name (case-insensitively) against the linked list's own categories; unmatched products become uncategorized - exactly per the human's decision comment. PantryCategoryEntity is fully dropped afterward, no compatibility leftover.

Backend: 6 Pantry-only category CRUD/query handlers + PantryCategoryId/PantryCategoryDto deleted. PantryProductEntity.CategoryId repointed at ShoppingCategoryId. Every handler that validated "does this category belong to my pantry" (CreatePantryProductCommandHandler, MovePantryProductCommandHandler, ScanPantryProductBarcodeCommandHandler, ImportPantryProductsFromCsvCommandHandler) now fetches the pantry's TargetShoppingListId server-side and validates against that list's own categories - never trusts client input for the scope. DuplicatePantryCommandHandler's category-copy logic became unnecessary (a duplicated pantry already shares the source's TargetShoppingListId, so it already sees the same categories) and was removed. New SetDefaultShoppingCategoryCommand (mirrors the deleted Pantry-only equivalent) added to the Shopping side and wired into both category managers, so Shopping gains the "set default" control Pantry already had, rather than either side losing a capability. One combined EF migration: raw-SQL name-based remap first, then the schema drop/FK repoint, following this repo's existing data-merge-migration pattern.

Frontend: PantryCategoryManager.tsx/PantryCategoryPicker.tsx kept as their own thin components (consistent with this codebase's existing "mirrors X.tsx" sibling-component convention elsewhere) but retargeted to call the Shopping category commands/types directly instead of Pantry-only ones.

Testing: dotnet test 1028/1028 green,
pm run coverage 1244/1244 green (the only failure seen across two full-suite runs this cycle was the already-known-flaky #175 burst-scan timing test, unrelated to this diff - separately flagged). Self-review + the security-review skill (plus a dedicated sub-agent IDOR check: can a caller reference a category from a shopping list other than the pantry's own linked one?) both came back clean. Live end-to-end verification against the rebuilt local review container (registered a real user, created a real shopping list + linked pantry, created categories from both the Shopping and Pantry sides via the real API, confirmed each showed up on both sides immediately, and created a pantry product assigned to a category created from the Pantry side, confirmed correctly filed) - not just automated tests.

Known, deliberately out-of-scope: behavior when a pantry's linked shopping list is deleted while the pantry still references it - explicitly called out as out of scope in the original story.

Done - merged to master as 96f9d545 (feature) + 838eccaf (coverage report). **Scope delivered (both AC bullets from the human's migration decision comment included):** - PantryCategoryEntity (a fully separate table keyed by PantryId) removed entirely - a pantry's category selection now shows exactly the same categories as its linked shopping list (same names, order, icons), because they are literally the same ShoppingCategoryEntity rows, not a copy. - Assigning a category to a pantry product assigns the *same* category that exists on the shopping list - no more parallel, independent assignment. - Creating/renaming/deleting/reordering a category from either the Pantry or the Shopping List side takes effect on both immediately, since both read/write the identical rows. - Migration remaps existing PantryProductEntity.CategoryId values by matching the old category's name (case-insensitively) against the linked list's own categories; unmatched products become uncategorized - exactly per the human's decision comment. PantryCategoryEntity is fully dropped afterward, no compatibility leftover. **Backend:** 6 Pantry-only category CRUD/query handlers + PantryCategoryId/PantryCategoryDto deleted. PantryProductEntity.CategoryId repointed at ShoppingCategoryId. Every handler that validated "does this category belong to my pantry" (CreatePantryProductCommandHandler, MovePantryProductCommandHandler, ScanPantryProductBarcodeCommandHandler, ImportPantryProductsFromCsvCommandHandler) now fetches the pantry's TargetShoppingListId server-side and validates against that list's own categories - never trusts client input for the scope. DuplicatePantryCommandHandler's category-copy logic became unnecessary (a duplicated pantry already shares the source's TargetShoppingListId, so it already sees the same categories) and was removed. New SetDefaultShoppingCategoryCommand (mirrors the deleted Pantry-only equivalent) added to the Shopping side and wired into both category managers, so Shopping gains the "set default" control Pantry already had, rather than either side losing a capability. One combined EF migration: raw-SQL name-based remap first, then the schema drop/FK repoint, following this repo's existing data-merge-migration pattern. **Frontend:** PantryCategoryManager.tsx/PantryCategoryPicker.tsx kept as their own thin components (consistent with this codebase's existing "mirrors X.tsx" sibling-component convention elsewhere) but retargeted to call the Shopping category commands/types directly instead of Pantry-only ones. **Testing:** dotnet test 1028/1028 green, pm run coverage 1244/1244 green (the only failure seen across two full-suite runs this cycle was the already-known-flaky #175 burst-scan timing test, unrelated to this diff - separately flagged). Self-review + the security-review skill (plus a dedicated sub-agent IDOR check: can a caller reference a category from a shopping list other than the pantry's own linked one?) both came back clean. **Live end-to-end verification** against the rebuilt local review container (registered a real user, created a real shopping list + linked pantry, created categories from both the Shopping and Pantry sides via the real API, confirmed each showed up on both sides immediately, and created a pantry product assigned to a category created from the Pantry side, confirmed correctly filed) - not just automated tests. **Known, deliberately out-of-scope:** behavior when a pantry's linked shopping list is deleted while the pantry still references it - explicitly called out as out of scope in the original story.
lena closed this issue 2026-09-08 09:53:37 +02:00
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#174
No description provided.