refactor(inventory): remove unused fields from InventoryItem and update related DTOs

This commit is contained in:
Nils-Johan Gynther
2026-04-18 09:01:14 +02:00
parent 6cec7ca6dd
commit fd188a3f95
9 changed files with 69 additions and 143 deletions
+60 -46
View File
@@ -5,7 +5,7 @@
--- ---
## Status — senast genomgånget: 2026-04-17 ## Status — senast genomgånget: 2026-04-18
| Funktion | Status | | Funktion | Status |
|---|---| |---|---|
@@ -15,7 +15,8 @@
| 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 — 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 | | 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 | | Admin: Bulk-kategorisering | ✅ Klart |
@@ -24,74 +25,87 @@
| Autentisering (JWT, Auth.js v5, User-modell) | ✅ Klart | | Autentisering (JWT, Auth.js v5, User-modell) | ✅ Klart |
| Användarprofil (firstName, lastName, email) | ✅ Klart | | Användarprofil (firstName, lastName, email) | ✅ Klart |
| Produktkategorier — hierarkisk struktur (3 nivåer) | ✅ Klart | | Produktkategorier — hierarkisk struktur (3 nivåer) | ✅ Klart |
| Kategori-seed (supplement, idempotent) | ✅ Klart |
| Kategoritilldelning i admin-UI | ✅ Klart |
| 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 | | Seed produktdata med kategoritilldelning | ⚠️ Script klart, ej aktiverat i init |
| Kategori-seed (supplement, idempotent) | ✅ Klart |
| 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 |
| Användarhantering i admin-UI | ❌ 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 ## Prioriterade förbättringar
### 1. Seed produktdata med kategoritilldelning ### 1. Seed-data (002-seed-products.sql)
`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. **Mål:** Möjliggöra demo och ny server utan manuell datainmatning.
- 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)
### 2. Matplan — djupare inventariejämförelse i frontend `db/init/002-seed-products.sql` är inaktiverad (`.disabled`) tills produkterna har rätt `categoryId`. Skript för kategorimappning finns i `db/seeds/`:
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. - Kör `db/seeds/categories_supplement.sql` → lägger till saknade kategorier
- Visa inköpslistan med tydliga statusindikatorer: ✅ Finns hemma / ⚠️ Delvis / ❌ Saknas - Kör `db/seeds/seed_product_categories.sql` → kopplar ~190 produkter till rätt kategori
- Möjlig placering: ny flik i matplanen eller sidopanel i veckovy - Verifiera att mappningarna ser rätt ut i admin-UI (filtrera på okategoriserade)
- Kräver: aggregering av `inventory-compare`-svaret per ingrediens över hela veckan - 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 ### 2. Användarroller
Idag har alla inloggade användare samma behörighetsnivå. Behövs: **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 - **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
- **Auth:** Rollen inkluderas i JWT-tokenen och i Auth.js session-objektet - **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 — 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 - **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: Jest + ts-jest är uppsatt. Tester finns för:
- `normalize-name.ts` — 10 tester - `normalize-name.ts` — 10 tester
- `base.parser.ts` (`parseIngredientLine`) — 12 tester - `base.parser.ts` (`parseIngredientLine`) — 12 tester
- `recipes.service.ts` (`normalizeUnit`, `convertUnit`) — 17 tester - `recipes.service.ts` (`normalizeUnit`, `convertUnit`) — 17 tester
Kör med `npm test` i `backend/`. 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
@@ -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`;
+1 -7
View File
@@ -88,16 +88,10 @@ model InventoryItem {
brand String? brand String?
receiptName String? receiptName String?
location String? location String?
priority Int?
purchaseDate DateTime? purchaseDate DateTime?
opened Boolean? opened Boolean?
shelfNote String?
suitableFor String? suitableFor String?
isOnSale Boolean? bestBeforeDate DateTime?
priceLevel Int?
bestBeforeDate DateTime?
proteinType String?
isLeftover Boolean?
comment String? comment String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -1,6 +1,5 @@
import { import {
IsBoolean, IsBoolean,
IsInt,
IsNumber, IsNumber,
IsOptional, IsOptional,
IsString, IsString,
@@ -22,10 +21,6 @@ export class CreateInventoryDto {
@IsString() @IsString()
location?: string; location?: string;
@IsOptional()
@IsInt()
priority?: number;
@IsOptional() @IsOptional()
purchaseDate?: string; purchaseDate?: string;
@@ -44,30 +39,10 @@ export class CreateInventoryDto {
@IsBoolean() @IsBoolean()
opened?: boolean; opened?: boolean;
@IsOptional()
@IsString()
shelfNote?: string;
@IsOptional() @IsOptional()
@IsString() @IsString()
suitableFor?: string; suitableFor?: string;
@IsOptional()
@IsBoolean()
isOnSale?: boolean;
@IsOptional()
@IsInt()
priceLevel?: number;
@IsOptional()
@IsString()
proteinType?: string;
@IsOptional()
@IsBoolean()
isLeftover?: boolean;
@IsOptional() @IsOptional()
@IsString() @IsString()
comment?: string; comment?: string;
@@ -1,6 +1,5 @@
import { import {
IsBoolean, IsBoolean,
IsInt,
IsNumber, IsNumber,
IsOptional, IsOptional,
IsString, IsString,
@@ -25,10 +24,6 @@ export class UpdateInventoryDto {
@IsString() @IsString()
location?: string; location?: string;
@IsOptional()
@IsInt()
priority?: number;
@IsOptional() @IsOptional()
purchaseDate?: string; purchaseDate?: string;
@@ -47,30 +42,10 @@ export class UpdateInventoryDto {
@IsBoolean() @IsBoolean()
opened?: boolean; opened?: boolean;
@IsOptional()
@IsString()
shelfNote?: string;
@IsOptional() @IsOptional()
@IsString() @IsString()
suitableFor?: string; suitableFor?: string;
@IsOptional()
@IsBoolean()
isOnSale?: boolean;
@IsOptional()
@IsInt()
priceLevel?: number;
@IsOptional()
@IsString()
proteinType?: string;
@IsOptional()
@IsBoolean()
isLeftover?: boolean;
@IsOptional() @IsOptional()
@IsString() @IsString()
comment?: string; comment?: string;
@@ -206,10 +206,6 @@ export class InventoryService {
updateData.receiptName = data.receiptName.trim(); updateData.receiptName = data.receiptName.trim();
} }
if (typeof data.priority === 'number') {
updateData.priority = data.priority;
}
if (typeof data.purchaseDate === 'string') { if (typeof data.purchaseDate === 'string') {
updateData.purchaseDate = data.purchaseDate updateData.purchaseDate = data.purchaseDate
? new Date(data.purchaseDate) ? new Date(data.purchaseDate)
@@ -226,30 +222,10 @@ export class InventoryService {
updateData.opened = data.opened; updateData.opened = data.opened;
} }
if (typeof data.shelfNote === 'string') {
updateData.shelfNote = data.shelfNote.trim();
}
if (typeof data.suitableFor === 'string') { if (typeof data.suitableFor === 'string') {
updateData.suitableFor = data.suitableFor.trim(); 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') { if (typeof data.comment === 'string') {
updateData.comment = data.comment.trim(); updateData.comment = data.comment.trim();
} }
-5
View File
@@ -1,5 +0,0 @@
import { redirect } from 'next/navigation';
export default function KvittoPage() {
redirect('/import?tab=kvitto');
}
-5
View File
@@ -1,5 +0,0 @@
import { redirect } from 'next/navigation';
export default function Page() {
redirect('/import?tab=recept');
}
-6
View File
@@ -52,17 +52,11 @@ export type InventoryItem = {
quantity: string; quantity: string;
unit: string; unit: string;
location: string | null; location: string | null;
priority: number | null;
purchaseDate: string | null; purchaseDate: string | null;
opened: boolean | null; opened: boolean | null;
shelfNote: string | null;
suitableFor: string | null; suitableFor: string | null;
isOnSale: boolean | null;
priceLevel: number | null;
bestBeforeDate: string | null; bestBeforeDate: string | null;
brand: string | null; brand: string | null;
proteinType: string | null;
isLeftover: boolean | null;
comment: string | null; comment: string | null;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;