Files
Cibello-app/docs/FAS3-COOKING-SESSIONS-AUDIT.md
T
Sven (AAMOS AI) 74f95daab2 feat(3d): meal-box merge, stored mutations, undo, mobile undo UI + i18n ×12
- Merge leftovers into existing meal_box when same recipeId + cookedAt date + frozen + available.
- Store mealBoxMutations JSONB on cooking_sessions for deterministic undo.
- Undo decrements portions/remaining, discards box at zero, appends correction ledger rows.
- Mobile: undo button in cooking/[id].tsx after-flow and meal-boxes.tsx within 24h window.
- i18n undo strings across all 12 locales; parity test green.
- 6 new integration tests: merge, no cross-date merge, frozen split, undo restore, undo discard, ledger invariant.
- Update FAS3 audit doc with 3d semantics.

Closes Fas 3d
2026-08-07 17:04:42 +07:00

51 lines
3.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 7. Steg 3d Restflöde till matlådor: merge, undo och mjölkprincipen
### 7.1 Semantik: alla rester blir matlådor
När en cooking session avslutas skapas eller uppdateras alltid en `meal_box` för `leftoverEstimatePortions` (default = `mealBoxPortions` om användaren inte anger något annat). `mealBoxPortions` är endast en validering: `leftoverEstimatePortions >= mealBoxPortions`. Det är `leftoverEstimatePortions` som driver boxens totala portionsantal.
### 7.2 Merge-regel
En befintlig `meal_box` får rester tillagda endast om:
- samma `recipeId`
- samma `cookedAt`-datum (UTC-datum, inte timestamp)
- samma `frozen`-status
- `status = 'available'`
Om ingen match finns skapas en ny `meal_box`.
`recommendedUseBy` sätts från sessionens lagningsdatum + 3 dagar (kyl) eller + 90 dagar (frys). Vid merge sätts `recommendedUseBy` till det tidigare av de två datumen.
### 7.3 Spårbarhet och deterministisk undo
Vid complete sparas `mealBoxMutations` JSONB på `cooking_sessions`. Varje mutation innehåller:
- `mealBoxId`: UUID för den berörda boxen
- `deltaPortions`: hur många portioner som lades till
- `frozen`: boxens frystillstånd (redundans för felsäkerhet)
Undo av en session:
1. Itererar `mealBoxMutations`.
2. För varje mutation minskas både `portions` och `portionsRemaining` med `deltaPortions`.
3. Om `portions` blir 0 sätts `status = 'discarded'` och `portionsRemaining = 0`.
4. Legacy-fallback: om `mealBoxMutations` saknas, försöker undo hitta boxen via `cookingSessionId` och behandla den som en enda mutation med hela boxens portionsantal.
5. Inventory-ledger är append-only: reverseringar skrivs som `correction`-transaktioner med motsatt delta; befintliga transaktioner rörs inte.
6. Sessionens status sätts till `undone`.
### 7.4 Mjölkprincipen
`recommendedUseBy` är en kvalitetssignal. UI använder texter som "Ät senast" och "lukta och smaka". Det finns ingen automatisk kassering när datum passeras; användaren avgör alltid med sinnena.
### 7.5 UI: undo-knapp
- **Efter matlagning** (`cooking/[id].tsx`): efter att användaren tryckt "Klart" visas en skärm med en "Ångra"-knapp inom 24 h.
- **Matlådelistan** (`meal-boxes.tsx`): varje box som har en `cookingSessionId` och är skapad inom de senaste 24 timmarna visar en "Ångra"-knapp. Knappen anropar `POST /v1/cooking-sessions/:cookingSessionId/undo`.
- Felfall visar serverns lokaliserade felmeddelande (aldrig hårdkodade strängar).
- i18n-nycklar: `common.undo`, `mealbox.undoConfirmTitle`, `mealbox.undoConfirmBody`, `mealbox.undoSuccess`.
### 7.6 Wrapper-beteende (OBLIGATORISK PUNKT 1)
`completeCookingSession()` i `apps/api/src/lib/cooking.ts` är den enda vägen för både legacy `/v1/recipes/:id/cook` och explicit `/v1/cooking-sessions/:id/complete`. Wrappern:
1. Beräknar `actualPortionsEaten` om det saknas: `Math.max(0, plannedPortions - mealBoxPortions)`.
2. Beräknar `leftoverEstimatePortions` om det saknas: samma som `mealBoxPortions`.
3. Skickar båda värdena explicit in i `completeCookingSessionCore`.
Detta stänger hålet där `/v1/recipes/:id/cook` tidigare lämnade fälten null och core fick gissa.
Regressionstest: `defaults leftover estimate to meal box portions: 3 eaten + 1 box = 100% deduction` (planned=4, eaten utelämnat → räknas till 3, box=1, leftoverEstimate=1 → consumption=min(4,3+1)=4 → 100% avdrag).