fadd7692b2
- Restore full docs/FAS3-COOKING-SESSIONS-AUDIT.md fromcb49301(hermetic test rules, REUSE/GAP matrix, anti-duplication rationale, step plans 3a-3d). Append section 7 with 3d merge/undo semantics. - Update 7.6 wrapper reference toc1ab2c8behaviour. - Fix migration 0017: cast completed_at/started_at/created_at to UTC before extracting DATE. Idempotent (WHERE meal_date IS NULL); already backfilled rows unaffected. Runner does not checksum files. - Clarify meal_date purpose: future UTC statistics/queries; undo uses stored mealBoxMutations. Refs:cb49301,c1ab2c8
302 lines
19 KiB
Markdown
302 lines
19 KiB
Markdown
# Fas 3 – Cooking Sessions: REUSE/GAP-matris och stegplan
|
||
|
||
> Levereras före kod. Styrande dokument: utbyggnadsprompten §6 + `docs/19-beslutslogg.md` + anti-dubblettkartan.
|
||
|
||
---
|
||
|
||
## Hermetiska testregler
|
||
|
||
Alla integrationstester ska vara hermetiska:
|
||
- De får inte bero på innehållet i `.env` (värden som `AAMOS_MODE`, `EMAIL_MODE`, `S3_MODE` skrivs över i `setup-env.ts`).
|
||
- De får inte bero på förkonfigurerad konfiguration eller externa tjänster.
|
||
- De får inte bero på databasinnehåll som skapats utanför testet (exempelvis seedade recept från en tidigare körning). Testkörningen ansvarar själv för att testdatabasen är migrerad **och** seedad innan testerna startar; seeden är idempotent.
|
||
- Körning: `pnpm --filter @app/database run db:test-setup` (migrate + seed) ska alltid föregå API-testerna.
|
||
|
||
## 1. REUSE/GAP-matris mot befintligt matlagningsflöde
|
||
|
||
| §6-krav / område | Befintlig implementation | Status | Rekommenderad åtgärd |
|
||
|------------------|-------------------------|--------|----------------------|
|
||
| **Lifecycle PLANNED → STARTED → COMPLETED/CANCELLED** | Saknas. `recipe_cooks` är en enda rad skapad vid `/cook` ("COMPLETED" implicit). Ingen separat sessionstabell. | **Saknas** | Inför `cooking_sessions` (se nedan). Behåll `recipe_cooks` som aggregerad historik för rekommendationer/variation. |
|
||
| **FEFO-avdrag från inventory (§6)** | `/v1/recipes/:id/cook` kör `allocateFefo()` och skriver `inventory_transactions.type = 'cook_use'` med `refType='recipe_cook'`. | **Finns** | Återanvänd oförändrad. Lägg till `cookingSessionId` i transaktionen för att kunna ångra en hel session. |
|
||
| **portioner / portionsCooked** | `cookRecipeInputSchema.portionsCooked` (1–24). Skalning i receptdetalj. | **Finns** | Återanvänd. |
|
||
| **Individuell förbrukning per ätare** | `eaters[]` med `portionFraction` loggas i `meals`. | **Finns** | Återanvänd. |
|
||
| **Minimala efterfrågor efteråt (§6.2)** | Efterflödet i `cooking/[id].tsx` frågar idag bara antal matlådor. Inga profiler för "hur mycket blev det kvar?" | **Delvis** | Inför 2–3 snabba frågor som extraheras till `cooking_sessions.actualPortionsEaten`, `leftoverEstimate` och `cookingSessionId` på meals. |
|
||
| **Partiell förbrukning (§6.3)** | Saknas. `/cook` drar allt på en gång. Användaren kan efteråt manuellt justera via `/v1/inventory/items/:id/transactions`. | **Saknas** | Låt `/cook` skapa `cook_use`-transaktioner med `undoUntil = cookingSessionId`; vid efterfrågan skriv reverseringstransaktioner för det som inte åts. |
|
||
| **undo_until (ångra hel session)** | Transaktionsmodellen stöder bara enskilda nya transaktioner. Ingen koppling tillbaka till en session. | **Saknas** | Lägg `cookingSessionId` på `inventory_transactions`, `meals`, `meal_boxes` och tillåt `POST /v1/cooking-sessions/:id/undo`. |
|
||
| **Rester till BEFINTLIGA meal_boxes (§6.4)** | `meal_boxes` finns. `/cook` kan skapa EN ny matlåda per tillfälle. | **Delvis** | Utöka så att rester kan läggas i en **befintlig** `meal_box` (matcha på recipeId + hushåll + `status=available`) istället för att alltid skapa ny. |
|
||
| **Anti-dubblettkartan: ingen prepared_food_batches-tabell** | Finns ingen sådan tabell. | **OK** | Behåll. Använd `meal_boxes` som enda restbehållare. Motivering: samma koncept (portioner kvar, ät-senast-datum, näringssnapshot), sparar dubblettlogik. |
|
||
| **Näringssnapshot på rester** | `meal_boxes.nutritionPerPortion` sparas vid skapande. | **Finns** | Återanvänd. |
|
||
| **recommendations läser recipe_cooks** | `recommendations.ts` gör `SELECT recipeId, max(cookedAt) FROM recipe_cooks GROUP BY recipeId`. | **Finns** | **Risk:** om vi ändrar semantiken i `recipe_cooks` måste denna fråga vara bakåtkompatibel. Rekommendation ska fortsätta se sista tillfället ett recept lagades. |
|
||
| **Testprotokoll steg 5** | "Laga nu → cooking mode → klart → logga → ingredienser dras från lagret; måltiden syns i Min dag". | **Finns** | Måste förbli sant. Nytt flöde får inte kräva fler steg än idag; nya frågor ska vara valbara/skipbara. |
|
||
| **Testprotokoll steg 8** | "Matlådor: laga recept med portioner till lådor → lådan syns med ät-senast-datum". | **Finns** | Måste förbli sant. Nya rest-flödet ska INTE bryta skapandet av nya lådor. |
|
||
| **Alla strängar ×12** | i18next + 12 locales. | **Finns** | Lägg till nya nycklar i alla 12 språk. |
|
||
| **Hermetiska tester** | `apps/api/test/*` använder `TEST_DATABASE_URL` och setup-env. | **Finns** | Fortsätt samma mönster. |
|
||
| **Mjölkprincipen i texter** | Alla UI-strängar undviker "kassera automatiskt" och skiljer bäst-före/sista-förbrukningsdag. | **Finns** | Fortsätt. Inga texter får garantera matsäkerhet. |
|
||
|
||
### Slutsats av matrisen
|
||
Cooking Sessions är en **utbyggnad**, inte ett parallellsystem:
|
||
- **Kärnan** (`recipe_cooks`, FEFO-avdrag, meals, meal_boxes) finns redan.
|
||
- **Det som saknas** är en session-boog (`cooking_sessions`) som knyter samman påbörjat/klart/ångrat, plus möjligheten att (a) skjuta upp delar av förbrukningen till efterfrågan och (b) lägga rester i befintliga matlådor.
|
||
|
||
---
|
||
|
||
## 2. Anti-dubblettkartan – varför INGEN `prepared_food_batches`-tabell
|
||
|
||
Befintlig `meal_boxes` kan redan representera:
|
||
- Portioner som återstår
|
||
- Receptursprung (`recipeId`)
|
||
- Näringsvärden per portion (`nutritionPerPortion`)
|
||
- Förvaringsplats och frystillstånd
|
||
- Rekommenderat ät-senast-datum
|
||
- Status (`available`/`consumed`/`discarded`)
|
||
- Reservering för användare
|
||
|
||
Att lägga till `prepared_food_batches` skulle skapa:
|
||
- Dubbla konsumtions-endpoints
|
||
- Dubbla "ät senast"-logiker
|
||
- Dubbla måltidsloggningsvägar
|
||
- Extra migrering och index
|
||
- Förvirring i UI: "är detta en matlåda eller en batch?"
|
||
|
||
**Beslut:** Utöka `meal_boxes` med ett fält `leftoverSource` (`cook_session`) och en kolumn `cookingSessionId`. Då är resterna fortfarande matlådor ur användarens perspektiv, men spårbart till en cooking session.
|
||
|
||
---
|
||
|
||
## 3. STEGPLAN
|
||
|
||
### Steg 3a – Lifecycle + planned usage ovanpå befintlig `/cook`
|
||
**Mål:** En cooking session kan startas, slutföras eller avbrytas utan att bryta dagens `/cook`-beteende.
|
||
|
||
**Ändringar:**
|
||
1. Ny tabell `cooking_sessions`:
|
||
- `id`, `recipeId`, `householdId`, `startedByUserId`
|
||
- `status`: `planned`, `started`, `completed`, `cancelled`
|
||
- `plannedPortions`, `plannedMealType`
|
||
- `startedAt`, `completedAt`, `cancelledAt`
|
||
- `cancelReason` (frivillig text, max 200)
|
||
2. Lägg `cookingSessionId` nullable på:
|
||
- `inventory_transactions`
|
||
- `meals`
|
||
- `meal_boxes`
|
||
- `recipe_cooks` (för spårbarhet)
|
||
3. Nytt API:
|
||
- `POST /v1/recipes/:id/cook/start` → skapar `cooking_session` med status `started`.
|
||
- `POST /v1/cooking-sessions/:id/complete` → kör dagens `/cook`-logik (FEFO, meals, mealBoxes) men länkar allt till `cookingSessionId`.
|
||
- `POST /v1/cooking-sessions/:id/cancel` → sätter status `cancelled`, ingen lagerpåverkan.
|
||
4. **Bakåtkompatibilitet:** Behåll `POST /v1/recipes/:id/cook` som ett kortkommando som skapar session + complete i samma anrop (så testprotokoll steg 5 fortsätter fungera).
|
||
|
||
**Berörda filer:**
|
||
- `packages/database/src/schema/recipes.ts` (cooking_sessions, recipe_cooks-kolumn)
|
||
- `packages/database/src/schema/meals.ts` (meal_boxes-kolumn)
|
||
- `packages/database/src/schema/inventory.ts` (inventory_transactions-kolumn)
|
||
- `packages/validation/src/recipes.ts` (nya schemas)
|
||
- `apps/api/src/routes/recipes.ts` (nytt `/cook/start`, refactor `/cook`)
|
||
- `apps/api/src/routes/cooking-sessions.ts` (ny)
|
||
- `apps/api/src/server.ts`
|
||
- `apps/mobile/src/app/cooking/[id].tsx` (starta session)
|
||
- `apps/mobile/src/app/recipe/[id].tsx` ("Laga nu" kan gå via start)
|
||
- `apps/mobile/src/locales/*/common.json` (+12)
|
||
|
||
**Migration:** 0011_cooking_sessions.sql
|
||
|
||
**Testplan:**
|
||
- POST /cook/start → status started
|
||
- POST /cooking-sessions/:id/complete → samma resultat som idag: inventory dras, meals skapas, mealBox skapas
|
||
- POST /recipes/:id/cook (gammalt) fortfarande grönt
|
||
- recipe_cooks.fas3_verifierat: rekommendationer ser fortfarande `max(cookedAt)` korrekt
|
||
|
||
---
|
||
|
||
### Steg 3b – Minimala efterfrågor §6.2 + antagandeprofiler
|
||
**Mål:** Efter "klart" ställs max 2–3 snabba frågor. Appen kan fylla i svar automatiskt från profiler (t.ex. "vi brukar äta alla portioner" / "vi brukar ha 2 lådor över").
|
||
|
||
**Ändringar:**
|
||
1. Lägg kolumner på `cooking_sessions`:
|
||
- `actualPortionsEaten` (nullable int)
|
||
- `leftoverEstimatePortions` (nullable int)
|
||
- `leftoverNote` (text)
|
||
- `postCookProfile` (jsonb med senaste svaren)
|
||
2. Lägg `postCookQuestions` i `user_preferences` eller `households`:
|
||
- Profil: alltid samma svar, t.ex. `"we_eat_all"` eller `"leftovers_n"`.
|
||
3. App-skärm efter cooking mode:
|
||
- "Hur många portioner åt ni?" (default = portionsCooked - mealBoxPortions)
|
||
- "Vill du spara rester som matlådor?" (default från profil)
|
||
- Frågorna ska kunna skippas med ett tryck (default antas).
|
||
|
||
**Berörda filer:**
|
||
- `packages/database/src/schema/recipes.ts` (kolumner)
|
||
- `packages/validation/src/recipes.ts`
|
||
- `apps/api/src/routes/cooking-sessions.ts`
|
||
- `apps/mobile/src/app/cooking/[id].tsx` (efterflöde)
|
||
- `apps/mobile/src/locales/*/common.json`
|
||
|
||
**Migration:** 0012_cooking_questions.sql
|
||
|
||
**Testplan:**
|
||
- Profil sparas och återanvänds nästa session
|
||
- Skippa frågor → defaults används
|
||
- Validering: `actualPortionsEaten + leftoverEstimatePortions ≤ portionsCooked`
|
||
|
||
---
|
||
|
||
### Steg 3c – Partiell förbrukning §6.3 + undo_until
|
||
**Mål:** Om användaren säger att de bara åt 3 av 4 portioner, ska FEFO-avdraget justeras så att motsvarande råvaror återstår. En completed session kan ångras inom 24 h via append-only reverseringstransaktioner.
|
||
|
||
**Ändringar:**
|
||
1. Vid complete: använd `actualPortionsEaten` för att räkna om råvaror.
|
||
- Exempel: 4 planerade portioner → 3 ätna = använd 75 % av varje ingrediens.
|
||
- Om `actualPortionsEaten` är null, använd `portionsCooked` (dagens beteende).
|
||
2. Lagra ursprungligt FEFO-avdrag i `cooking_sessions.plannedDeductions` (jsonb).
|
||
3. `POST /v1/cooking-sessions/:id/undo`:
|
||
- Status måste vara `completed`.
|
||
- `undo_until = completedAt + 24 h` (server-side, UTC). Efter fönstret: 409 med i18n-nyckeln `cooked.undoWindowExpired`.
|
||
- För varje `inventory_transactions.type = 'cook_use'` med `cookingSessionId`: skapa en `correction` med motsatt delta (append-only; befintliga rader rörs inte).
|
||
- Lagersaldo räknas om från transaktionshistoriken; `depletedAt` nollas där saldot blir > 0 igen.
|
||
- För varje `meal` med `cookingSessionId`: radera och emitta `MEAL_REMOVED`.
|
||
- För varje `meal_box` med `cookingSessionId`: sätt status `discarded`, `portionsRemaining = 0`, emitta `MEAL_BOX_DISCARDED`.
|
||
- Ta bort `recipe_cooks`-raden och backa `recipes.cookCount` med `GREATEST(cookCount - 1, 0)`.
|
||
- Sätt `cooking_sessions.status = 'undone'` (TEXT, valideras mot `COOKING_SESSION_STATUSES` i shared-types; ingen pgEnum, inga nya tabeller).
|
||
- Emitta `COOKING_SESSION_UNDONE` och analytics-event `cooking_session_undone` (consent-gatat via `trackProductAnalytics`, ingen fritext).
|
||
- Aktiveringsmilstolpar backas INTE (once-ever).
|
||
4. Antagandeprofiler rullas tillbaka deterministiskt via `lastSessionAnswers.sessionId`:
|
||
- `rollbackCookingAssumptionProfile(sessionId, existingProfile)` är en ren funktion i `packages/inventory-engine/src/cooking-profiles.ts`.
|
||
- Den tar bort det aktuella sessions-id:t från `lastSessionAnswers`, räknar om EMA från scratch i kronologisk ordning och sätter `observationCount = history.length`.
|
||
- Om historiken blir tom raderas profilen (inte nollas); det förhindrar dubbelräkning vid undo + ny complete.
|
||
5. Lägg `cookingSessionId` i inventory item detail så användaren kan ångra enskilda transaktioner därifrån också.
|
||
|
||
**Berörda filer:**
|
||
- `apps/api/src/lib/cooking.ts` (`completeCookingSession`, `undoCookingSession`)
|
||
- `apps/api/src/routes/cooking-sessions.ts`
|
||
- `apps/api/src/lib/i18n.ts`
|
||
- `packages/inventory-engine/src/cooking-profiles.ts`
|
||
- `packages/shared-types/src/enums.ts`, `packages/shared-types/src/analytics.ts`
|
||
- `packages/events/src/index.ts`
|
||
- `packages/analytics/src/builders.ts`
|
||
- `apps/mobile/src/locales/*/common.json` (+12)
|
||
|
||
**Migration:** 0014_expand_event_types.sql (nya domänhändelser i `event_type`-enum)
|
||
|
||
**Testplan:**
|
||
- Laga 4 portioner, ät 3 → lager innehåller 25 % kvar av varje ingrediens
|
||
- Undo → allt återställs
|
||
- Invariant: `computeBalance(transaktioner) === item.quantity` efter (a) complete, (b) undo, (c) undo + ny complete
|
||
- Append-only-bevis: antalet transaktioner ökar vid undo
|
||
- Undo efter 24 h → 409 med lokaliserat fel
|
||
- Legacy `/cook` → undo fungerar via samma kodväg
|
||
|
||
---
|
||
|
||
### Steg 3d – Rester §6.4 via befintliga meal_boxes
|
||
**Mål:** Efterfrågade rester hamnar i antingen (1) befintlig available matlåda med samma `recipeId`, eller (2) ny matlåda.
|
||
|
||
**Ändringar:**
|
||
1. Vid complete, om `leftoverEstimatePortions > 0`:
|
||
- Sök `meal_boxes` där `recipeId = recipe.id`, `householdId`, `status = 'available'`, `frozen = false` (eller matcha valt frystillstånd), sorterat på `recommendedUseBy`.
|
||
- Om träff: öka `portions` och `portionsRemaining` med `leftoverEstimatePortions`, uppdatera `recommendedUseBy` till det tidigare av de två datumen (mjölkprincipen: behåll kortaste hållbarhet).
|
||
- Om ingen träff: skapa ny matlåda (dagens logik).
|
||
2. Lägg `leftoverSource = 'cook_session'` och `cookingSessionId` på meal_box.
|
||
3. Frysval: om användaren väljer frys, skapa/utöka fryst matlåda med `frozen=true`, useBy=90 dagar.
|
||
|
||
**Berörda filer:**
|
||
- `packages/database/src/schema/meals.ts`
|
||
- `apps/api/src/routes/recipes.ts` eller `cooking-sessions.ts`
|
||
- `apps/mobile/src/app/cooking/[id].tsx`
|
||
- `apps/mobile/src/app/meal-boxes.tsx` (visa source-ikon om det är rester)
|
||
|
||
**Migration:** 0013_meal_box_leftover_source.sql
|
||
|
||
**Testplan:**
|
||
- Två cooking sessions med samma recept → en matlåda har ökade portioner
|
||
- Frysta rester → separat fryst låda
|
||
- Ingen dubblett i rekommendationer: matlådor-först-sektionen räknar fortfarande `portionsRemaining > 0`
|
||
|
||
---
|
||
|
||
## 4. RISKER
|
||
|
||
| Risk | Påverkan | Minskning |
|
||
|------|----------|-----------|
|
||
| `recommendations.ts` använder `recipe_cooks` för "senast lagat". Om `cookedAt` ändras semantik bryts variation. | Medel | Behåll `recipe_cooks.cookedAt` som tidpunkt för färdig session. Lägg separata tidsstämplar på `cooking_sessions`. |
|
||
| Testprotokoll steg 5 kräver att "Laga nu → klart → logga" fortsätter fungera utan extra kranar. | Hög | Behåll `/recipes/:id/cook` som kortkommando. Nya frågor ska vara skipbara med tydliga defaults. |
|
||
| Testprotokoll steg 8 kräver att nya matlådor skapas. | Hög | Nya rest-flödet ska inte blockera skapandet; befintlig-låda-utökning är opt-in. |
|
||
| Partiell förbrukning kan leda till brutna invarianter om omräkning blir fel. | Hög | Invariantstest efter varje complete/undo; använd samma enhetskonvertering som FEFO. |
|
||
| Undo kan radera måltider som andra flöden refererar till. | Medel | Använd soft-delete (`deletedAt` på meals) eller markera som `cancelled` istället för hård radering. |
|
||
| UI-blödning: nya frågor kan kännas som ett formulär. | Medel | Designa som kort med +/- och "kom ihåg mitt val". |
|
||
| Matsäkerhetstexter kan osynligt bli för skarpa. | Medel | Varje ny i18n-nyckel granskas: aldrig "säkert att äta", alltid "lukta och smaka" / "rekommenderas före". |
|
||
| Hermetiska tester kan läcka `.env`. | Låg | Fortsätt `setup-env.ts` + `vitest.config.ts`. |
|
||
|
||
---
|
||
|
||
## 5. Översiktliga berörda tabeller
|
||
|
||
| Tabell | Förändring |
|
||
|--------|------------|
|
||
| `cooking_sessions` | **Ny** |
|
||
| `recipe_cooks` | `cookingSessionId` nullable |
|
||
| `inventory_transactions` | `cookingSessionId` nullable |
|
||
| `meals` | `cookingSessionId` nullable, ev. `deletedAt` |
|
||
| `meal_boxes` | `cookingSessionId` nullable, `leftoverSource` text |
|
||
| `users` / `households` | post-cook-profil jsonb (steg 3b) |
|
||
|
||
---
|
||
|
||
## 6. Leveransordning
|
||
|
||
1. Steg 3a: lifecycle + bakåtkompatibel `/cook`
|
||
2. Steg 3b: efterfrågor + profiler
|
||
3. Steg 3c: partiell förbrukning + undo
|
||
4. Steg 3d: rester till befintliga meal_boxes
|
||
|
||
Per steg: `brand-guard`, `typecheck`, `test`, `build`, `first-deploy staging`, patch + bundle.
|
||
|
||
---
|
||
|
||
*Rapport färdig. Inväntar godkännande innan kod påbörjas.*
|
||
|
||
## 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`. I `c1ab2c8` stängdes hålet där legacy `/cook` tidigare lämnade fälten null. 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`.
|
||
|
||
Regressionstest från `c1ab2c8`: `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).
|