Einkaufsliste - Produkte einem Mitglied zuweisen (wie bei To-Do-Listen) #202

Closed
opened 2026-09-27 15:53:10 +02:00 by lena · 2 comments
Collaborator

Story: Einkaufsliste - Produkte einem Mitglied zuweisen

As a Mitglied einer geteilten Einkaufsliste,
I want to ein Produkt einer bestimmten Person zuweisen,
so that klar ist, wer es besorgen soll - genau wie bei To-Do-Listen (#16).

Kontext (verifiziert im Code): Fuer Todos existiert bereits AssigneeId/AssignTodoCommand (#16) mit Zuweisungs-Picker (AssignmentPicker.tsx) und Avatar-Anzeige (AssigneeBadge.tsx). ShoppingProductEntity hat aktuell kein Assignee-Feld. GetShoppingListMembersQuery (Mitgliederliste der Einkaufsliste) existiert bereits (#89) und kann fuer den Picker wiederverwendet werden.

Acceptance criteria:

  • Jedes Mitglied einer Einkaufsliste kann ein Produkt einem beliebigen anderen Mitglied zuweisen (inkl. sich selbst)
  • Der/die Zugewiesene wird am Produkt als Avatar (bzw. Initialen ohne Avatarbild) und Name angezeigt
  • Eine bestehende Zuweisung kann geaendert oder aufgehoben werden (unassigned)
  • Nicht zugewiesene Produkte sind fuer alle Mitglieder normal sichtbar
  • Wird ein Mitglied aus der Liste entfernt, wird eine bestehende Zuweisung von ihm aufgehoben (Produkt wird unassigned) - analog #16

Out of scope for this story:

  • Ein "Meine Produkte"-Filter (analog zum Todo-"My todos"-Toggle) - kann bei Bedarf als eigene Folge-Story ergaenzt werden
  • Benachrichtigung bei Zuweisung (analog NotificationKind.TodoAssigned) - der Code behandelt Einkaufsliste/Pantry/Masterpack bisher bewusst als "kein Item-Assignee"; wird nur mitgenommen, wenn der Zusatzaufwand gering bleibt, ist aber kein hartes Muss fuer diese Story
  • WebSocket-Live-Sync fuer Produktaenderungen allgemein (Shopping-Produkte werden aktuell aus der Command-Response lokal gepatcht statt per subscribeToDtoChanges) - bleibt unveraendert, keine Ausweitung in dieser Story

Blockers: None

Priority: Should - vom Nutzer direkt angefragte Kernfunktion fuer geteilte Einkaufslisten, analog zur bereits bestehenden Todo-Funktion.

## Story: Einkaufsliste - Produkte einem Mitglied zuweisen **As a** Mitglied einer geteilten Einkaufsliste, **I want to** ein Produkt einer bestimmten Person zuweisen, **so that** klar ist, wer es besorgen soll - genau wie bei To-Do-Listen (`#16`). **Kontext (verifiziert im Code):** Fuer Todos existiert bereits `AssigneeId`/`AssignTodoCommand` (`#16`) mit Zuweisungs-Picker (`AssignmentPicker.tsx`) und Avatar-Anzeige (`AssigneeBadge.tsx`). `ShoppingProductEntity` hat aktuell kein Assignee-Feld. `GetShoppingListMembersQuery` (Mitgliederliste der Einkaufsliste) existiert bereits (`#89`) und kann fuer den Picker wiederverwendet werden. **Acceptance criteria:** - [ ] Jedes Mitglied einer Einkaufsliste kann ein Produkt einem beliebigen anderen Mitglied zuweisen (inkl. sich selbst) - [ ] Der/die Zugewiesene wird am Produkt als Avatar (bzw. Initialen ohne Avatarbild) und Name angezeigt - [ ] Eine bestehende Zuweisung kann geaendert oder aufgehoben werden (unassigned) - [ ] Nicht zugewiesene Produkte sind fuer alle Mitglieder normal sichtbar - [ ] Wird ein Mitglied aus der Liste entfernt, wird eine bestehende Zuweisung von ihm aufgehoben (Produkt wird unassigned) - analog `#16` **Out of scope for this story:** - Ein "Meine Produkte"-Filter (analog zum Todo-"My todos"-Toggle) - kann bei Bedarf als eigene Folge-Story ergaenzt werden - Benachrichtigung bei Zuweisung (analog `NotificationKind.TodoAssigned`) - der Code behandelt Einkaufsliste/Pantry/Masterpack bisher bewusst als "kein Item-Assignee"; wird nur mitgenommen, wenn der Zusatzaufwand gering bleibt, ist aber kein hartes Muss fuer diese Story - WebSocket-Live-Sync fuer Produktaenderungen allgemein (Shopping-Produkte werden aktuell aus der Command-Response lokal gepatcht statt per `subscribeToDtoChanges`) - bleibt unveraendert, keine Ausweitung in dieser Story **Blockers:** None **Priority:** Should - vom Nutzer direkt angefragte Kernfunktion fuer geteilte Einkaufslisten, analog zur bereits bestehenden Todo-Funktion.
lena self-assigned this 2026-09-27 15:53:29 +02:00
Author
Collaborator

Claimed. Plan: add nullable AssigneeId/Assignee (UserId, FK -> UserEntity, ON DELETE SET NULL) to ShoppingProductEntity with a migration; extend ShoppingProductDto with an assignee DTO and thread it through every existing ShoppingProductDto projection site in Checkly/Features/Shopping/; add AssignShoppingProductCommand + handler modeled on AssignTodoCommandHandler (validate product exists, validate assignee is a ShoppingListToUserEntity member, ExecuteUpdateAsync, authorize via AuthorizeShoppingListAccessForCurrentUserQuery); frontend: a shopping-product assignment picker + AssigneeBadge reuse wired into ShoppingProductItem.tsx same as TodoItem.tsx; backend + frontend tests mirroring AssignTodoCommandHandlerTests.cs and AssignmentPicker.test.tsx. Notifications and product-level WS sync are out of scope per the story.

Claimed. Plan: add nullable AssigneeId/Assignee (UserId, FK -> UserEntity, ON DELETE SET NULL) to ShoppingProductEntity with a migration; extend ShoppingProductDto with an assignee DTO and thread it through every existing ShoppingProductDto projection site in Checkly/Features/Shopping/; add AssignShoppingProductCommand + handler modeled on AssignTodoCommandHandler (validate product exists, validate assignee is a ShoppingListToUserEntity member, ExecuteUpdateAsync, authorize via AuthorizeShoppingListAccessForCurrentUserQuery); frontend: a shopping-product assignment picker + AssigneeBadge reuse wired into ShoppingProductItem.tsx same as TodoItem.tsx; backend + frontend tests mirroring AssignTodoCommandHandlerTests.cs and AssignmentPicker.test.tsx. Notifications and product-level WS sync are out of scope per the story.
Author
Collaborator

Done, pushed as dc186876.

Scope delivered:

  • ShoppingProductEntity gained a nullable AssigneeId/Assignee (UserId FK -> UserEntity, ON DELETE SET NULL), migration 20260927140434_AddAssigneeToShoppingProduct.
  • New AssignShoppingProductCommand/Handler: assign and unassign are the same command (assigneeId: null clears it, same shape as AssignTodoCommand from #16); validates the target is a member of the list via ShoppingListToUserEntity before assigning.
  • ShoppingProductDto.Assignee (reuses TodoAssigneeDto) is threaded through every mutation handler's response (Rename/Move/Activate/Deactivate/SetInCart/SetRecurrence), via a small shared ShoppingProductAssigneeResolver, not left as a CommentCount/Labels/RecurrenceRule-style known gap - the assignee stays visible across edits instead of flickering to unassigned.
  • RemoveShoppingListMemberCommand / LeaveShoppingListCommand now clear a member's product assignments on that list, mirroring the Todo side.
  • Frontend: ShoppingProductAssignmentPicker.tsx (sibling of the Todo AssignmentPicker, since Shopping products have no WS sync and need an onChanged callback instead) wired into ShoppingProductItem's actions menu + an AssigneeBadge on the row.

Tests: new AssignShoppingProductCommandHandlerTests.cs (assign/unassign/reassign, non-member rejection, cross-list product rejection) + assignment-clearing tests added to Remove/LeaveShoppingListCommandHandlerTests, an assignee-preservation regression test on RenameShoppingProductCommandHandlerTests, and a projection test on GetShoppingProductsForListQueryHandlerTests. Frontend: ShoppingProductAssignmentPicker.test.tsx (mirrors AssignmentPicker.test.tsx) + a new assignment describe block in ShoppingProductItem.test.tsx.

Verified: dotnet test (Common.Tests 119, Checkly.WebApi.Tests 63, Checkly.Tests 1004 incl. the new ones) all green; npm run build + npx tsc -b clean; npm run coverage (141 files, 1380 tests) green; security-review pass found no findings. Local review container rebuilt and healthy at localhost:8090.

Out of scope per the story (documented in code comments): a "My products" filter, an assignment notification (NotificationKind has no ShoppingProductAssigned - #185's existing comment-notification model is unrelated and untouched), and product-level WebSocket sync (unchanged pre-existing gap, not introduced by this story).

Done, pushed as dc186876. Scope delivered: - ShoppingProductEntity gained a nullable AssigneeId/Assignee (UserId FK -> UserEntity, ON DELETE SET NULL), migration 20260927140434_AddAssigneeToShoppingProduct. - New AssignShoppingProductCommand/Handler: assign and unassign are the same command (assigneeId: null clears it, same shape as AssignTodoCommand from `#16`); validates the target is a member of the list via ShoppingListToUserEntity before assigning. - ShoppingProductDto.Assignee (reuses TodoAssigneeDto) is threaded through every mutation handler's response (Rename/Move/Activate/Deactivate/SetInCart/SetRecurrence), via a small shared ShoppingProductAssigneeResolver, not left as a CommentCount/Labels/RecurrenceRule-style known gap - the assignee stays visible across edits instead of flickering to unassigned. - RemoveShoppingListMemberCommand / LeaveShoppingListCommand now clear a member's product assignments on that list, mirroring the Todo side. - Frontend: ShoppingProductAssignmentPicker.tsx (sibling of the Todo AssignmentPicker, since Shopping products have no WS sync and need an onChanged callback instead) wired into ShoppingProductItem's actions menu + an AssigneeBadge on the row. Tests: new AssignShoppingProductCommandHandlerTests.cs (assign/unassign/reassign, non-member rejection, cross-list product rejection) + assignment-clearing tests added to Remove/LeaveShoppingListCommandHandlerTests, an assignee-preservation regression test on RenameShoppingProductCommandHandlerTests, and a projection test on GetShoppingProductsForListQueryHandlerTests. Frontend: ShoppingProductAssignmentPicker.test.tsx (mirrors AssignmentPicker.test.tsx) + a new assignment describe block in ShoppingProductItem.test.tsx. Verified: dotnet test (Common.Tests 119, Checkly.WebApi.Tests 63, Checkly.Tests 1004 incl. the new ones) all green; npm run build + npx tsc -b clean; npm run coverage (141 files, 1380 tests) green; security-review pass found no findings. Local review container rebuilt and healthy at localhost:8090. Out of scope per the story (documented in code comments): a "My products" filter, an assignment notification (NotificationKind has no ShoppingProductAssigned - `#185`'s existing comment-notification model is unrelated and untouched), and product-level WebSocket sync (unchanged pre-existing gap, not introduced by this story).
lena closed this issue 2026-09-27 16:37:28 +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#202
No description provided.