import { boolean, date, doublePrecision, index, integer, jsonb, pgTable, text, timestamp, uuid, } from "drizzle-orm/pg-core"; import type { NutritionValues } from "@app/shared-types"; import { createdAt, mealLogSourceEnum, mealTypeEnum } from "./_shared.js"; import { households, storageLocations } from "./households.js"; import { recipes, cookingSessions } from "./recipes.js"; import { users } from "./users.js"; /** * Måltidslogg (spec §23). Näringsvärden är alltid deterministiskt härledda * (recept/produkt/användarinmatning) – aldrig påhittade av AI (spec §61.1). */ export const meals = pgTable( "meals", { id: uuid("id").primaryKey().defaultRandom(), userId: uuid("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), householdId: uuid("household_id").references(() => households.id, { onDelete: "set null" }), date: date("date").notNull(), mealType: mealTypeEnum("meal_type").notNull(), source: mealLogSourceEnum("source").notNull(), recipeId: uuid("recipe_id").references(() => recipes.id, { onDelete: "set null" }), cookingSessionId: uuid("cooking_session_id").references(() => cookingSessions.id, { onDelete: "set null", }), titleSv: text("title_sv").notNull(), portionFraction: doublePrecision("portion_fraction").notNull().default(1), nutrition: jsonb("nutrition").$type().notNull(), nutritionIsEstimate: boolean("nutrition_is_estimate").notNull().default(false), estimateMinKcal: doublePrecision("estimate_min_kcal"), estimateMaxKcal: doublePrecision("estimate_max_kcal"), items: jsonb("items"), photoUrl: text("photo_url"), scanJobId: uuid("scan_job_id"), loggedAt: timestamp("logged_at", { withTimezone: true }).notNull().defaultNow(), }, (t) => [ index("meals_user_date_idx").on(t.userId, t.date), index("meals_household_idx").on(t.householdId), ], ); /** Matlådor (spec §24) – rekommenderas före ny matlagning när rimligt. */ export const mealBoxes = pgTable( "meal_boxes", { id: uuid("id").primaryKey().defaultRandom(), householdId: uuid("household_id") .notNull() .references(() => households.id, { onDelete: "cascade" }), recipeId: uuid("recipe_id").references(() => recipes.id, { onDelete: "set null" }), cookingSessionId: uuid("cooking_session_id").references(() => cookingSessions.id, { onDelete: "set null", }), titleSv: text("title_sv").notNull(), portions: integer("portions").notNull(), portionsRemaining: integer("portions_remaining").notNull(), nutritionPerPortion: jsonb("nutrition_per_portion").$type(), cookedAt: date("cooked_at").notNull(), storageLocationId: uuid("storage_location_id") .notNull() .references(() => storageLocations.id), frozen: boolean("frozen").notNull().default(false), recommendedUseBy: date("recommended_use_by").notNull(), leftoverSource: text("leftover_source"), reservedForUserId: uuid("reserved_for_user_id").references(() => users.id, { onDelete: "set null", }), status: text("status").notNull().default("available"), createdAt: createdAt(), }, (t) => [ index("meal_boxes_household_idx").on(t.householdId), index("meal_boxes_use_by_idx").on(t.householdId, t.recommendedUseBy), ], );