diff --git a/NEXT_STEPS.md b/NEXT_STEPS.md index ee1eff84..bfb2a63b 100644 --- a/NEXT_STEPS.md +++ b/NEXT_STEPS.md @@ -5,7 +5,7 @@ --- -## Status — senast genomgånget: 2026-04-17 +## Status — senast genomgånget: 2026-04-18 | Funktion | Status | |---|---| @@ -15,7 +15,8 @@ | Kvittoimport (Mistral AI, OCR, alias) | ✅ Klart | | Matplanering (veckovy, inköpslista) | ✅ Klart | | Matplan — portionsjustering per dag | ✅ Klart | -| Matplan — inventariejämförelse | ✅ Klart | +| Matplan — inventariejämförelse (backend) | ✅ Klart | +| Matplan — inventariejämförelse (frontend-vy) | ⚠️ Grundläggande, saknar ✅/⚠️/❌-status | | Baslager (lista, lägg till, ta bort) | ✅ Klart | | Admin: Produkter (edit, merge, duplicate, restore, reset) | ✅ Klart | | Admin: Bulk-kategorisering | ✅ Klart | @@ -24,74 +25,87 @@ | Autentisering (JWT, Auth.js v5, User-modell) | ✅ Klart | | Användarprofil (firstName, lastName, email) | ✅ Klart | | Produktkategorier — hierarkisk struktur (3 nivåer) | ✅ Klart | +| Kategori-seed (supplement, idempotent) | ✅ Klart | +| Kategoritilldelning i admin-UI | ✅ Klart | | Taggning av produkter | ✅ Klart | | Näringsvärden på produkter | ✅ Klart (schema + API) | -| Kategoritilldelning i admin-UI | ✅ Klart | -| Kategori-seed (supplement, idempotent) | ✅ Klart | -| Seed produktdata med kategoritilldelning | ❌ Saknas (002-seed-products.sql.disabled) | +| Seed produktdata med kategoritilldelning | ⚠️ Script klart, ej aktiverat i init | | Användarspecifika produkter (UserProduct) | ⚠️ Schema klart, UI basic | | Användarroller (user / admin) | ❌ Saknas | | Användarhantering i admin-UI | ❌ Saknas | +| Teknisk skuld — oanvända InventoryItem-fält | ✅ Klart (migration 20260418) | +| Teknisk skuld — redirect-routes städade | ✅ Klart | +| Avancerad AI-integration (veckoplanering, kampanjdata) | ❌ Planerad | --- ## Prioriterade förbättringar -### 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` (nu inklusive supplement-kategorier) -- Aktivera filen igen genom att ta bort `.disabled`-suffixet -- Kontrollera att varje produkts `categoryId` matchar mot det ID som genereras i databasen (auto-increment — kör ett SELECT för att verifiera) +### 1. Seed-data (002-seed-products.sql) +**Mål:** Möjliggöra demo och ny server utan manuell datainmatning. -### 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 +`db/init/002-seed-products.sql` är inaktiverad (`.disabled`) tills produkterna har rätt `categoryId`. Skript för kategorimappning finns i `db/seeds/`: +- Kör `db/seeds/categories_supplement.sql` → lägger till saknade kategorier +- Kör `db/seeds/seed_product_categories.sql` → kopplar ~190 produkter till rätt kategori +- Verifiera att mappningarna ser rätt ut i admin-UI (filtrera på okategoriserade) +- Generera en ny `002-seed-products.sql` med korrekt `categoryId` per rad (via `SELECT` mot live-db) +- Ta bort `.disabled`-suffixet och testa fresh install -### 3. Användarroller och användarhantering i admin -Idag har alla inloggade användare samma behörighetsnivå. Behövs: +### 2. Användarroller +**Mål:** Säkerställa att endast behöriga användare har admin-rättigheter. + +Idag har alla inloggade användare samma behörighetsnivå — ett säkerhetsproblem inför lansering. - **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 - **Auth:** Rollen inkluderas i JWT-tokenen och i Auth.js session-objektet - **Frontend — admin-UI (`/admin/users/`):** Lista användare, skapa nya konton (namn, e-post, lösenord, roll), ändra roll, avaktivera konto - **Frontend — skyddade routes:** `/admin/*` kräver admin-roll; omdirigerar annars till startsidan +### 3. Matplan-vy (frontend-polish) +**Mål:** Ge användare tydlig feedback på lagerstatus och underlätta inköp. + +Backend-endpointen `GET /api/meal-plan/inventory-compare?from=...&to=...` finns och fungerar. Det som saknas är en tydlig frontend-vy: +- Visa inköpslistan med statusindikatorer: ✅ Finns hemma / ⚠️ Delvis / ❌ Saknas +- Aggregera `inventory-compare`-svaret per ingrediens över hela veckan +- Möjlig placering: ny flik i matplanen eller sidopanel i veckovy + +### 4. Teknisk skuld (underhåll) +**Mål:** Minska komplexitet och risk för buggar. + +#### A. CanonicalNameForm och NameForm ✅ +Filerna var redan borttagna — inga aktiva imports hittades. Inget att göra. + +#### B. Oanvända fält på InventoryItem ✅ +Följande 6 fält togs bort via Prisma-migration (`20260418000000_remove_unused_inventory_fields`): +`priority`, `shelfNote`, `isOnSale`, `priceLevel`, `proteinType`, `isLeftover` +- Schema, DTOs (create + update), service och frontend-typen är städade. +- `opened` och `suitableFor` behölls — de används i UI. + +#### C. Validering av DTO:er i admin-actions ✅ +Redan implementerat — `trim()` + max 100 tecken på alla fält i `actions.ts`. Inget att göra. + +#### D. Routing-städning för kvitto och import ✅ +`frontend/app/kvitto/page.tsx` och `frontend/app/recipes/import/page.tsx` är borttagna. +`/import` täcker båda use-cases via flikar. + +#### E. Seed-data i versionshantering ✅ +`data/matvaror_sverige.csv` och `data/seed_products.sql` behålls i git för reproducerbarhet. Inga ändringar i `.gitignore`. + +### 5. Avancerad AI-integration +**Mål:** Smarta receptförslag och veckoplanering baserat på inventarie och kampanjdata. + +Nuvarande AI-funktionalitet (Mistral för kvittotolkning) är ett bra fundament. Nästa steg: +- Receptförslag utifrån vad som finns hemma ("Vad ska jag laga idag?") +- Veckoplanering med hänsyn till kampanjpriser (kräver extern datakälla) +- Kräver: tydlig API-design, kostnadskontroll och eventuellt modellval per use-case + --- -## Teknisk skuld och städning +## Enhetstester -### A. CanonicalNameForm och NameForm — ta bort gamla filer -`frontend/app/admin/products/NameForm.tsx` och `CanonicalNameForm.tsx` ersattes av `EditProductForm.tsx`. Kontrollera om de gamla filerna fortfarande importeras och radera dem om inte. - -### B. Oanvända fält på InventoryItem -Följande fält finns i Prisma-schemat men används varken i backend-endpoints eller frontend-UI: -`priority`, `opened`, `shelfNote`, `suitableFor`, `isOnSale`, `priceLevel`, `proteinType`, `isLeftover` -Besluta: implementera stöd för dem eller ta bort via migration. - -### C. Seed-data i versionshantering -`data/matvaror_sverige.csv` och `data/seed_products.sql` ligger lokalt men är inte committade. Bestäm om de ska in i repot (för reproducerbarhet) eller hållas utanför. - -### D. Enhetstester ✅ Jest + ts-jest är uppsatt. Tester finns för: - `normalize-name.ts` — 10 tester - `base.parser.ts` (`parseIngredientLine`) — 12 tester - `recipes.service.ts` (`normalizeUnit`, `convertUnit`) — 17 tester Kör med `npm test` i `backend/`. - -### E. Validering av DTO:er i admin-actions -Frontend-server-actions saknar validering på inkommande fält (tom sträng, för lång sträng, osv.). Lägg till enkel `trim()` + max-längd-kontroll i `frontend/app/admin/products/actions.ts`. - -### F. Routing-städning för kvitto och import -`/kvitto/page.tsx` redirectar till `/import?tab=kvitto`. `/recipes/import/page.tsx` redirectar till `/import?tab=recept`. Om dessa routes inte exponeras i navigeringen kan de tas bort; om de behövs som deep links är redirectarna ok men bör dokumenteras. - ---- - -## Produktdatabasen - -Produktdatabasen är just nu tom — seedfilen `db/init/002-seed-products.sql.disabled` innehåller ~190 svenska baslivsmedel men är inaktiverad tills produkterna har tilldelats rätt `categoryId`. Nästa naturliga steg: -- Gå igenom produkterna och tilldela kategorier via admin-UI eller uppdatera seed-filen direkt -- Aktivera seed-filen igen (`002-seed-products.sql`) för reproducerbarhet vid fresh install -- Lägg till fler produkter som dyker upp vid receptimport -- Kontrollera att `canonicalName` är ifyllt för alla produkter diff --git a/backend/prisma/migrations/20260418000000_remove_unused_inventory_fields/migration.sql b/backend/prisma/migrations/20260418000000_remove_unused_inventory_fields/migration.sql new file mode 100644 index 00000000..0ea60e4d --- /dev/null +++ b/backend/prisma/migrations/20260418000000_remove_unused_inventory_fields/migration.sql @@ -0,0 +1,8 @@ +-- Remove unused fields from InventoryItem +ALTER TABLE `InventoryItem` + DROP COLUMN `priority`, + DROP COLUMN `shelfNote`, + DROP COLUMN `isOnSale`, + DROP COLUMN `priceLevel`, + DROP COLUMN `proteinType`, + DROP COLUMN `isLeftover`; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index c7f04e6c..b4b9af6a 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -88,16 +88,10 @@ model InventoryItem { brand String? receiptName String? location String? - priority Int? purchaseDate DateTime? opened Boolean? - shelfNote String? suitableFor String? - isOnSale Boolean? - priceLevel Int? - bestBeforeDate DateTime? - proteinType String? - isLeftover Boolean? + bestBeforeDate DateTime? comment String? createdAt DateTime @default(now()) diff --git a/backend/src/inventory/dto/create-inventory.dto.ts b/backend/src/inventory/dto/create-inventory.dto.ts index 87f578e5..dfba81b8 100644 --- a/backend/src/inventory/dto/create-inventory.dto.ts +++ b/backend/src/inventory/dto/create-inventory.dto.ts @@ -1,6 +1,5 @@ import { IsBoolean, - IsInt, IsNumber, IsOptional, IsString, @@ -22,10 +21,6 @@ export class CreateInventoryDto { @IsString() location?: string; - @IsOptional() - @IsInt() - priority?: number; - @IsOptional() purchaseDate?: string; @@ -44,30 +39,10 @@ export class CreateInventoryDto { @IsBoolean() opened?: boolean; - @IsOptional() - @IsString() - shelfNote?: string; - @IsOptional() @IsString() suitableFor?: string; - @IsOptional() - @IsBoolean() - isOnSale?: boolean; - - @IsOptional() - @IsInt() - priceLevel?: number; - - @IsOptional() - @IsString() - proteinType?: string; - - @IsOptional() - @IsBoolean() - isLeftover?: boolean; - @IsOptional() @IsString() comment?: string; diff --git a/backend/src/inventory/dto/update-inventory.dto.ts b/backend/src/inventory/dto/update-inventory.dto.ts index c3511107..c971b91e 100644 --- a/backend/src/inventory/dto/update-inventory.dto.ts +++ b/backend/src/inventory/dto/update-inventory.dto.ts @@ -1,6 +1,5 @@ import { IsBoolean, - IsInt, IsNumber, IsOptional, IsString, @@ -25,10 +24,6 @@ export class UpdateInventoryDto { @IsString() location?: string; - @IsOptional() - @IsInt() - priority?: number; - @IsOptional() purchaseDate?: string; @@ -47,30 +42,10 @@ export class UpdateInventoryDto { @IsBoolean() opened?: boolean; - @IsOptional() - @IsString() - shelfNote?: string; - @IsOptional() @IsString() suitableFor?: string; - @IsOptional() - @IsBoolean() - isOnSale?: boolean; - - @IsOptional() - @IsInt() - priceLevel?: number; - - @IsOptional() - @IsString() - proteinType?: string; - - @IsOptional() - @IsBoolean() - isLeftover?: boolean; - @IsOptional() @IsString() comment?: string; diff --git a/backend/src/inventory/inventory.service.ts b/backend/src/inventory/inventory.service.ts index 4dc4ee98..2a95ec3d 100644 --- a/backend/src/inventory/inventory.service.ts +++ b/backend/src/inventory/inventory.service.ts @@ -206,10 +206,6 @@ export class InventoryService { updateData.receiptName = data.receiptName.trim(); } - if (typeof data.priority === 'number') { - updateData.priority = data.priority; - } - if (typeof data.purchaseDate === 'string') { updateData.purchaseDate = data.purchaseDate ? new Date(data.purchaseDate) @@ -226,30 +222,10 @@ export class InventoryService { updateData.opened = data.opened; } - if (typeof data.shelfNote === 'string') { - updateData.shelfNote = data.shelfNote.trim(); - } - if (typeof data.suitableFor === 'string') { updateData.suitableFor = data.suitableFor.trim(); } - if (typeof data.isOnSale === 'boolean') { - updateData.isOnSale = data.isOnSale; - } - - if (typeof data.priceLevel === 'number') { - updateData.priceLevel = data.priceLevel; - } - - if (typeof data.proteinType === 'string') { - updateData.proteinType = data.proteinType.trim(); - } - - if (typeof data.isLeftover === 'boolean') { - updateData.isLeftover = data.isLeftover; - } - if (typeof data.comment === 'string') { updateData.comment = data.comment.trim(); } diff --git a/frontend/app/kvitto/page.tsx b/frontend/app/kvitto/page.tsx deleted file mode 100644 index 1ebd8154..00000000 --- a/frontend/app/kvitto/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from 'next/navigation'; - -export default function KvittoPage() { - redirect('/import?tab=kvitto'); -} diff --git a/frontend/app/recipes/import/page.tsx b/frontend/app/recipes/import/page.tsx deleted file mode 100644 index 150d80f8..00000000 --- a/frontend/app/recipes/import/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from 'next/navigation'; - -export default function Page() { - redirect('/import?tab=recept'); -} diff --git a/frontend/features/inventory/types.ts b/frontend/features/inventory/types.ts index 6b58410b..6855adfa 100644 --- a/frontend/features/inventory/types.ts +++ b/frontend/features/inventory/types.ts @@ -52,17 +52,11 @@ export type InventoryItem = { quantity: string; unit: string; location: string | null; - priority: number | null; purchaseDate: string | null; opened: boolean | null; - shelfNote: string | null; suitableFor: string | null; - isOnSale: boolean | null; - priceLevel: number | null; bestBeforeDate: string | null; brand: string | null; - proteinType: string | null; - isLeftover: boolean | null; comment: string | null; createdAt: string; updatedAt: string;