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 |
|---|---|
@@ -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
@@ -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?
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())
@@ -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;
@@ -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;
@@ -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();
}
-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;
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;