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
+21 -15
View File
@@ -32,8 +32,8 @@
| Kategoritilldelning i admin-UI | ✅ 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) |
| Seed produktdata med kategoritilldelning | ✅ Klart (seed_all.sql) | | Seed produktdata med kategoritilldelning | ✅ Ersatt — seed innehåller nu enbart kategorier (2026-05-02) |
| Användarspecifika produkter (UserProduct) | ⚠️ Schema klart, UI basic | | Användarspecifika produkter (UserProduct) | ✅ Klart (2026-05-02) — `Product.ownerId` obligatorisk, globala produkter borttagna |
| Användarroller (user / admin) | ✅ Klart | | Användarroller (user / admin) | ✅ Klart |
| Användarhantering i admin-UI | ✅ Klart | | Användarhantering i admin-UI | ✅ Klart |
| Profilsida med flikar (Min profil / Användare / Databas med undertabbar) | ✅ Klart | | Profilsida med flikar (Min profil / Användare / Databas med undertabbar) | ✅ Klart |
@@ -54,34 +54,40 @@
| Avancerad AI-integration (veckoplanering, receptförslag) | ❌ Planerad | | Avancerad AI-integration (veckoplanering, receptförslag) | ❌ Planerad |
| EAN-skanning via Open Food Facts API | ❌ Planerad | | EAN-skanning via Open Food Facts API | ❌ Planerad |
## Status — senast genomgånget: 2026-05-01 ## Status — senast genomgånget: 2026-05-02
### Nyheter och förbättringar ### Nyheter och förbättringar
- **Produkter user-scoped — ny databasarkitektur (2026-05-02)** — `Product.ownerId` är nu obligatorisk (non-nullable). Alla globala seed-produkter är borttagna. Varje produkt ägs av en enskild användare och raderas vid kontoradering (CASCADE). `seed_all.sql` innehåller nu enbart kategorier. Kvittoimportens matchning filtrerar på `ownerId = userId` från JWT. Se TEKNISK_BESKRIVNING.md för fullständig beskrivning.
- **Kategorier utökade (2026-05-02)** — Nya L2/L3-noder: `Bröd & Kakor > Kondis & fika > Kaffebröd` (wienerbröd, donuts, munkar m.m.) och `Dryck > Te & choklad > Te` (chai, vanilla chai, ceylon te m.m.). Nya L3-noder under `Mejeri, ost & ägg > Allergi mejeri`: Laktosfri mjölk, Filmjölk & Yoghurt, Kvarg & Cottage cheese, Matfett, Allergi matlagning.
- **Regelbaserad kategoridetektion utökad (2026-05-02)** — `ruleBasedCategorySuggestion()` täcker nu Te (te, tea, chai, tepas) och Kaffebröd (wienerbröd, donut, munk, croissant, kanelbulle, bakelse, semla m.fl.) utöver befintliga mejeri-regler.
- **AI-guardrail (2026-05-02)** — `AiService.suggestCategory()` remappar nu `low`/`medium`-konfidenspoäng till L1-föräldern istället för att returnera potentiellt fel L2/L3-kategori.
- **Förbättrad produktmatchning (2026-05-02)** — `findWordMatch` normaliserar nu diakritik (ä→a, ö→o, å→a) före jämförelse och tillåter enstaka stark partiell matchning för ord ≥5 tecken (löser t.ex. "vispgrädde" → produkt "grädde").
- **Kategorisuggest för matchade produkter (2026-05-02)** — `matchProducts()` läser nu in `categoryRef` för matchade produkter och sätter `categorySuggestion` direkt. `enrichWithAiCategories()` körs för alla items utan `categorySuggestion`, inte bara ej matchade.
- **Kvittoimport Fas 6b klar (2026-05-01)** — Flutter-granskningsflödet färdigt: per-rad checkbox, redigeringsdialog med destination-väljare (Inventarie/Baslager), merge-förhandsvisning, parallell laddning av inventarie och baslager, snackbar med separat räkning. - **Kvittoimport Fas 6b klar (2026-05-01)** — Flutter-granskningsflödet färdigt: per-rad checkbox, redigeringsdialog med destination-väljare (Inventarie/Baslager), merge-förhandsvisning, parallell laddning av inventarie och baslager, snackbar med separat räkning.
- **Kvittoimport Fas 6c klar (2026-05-01)** — Separering av AI-chip och produktsuggestions-chip, produktnamns-normalisering, och validering av AI-kategorier. - **Kvittoimport Fas 6c klar (2026-05-01)** — Separering av AI-chip och produktsuggestions-chip, produktnamns-normalisering, och validering av AI-kategorier.
- **Microservice-importer integrerad (2026-04-30)** — All import-logik (URL-skrapning, OCR, PDF-parsning, AI-kvittoparsning) delegeras nu till `importer-api` som körs som intern Docker-tjänst. `recipe-api` behåller Levenshtein-matchning, produktdatabas och AI-kategorisering. Se [migrering-MSI.md](migrering-MSI.md) för detaljer. - **Microservice-importer integrerad (2026-04-30)** — All import-logik (URL-skrapning, OCR, PDF-parsning, AI-kvittoparsning) delegeras nu till `importer-api` som körs som intern Docker-tjänst. `recipe-api` behåller produktmatchning och AI-kategorisering. Se [migrering-MSI.md](migrering-MSI.md) för detaljer.
- **User-scope för pantry och matplan** — Alla baslager- och matplansdata är nu per användare. Backend och Prisma-schema är migrerade. - **User-scope för pantry och matplan** — Alla baslager- och matplansdata är nu per användare. Backend och Prisma-schema är migrerade.
- **Robust bildimport** — Bild-URL normaliseras, laddas ner och optimeras i backend. Bilden kopplas till receptet och raderas vid delete. Diagnostikloggning på alla steg. - **Robust bildimport** — Bild-URL normaliseras, laddas ner och optimeras i backend. Bilden kopplas till receptet och raderas vid delete. Diagnostikloggning på alla steg.
- **Importflöde** — Quick-import och receipt-import har förbättrats med robust multipart-hantering, timeout, och felhantering. Markdown och bild-url skickas hela vägen till UI. - **Importflöde** — Quick-import och receipt-import har förbättrats med robust multipart-hantering, timeout, och felhantering.
- **Flutter-parity** — Matplan, inventarie, baslager och receptflöden är nu fullt migrerade till Flutter med user-scope och robust felhantering. - **Flutter-parity** — Matplan, inventarie, baslager och receptflöden är nu fullt migrerade till Flutter med user-scope och robust felhantering.
### Kända begränsningar ### Kända begränsningar
- Kvittoimport (Fas 6b) är påbörjad men granskningssteg och bulk-spara återstår. - Kvittoimport: användare utan egna produkter får inga produktmatchningar — enbart kategorisuggestioner via regler/AI (förväntat beteende, byggs upp allt eftersom).
- Bildimport kräver att containrar är uppdaterade med senaste kod — kontrollera att diagnostikloggar syns vid felsökning. - `ReceiptAlias` är ännu inte user-scoped (alias är fortfarande globala — se aliasstrategin nedan).
- `receipt_import_tab.dart` (~1 400 rader) och `swipeable_inventory_tile.dart` är ännu inte lokaliserade.
- Vissa adminfunktioner och avancerad AI-integration är planerade men ej migrerade. - Vissa adminfunktioner och avancerad AI-integration är planerade men ej migrerade.
--- ---
## Nästa steg ## Nästa steg
1. Kvittoimport steg 2: persistenta förpackningsfält i inventarie (packCount, packSizeQuantity, packSizeUnit) + visning/redigering i inventory-UI. 1. Bygg upp UI-flöde för att skapa/spara egna produkter vid kvittobekräftelse (POST /products med `ownerId` från JWT).
2. Inför hybrid alias-modell för kvittoimport: user-scope alias som standard + global alias som admin-verifierad fallback. 2. Inför user-scope för `ReceiptAlias` (lägg till `userId`, unika index, admin-godkänd global fallback).
3. Uppdatera backend-matchordning för alias: user-alias -> global alias -> poängbaserat namnförslag -> AI-kategori. 3. Implementera automatisk alias-inlärning vid manuell korrigering i importflödet.
4. Implementera automatisk alias-inlärning vid manuell korrigering i importflödet (först user-scope). 4. Kvittoimport steg 2: persistenta förpackningsfält i inventarie (packCount, packSizeQuantity, packSizeUnit) + visning/redigering i inventory-UI.
5. Deploy och smoke-test av kvittoimportflödet på server. 5. Lokalisera `receipt_import_tab.dart` och `swipeable_inventory_tile.dart`.
6. ✅ Flutter-lokalisering (ARB) — alla huvudskärmar klara (2026-05-02). Kvarstår: `receipt_import_tab.dart` (~1 400 rader) och `swipeable_inventory_tile.dart` (`'Bäst före: '`). 6. Smoke-test på testdomän och avstämning.
7. Smoke-test på testdomän och avstämning. 7. Planera och påbörja avancerad AI-integration och EAN-skanning.
8. Planera och påbörja avancerad AI-integration och EAN-skanning.
## Beslut 2026-05-02 - Aliasstrategi för kvittoimport ## Beslut 2026-05-02 - Aliasstrategi för kvittoimport
+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) ## 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. - **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.
@@ -0,0 +1,17 @@
-- Steg 1: Ta bort alla produkter utan ägare (globala seed-produkter)
-- Detta tar automatiskt bort relaterade rader via ON DELETE CASCADE
-- för: InventoryItem, PantryItem, ReceiptAlias, ProductTag, Nutrition, UserProduct
DELETE FROM `Product` WHERE `ownerId` IS NULL;
-- Steg 2: Gör ownerId obligatoriskt
ALTER TABLE `Product`
MODIFY COLUMN `ownerId` INT NOT NULL;
-- Steg 3: Uppdatera foreign key constraint till CASCADE (ta bort gammal, lägg till ny)
ALTER TABLE `Product`
DROP FOREIGN KEY IF EXISTS `Product_ownerId_fkey`;
ALTER TABLE `Product`
ADD CONSTRAINT `Product_ownerId_fkey`
FOREIGN KEY (`ownerId`) REFERENCES `User`(`id`)
ON DELETE CASCADE ON UPDATE CASCADE;
+2 -2
View File
@@ -48,8 +48,8 @@ model Product {
receiptAliases ReceiptAlias[] receiptAliases ReceiptAlias[]
tags ProductTag[] tags ProductTag[]
nutrition Nutrition? nutrition Nutrition?
ownerId Int? ownerId Int
owner User? @relation(fields: [ownerId], references: [id], onDelete: SetNull) owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
userProducts UserProduct[] userProducts UserProduct[]
categoryId Int? categoryId Int?
categoryRef Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) categoryRef Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
@@ -49,6 +49,7 @@ export class ReceiptImportController {
); );
} }
const isPremium = req?.user?.isPremium === true || req?.user?.role === 'admin'; const isPremium = req?.user?.isPremium === true || req?.user?.role === 'admin';
return this.receiptImportService.parseReceipt(file, isPremium); const userId = typeof req?.user?.id === 'number' ? req.user.id : undefined;
return this.receiptImportService.parseReceipt(file, isPremium, userId);
} }
} }
@@ -55,12 +55,12 @@ export class ReceiptImportService {
private readonly categoriesService: CategoriesService, private readonly categoriesService: CategoriesService,
) {} ) {}
async parseReceipt(file: Express.Multer.File, isPremium = false): Promise<ParsedReceiptItem[]> { async parseReceipt(file: Express.Multer.File, isPremium = false, userId?: number): Promise<ParsedReceiptItem[]> {
// Steg 1: Delegera AI-parsning till microservice-importer // Steg 1: Delegera AI-parsning till microservice-importer
const rawItems = await this.parseReceiptViaImporter(file); const rawItems = await this.parseReceiptViaImporter(file);
// Steg 2: Matchning mot produktdatabas (kräver DB — stannar i recipe-app) // Steg 2: Matchning mot produktdatabas (kräver DB — stannar i recipe-app)
const matched = await this.matchProducts(rawItems); const matched = await this.matchProducts(rawItems, userId);
// Steg 3: AI-kategorisering för premium-användare // Steg 3: AI-kategorisering för premium-användare
if (isPremium) { if (isPremium) {
@@ -110,15 +110,21 @@ export class ReceiptImportService {
private async matchProducts( private async matchProducts(
items: ParsedReceiptItem[], items: ParsedReceiptItem[],
userId?: number,
): Promise<ParsedReceiptItem[]> { ): Promise<ParsedReceiptItem[]> {
// Hämta alias och produkter parallellt // Hämta alias och produkter parallellt — filtrera på userId om angivet
const productFilter = userId ? { isActive: true, ownerId: userId } : { isActive: true };
const aliasFilter = userId
? { product: { ownerId: userId } }
: {};
const [aliases, products] = await Promise.all([ const [aliases, products] = await Promise.all([
this.prisma.receiptAlias.findMany({ this.prisma.receiptAlias.findMany({
select: { receiptName: true, productId: true, product: { select: { id: true, name: true, canonicalName: true } } }, where: aliasFilter,
select: { receiptName: true, productId: true, product: { select: { id: true, name: true, canonicalName: true, categoryId: true, categoryRef: { select: { id: true, name: true, path: true } } } } },
}), }),
this.prisma.product.findMany({ this.prisma.product.findMany({
where: { isActive: true }, where: productFilter,
select: { id: true, name: true, canonicalName: true }, select: { id: true, name: true, canonicalName: true, categoryId: true, categoryRef: { select: { id: true, name: true, path: true } } },
}), }),
]); ]);
@@ -129,29 +135,34 @@ export class ReceiptImportService {
// 1. Alias-match (säker, användaren behöver inte bekräfta) // 1. Alias-match (säker, användaren behöver inte bekräfta)
const alias = aliases.find((a) => a.receiptName === raw); const alias = aliases.find((a) => a.receiptName === raw);
if (alias) { if (alias) {
const cat = alias.product.categoryRef;
return { return {
...item, ...item,
matchedProductId: alias.product.id, matchedProductId: alias.product.id,
matchedProductName: alias.product.canonicalName ?? alias.product.name, matchedProductName: alias.product.canonicalName ?? alias.product.name,
...(cat ? { categorySuggestion: { categoryId: cat.id, categoryName: cat.name, path: cat.path, confidence: 'high' as const, usedFallback: false } } : {}),
}; };
} }
// 2. Ordbaserad matchning (förslag, kräver bekräftelse) // 2. Ordbaserad matchning (förslag, kräver bekräftelse)
const suggestion = this.findWordMatch(raw, products); const suggestion = this.findWordMatch(raw, products);
if (!suggestion) {
return { ...item };
}
const cat = suggestion.categoryRef;
return { return {
...item, ...item,
suggestedProductId: suggestion?.id, suggestedProductId: suggestion.id,
suggestedProductName: suggestion suggestedProductName: suggestion.canonicalName ?? suggestion.name,
? (suggestion.canonicalName ?? suggestion.name) ...(cat ? { categorySuggestion: { categoryId: cat.id, categoryName: cat.name, path: cat.path, confidence: 'medium' as const, usedFallback: false } } : {}),
: undefined,
}; };
}); });
} }
private findWordMatch( private findWordMatch(
raw: string, raw: string,
products: { id: number; name: string; canonicalName: string | null }[], products: { id: number; name: string; canonicalName: string | null; categoryId: number | null; categoryRef: { id: number; name: string; path: string } | null }[],
): { id: number; name: string; canonicalName: string | null } | undefined { ): { id: number; name: string; canonicalName: string | null; categoryId: number | null; categoryRef: { id: number; name: string; path: string } | null } | undefined {
// Dela upp kvittonamnet i ord (min 3 tecken) // Dela upp kvittonamnet i ord (min 3 tecken)
const rawWords = tokenize(raw); const rawWords = tokenize(raw);
if (rawWords.length === 0) return undefined; if (rawWords.length === 0) return undefined;
@@ -229,7 +240,8 @@ export class ReceiptImportService {
} }
private async enrichWithAiCategories(items: ParsedReceiptItem[]): Promise<ParsedReceiptItem[]> { private async enrichWithAiCategories(items: ParsedReceiptItem[]): Promise<ParsedReceiptItem[]> {
const unmatched = items.filter((i) => !i.matchedProductId && !i.suggestedProductId && i.rawName); // Kör regler/AI för alla items som saknar categorySuggestion och har ett rawName
const unmatched = items.filter((i) => !i.categorySuggestion && i.rawName);
if (unmatched.length === 0) return items; if (unmatched.length === 0) return items;
let categories: Awaited<ReturnType<CategoriesService['findFlattened']>>; let categories: Awaited<ReturnType<CategoriesService['findFlattened']>>;
+1 -553
View File
@@ -1,4 +1,4 @@
-- ============================================================ -- ============================================================
-- seed_all.sql — Komplett seed för kategorier + produkter -- seed_all.sql — Komplett seed för kategorier + produkter
-- --
-- Seeden är ensam sanningskälla för kategorier. -- Seeden är ensam sanningskälla för kategorier.
@@ -523,555 +523,3 @@ INSERT INTO `Category` (`name`, `parentId`)
SELECT 'Frysta skaldjur & havsdelikatesser', c2.id FROM `Category` c1 SELECT 'Frysta skaldjur & havsdelikatesser', c2.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Skaldjur & Havsdelikatesser' JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Skaldjur & Havsdelikatesser'
WHERE c1.name = 'Fisk & Skaldjur' AND c1.parentId IS NULL; WHERE c1.name = 'Fisk & Skaldjur' AND c1.parentId IS NULL;
-- ============================================================
-- STEG 2: PRODUKTER (INSERT IGNORE — hoppar över befintliga)
-- (från 002-seed-products.sql.disabled)
-- ============================================================
INSERT IGNORE INTO `Product` (`name`, `normalizedName`, `isActive`, `createdAt`, `updatedAt`) VALUES
('Ananas', 'ananas', 1, NOW(), NOW()),
('Anka', 'anka', 1, NOW(), NOW()),
('Ankbröst', 'ankbrost', 1, NOW(), NOW()),
('Apelsin', 'apelsin', 1, NOW(), NOW()),
('Aubergine', 'aubergine', 1, NOW(), NOW()),
('Avokado', 'avokado', 1, NOW(), NOW()),
('Banan', 'banan', 1, NOW(), NOW()),
('Basilika', 'basilika', 1, NOW(), NOW()),
('Blandfärs', 'blandfars', 1, NOW(), NOW()),
('Blomkål', 'blomkal', 1, NOW(), NOW()),
('Bläckfisk', 'blackfisk', 1, NOW(), NOW()),
('Blåbär', 'blabar', 1, NOW(), NOW()),
('Broccoli', 'broccoli', 1, NOW(), NOW()),
('Bröd', 'brod', 1, NOW(), NOW()),
('Buljong', 'buljong', 1, NOW(), NOW()),
('Bönor', 'bonor', 1, NOW(), NOW()),
('Chiliflingor', 'chiliflingor', 1, NOW(), NOW()),
('Chilipulver', 'chilipulver', 1, NOW(), NOW()),
('Chips', 'chips', 1, NOW(), NOW()),
('Choklad', 'choklad', 1, NOW(), NOW()),
('Citron', 'citron', 1, NOW(), NOW()),
('Curry (mild)', 'currymild', 1, NOW(), NOW()),
('Curry (stark)', 'currystark', 1, NOW(), NOW()),
('Dill', 'dill', 1, NOW(), NOW()),
('Druvor', 'druvor', 1, NOW(), NOW()),
('Entrecote', 'entrecote', 1, NOW(), NOW()),
('Falukorv', 'falukorv', 1, NOW(), NOW()),
('Fasan', 'fasan', 1, NOW(), NOW()),
('Fil', 'fil', 1, NOW(), NOW()),
('Fisk', 'fisk', 1, NOW(), NOW()),
('Fläskfilé', 'flaskfile', 1, NOW(), NOW()),
('Fläskkarré', 'flaskkarre', 1, NOW(), NOW()),
('Fläskkotlett', 'flaskkotlett', 1, NOW(), NOW()),
('Fläskrevben', 'flaskrevben', 1, NOW(), NOW()),
('Fläsksidfläsk', 'flasksidflask', 1, NOW(), NOW()),
('Fläskytterfilé', 'flaskytterfile', 1, NOW(), NOW()),
('Fryst dill', 'frystdill', 1, NOW(), NOW()),
('Fryst persilja', 'frystpersilja', 1, NOW(), NOW()),
('Färsk basilika', 'farskbasilika', 1, NOW(), NOW()),
('Färsk chili', 'farskchili', 1, NOW(), NOW()),
('Färsk dill', 'farskdill', 1, NOW(), NOW()),
('Färsk ingefära', 'farskingefara', 1, NOW(), NOW()),
('Färsk koriander', 'farskkoriander', 1, NOW(), NOW()),
('Färsk oregano', 'farskoregano', 1, NOW(), NOW()),
('Färsk persilja', 'farskpersilja', 1, NOW(), NOW()),
('Färsk rosmarin', 'farskrosmarin', 1, NOW(), NOW()),
('Färsk timjan', 'farsktimjan', 1, NOW(), NOW()),
('Glass', 'glass', 1, NOW(), NOW()),
('Grädde', 'gradde', 1, NOW(), NOW()),
('Gräddfil', 'graddfil', 1, NOW(), NOW()),
('Grönkål', 'gronkal', 1, NOW(), NOW()),
('Gurka', 'gurka', 1, NOW(), NOW()),
('Gås', 'gas', 1, NOW(), NOW()),
('Hallon', 'hallon', 1, NOW(), NOW()),
('Havregryn', 'havregryn', 1, NOW(), NOW()),
('Ingefärspulver', 'ingefarspulver', 1, NOW(), NOW()),
('Jordgubbar', 'jordgubbar', 1, NOW(), NOW()),
('Juice', 'juice', 1, NOW(), NOW()),
('Kaffe', 'kaffe', 1, NOW(), NOW()),
('Kakor', 'kakor', 1, NOW(), NOW()),
('Kalkon', 'kalkon', 1, NOW(), NOW()),
('Kalkonfilé', 'kalkonfile', 1, NOW(), NOW()),
('Kalkonfärs', 'kalkonfars', 1, NOW(), NOW()),
('Kalvfilé', 'kalvfile', 1, NOW(), NOW()),
('Kalvkotlett', 'kalvkotlett', 1, NOW(), NOW()),
('Kanelstång', 'kanelstang', 1, NOW(), NOW()),
('Ketchup', 'ketchup', 1, NOW(), NOW()),
('Kex', 'kex', 1, NOW(), NOW()),
('Kikärter', 'kikarter', 1, NOW(), NOW()),
('Kiwi', 'kiwi', 1, NOW(), NOW()),
('Korianderfrön', 'korianderfron', 1, NOW(), NOW()),
('Korv', 'korv', 1, NOW(), NOW()),
('Kryddor', 'kryddor', 1, NOW(), NOW()),
('Kumminsfrön', 'kumminsfron', 1, NOW(), NOW()),
('Kyckling', 'kyckling', 1, NOW(), NOW()),
('Kycklingben', 'kycklingben', 1, NOW(), NOW()),
('Kycklingben (lårben)', 'kycklingbenlarben', 1, NOW(), NOW()),
('Kycklingben (vingben)', 'kycklingbenvingben', 1, NOW(), NOW()),
('Kycklingbröst', 'kycklingbrost', 1, NOW(), NOW()),
('Kycklingfilé', 'kycklingfile', 1, NOW(), NOW()),
('Kycklingfärs', 'kycklingfars', 1, NOW(), NOW()),
('Kycklinggrund', 'kycklinggrund', 1, NOW(), NOW()),
('Kycklinghals', 'kycklinghals', 1, NOW(), NOW()),
('Kycklinghel', 'kycklinghel', 1, NOW(), NOW()),
('Kycklinghjärta', 'kycklinghjarta', 1, NOW(), NOW()),
('Kycklingkarré', 'kycklingkarre', 1, NOW(), NOW()),
('Kycklingklubba', 'kycklingklubba', 1, NOW(), NOW()),
('Kycklingkropp', 'kycklingkropp', 1, NOW(), NOW()),
('Kycklinglever', 'kycklinglever', 1, NOW(), NOW()),
('Kycklinglår', 'kycklinglar', 1, NOW(), NOW()),
('Kycklingmälta', 'kycklingmalta', 1, NOW(), NOW()),
('Kycklingrevben', 'kycklingrevben', 1, NOW(), NOW()),
('Kycklingvinge', 'kycklingvinge', 1, NOW(), NOW()),
('Kålrot', 'kalrot', 1, NOW(), NOW()),
('Köttbullar', 'kottbullar', 1, NOW(), NOW()),
('Köttfärs', 'kottfars', 1, NOW(), NOW()),
('Lammbog', 'lammbog', 1, NOW(), NOW()),
('Lammfärs', 'lammfars', 1, NOW(), NOW()),
('Lammhals', 'lammhals', 1, NOW(), NOW()),
('Lammkotlett', 'lammkotlett', 1, NOW(), NOW()),
('Lammrack', 'lammrack', 1, NOW(), NOW()),
('Lammrevben', 'lammrevben', 1, NOW(), NOW()),
('Lammstek', 'lammstek', 1, NOW(), NOW()),
('Lasagne', 'lasagne', 1, NOW(), NOW()),
('Lax', 'lax', 1, NOW(), NOW()),
('Lime', 'lime', 1, NOW(), NOW()),
('Linser', 'linser', 1, NOW(), NOW()),
('Läsk', 'lask', 1, NOW(), NOW()),
('Lök', 'lok', 1, NOW(), NOW()),
('Macka', 'macka', 1, NOW(), NOW()),
('Majonnäs', 'majonnas', 1, NOW(), NOW()),
('Malen kanel', 'malenkanel', 1, NOW(), NOW()),
('Malet kummin', 'maletkummin', 1, NOW(), NOW()),
('Mango', 'mango', 1, NOW(), NOW()),
('Marmelad', 'marmelad', 1, NOW(), NOW()),
('Mjöl', 'mjol', 1, NOW(), NOW()),
('Mjölk', 'mjolk', 1, NOW(), NOW()),
('Morot', 'morot', 1, NOW(), NOW()),
('Musslor', 'musslor', 1, NOW(), NOW()),
('Must', 'must', 1, NOW(), NOW()),
('Müsli', 'musli', 1, NOW(), NOW()),
('Nötfärs', 'notfars', 1, NOW(), NOW()),
('Nötter', 'notter', 1, NOW(), NOW()),
('Nötterrin', 'notterrin', 1, NOW(), NOW()),
('Olivolja', 'olivolja', 1, NOW(), NOW()),
('Oregano', 'oregano', 1, NOW(), NOW()),
('Ost', 'ost', 1, NOW(), NOW()),
('Ostskivor', 'ostskivor', 1, NOW(), NOW()),
('Oxfile', 'oxfile', 1, NOW(), NOW()),
('Oxhjärta', 'oxhjarta', 1, NOW(), NOW()),
('Oxlever', 'oxlever', 1, NOW(), NOW()),
('Palsternacka', 'palsternacka', 1, NOW(), NOW()),
('Paprika', 'paprika', 1, NOW(), NOW()),
('Paprikapulver', 'paprikapulver', 1, NOW(), NOW()),
('Pasta', 'pasta', 1, NOW(), NOW()),
('Persilja', 'persilja', 1, NOW(), NOW()),
('Pizza', 'pizza', 1, NOW(), NOW()),
('Potatis', 'potatis', 1, NOW(), NOW()),
('Proteinpulver', 'proteinpulver', 1, NOW(), NOW()),
('Purjolök', 'purjolok', 1, NOW(), NOW()),
('Päron', 'paron', 1, NOW(), NOW()),
('Quorn', 'quorn', 1, NOW(), NOW()),
('Renkött', 'renkott', 1, NOW(), NOW()),
('Rimmat kött', 'rimmatkott', 1, NOW(), NOW()),
('Ris', 'ris', 1, NOW(), NOW()),
('Rosmarin', 'rosmarin', 1, NOW(), NOW()),
('Russin', 'russin', 1, NOW(), NOW()),
('Räkor', 'rakor', 1, NOW(), NOW()),
('Råbiff', 'rabiff', 1, NOW(), NOW()),
('Rödbeta', 'rodbeta', 1, NOW(), NOW()),
('Rödlök', 'rodlok', 1, NOW(), NOW()),
('Rökt paprikapulver', 'roktpaprikapulver', 1, NOW(), NOW()),
('Saft', 'saft', 1, NOW(), NOW()),
('Sallad', 'sallad', 1, NOW(), NOW()),
('Salt', 'salt', 1, NOW(), NOW()),
('Selleri', 'selleri', 1, NOW(), NOW()),
('Sill', 'sill', 1, NOW(), NOW()),
('Skinka', 'skinka', 1, NOW(), NOW()),
('Smör', 'smor', 1, NOW(), NOW()),
('Socker', 'socker', 1, NOW(), NOW()),
('Spaghetti', 'spaghetti', 1, NOW(), NOW()),
('Spenat', 'spenat', 1, NOW(), NOW()),
('Stark paprikapulver', 'starkpaprikapulver', 1, NOW(), NOW()),
('Strömming', 'stromming', 1, NOW(), NOW()),
('Svartpeppar', 'svartpeppar', 1, NOW(), NOW()),
('Söt paprikapulver', 'sotpaprikapulver', 1, NOW(), NOW()),
('Sötpotatis', 'sotpotatis', 1, NOW(), NOW()),
('Tartar', 'tartar', 1, NOW(), NOW()),
('Te', 'te', 1, NOW(), NOW()),
('Timjan', 'timjan', 1, NOW(), NOW()),
('Tofu', 'tofu', 1, NOW(), NOW()),
('Tomat', 'tomat', 1, NOW(), NOW()),
('Torkad basilika', 'torkadbasilika', 1, NOW(), NOW()),
('Torkad chili', 'torkadchili', 1, NOW(), NOW()),
('Torkad dill', 'torkaddill', 1, NOW(), NOW()),
('Torkad ingefära', 'torkadingefara', 1, NOW(), NOW()),
('Torkad koriander', 'torkadkoriander', 1, NOW(), NOW()),
('Torkad oregano', 'torkadoregano', 1, NOW(), NOW()),
('Torkad persilja', 'torkadpersilja', 1, NOW(), NOW()),
('Torkad rosmarin', 'torkadrosmarin', 1, NOW(), NOW()),
('Torkad timjan', 'torkadtimjan', 1, NOW(), NOW()),
('Torskrygg', 'torskrygg', 1, NOW(), NOW()),
('Vatten', 'vatten', 1, NOW(), NOW()),
('Vegebullar', 'vegebullar', 1, NOW(), NOW()),
('Vegobitar', 'vegobitar', 1, NOW(), NOW()),
('Vegofärs', 'vegofars', 1, NOW(), NOW()),
('Vegokorv', 'vegokorv', 1, NOW(), NOW()),
('Vildsvin', 'vildsvin', 1, NOW(), NOW()),
('Viltkött', 'viltkott', 1, NOW(), NOW()),
('Vinäger', 'vinager', 1, NOW(), NOW()),
('Vitkål', 'vitkal', 1, NOW(), NOW()),
('Vitlök', 'vitlok', 1, NOW(), NOW()),
('Vitpeppar', 'vitpeppar', 1, NOW(), NOW()),
('Ägg', 'agg', 1, NOW(), NOW()),
('Älgkött', 'algkott', 1, NOW(), NOW());
-- ============================================================
-- STEG 3: KATEGORITILLDELNING (UPDATE — körs alltid, oavsett om produkten är ny eller gammal)
-- (från seed_product_categories.sql)
-- ============================================================
-- Frukt & Grönt > Frukt > Druvor
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Frukt'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Druvor'
WHERE c1.name = 'Frukt & Grönt' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Druvor', 'Gröna druvor', 'Röda druvor');
-- Frukt & Grönt > Frukt > Citrusfrukt
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Frukt'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Citrusfrukt'
WHERE c1.name = 'Frukt & Grönt' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Apelsin', 'Citron', 'Lime');
-- Frukt & Grönt > Frukt
UPDATE `Product` SET `categoryId` = (
SELECT c2.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Frukt'
WHERE c1.name = 'Frukt & Grönt' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Ananas', 'Banan', 'Kiwi', 'Mango', 'Päron');
-- Frukt & Grönt > Färska bär
UPDATE `Product` SET `categoryId` = (
SELECT c2.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Färska bär'
WHERE c1.name = 'Frukt & Grönt' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Blåbär', 'Hallon', 'Jordgubbar');
-- Frukt & Grönt > Grönsaker > Tomater
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Grönsaker'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Tomater'
WHERE c1.name = 'Frukt & Grönt' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Tomat');
-- Frukt & Grönt > Grönsaker > Paprika
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Grönsaker'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Paprika'
WHERE c1.name = 'Frukt & Grönt' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Paprika');
-- Frukt & Grönt > Grönsaker > Övriga grönsaker
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Grönsaker'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Övriga grönsaker'
WHERE c1.name = 'Frukt & Grönt' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN (
'Aubergine', 'Avokado', 'Blomkål', 'Broccoli', 'Grönkål', 'Gurka',
'Sallad', 'Selleri', 'Spenat', 'Vitkål'
);
-- Frukt & Grönt > Potatis & rotsaker > Lök
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Potatis & rotsaker'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Lök'
WHERE c1.name = 'Frukt & Grönt' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Lök', 'Purjolök', 'Rödlök', 'Vitlök');
-- Frukt & Grönt > Potatis & rotsaker > Rotsaker
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Potatis & rotsaker'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Rotsaker'
WHERE c1.name = 'Frukt & Grönt' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Kålrot', 'Morot', 'Palsternacka', 'Rödbeta');
-- Frukt & Grönt > Potatis & rotsaker
UPDATE `Product` SET `categoryId` = (
SELECT c2.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Potatis & rotsaker'
WHERE c1.name = 'Frukt & Grönt' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Potatis', 'Sötpotatis');
-- Frukt & Grönt > Kryddor & smaksättare > Smaksättare (färska örter)
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Kryddor & smaksättare'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Smaksättare'
WHERE c1.name = 'Frukt & Grönt' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN (
'Färsk basilika', 'Färsk chili', 'Färsk dill', 'Färsk ingefära',
'Färsk koriander', 'Färsk oregano', 'Färsk persilja',
'Färsk rosmarin', 'Färsk timjan'
);
-- Mejeri, ost & ägg > Mjölk
UPDATE `Product` SET `categoryId` = (
SELECT c2.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Mjölk'
WHERE c1.name = 'Mejeri, ost & ägg' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Mjölk');
-- Mejeri, ost & ägg > Filmjölk & Yoghurt > Filmjölk
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Filmjölk & Yoghurt'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Filmjölk'
WHERE c1.name = 'Mejeri, ost & ägg' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Fil');
-- Mejeri, ost & ägg (ingen djupare nivå)
UPDATE `Product` SET `categoryId` = (
SELECT id FROM `Category`
WHERE name = 'Mejeri, ost & ägg' AND parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Ägg', 'Grädde', 'Gräddfil', 'Smör');
-- Kött, chark & fågel > Fågel
UPDATE `Product` SET `categoryId` = (
SELECT c2.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Fågel'
WHERE c1.name = 'Kött, chark & fågel' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN (
'Anka', 'Ankbröst', 'Fasan', 'Gås',
'Kalkon', 'Kalkonfilé', 'Kalkonfärs',
'Kyckling', 'Kycklingben', 'Kycklingben (lårben)', 'Kycklingben (vingben)',
'Kycklingbröst', 'Kycklingfilé', 'Kycklingfärs', 'Kycklinggrund',
'Kycklinghals', 'Kycklinghel', 'Kycklinghjärta', 'Kycklingkarré',
'Kycklingklubba', 'Kycklingkropp', 'Kycklinglever', 'Kycklinglår',
'Kycklingmälta', 'Kycklingrevben', 'Kycklingvinge'
);
-- Kött, chark & fågel > Kött > Fläsk
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Kött'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Fläsk'
WHERE c1.name = 'Kött, chark & fågel' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN (
'Fläskfilé', 'Fläskkarré', 'Fläskkotlett',
'Fläskrevben', 'Fläsksidfläsk', 'Fläskytterfilé'
);
-- Kött, chark & fågel > Kött > Färdiglagat & pannfärdigt
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Kött'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Färdiglagat & pannfärdigt'
WHERE c1.name = 'Kött, chark & fågel' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Köttbullar');
-- Kött, chark & fågel > Kött
UPDATE `Product` SET `categoryId` = (
SELECT c2.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Kött'
WHERE c1.name = 'Kött, chark & fågel' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN (
'Blandfärs', 'Entrecote',
'Kalvfilé', 'Kalvkotlett',
'Köttfärs', 'Lammbog', 'Lammfärs', 'Lammhals', 'Lammkotlett',
'Lammrack', 'Lammrevben', 'Lammstek',
'Nötfärs', 'Oxfile', 'Oxhjärta', 'Oxlever',
'Råbiff', 'Renkött', 'Rimmat kött', 'Tartar',
'Vildsvin', 'Viltkött', 'Älgkött'
);
-- Kött, chark & fågel > Korv
UPDATE `Product` SET `categoryId` = (
SELECT c2.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Korv'
WHERE c1.name = 'Kött, chark & fågel' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Falukorv', 'Korv');
-- Kött, chark & fågel > Pålägg > Skivat pålägg
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Pålägg'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Skivat pålägg'
WHERE c1.name = 'Kött, chark & fågel' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Skinka');
-- Glass, godis & snacks > Choklad
UPDATE `Product` SET `categoryId` = (
SELECT c2.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Choklad'
WHERE c1.name = 'Glass, godis & snacks' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Choklad');
-- Glass, godis & snacks > Chips, snacks & dip > Chips
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Chips, snacks & dip'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Chips'
WHERE c1.name = 'Glass, godis & snacks' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Chips');
-- Dryck > Läsk och Energidryck
UPDATE `Product` SET `categoryId` = (
SELECT c2.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Läsk och Energidryck'
WHERE c1.name = 'Dryck' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Läsk');
-- Dryck (toppnivå)
UPDATE `Product` SET `categoryId` = (
SELECT id FROM `Category`
WHERE name = 'Dryck' AND parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Juice', 'Kaffe', 'Must', 'Saft', 'Te', 'Vatten');
-- Fryst > Färdigmat > Pizza, paj & piroger
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Färdigmat'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Pizza, paj & piroger'
WHERE c1.name = 'Fryst' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Pizza');
-- Fryst > Grönsaker & kryddor > Grönsaker
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Grönsaker & kryddor'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Grönsaker'
WHERE c1.name = 'Fryst' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Fryst dill', 'Fryst persilja');
-- Skafferi > Pasta, ris & matgryn > Pasta
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Pasta, ris & matgryn'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Pasta'
WHERE c1.name = 'Skafferi' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Lasagne', 'Pasta', 'Spaghetti');
-- Skafferi > Pasta, ris & matgryn
UPDATE `Product` SET `categoryId` = (
SELECT c2.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Pasta, ris & matgryn'
WHERE c1.name = 'Skafferi' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Havregryn', 'Müsli', 'Ris');
-- Skafferi > Kryddor & smaksättare > Kryddor (torkade örter och malda kryddor)
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Kryddor & smaksättare'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Kryddor'
WHERE c1.name = 'Skafferi' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN (
'Basilika', 'Chiliflingor', 'Chilipulver', 'Dill',
'Ingefärspulver', 'Kanelstång', 'Korianderfrön', 'Kryddor', 'Kumminsfrön',
'Malen kanel', 'Malet kummin', 'Oregano',
'Paprikapulver', 'Persilja', 'Rosmarin', 'Salt',
'Stark paprikapulver', 'Svartpeppar', 'Söt paprikapulver',
'Rökt paprikapulver', 'Timjan', 'Vitpeppar',
'Torkad basilika', 'Torkad chili', 'Torkad dill',
'Torkad ingefära', 'Torkad koriander', 'Torkad oregano',
'Torkad persilja', 'Torkad rosmarin', 'Torkad timjan'
);
-- Skafferi > Kryddor & smaksättare > Sås, dressing & majonnäs
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Kryddor & smaksättare'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Sås, dressing & majonnäs'
WHERE c1.name = 'Skafferi' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Ketchup', 'Majonnäs', 'Vinäger');
-- Skafferi > Kryddor & smaksättare > Övriga smaksättare
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Kryddor & smaksättare'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Övriga smaksättare'
WHERE c1.name = 'Skafferi' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Olivolja');
-- Skafferi > Kryddor & smaksättare (ingen djupare nivå)
UPDATE `Product` SET `categoryId` = (
SELECT c2.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Kryddor & smaksättare'
WHERE c1.name = 'Skafferi' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Buljong');
-- Skafferi > Torra baljväxter
UPDATE `Product` SET `categoryId` = (
SELECT c2.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Torra baljväxter'
WHERE c1.name = 'Skafferi' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Bönor', 'Kikärter', 'Linser');
-- Skafferi > Torkad frukt
UPDATE `Product` SET `categoryId` = (
SELECT c2.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Torkad frukt'
WHERE c1.name = 'Skafferi' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Russin');
-- Skafferi > Bakning > Baktillbehör
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Bakning'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Baktillbehör'
WHERE c1.name = 'Skafferi' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Mjöl', 'Socker');
-- Skafferi > Konserver & burkar > Fruktkonserver
UPDATE `Product` SET `categoryId` = (
SELECT c3.id FROM `Category` c1
JOIN `Category` c2 ON c2.parentId = c1.id AND c2.name = 'Konserver & burkar'
JOIN `Category` c3 ON c3.parentId = c2.id AND c3.name = 'Fruktkonserver'
WHERE c1.name = 'Skafferi' AND c1.parentId IS NULL
LIMIT 1
) WHERE `name` IN ('Marmelad');