feat(docs): update NEXT_STEPS, README, and TEKNISK_BESKRIVNING with new features and improvements
This commit is contained in:
+12
-20
@@ -14,8 +14,11 @@
|
|||||||
| Snabbimport (URL/PDF/bild/ICA) | ✅ Klart |
|
| Snabbimport (URL/PDF/bild/ICA) | ✅ Klart |
|
||||||
| Kvittoimport (Mistral AI, OCR, alias) | ✅ Klart |
|
| Kvittoimport (Mistral AI, OCR, alias) | ✅ Klart |
|
||||||
| Matplanering (veckovy, inköpslista) | ✅ Klart |
|
| Matplanering (veckovy, inköpslista) | ✅ Klart |
|
||||||
|
| Matplan — portionsjustering per dag | ✅ Klart |
|
||||||
|
| Matplan — inventariejämförelse | ✅ Klart |
|
||||||
| Baslager (lista, lägg till, ta bort) | ✅ Klart |
|
| Baslager (lista, lägg till, ta bort) | ✅ Klart |
|
||||||
| Admin: Produkter (edit, merge, duplicate, restore, reset) | ✅ Klart |
|
| Admin: Produkter (edit, merge, duplicate, restore, reset) | ✅ Klart |
|
||||||
|
| Admin: Bulk-kategorisering | ✅ Klart |
|
||||||
| Receptredigering (frontend UX) | ✅ Klart |
|
| Receptredigering (frontend UX) | ✅ Klart |
|
||||||
| Receptbilder (upload URL) | ✅ Klart |
|
| Receptbilder (upload URL) | ✅ Klart |
|
||||||
| Autentisering (JWT, Auth.js v5, User-modell) | ✅ Klart |
|
| Autentisering (JWT, Auth.js v5, User-modell) | ✅ Klart |
|
||||||
@@ -24,8 +27,7 @@
|
|||||||
| Taggning av produkter | ✅ Klart |
|
| Taggning av produkter | ✅ Klart |
|
||||||
| Näringsvärden på produkter | ✅ Klart (schema + API) |
|
| Näringsvärden på produkter | ✅ Klart (schema + API) |
|
||||||
| Kategoritilldelning i admin-UI | ✅ Klart |
|
| Kategoritilldelning i admin-UI | ✅ Klart |
|
||||||
| Portionsjustering | ❌ Saknas |
|
| Kategori-seed (supplement, idempotent) | ✅ Klart |
|
||||||
| Matplan — inventariejämförelse | ❌ Saknas |
|
|
||||||
| Seed produktdata med kategoritilldelning | ❌ Saknas (002-seed-products.sql.disabled) |
|
| Seed produktdata med kategoritilldelning | ❌ Saknas (002-seed-products.sql.disabled) |
|
||||||
| Användarspecifika produkter (UserProduct) | ⚠️ Schema klart, UI basic |
|
| Användarspecifika produkter (UserProduct) | ⚠️ Schema klart, UI basic |
|
||||||
| Användarroller (user / admin) | ❌ Saknas |
|
| Användarroller (user / admin) | ❌ Saknas |
|
||||||
@@ -37,27 +39,17 @@
|
|||||||
|
|
||||||
### 1. Seed produktdata med kategoritilldelning
|
### 1. Seed produktdata med kategoritilldelning
|
||||||
`db/init/002-seed-products.sql` är inaktiverad (`.disabled`) tills den uppdateras med rätt `categoryId` för varje produkt. Utan detta är produktdatabasen tom vid fresh install.
|
`db/init/002-seed-products.sql` är inaktiverad (`.disabled`) tills den uppdateras med rätt `categoryId` för varje produkt. Utan detta är produktdatabasen tom vid fresh install.
|
||||||
- Gå igenom de ~190 produkterna och tilldela rätt kategori-ID från tabellen `Category`
|
- Gå igenom de ~190 produkterna och tilldela rätt kategori-ID från tabellen `Category` (nu inklusive supplement-kategorier)
|
||||||
- Aktivera filen igen genom att ta bort `.disabled`-suffixet
|
- Aktivera filen igen genom att ta bort `.disabled`-suffixet
|
||||||
- Alternativt: bygg ett admin-verktyg för bulk-kategorisering
|
- Kontrollera att varje produkts `categoryId` matchar mot det ID som genereras i databasen (auto-increment — kör ett SELECT för att verifiera)
|
||||||
|
|
||||||
### 2. Portionsjustering av recept
|
### 2. Matplan — djupare inventariejämförelse i frontend
|
||||||
Recept lagras utan portionsangivelse. Lägg till ett `servings`-fält och låt användaren justera antal portioner i receptvyn — ingrediensmängderna räknas om proportionellt (t.ex. 4 → 6 pers: × 1,5).
|
Backend-endpointen `GET /api/meal-plan/inventory-compare?from=...&to=...` returnerar ingrediensstatus per dag. Funktionen saknar dock en frontend-vy som tydligt visar "vad behöver jag handla — och vad har jag redan hemma?" aggregerat för hela veckan.
|
||||||
- **Databas:** `servings Int?` på `Recipe` i Prisma + migration
|
- Visa inköpslistan med tydliga statusindikatorer: ✅ Finns hemma / ⚠️ Delvis / ❌ Saknas
|
||||||
- **Backend:** `servings` exponeras i `RecipeDto`, sätts vid create/update
|
- Möjlig placering: ny flik i matplanen eller sidopanel i veckovy
|
||||||
- **Frontend (`app/recipes/[id]/`):** räknare (+ / −) bredvid ingredienslistan, beräkning i klientkomponent utan extra API-anrop
|
- Kräver: aggregering av `inventory-compare`-svaret per ingrediens över hela veckan
|
||||||
- **Receptskapande (`write/`):** lägg till grundportioner-fält
|
|
||||||
- **Matplan (`app/matplan/`):** inköpslistan justeras efter önskat portionsantal per dag
|
|
||||||
|
|
||||||
### 3. Matplanering — jämförelse mot inventariet
|
### 3. Användarroller och användarhantering i admin
|
||||||
Veckovy och inköpslista fungerar. Nästa steg är att visa vilka ingredienser på inköpslistan som redan finns hemma och i vilken mängd — liknande receptvyns inventory-preview. Implementeras via `GET /api/recipes/:id/inventory-preview` per recept, aggregerat på veckonivå.
|
|
||||||
|
|
||||||
### 4. Bulk-kategorisering av produkter i admin
|
|
||||||
Admin-UI:t tillåter idag att sätta kategori per produkt. För att effektivt kategorisera hundratals produkter behövs:
|
|
||||||
- Filtervy för okategoriserade produkter
|
|
||||||
- Möjlighet att sätta kategori på flera produkter samtidigt (bulk-select)
|
|
||||||
|
|
||||||
### 5. Användarroller och användarhantering i admin
|
|
||||||
Idag har alla inloggade användare samma behörighetsnivå. Behövs:
|
Idag har alla inloggade användare samma behörighetsnivå. Behövs:
|
||||||
- **Databas:** Lägg till `role` (enum `user` | `admin`) på `User`-modellen i Prisma + migration
|
- **Databas:** Lägg till `role` (enum `user` | `admin`) på `User`-modellen i Prisma + migration
|
||||||
- **Backend:** Rollbaserad guard (`@Roles('admin')`) — skyddar admin-endpoints; vanliga användare nekas med 403
|
- **Backend:** Rollbaserad guard (`@Roles('admin')`) — skyddar admin-endpoints; vanliga användare nekas med 403
|
||||||
|
|||||||
@@ -14,30 +14,46 @@ En fullstack-applikation för hantering av hemmavaror och recept. Håll koll på
|
|||||||
- **Filtrera och sortera** — efter plats (kyl, frys, skafferi), bäst före-datum, och namn (A–Ö)
|
- **Filtrera och sortera** — efter plats (kyl, frys, skafferi), bäst före-datum, och namn (A–Ö)
|
||||||
- **Konsumera varor** — registrera förbrukad mängd med eventuell kommentar
|
- **Konsumera varor** — registrera förbrukad mängd med eventuell kommentar
|
||||||
- **Konsumtionshistorik** — spåra vad som använts när och i vilken mängd
|
- **Konsumtionshistorik** — spåra vad som använts när och i vilken mängd
|
||||||
- **Utförlig information** — stöd för varumärke, lagringsnot, tillkomsttid, mera detaljer
|
- **Utförlig information** — stöd för varumärke, lagringsnot, tillkomsttid och mer
|
||||||
|
|
||||||
### Recept
|
### Recept
|
||||||
- **Skapa och redigera recept** — med ingredienser, kvantiteter, enheter och instruktioner (Markdown-stöd)
|
- **Skapa och redigera recept** — med namn, beskrivning, portionsantal, ingredienser (kvantitet och enhet) och instruktioner i Markdown-format
|
||||||
- **Receptjämförelse mot inventorie** — se direkt vilka ingredienser du har hemma, vad som saknas och enhetskonflikt
|
- **Portionsjustering** — ange antal portioner vid skapandet; matplanen räknar automatiskt om ingrediensmängder om du lagar fler eller färre portioner
|
||||||
|
- **Receptjämförelse mot inventorie** — se direkt vilka ingredienser du har hemma, vad som saknas och om enheter är inkompatibla
|
||||||
- **Importera recept från Markdown** — klistra in ett recept i enkelt format, låt systemet matcha ingredienser, granska och spara med ett klick
|
- **Importera recept från Markdown** — klistra in ett recept i enkelt format, låt systemet matcha ingredienser, granska och spara med ett klick
|
||||||
- **Importera recept från PDF, bild eller länk** — stöd för PDF-textutvinning, OCR av bilder och webbimport via snabbimport
|
- **Importera recept från PDF, bild eller länk** — stöd för PDF-textutvinning, OCR av bilder och webbimport via snabbimport
|
||||||
- **Intelligenta matchningar** — Levenshtein-baserad likhetsbedömning hittar rätt produkt även på osäker stavning
|
- **Intelligenta matchningar** — Levenshtein-baserad likhetsbedömning hittar rätt produkt även vid osäker stavning
|
||||||
- **Enhetskonvertering** — automatisk konvertering mellan viktenheter (g/kg), volymenheter (ml/dl) och portionsenheter (tsk/msk)
|
- **Enhetskonvertering** — automatisk konvertering mellan viktenheter (g/kg), volymenheter (ml/dl) och portionsenheter (tsk/msk)
|
||||||
|
|
||||||
|
### Matplanering
|
||||||
|
- **Veckovy** — planera veckans måltider dag för dag med ett enkelt receptval
|
||||||
|
- **Portionsjustering per dag** — välj hur många portioner du vill laga för varje dag; avviker du från receptets grundportioner visas en återställningsknapp
|
||||||
|
- **Inköpslista** — genereras automatiskt utifrån veckans planerade recept; ingrediensmängder skalas proportionellt om portioner justerats
|
||||||
|
- **Inventariejämförelse** — jämför inköpslistans ingredienser mot vad du faktiskt har hemma (aggregerat per vecka)
|
||||||
|
|
||||||
|
### Kvittoimport
|
||||||
|
- **Fotografera eller ladda upp kvitto** — JPEG, PNG, WebP, HEIC och PDF stöds (max 15 MB)
|
||||||
|
- **AI-tolkning via Mistral** — Mistral AI extraherar varunamn och mängder direkt från kvittobilden
|
||||||
|
- **Alias-matchning** — kvittots produktnamn matchas mot kända alias (t.ex. "ICA Kvarg Jordg" → "Kvarg") och mot befintliga produkter
|
||||||
|
- **Granska och lägg till** — se tolkningsresultatet, justera kvantitet och enhet, och lägg till direkt i inventariet
|
||||||
|
|
||||||
### Baslager
|
### Baslager
|
||||||
- **Ständigt lager** — markera produkter du alltid räknar med att ha hemma
|
- **Ständigt lager** — markera produkter du alltid räknar med att ha hemma
|
||||||
- **Grupperat per kategori** — produkterna i baslagret visas grupperade
|
- **Grupperat per kategori** — produkterna i baslagret visas grupperade under kategorirubrik
|
||||||
- **Lägg till och ta bort** — välj från produktlistan via dropdown, ta bort med ett klick
|
- **Lägg till och ta bort** — välj från produktlistan via sökbar dropdown, ta bort med ett klick
|
||||||
|
|
||||||
### Admin: Produkter
|
### Admin: Produkter
|
||||||
- **Redigera produkter** — uppdatera visningsnamn (name), canonical name, kategori (hierarkisk dropdown) och varumärke inline direkt i listan
|
- **Redigera produkter** — uppdatera visningsnamn, canonical name, kategori (hierarkisk dropdown) och varumärke inline direkt i listan
|
||||||
- **Kategoritilldelning** — välj kategori ur ett 3-nivåträd (huvudkategori → underkategori → typ) som laddas dynamiskt från API:et
|
- **Kategoritilldelning** — välj kategori ur ett 3-nivåträd (huvudkategori → underkategori → typ) som laddas dynamiskt från API:et
|
||||||
- **Ta bort produkter** — soft-delete enskilda produkter
|
- **Bulk-kategorisering** — filtrera fram okategoriserade produkter, markera flera (eller "välj alla synliga") och sätt kategori på alla markerade på en gång — effektivt för att kategorisera många produkter i ett svep
|
||||||
- **Hitta dubbletter** — identifiera produkter med samma normaliserade namn
|
- **Hitta dubbletter** — identifiera produkter med samma normaliserade namn
|
||||||
- **Slå ihop produkter** — merge två produktposter, flytta alla inventarieföremål till målprodukten (källan soft-deleteras)
|
- **Slå ihop produkter** — merge av två produktposter: alla inventarieföremål och receptreferenser flyttas till målprodukten, källan soft-deleteras
|
||||||
- **Förhandsvisning** — granska vad som kommer att hända innan merge genomförs
|
- **Förhandsvisning** — granska vad som händer (inventarieräkningar, utfall) innan merge genomförs
|
||||||
- **Återställ produkter** — restore tidigare raderade produkter
|
- **Ta bort och återställ** — soft-delete enskilda produkter, återställ med ett klick
|
||||||
- **Återställ all produktdata** — rensningsknapp som raderar alla produkter, inventorie, taggar och kvitto-alias (behåller användare och kategorier)
|
- **Återställ all produktdata** — rensningsknapp som raderar alla produkter, inventarie, taggar och kvitto-alias (behåller användare och kategorier)
|
||||||
|
|
||||||
|
### Användarprofil
|
||||||
|
- **Redigera profilinformation** — uppdatera förnamn, efternamn och e-postadress under "Min profil"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+159
-15
@@ -82,18 +82,19 @@ docker exec recipe-db mariadb -uroot -p"LÖSENORD" recipe_app -e "SHOW TABLES;"
|
|||||||
| **Lägg till recept** | `app/recipes/create/page.tsx` | Server component med Navigation |
|
| **Lägg till recept** | `app/recipes/create/page.tsx` | Server component med Navigation |
|
||||||
| | `app/recipes/create/CreateRecipeClient.tsx` | Klientkomponent: snabbimport + metodval |
|
| | `app/recipes/create/CreateRecipeClient.tsx` | Klientkomponent: snabbimport + metodval |
|
||||||
| **Skriv in recept** | `app/recipes/write/page.tsx` | Server component med Navigation |
|
| **Skriv in recept** | `app/recipes/write/page.tsx` | Server component med Navigation |
|
||||||
| | `app/recipes/write/WriteRecipePage.tsx` | Markdown-baserat receptskapande (3-steg) |
|
| | `app/recipes/write/WriteRecipePage.tsx` | Markdown-baserat receptskapande (3-steg): Markdown-inmatning → ingrediensgranskning (produktval + portionsantal) → spara |
|
||||||
| **Importera från fil** | `app/recipes/import/page.tsx` | Startpunkt för fil/länk-import |
|
| **Importera från fil** | `app/recipes/import/page.tsx` | Startpunkt för fil/länk-import |
|
||||||
| | `app/recipes/import/ImportFilePage.tsx` | Fil-/länk-import (PDF, URL, etc) |
|
| | `app/recipes/import/ImportFilePage.tsx` | Fil-/länk-import (PDF, URL, etc) |
|
||||||
| **Import (flikar)** | `app/import/page.tsx` | Server component med Navigation + flikvy |
|
| **Matplan** | `app/matplan/page.tsx` | Matplanering (server component) |
|
||||||
|
| | `app/matplan/MealPlanClient.tsx` | Veckovy, receptval per dag, portionsjustering, inköpslista, inventariejämförelse |
|
||||||
|
| **Kvittoimport** | `app/import/page.tsx` | Server component med Navigation + flikvy |
|
||||||
| | `app/import/ImportTabsClient.tsx` | Klientkomponent: kvitto/recept-flikar |
|
| | `app/import/ImportTabsClient.tsx` | Klientkomponent: kvitto/recept-flikar |
|
||||||
| **Recipe detail** | `app/recipes/[id]/` | Enskilt recept (detaljer, redigering) |
|
|
||||||
| **Admin: Produkter** | `app/admin/products/page.tsx` | Produktadmin-panel |
|
| **Admin: Produkter** | `app/admin/products/page.tsx` | Produktadmin-panel |
|
||||||
| | `AdminProductList.tsx` | Lista produkter, sök, sortera |
|
| | `AdminProductList.tsx` | Lista produkter, sök, sortera, filter okategoriserade, bulk-select + bulk-kategorisering |
|
||||||
| | `EditProductForm.tsx` | Inline redigering: name, canonicalName, kategori (hierarkisk dropdown), brand, taggar |
|
| | `EditProductForm.tsx` | Inline redigering: name, canonicalName, kategori (hierarkisk dropdown), brand, taggar |
|
||||||
| | `ResetProductsButton.tsx` | Knapp för att rensa all produktdata |
|
| | `ResetProductsButton.tsx` | Knapp för att rensa all produktdata |
|
||||||
| | `MergePreviewForm.tsx` | Förhandsgranska merge |
|
| | `MergePreviewForm.tsx` | Förhandsgranska merge |
|
||||||
| | `actions.ts` | Server actions: updateProduct, deleteProduct, resetAllProducts |
|
| | `actions.ts` | Server actions: updateProduct, deleteProduct, resetAllProducts, bulkSetCategory |
|
||||||
| **Baslager** | `app/baslager/page.tsx` | Visa och hantera baslager (server component) |
|
| **Baslager** | `app/baslager/page.tsx` | Visa och hantera baslager (server component) |
|
||||||
| | `AddToPantryForm.tsx` | Lägg till produkt i baslager (dropdown) |
|
| | `AddToPantryForm.tsx` | Lägg till produkt i baslager (dropdown) |
|
||||||
| | `PantryList.tsx` | Visa baslager grupperat per kategori |
|
| | `PantryList.tsx` | Visa baslager grupperat per kategori |
|
||||||
@@ -116,6 +117,9 @@ Alla proxy-routes läser auth-token via `auth()` (Auth.js v5) och vidarebefordra
|
|||||||
| `/api/recipe-preview-proxy` | GET | Receptförhandsvisning |
|
| `/api/recipe-preview-proxy` | GET | Receptförhandsvisning |
|
||||||
| `/api/admin/merge-preview-proxy` | GET | Produktmerge-preview |
|
| `/api/admin/merge-preview-proxy` | GET | Produktmerge-preview |
|
||||||
| `/api/receipt-import-proxy` | POST | Kvittoimport via Mistral AI |
|
| `/api/receipt-import-proxy` | POST | Kvittoimport via Mistral AI |
|
||||||
|
| `/api/meal-plan-proxy` | GET, POST, DELETE | Matplanering (veckovy, upsert, ta bort) |
|
||||||
|
| `/api/meal-plan-shopping-proxy` | GET | Inköpslista för datumintervall |
|
||||||
|
| `/api/meal-plan-compare-proxy` | GET | Inventariejämförelse för datumintervall |
|
||||||
| `/api/user-products` | GET, POST, DELETE | Användarspecifika produkter |
|
| `/api/user-products` | GET, POST, DELETE | Användarspecifika produkter |
|
||||||
|
|
||||||
### Autentisering (Auth.js v5)
|
### Autentisering (Auth.js v5)
|
||||||
@@ -203,6 +207,21 @@ backend/src/
|
|||||||
│ ├── base.parser.ts # Abstract RecipeParser class
|
│ ├── base.parser.ts # Abstract RecipeParser class
|
||||||
│ ├── ica.parser.ts # ICA.se-specifik parser (JSON-LD)
|
│ ├── ica.parser.ts # ICA.se-specifik parser (JSON-LD)
|
||||||
│ └── generic.parser.ts # Fallback-parser (HTML + JSON-LD)
|
│ └── generic.parser.ts # Fallback-parser (HTML + JSON-LD)
|
||||||
|
├── meal-plan/
|
||||||
|
│ ├── meal-plan.controller.ts # GET/POST/DELETE + shopping-list + inventory-compare
|
||||||
|
│ ├── meal-plan.service.ts # Upsert, shoppingList (portionsskalad), inventoryCompare
|
||||||
|
│ ├── meal-plan.module.ts
|
||||||
|
│ └── dto/
|
||||||
|
│ └── create-meal-plan-entry.dto.ts # { date, recipeId, servings? }
|
||||||
|
├── receipt-import/
|
||||||
|
│ ├── receipt-import.controller.ts # POST /api/receipt-import (multipart)
|
||||||
|
│ ├── receipt-import.service.ts # Mistral AI-anrop, bildtolkning
|
||||||
|
│ └── dto/
|
||||||
|
│ └── parsed-receipt-item.dto.ts
|
||||||
|
├── receipt-alias/
|
||||||
|
│ ├── receipt-alias.controller.ts # CRUD /api/receipt-alias
|
||||||
|
│ ├── receipt-alias.service.ts
|
||||||
|
│ └── dto/
|
||||||
└── recipes/
|
└── recipes/
|
||||||
├── recipes.controller.ts # Recept endpoints
|
├── recipes.controller.ts # Recept endpoints
|
||||||
├── recipes.service.ts # Recept + Markdown-parsing
|
├── recipes.service.ts # Recept + Markdown-parsing
|
||||||
@@ -282,6 +301,21 @@ backend/src/
|
|||||||
- **Soft delete & restore:**
|
- **Soft delete & restore:**
|
||||||
- `remove()` - Soft-delete produkt (isActive = false)
|
- `remove()` - Soft-delete produkt (isActive = false)
|
||||||
- `restore()` - Återställ borttagen produkt
|
- `restore()` - Återställ borttagen produkt
|
||||||
|
- **Bulk-uppdatering:** `bulkUpdate(ids, data)` — Uppdatera ett godtyckligt antal produkter i ett enda DB-anrop (`updateMany`). Används primärt för bulk-kategorisering i admin-UI. Body: `{ ids: number[], categoryId?: number | null }`
|
||||||
|
|
||||||
|
**Matplan-API:**
|
||||||
|
- **`upsert(dto)`** — Skapar eller uppdaterar en `MealPlanEntry` för ett givet datum (unik per dag). Sparar `recipeId` och valfritt `servings`.
|
||||||
|
- **`findByRange(from, to)`** — Hämtar alla planerade dagar i ett datumintervall, inkl. receptinfo.
|
||||||
|
- **`shoppingList(from, to)`** — Aggregerar ingrediensmängder för alla planerade recept i intervallet.
|
||||||
|
- Om `entry.servings` och `recipe.servings` är satta beräknas en skala: `scale = entry.servings / recipe.servings`
|
||||||
|
- Ingrediensmängder multipliceras med skalan innan aggregering
|
||||||
|
- Returnerar lista av `{ productName, quantity, unit }`
|
||||||
|
- **`inventoryCompare(from, to)`** — Kör samma aggregering som `shoppingList` men jämför sedan varje ingrediens mot aktuellt inventarielager. Returnerar status per ingrediens: `räcker | saknas | enhetskonflikt`.
|
||||||
|
|
||||||
|
**Kvittoimport-API:**
|
||||||
|
- **`parseReceipt(file)`** — Tar emot en bildel eller PDF (max 15 MB), skickar den till Mistral AI för tolkning och returnerar en lista av kandidatprodukter med namn, kvantitet och enhet.
|
||||||
|
- Alias-matchning: före returneringen slås varje rånamn upp mot `ReceiptAlias`-tabellen och mot `Product.normalizedName`. Träffar kopplas automatiskt till rätt produkt-ID.
|
||||||
|
- Stödda MIME-typer: `image/jpeg`, `image/png`, `image/webp`, `image/heic`, `image/heif`, `application/pdf`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -333,6 +367,8 @@ POST /api/products/merge Slå ihop två produkter
|
|||||||
PATCH /api/products/:id/canonical-name Uppdatera canonical name
|
PATCH /api/products/:id/canonical-name Uppdatera canonical name
|
||||||
POST /api/products/backfill-canonical Backfill canonical names (admin)
|
POST /api/products/backfill-canonical Backfill canonical names (admin)
|
||||||
POST /api/products/reset-all Rensa all produktdata (admin)
|
POST /api/products/reset-all Rensa all produktdata (admin)
|
||||||
|
POST /api/products/bulk-update Uppdatera flera produkter (t.ex. sätt kategori)
|
||||||
|
Body: { ids: number[], categoryId?: number | null }
|
||||||
```
|
```
|
||||||
|
|
||||||
### Kategori-endpoints
|
### Kategori-endpoints
|
||||||
@@ -355,6 +391,30 @@ POST /api/pantry Lägg till produkt i baslagret
|
|||||||
DELETE /api/pantry/:id Ta bort produkt från baslagret
|
DELETE /api/pantry/:id Ta bort produkt från baslagret
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 🗓️ Matplan-endpoints
|
||||||
|
```
|
||||||
|
GET /api/meal-plan?from=YYYY-MM-DD&to=YYYY-MM-DD Lista planerade recept för datumintervall
|
||||||
|
POST /api/meal-plan Skapa eller uppdatera post (upsert per datum)
|
||||||
|
Body: { date, recipeId, servings? }
|
||||||
|
DELETE /api/meal-plan/:date Ta bort recept för ett specifikt datum
|
||||||
|
|
||||||
|
GET /api/meal-plan/shopping-list?from=...&to=... Generera inköpslista för veckan
|
||||||
|
Skalad proportionellt efter portionsjustering
|
||||||
|
GET /api/meal-plan/inventory-compare?from=...&to=... Jämför inköpslista mot inventarie
|
||||||
|
Returnerar status per ingrediens: räcker | saknas | enhetskonflikt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🧾 Kvitto-endpoints
|
||||||
|
```
|
||||||
|
POST /api/receipt-import Tolka kvittobild (JPEG, PNG, WebP, HEIC, PDF)
|
||||||
|
Multipart-form med "file"; max 15 MB
|
||||||
|
Returnerar lista av { name, quantity, unit, productId?, confidence }
|
||||||
|
|
||||||
|
GET /api/receipt-alias Lista alla kvitto-alias
|
||||||
|
POST /api/receipt-alias Skapa nytt alias (receiptName → productId)
|
||||||
|
DELETE /api/receipt-alias/:id Ta bort alias
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Datamodell (Prisma ORM)
|
## Datamodell (Prisma ORM)
|
||||||
@@ -475,14 +535,19 @@ model Recipe {
|
|||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String # Receptnamn
|
name String # Receptnamn
|
||||||
description String? # Receptbeskrivning
|
description String? # Receptbeskrivning
|
||||||
|
servings Int? # Antal portioner receptet är dimensionerat för
|
||||||
|
imageUrl String? # URL till receptbild (valfritt)
|
||||||
instructions String? @db.Text # Tillagningsinstruktioner (kan vara långt, stöder Markdown)
|
instructions String? @db.Text # Tillagningsinstruktioner (kan vara långt, stöder Markdown)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
ingredients RecipeIngredient[]
|
ingredients RecipeIngredient[]
|
||||||
|
mealPlanEntries MealPlanEntry[]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`servings` är grundportionsantalet — matplanen använder det för att skala ingrediensmängder om användaren anger ett avvikande portionsantal per dag.
|
||||||
|
|
||||||
### RecipeIngredient
|
### RecipeIngredient
|
||||||
```prisma
|
```prisma
|
||||||
model RecipeIngredient {
|
model RecipeIngredient {
|
||||||
@@ -512,6 +577,38 @@ model PantryItem {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### MealPlanEntry
|
||||||
|
```prisma
|
||||||
|
model MealPlanEntry {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
date DateTime # Datum för planerad måltid (en per dag)
|
||||||
|
recipeId Int # Foreign key till Recipe
|
||||||
|
servings Int? # Justerat portionsantal för den dagen (null = använd receptets grundvärde)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
recipe Recipe @relation(fields: [recipeId], references: [id])
|
||||||
|
|
||||||
|
@@unique([date]) # Bara ett recept per dag
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Portionsskalning:** Om `servings` är satt och skiljer sig från `recipe.servings` beräknar `shoppingList()` och `inventoryCompare()` en skala: `scale = entry.servings / recipe.servings`. Alla ingrediensmängder multipliceras med denna faktor.
|
||||||
|
|
||||||
|
### ReceiptAlias
|
||||||
|
```prisma
|
||||||
|
model ReceiptAlias {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
receiptName String @unique # Namn som kvittosystemet returnerar (råtext)
|
||||||
|
productId Int # FK till matchad Product
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
product Product @relation(...)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Kvitto-alias lagrar mappningar från kvittots råtext till produkt-ID. När Mistral AI returnerar t.ex. "ICA Kvarg Jordg" slås det upp mot alias-tabellen. Om träff hoppas manuell matchning över.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Receptimport och receptskaping — Detaljerad arkitektur
|
## Receptimport och receptskaping — Detaljerad arkitektur
|
||||||
@@ -754,6 +851,52 @@ Top 5: Max 5 förslag per ingrediens
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Matplanering och portionsjustering — Detaljerad arkitektur
|
||||||
|
|
||||||
|
### Syfte
|
||||||
|
Matplaneringsfunktionen låter användaren planera veckans måltider dag för dag och generera en inköpslista automatiskt. Portionsjusteringen gör det möjligt att anpassa mängden per dag utan att ändra receptet — t.ex. laga en dubbel sats en dag.
|
||||||
|
|
||||||
|
### Dataflöde
|
||||||
|
|
||||||
|
```
|
||||||
|
Användaren väljer recept + portionsantal för ett datum
|
||||||
|
→ POST /api/meal-plan { date, recipeId, servings }
|
||||||
|
→ MealPlanEntry upserteras (unik per datum)
|
||||||
|
|
||||||
|
Veckovy hämtar alla poster i intervallet
|
||||||
|
→ GET /api/meal-plan?from=...&to=...
|
||||||
|
|
||||||
|
Inköpslista genereras
|
||||||
|
→ GET /api/meal-plan/shopping-list?from=...&to=...
|
||||||
|
→ Varje ingredient × scale (entry.servings / recipe.servings, eller 1 om ej satt)
|
||||||
|
→ Aggregerat per produkt + enhet
|
||||||
|
|
||||||
|
Inventariejämförelse
|
||||||
|
→ GET /api/meal-plan/inventory-compare?from=...&to=...
|
||||||
|
→ Samma aggregering, sedan jämförs mot aktuell inventarie
|
||||||
|
→ Status: räcker | saknas | enhetskonflikt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend: MealPlanClient
|
||||||
|
|
||||||
|
- Veckovy renderar en kolumn per dag med aktuellt recept
|
||||||
|
- Om receptet har `servings` satt visas ett portionsinmatningsfält direkt i dagsvyn
|
||||||
|
- Avviker inmatat portionsantal från receptets grundvärde visas en återställningsknapp (↩ N portioner)
|
||||||
|
- `handleServingsChange()` POSTar direkt till backend och uppdaterar lokal state utan sidomladdning
|
||||||
|
|
||||||
|
### Portionsskalning i backend
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const scale = recipeServings && entryServings
|
||||||
|
? entryServings / recipeServings
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
// Exempel: recept för 4, vill laga 6 → scale = 1.5
|
||||||
|
// 200 g pasta → 300 g pasta i inköpslistan
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Enhetskonvertering (backendsida)
|
## Enhetskonvertering (backendsida)
|
||||||
|
|
||||||
### Stödda enhetstyper
|
### Stödda enhetstyper
|
||||||
@@ -882,18 +1025,19 @@ Konfigureras via `.env` eller `docker compose up`:
|
|||||||
|
|
||||||
## Säkerhet & Utbyggbarhet
|
## Säkerhet & Utbyggbarhet
|
||||||
|
|
||||||
- **Ingen auth i grundutförande** (kan enkelt byggas på)
|
- **Autentisering:** JWT-baserad, 7 dagars token. Auth.js v5 (Credentials provider) i frontend. Alla backend-routes skyddas av `JwtAuthGuard` — öppna endpoints markeras med `@Public()`.
|
||||||
- **Validering:** Alla DTO:er valideras med class-validator
|
- **Middleware:** `middleware.ts` skyddar alla Next.js-routes utom `/login`, `/register` och `/api/auth`. Oinloggade användare omdirigeras automatiskt.
|
||||||
- **Felhantering:** GlobalExceptionFilter med svenska meddelanden
|
- **Validering:** Alla DTO:er valideras med `class-validator`. Inkommande fält i server actions bör kompletteringsvalideras (se teknisk skuld E).
|
||||||
- **CORS:** Proxies hanteras via Next.js API routes
|
- **Felhantering:** `GlobalExceptionFilter` fångar alla oupphanterade fel och returnerar svenska felmeddelanden.
|
||||||
|
- **CORS:** API-anrop proxias via Next.js API routes — klientkod når aldrig backenden direkt.
|
||||||
|
- **Filuppladdning:** Multer med `memoryStorage` och MIME-typvalidering; max 15 MB för kvittoimport.
|
||||||
|
|
||||||
### Möjliga utbyggnader
|
### Möjliga utbyggnader
|
||||||
- Authentication (JWT, OAuth)
|
- Användarroller (user / admin) — rollbaserad guard, skyddade admin-routes
|
||||||
- Multi-user support
|
- Delade recept / recept-export
|
||||||
- Shoppinglistor
|
- Push-notifieringar för utgångna varor
|
||||||
- Recept-delning
|
- Nutrition-baserat receptförslag
|
||||||
- Nutrition facts
|
- Allergi-tracking per användare
|
||||||
- Allergi-tracking
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user