feat(docs): update NEXT_STEPS, README, and TEKNISK_BESKRIVNING with new features and improvements

This commit is contained in:
Nils-Johan Gynther
2026-04-17 23:20:21 +02:00
parent 470763715d
commit 84b49bc186
3 changed files with 200 additions and 48 deletions
+12 -20
View File
@@ -14,8 +14,11 @@
| Snabbimport (URL/PDF/bild/ICA) | ✅ Klart |
| Kvittoimport (Mistral AI, OCR, alias) | ✅ Klart |
| Matplanering (veckovy, inköpslista) | ✅ Klart |
| Matplan — portionsjustering per dag | ✅ Klart |
| Matplan — inventariejämförelse | ✅ Klart |
| Baslager (lista, lägg till, ta bort) | ✅ Klart |
| Admin: Produkter (edit, merge, duplicate, restore, reset) | ✅ Klart |
| Admin: Bulk-kategorisering | ✅ Klart |
| Receptredigering (frontend UX) | ✅ Klart |
| Receptbilder (upload URL) | ✅ Klart |
| Autentisering (JWT, Auth.js v5, User-modell) | ✅ Klart |
@@ -24,8 +27,7 @@
| Taggning av produkter | ✅ Klart |
| Näringsvärden på produkter | ✅ Klart (schema + API) |
| Kategoritilldelning i admin-UI | ✅ Klart |
| Portionsjustering | ❌ Saknas |
| Matplan — inventariejämförelse | ❌ Saknas |
| Kategori-seed (supplement, idempotent) | ✅ Klart |
| Seed produktdata med kategoritilldelning | ❌ Saknas (002-seed-products.sql.disabled) |
| Användarspecifika produkter (UserProduct) | ⚠️ Schema klart, UI basic |
| Användarroller (user / admin) | ❌ Saknas |
@@ -37,27 +39,17 @@
### 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.
- 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
- 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
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).
- **Databas:** `servings Int?``Recipe` i Prisma + migration
- **Backend:** `servings` exponeras i `RecipeDto`, sätts vid create/update
- **Frontend (`app/recipes/[id]/`):** räknare (+ / ) bredvid ingredienslistan, beräkning i klientkomponent utan extra API-anrop
- **Receptskapande (`write/`):** lägg till grundportioner-fält
- **Matplan (`app/matplan/`):** inköpslistan justeras efter önskat portionsantal per dag
### 2. Matplan — djupare inventariejämförelse i frontend
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.
- Visa inköpslistan med tydliga statusindikatorer: ✅ Finns hemma / ⚠️ Delvis / ❌ Saknas
- Möjlig placering: ny flik i matplanen eller sidopanel i veckovy
- Kräver: aggregering av `inventory-compare`-svaret per ingrediens över hela veckan
### 3. Matplanering — jämförelse mot inventariet
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
### 3. Användarroller och användarhantering i admin
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
- **Backend:** Rollbaserad guard (`@Roles('admin')`) — skyddar admin-endpoints; vanliga användare nekas med 403
+28 -12
View File
@@ -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–Ö)
- **Konsumera varor** — registrera förbrukad mängd med eventuell kommentar
- **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
- **Skapa och redigera recept** — med ingredienser, kvantiteter, enheter och instruktioner (Markdown-stöd)
- **Receptjämförelse mot inventorie** — se direkt vilka ingredienser du har hemma, vad som saknas och enhetskonflikt
- **Skapa och redigera recept** — med namn, beskrivning, portionsantal, ingredienser (kvantitet och enhet) och instruktioner i Markdown-format
- **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 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 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)
### 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
- **Ständigt lager** — markera produkter du alltid räknar med att ha hemma
- **Grupperat per kategori** — produkterna i baslagret visas grupperade
- **Lägg till och ta bort** — välj från produktlistan via dropdown, ta bort med ett klick
- **Grupperat per kategori** — produkterna i baslagret visas grupperade under kategorirubrik
- **Lägg till och ta bort** — välj från produktlistan via sökbar dropdown, ta bort med ett klick
### 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
- **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
- **Slå ihop produkter** — merge två produktposter, flytta alla inventarieföremål till målprodukten (källan soft-deleteras)
- **Förhandsvisning** — granska vad som kommer att hända innan merge genomförs
- **Återställ produkter** — restore tidigare raderade produkter
- **Återställ all produktdata** — rensningsknapp som raderar alla produkter, inventorie, taggar och kvitto-alias (behåller användare och kategorier)
- **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 händer (inventarieräkningar, utfall) innan merge genomförs
- **Ta bort och återställ** — soft-delete enskilda produkter, återställ med ett klick
- **Å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
View File
@@ -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 |
| | `app/recipes/create/CreateRecipeClient.tsx` | Klientkomponent: snabbimport + metodval |
| **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 |
| | `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 |
| **Recipe detail** | `app/recipes/[id]/` | Enskilt recept (detaljer, redigering) |
| **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 |
| | `ResetProductsButton.tsx` | Knapp för att rensa all produktdata |
| | `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) |
| | `AddToPantryForm.tsx` | Lägg till produkt i baslager (dropdown) |
| | `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/admin/merge-preview-proxy` | GET | Produktmerge-preview |
| `/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 |
### Autentisering (Auth.js v5)
@@ -203,6 +207,21 @@ backend/src/
│ ├── base.parser.ts # Abstract RecipeParser class
│ ├── ica.parser.ts # ICA.se-specifik parser (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.controller.ts # Recept endpoints
├── recipes.service.ts # Recept + Markdown-parsing
@@ -282,6 +301,21 @@ backend/src/
- **Soft delete & restore:**
- `remove()` - Soft-delete produkt (isActive = false)
- `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
POST /api/products/backfill-canonical Backfill canonical names (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
@@ -355,6 +391,30 @@ POST /api/pantry Lägg till produkt i 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)
@@ -475,14 +535,19 @@ model Recipe {
id Int @id @default(autoincrement())
name String # Receptnamn
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)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
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
```prisma
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
@@ -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)
### Stödda enhetstyper
@@ -882,18 +1025,19 @@ Konfigureras via `.env` eller `docker compose up`:
## Säkerhet & Utbyggbarhet
- **Ingen auth i grundutförande** (kan enkelt byggas på)
- **Validering:** Alla DTO:er valideras med class-validator
- **Felhantering:** GlobalExceptionFilter med svenska meddelanden
- **CORS:** Proxies hanteras via Next.js API routes
- **Autentisering:** JWT-baserad, 7 dagars token. Auth.js v5 (Credentials provider) i frontend. Alla backend-routes skyddas av `JwtAuthGuard` — öppna endpoints markeras med `@Public()`.
- **Middleware:** `middleware.ts` skyddar alla Next.js-routes utom `/login`, `/register` och `/api/auth`. Oinloggade användare omdirigeras automatiskt.
- **Validering:** Alla DTO:er valideras med `class-validator`. Inkommande fält i server actions bör kompletteringsvalideras (se teknisk skuld E).
- **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
- Authentication (JWT, OAuth)
- Multi-user support
- Shoppinglistor
- Recept-delning
- Nutrition facts
- Allergi-tracking
- Användarroller (user / admin) — rollbaserad guard, skyddade admin-routes
- Delade recept / recept-export
- Push-notifieringar för utgångna varor
- Nutrition-baserat receptförslag
- Allergi-tracking per användare
---