feat(migration): enforce ownerId requirement in Product table

- Removed all products without an owner to maintain data integrity.
- Updated ownerId column to be non-nullable.
- Modified foreign key constraint for ownerId to use ON DELETE CASCADE.
This commit is contained in:
Nils-Johan Gynther
2026-05-02 19:05:33 +02:00
parent ec24f49836
commit 4e568b4d2e
7 changed files with 652 additions and 1108 deletions
+60
View File
@@ -15,6 +15,66 @@ sker på remote server. Säkerställ att inga absoluta Windows-sökvägar anv
---
## Nyheter och förbättringar (2026-05-02)
### Ny databasarkitektur: user-scoped produkter
Produkttabellen är omgjord till ett fullständigt user-scope-modell. Beslutet grundar sig på att en global produktkatalog skapade falska matchningar i kvittoimport för nya användare (produkter "hittades" fast användaren aldrig lagt till dem).
**Vad som ändrades:**
| Komponent | Förändring |
|---|---|
| `Product.ownerId` | `Int?``Int` (obligatorisk, non-nullable) |
| `Product.owner` | `onDelete: SetNull``onDelete: Cascade` |
| `db/seeds/seed_all.sql` | Innehåller nu enbart kategorier — inga `INSERT INTO Product` |
| Migration `20260502160000` | Raderar alla globala produkter (`ownerId IS NULL`), gör FK non-null |
| `receipt-import.service.ts` | `matchProducts(items, userId)` filtrerar på `ownerId = userId` |
| `receipt-import.controller.ts` | Extraherar `userId` från JWT och skickar till service |
**Flöde för nya användare:**
1. Kvittoimport → AI/OCR parsar kvitto → inga produktmatcher (user har inga produkter ännu)
2. Regelbaserad kategoridetektion + AI-kategorisering körs för alla rader
3. Användaren bekräftar i Flutter → produkten skapas via `POST /products` med `ownerId = userId`
4. Nästa kvittoimport med samma vara → alias/ordmatch hittar den user-ägda produkten
**Framtida mallar (planerat):** En mallhanterare i UI kan låta användare seeda sin produktkatalog från fördefinierade livsmedelsmallar utan att det kräver global data i databasen.
### Kategorisystem utökat
Nya noder sedan 2026-05-01:
| Nivå | Kategori |
|---|---|
| L2 under Bröd & Kakor | Kondis & fika |
| L3 under Kondis & fika | Kaffebröd (wienerbröd, donuts, munkar, kanelbullar m.m.) |
| L2 under Dryck | Te & choklad |
| L3 under Te & choklad | Te (chai, vanilla chai, ceylon te m.m.) |
| L3 under Allergi mejeri | Laktosfri mjölk |
| L3 under Allergi mejeri | Filmjölk & Yoghurt |
| L3 under Allergi mejeri | Kvarg & Cottage cheese |
| L3 under Allergi mejeri | Matfett |
| L3 under Allergi mejeri | Allergi matlagning |
### Regelbaserad kategoridetektion (`ruleBasedCategorySuggestion`)
Funktionen i `receipt-import.service.ts` matchar kvittonamn mot nyckelord och returnerar rätt kategori direkt — utan AI-anrop. Täcker:
- **Te** — `te`, `tea`, `chai`, `tepas`, `tepak`
- **Kaffebröd** — `wienerbrod`, `donut`, `munk`, `croissant`, `kanelbulle`, `bakelse`, `semla`, `dammsugare`, `kladdkaka`, `muffin`, `cupcake`, `chokladboll`
- **Allergi mejeri** — kombinationer av mejeri-markörer + allergen/växtbaserade markörer
### AI-guardrail
`AiService.suggestCategory()` remappar `low`/`medium`-konfidenspoäng till L1-föräldern istället för att returnera ett potentiellt fel L2/L3. Loggning sker via NestJS Logger.
### Förbättrad produktmatchning
`findWordMatch()` i `receipt-import.service.ts`:
- **Diakritiksnormalisering** — `normalizeToken()` konverterar å→a, ä→a, ö→o före jämförelse. Löser t.ex. `gradde` == `grädde`.
- **Enstaka lång partiell matchning** — ett produktord på ≥5 tecken som är en delmatchning räcker nu som stark signal. Löser t.ex. "Vispgrädde 5dl" → produkt "grädde".
---
## Nyheter och förbättringar (2026-04-30)
- **Microservice-importer integrerad** — `importer-api` körs nu som intern Docker-tjänst i `recipe-app/compose.yml`. All URL-skrapning, OCR, PDF-parsning och AI-kvittoparsning delegeras dit. `recipe-api` behåller Levenshtein-matchning, produktdatabas och AI-kategorisering. Se [migrering-MSI.md](migrering-MSI.md) för fullständig lista över ändrade filer.