Vorratsschrank - Benachrichtigung bei bald ablaufenden/abgelaufenen Produkten #186

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

Story: Vorratsschrank - Benachrichtigung bei bald ablaufenden/abgelaufenen Produkten

As a Mitglied eines Vorratsschranks,
I want to proaktiv benachrichtigt zu werden, wenn ein Produkt bald ablaeuft oder bereits abgelaufen ist,
so that ich es rechtzeitig verbrauchen oder wegwerfen kann, ohne jeden Tag manuell durch den Vorratsschrank zu scrollen und auf die "laeuft bald ab"-Markierung zu achten.

Kontext (verifiziert im Code): #164 hat PantryProductBestBeforeDate plus eine rein visuelle "laeuft bald ab"/"abgelaufen"-Markierung (isExpiringSoon/isExpired auf PantryDto, PantryBestBeforeDateDialog.tsx) eingefuehrt - aber keine proaktive Benachrichtigung. Fuer Todos existiert bereits ein analoges Muster (NotificationKind.TodoDueToday/TodoOverdue, taeglicher Check) - siehe docs/-Kontext zu Due-Date-Reminders. Dieses Muster ist direkt auf Vorratsschrank-Produkte mit Mindesthaltbarkeitsdatum uebertragbar.

Acceptance criteria:

  • Neue NotificationKind-Werte PantryProductExpiringSoon/PantryProductExpired (analog zu TodoDueToday/TodoOverdue).
  • Ein taeglicher Check (wiederverwendet denselben Scheduling-Mechanismus wie die Todo-Due-Date-Reminder) benachrichtigt jedes Vorratsschrank-Mitglied einmalig, wenn ein Produkt neu in den "laeuft bald ab"-Zeitraum eintritt bzw. neu abgelaufen ist - keine taegliche Wiederholung fuer dasselbe Produkt/denselben Status (sonst Benachrichtigungs-Spam bei laenger unbeachteten Produkten).
  • Folgt der bestehenden Email/Push-Digest-Pipeline automatisch.
  • Ein Produkt ohne gesetztes Mindesthaltbarkeitsdatum loest naturgemaess nichts aus (unveraendertes Verhalten, wie bereits bei der rein visuellen Markierung aus #164).

Out of scope for this story:

  • Aenderungen an der bestehenden visuellen Markierung selbst (#164 bleibt wie sie ist).
  • Konfigurierbarkeit des "bald ablaufend"-Zeitraums (uebernimmt den bereits in #164 festgelegten festen Schwellenwert).

Open questions: (escalate to human if unanswered)

  • Keine - ueberträgt ein bereits fuer Todos gebautes und bewaehrtes taegliches Reminder-Muster auf eine bereits vorhandene Datengrundlage (#164).
## Story: Vorratsschrank - Benachrichtigung bei bald ablaufenden/abgelaufenen Produkten **As a** Mitglied eines Vorratsschranks, **I want to** proaktiv benachrichtigt zu werden, wenn ein Produkt bald ablaeuft oder bereits abgelaufen ist, **so that** ich es rechtzeitig verbrauchen oder wegwerfen kann, ohne jeden Tag manuell durch den Vorratsschrank zu scrollen und auf die "laeuft bald ab"-Markierung zu achten. **Kontext (verifiziert im Code):** #164 hat `PantryProductBestBeforeDate` plus eine rein visuelle "laeuft bald ab"/"abgelaufen"-Markierung (`isExpiringSoon`/`isExpired` auf `PantryDto`, `PantryBestBeforeDateDialog.tsx`) eingefuehrt - aber keine proaktive Benachrichtigung. Fuer Todos existiert bereits ein analoges Muster (`NotificationKind.TodoDueToday`/`TodoOverdue`, taeglicher Check) - siehe `docs/`-Kontext zu Due-Date-Reminders. Dieses Muster ist direkt auf Vorratsschrank-Produkte mit Mindesthaltbarkeitsdatum uebertragbar. **Acceptance criteria:** - [ ] Neue `NotificationKind`-Werte `PantryProductExpiringSoon`/`PantryProductExpired` (analog zu `TodoDueToday`/`TodoOverdue`). - [ ] Ein taeglicher Check (wiederverwendet denselben Scheduling-Mechanismus wie die Todo-Due-Date-Reminder) benachrichtigt jedes Vorratsschrank-Mitglied einmalig, wenn ein Produkt neu in den "laeuft bald ab"-Zeitraum eintritt bzw. neu abgelaufen ist - keine taegliche Wiederholung fuer dasselbe Produkt/denselben Status (sonst Benachrichtigungs-Spam bei laenger unbeachteten Produkten). - [ ] Folgt der bestehenden Email/Push-Digest-Pipeline automatisch. - [ ] Ein Produkt ohne gesetztes Mindesthaltbarkeitsdatum loest naturgemaess nichts aus (unveraendertes Verhalten, wie bereits bei der rein visuellen Markierung aus #164). **Out of scope for this story:** - Aenderungen an der bestehenden visuellen Markierung selbst (#164 bleibt wie sie ist). - Konfigurierbarkeit des "bald ablaufend"-Zeitraums (uebernimmt den bereits in #164 festgelegten festen Schwellenwert). **Open questions:** (escalate to human if unanswered) - Keine - ueberträgt ein bereits fuer Todos gebautes und bewaehrtes taegliches Reminder-Muster auf eine bereits vorhandene Datengrundlage (#164).
lena self-assigned this 2026-09-10 21:54:18 +02:00
Author
Collaborator

Claiming this for the current autonomous cycle. Plan: mirror the existing TodoDueToday/TodoOverdue daily-check pattern onto pantry best-before dates. Add NotificationKind.PantryProductExpiringSoon / PantryProductExpired, a daily background check (reusing the same scheduling mechanism as the Todo due-date reminders) that scans PantryProductEntity rows with a BestBeforeDate, computes the same expiring-soon/expired thresholds already used by PantryDto.isExpiringSoon/isExpired (from #164), and emits one notification per pantry member the first time a product newly enters either state (no repeat spam for a product that stays expired/expiring). Delivery goes through the existing CreateNotificationCommand infra so email/push digest pickup is automatic, no new send path. No change to the existing visual badge itself. Not overlapping with the #181 work currently in progress on the appearance/color-icon feature (different files/domain).

Claiming this for the current autonomous cycle. Plan: mirror the existing TodoDueToday/TodoOverdue daily-check pattern onto pantry best-before dates. Add NotificationKind.PantryProductExpiringSoon / PantryProductExpired, a daily background check (reusing the same scheduling mechanism as the Todo due-date reminders) that scans PantryProductEntity rows with a BestBeforeDate, computes the same expiring-soon/expired thresholds already used by PantryDto.isExpiringSoon/isExpired (from #164), and emits one notification per pantry member the first time a product newly enters either state (no repeat spam for a product that stays expired/expiring). Delivery goes through the existing CreateNotificationCommand infra so email/push digest pickup is automatic, no new send path. No change to the existing visual badge itself. Not overlapping with the #181 work currently in progress on the appearance/color-icon feature (different files/domain).
Author
Collaborator

Implemented and merged (47de9873).

Scope: two new NotificationKind values (PantryProductExpiringSoon, PantryProductExpired), a daily background sweep (CreatePantryDueNotificationsCommandHandler, bundled into the existing DueNotificationJob loop alongside the todo due-notification sweep) that notifies every member of a pantry-s target shopping list once when a product newly enters the expiring-soon (3-day, same threshold as #164-s visual badge) or expired state. No repeat for the same product/status on later runs (deliberately stricter idempotency than the todo pattern, which repeats once per day) - enforced by a new filtered unique index (RecipientId, PantryId, PantryProductId, Kind), no day component.

Design decisions:

  • NotificationEntity/NotificationDto now support Pantry as a second, mutually-exclusive notification target alongside TodoList (nullable column pairs + a new CK_NotificationEntity_ExactlyOneTarget check constraint), rather than a separate table - keeps the existing GetNotificationsForCurrentUserQuery/read path and WS push path shared across both.
  • Archived pantries are skipped by the sweep (no notification spam for pantries nobody is looking at any more) - not explicitly required by the AC but a low-risk, reversible default; flagging it here in case that is not what was wanted.
  • The expiring-soon/expired threshold logic (3 days) was extracted into a shared PantryExpiryStatus helper so the notification sweep and the existing visual badge (#164) can never drift apart.
  • Notification body text stays hardcoded English server-side, same as the existing TodoDueToday/TodoOverdue precedent (no i18n-driven notification-kind label mapping exists anywhere in the frontend yet - introducing one was out of scope for this story).

Tests: 8 new backend handler tests (CreatePantryDueNotificationsCommandHandlerTests: expiring-soon, expired, no-date, far-from-expiry, archived-pantry, multi-member fan-out, idempotency-across-runs, WS-publish) plus 4 new frontend tests (NotificationItem: routes to /pantry/:id vs /list/:id, shows pantry title, marks read). Full backend suite (1093 tests) and frontend suite (1299 tests) green after the change.

Migration: 20260910200509_AddPantryNotificationTarget (TodoListId now nullable, PantryId/PantryProductId added, check constraint, new filtered unique index).

Implemented and merged (47de9873). **Scope:** two new NotificationKind values (PantryProductExpiringSoon, PantryProductExpired), a daily background sweep (CreatePantryDueNotificationsCommandHandler, bundled into the existing DueNotificationJob loop alongside the todo due-notification sweep) that notifies every member of a pantry-s target shopping list once when a product newly enters the expiring-soon (3-day, same threshold as #164-s visual badge) or expired state. No repeat for the same product/status on later runs (deliberately stricter idempotency than the todo pattern, which repeats once per day) - enforced by a new filtered unique index (RecipientId, PantryId, PantryProductId, Kind), no day component. **Design decisions:** - NotificationEntity/NotificationDto now support Pantry as a second, mutually-exclusive notification target alongside TodoList (nullable column pairs + a new CK_NotificationEntity_ExactlyOneTarget check constraint), rather than a separate table - keeps the existing GetNotificationsForCurrentUserQuery/read path and WS push path shared across both. - Archived pantries are skipped by the sweep (no notification spam for pantries nobody is looking at any more) - not explicitly required by the AC but a low-risk, reversible default; flagging it here in case that is not what was wanted. - The expiring-soon/expired threshold logic (3 days) was extracted into a shared PantryExpiryStatus helper so the notification sweep and the existing visual badge (#164) can never drift apart. - Notification body text stays hardcoded English server-side, same as the existing TodoDueToday/TodoOverdue precedent (no i18n-driven notification-kind label mapping exists anywhere in the frontend yet - introducing one was out of scope for this story). **Tests:** 8 new backend handler tests (CreatePantryDueNotificationsCommandHandlerTests: expiring-soon, expired, no-date, far-from-expiry, archived-pantry, multi-member fan-out, idempotency-across-runs, WS-publish) plus 4 new frontend tests (NotificationItem: routes to /pantry/:id vs /list/:id, shows pantry title, marks read). Full backend suite (1093 tests) and frontend suite (1299 tests) green after the change. **Migration:** 20260910200509_AddPantryNotificationTarget (TodoListId now nullable, PantryId/PantryProductId added, check constraint, new filtered unique index).
lena closed this issue 2026-09-10 22:40:16 +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#186
No description provided.