generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } model User { id Int @id @default(autoincrement()) username String @unique email String @unique firstName String? lastName String? passwordHash String role String @default("user") isPremium Boolean @default(false) canShareRecipes Boolean @default(true) aiEngineEnabled Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userProducts UserProduct[] ownedRecipes Recipe[] @relation("RecipeOwner") sharedRecipes RecipeShare[] ownedProducts Product[] inventoryItems InventoryItem[] pantryItems PantryItem[] mealPlanEntries MealPlanEntry[] receiptAliases ReceiptAlias[] unitMappings UnitMapping[] flyerSessions FlyerSession[] flyerSelections FlyerSelection[] shoppingListItems ShoppingListItem[] aiTraces AiTrace[] } model Product { id Int @id @default(autoincrement()) name String normalizedName String @unique canonicalName String? isActive Boolean @default(true) status String @default("active") deletedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt inventoryItems InventoryItem[] recipeIngredients RecipeIngredient[] pantryItems PantryItem[] receiptAliases ReceiptAlias[] tags ProductTag[] nutrition Nutrition? ownerId Int owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade) userProducts UserProduct[] categoryId Int? categoryRef Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) isPrivate Boolean @default(false) unitMappings UnitMapping[] shoppingListItems ShoppingListItem[] } model Category { id Int @id @default(autoincrement()) name String parentId Int? parent Category? @relation("CategoryTree", fields: [parentId], references: [id], onDelete: SetNull) children Category[] @relation("CategoryTree") products Product[] flyerItems FlyerItem[] shoppingListItems ShoppingListItem[] @@unique([name, parentId]) @@index([parentId]) } model UserProduct { id Int @id @default(autoincrement()) userId Int productId Int note String? @db.Text preferredBrand String? preferredStore String? isPrivate Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) product Product @relation(fields: [productId], references: [id], onDelete: Cascade) @@unique([userId, productId]) @@index([userId]) @@index([productId]) } model InventoryItem { id Int @id @default(autoincrement()) userId Int productId Int quantity Decimal @db.Decimal(10, 2) unit String brand String? origin String? originCountries Json? receiptName String? location String? purchaseDate DateTime? opened Boolean? suitableFor String? bestBeforeDate DateTime? comment String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) product Product @relation(fields: [productId], references: [id], onDelete: Cascade) consumptions InventoryConsumption[] @@index([userId]) @@index([productId]) } model InventoryConsumption { id Int @id @default(autoincrement()) inventoryItem InventoryItem @relation(fields: [inventoryItemId], references: [id]) inventoryItemId Int amountUsed Decimal @db.Decimal(10, 2) comment String? createdAt DateTime @default(now()) } model Recipe { id Int @id @default(autoincrement()) name String description String? @db.Text instructions String? @db.Text imageUrl String? servings Int? isPublic Boolean @default(false) ownerId Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt owner User? @relation("RecipeOwner", fields: [ownerId], references: [id], onDelete: SetNull) ingredients RecipeIngredient[] mealPlanEntries MealPlanEntry[] shares RecipeShare[] } model RecipeShare { recipeId Int userId Int recipe Recipe @relation(fields: [recipeId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@id([recipeId, userId]) @@index([userId]) } model RecipeIngredient { id Int @id @default(autoincrement()) recipe Recipe @relation(fields: [recipeId], references: [id]) recipeId Int product Product? @relation(fields: [productId], references: [id]) productId Int? rawName String @default("") rawLine String? @db.Text quantity Decimal? @db.Decimal(10, 2) unit String? note String? alternativeProductIds Json? // [id, id, ...] — alternativa produkter (t.ex. "ris eller couscous") matchConfidence Float? matchSource String? analysisStatus String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model PantryItem { id Int @id @default(autoincrement()) userId Int productId Int location String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) product Product @relation(fields: [productId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([userId, productId]) @@index([userId]) } model ReceiptAlias { id Int @id @default(autoincrement()) receiptName String // normaliserat kvittonamn (lowercase, trim) ownerId Int? owner User? @relation(fields: [ownerId], references: [id], onDelete: Cascade) isGlobal Boolean @default(false) productId Int product Product @relation(fields: [productId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@unique([receiptName, ownerId, isGlobal]) @@index([ownerId]) @@index([isGlobal]) @@index([receiptName]) } model MealPlanEntry { id Int @id @default(autoincrement()) userId Int date DateTime @db.Date recipe Recipe @relation(fields: [recipeId], references: [id], onDelete: Cascade) recipeId Int user User @relation(fields: [userId], references: [id], onDelete: Cascade) servings Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Bara ett recept per dag och anvandare. @@unique([userId, date]) @@index([userId]) @@index([date]) } model Tag { id Int @id @default(autoincrement()) name String @unique products ProductTag[] } model ProductTag { productId Int tagId Int product Product @relation(fields: [productId], references: [id], onDelete: Cascade) tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) @@id([productId, tagId]) @@index([tagId]) } model Nutrition { id Int @id @default(autoincrement()) productId Int @unique calories Float? protein Float? fat Float? carbohydrates Float? salt Float? sugar Float? fiber Float? product Product @relation(fields: [productId], references: [id], onDelete: Cascade) } model UnitMapping { id Int @id @default(autoincrement()) productId Int originalUnit String preferredUnit String userId Int product Product @relation(fields: [productId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([productId, originalUnit, userId]) @@index([productId]) @@index([userId]) } model HelpText { id Int @id @default(autoincrement()) key String scope String @default("default") title String content String @db.Text isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([key, scope]) @@index([key, isActive]) } model FlyerSession { id Int @id @default(autoincrement()) userId Int retailer String weekKey String status String @default("draft") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt expiresAt DateTime? sourceFileName String? sourceMimeType String? sourceFileSize Int? sourceStorageKey String? sourceData Bytes? user User @relation(fields: [userId], references: [id], onDelete: Cascade) items FlyerItem[] selections FlyerSelection[] @@index([userId]) @@index([weekKey]) @@index([status]) } model FlyerItem { id Int @id @default(autoincrement()) sessionId Int rawName String normalizedName String brand String? categoryHint String? categoryId Int? price Decimal? @db.Decimal(10, 2) priceUnit String? comparisonPrice Decimal? @db.Decimal(10, 2) comparisonUnit String? weight String? bundleWeight String? isBundle Boolean @default(false) bundleItems Json? signals Json? displayNameDetailed String? offerText String? parseConfidence Float parseReasons Json? matchedProductId Int? matchedProductName String? matchedVia String? matchConfidence Float? matchReasons Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt session FlyerSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) categoryRef Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) selections FlyerSelection[] @@index([sessionId]) @@index([normalizedName]) @@index([categoryId]) } model FlyerSelection { id Int @id @default(autoincrement()) sessionId Int itemId Int userId Int plannedQuantity Decimal? @db.Decimal(10, 2) plannedUnit String? priority String @default("normal") note String? status String @default("planned") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt session FlyerSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) item FlyerItem @relation(fields: [itemId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([sessionId, itemId]) @@index([sessionId]) @@index([userId, status]) } model ShoppingListItem { id Int @id @default(autoincrement()) userId Int name String productId Int? categoryId Int? quantity Decimal? @db.Decimal(10, 2) unit String? source String @default("manual") status String @default("open") checkedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) product Product? @relation(fields: [productId], references: [id], onDelete: SetNull) categoryRef Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) @@index([userId, status]) @@index([productId, unit, status]) @@index([categoryId]) } model AiTrace { id Int @id @default(autoincrement()) source String userId Int? sessionId Int? model String? prompt String? @db.LongText rawOutput String? @db.LongText normalizedOutput Json? status String error String? @db.Text durationMs Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([source, createdAt]) @@index([userId, createdAt]) @@index([status, createdAt]) }