- 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
3.3 KiB
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 boxendeltaPortions: hur många portioner som lades tillfrozen: boxens frystillstånd (redundans för felsäkerhet)
Undo av en session:
- Itererar
mealBoxMutations. - För varje mutation minskas både
portionsochportionsRemainingmeddeltaPortions. - Om
portionsblir 0 sättsstatus = 'discarded'ochportionsRemaining = 0. - Legacy-fallback: om
mealBoxMutationssaknas, försöker undo hitta boxen viacookingSessionIdoch behandla den som en enda mutation med hela boxens portionsantal. - Inventory-ledger är append-only: reverseringar skrivs som
correction-transaktioner med motsatt delta; befintliga transaktioner rörs inte. - 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 encookingSessionIdoch är skapad inom de senaste 24 timmarna visar en "Ångra"-knapp. Knappen anroparPOST /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:
- Beräknar
actualPortionsEatenom det saknas:Math.max(0, plannedPortions - mealBoxPortions). - Beräknar
leftoverEstimatePortionsom det saknas: samma sommealBoxPortions. - 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).