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

3.3 KiB
Raw Blame History

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).