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`;
-6
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?
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;