-- Migration 00001: Initial Schema for KitchenStock CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- Enums CREATE TYPE household_role AS ENUM ('OWNER', 'ADMIN', 'MEMBER', 'VIEWER'); CREATE TYPE aldi_region_type AS ENUM ('NORD', 'SUED', 'UNSPECIFIED'); CREATE TYPE unit_type AS ENUM ('COUNT', 'WEIGHT', 'VOLUME', 'OTHER'); CREATE TYPE stock_movement_reason AS ENUM ('PURCHASE', 'CONSUMED', 'CORRECTION', 'INVENTORY', 'EXPIRED', 'DISCARDED', 'TRANSFER', 'INITIAL'); CREATE TYPE shopping_list_status AS ENUM ('ACTIVE', 'COMPLETED', 'ARCHIVED'); CREATE TYPE price_type AS ENUM ('REGULAR', 'OFFER', 'LOYALTY', 'UNKNOWN'); CREATE TYPE source_kind AS ENUM ('USER_PURCHASE', 'USER_ESTIMATE', 'OFFICIAL_RETAILER', 'OFFICIAL_PROSPECT', 'GEMINI_GROUNDED', 'HISTORICAL_AVERAGE', 'AI_ESTIMATE'); CREATE TYPE confidence_level AS ENUM ('HIGH', 'MEDIUM', 'LOW'); CREATE TYPE budget_period AS ENUM ('WEEKLY', 'MONTHLY', 'CUSTOM'); CREATE TYPE inventory_session_status AS ENUM ('OPEN', 'COMPLETED', 'CANCELLED'); CREATE TYPE diet_type AS ENUM ('OMNIVORE', 'VEGETARIAN', 'VEGAN', 'PESCETARIAN', 'CUSTOM'); CREATE TYPE pending_action_status AS ENUM ('PENDING', 'CONFIRMED', 'REJECTED', 'EXPIRED'); -- Trigger to update updated_at CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; -- 1. Profiles CREATE TABLE IF NOT EXISTS public.profiles ( id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, display_name TEXT NOT NULL, avatar_path TEXT, locale TEXT NOT NULL DEFAULT 'de-DE', timezone TEXT NOT NULL DEFAULT 'Europe/Berlin', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 2. Households CREATE TABLE IF NOT EXISTS public.households ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT NOT NULL, currency TEXT NOT NULL DEFAULT 'EUR', locale TEXT NOT NULL DEFAULT 'de-DE', timezone TEXT NOT NULL DEFAULT 'Europe/Berlin', postal_code TEXT, city TEXT, aldi_region aldi_region_type NOT NULL DEFAULT 'UNSPECIFIED', default_servings INT NOT NULL DEFAULT 2 CHECK (default_servings > 0), owner_id UUID NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 3. Household Members CREATE TABLE IF NOT EXISTS public.household_members ( household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE, role household_role NOT NULL DEFAULT 'MEMBER', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (household_id, user_id) ); -- 4. Categories CREATE TABLE IF NOT EXISTS public.categories ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, name TEXT NOT NULL, icon TEXT, sort_order INT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 5. Locations (supports nesting) CREATE TABLE IF NOT EXISTS public.locations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, parent_location_id UUID REFERENCES public.locations(id) ON DELETE SET NULL, name TEXT NOT NULL, icon TEXT, sort_order INT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 6. Units CREATE TABLE IF NOT EXISTS public.units ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID REFERENCES public.households(id) ON DELETE CASCADE, -- NULL for system defaults name TEXT NOT NULL, abbreviation TEXT NOT NULL, unit_type unit_type NOT NULL DEFAULT 'COUNT', allows_decimals BOOLEAN NOT NULL DEFAULT false, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 7. Products CREATE TABLE IF NOT EXISTS public.products ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, category_id UUID REFERENCES public.categories(id) ON DELETE SET NULL, default_location_id UUID REFERENCES public.locations(id) ON DELETE SET NULL, default_unit_id UUID REFERENCES public.units(id) ON DELETE SET NULL, name TEXT NOT NULL, brand TEXT, description TEXT, barcode TEXT, image_path TEXT, package_size NUMERIC(10, 3) NOT NULL DEFAULT 1.0, package_unit TEXT NOT NULL DEFAULT 'Stück', minimum_quantity NUMERIC(10, 3) NOT NULL DEFAULT 0.0 CHECK (minimum_quantity >= 0), favorite BOOLEAN NOT NULL DEFAULT false, preferred_store TEXT, estimated_price_cents INT CHECK (estimated_price_cents >= 0), price_updated_at TIMESTAMPTZ, archived BOOLEAN NOT NULL DEFAULT false, created_by UUID REFERENCES public.profiles(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 8. Inventory Batches CREATE TABLE IF NOT EXISTS public.inventory_batches ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, product_id UUID NOT NULL REFERENCES public.products(id) ON DELETE CASCADE, location_id UUID REFERENCES public.locations(id) ON DELETE SET NULL, quantity NUMERIC(10, 3) NOT NULL DEFAULT 0.0 CHECK (quantity >= 0), expiration_date DATE, purchase_date DATE, purchase_price_cents INT CHECK (purchase_price_cents >= 0), store TEXT, lot_number TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 9. Stock Movements CREATE TABLE IF NOT EXISTS public.stock_movements ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, product_id UUID NOT NULL REFERENCES public.products(id) ON DELETE CASCADE, batch_id UUID REFERENCES public.inventory_batches(id) ON DELETE SET NULL, quantity_before NUMERIC(10, 3) NOT NULL, quantity_change NUMERIC(10, 3) NOT NULL, quantity_after NUMERIC(10, 3) NOT NULL CHECK (quantity_after >= 0), reason stock_movement_reason NOT NULL, note TEXT, created_by UUID REFERENCES public.profiles(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 10. Shopping Lists CREATE TABLE IF NOT EXISTS public.shopping_lists ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, name TEXT NOT NULL DEFAULT 'Einkaufsliste', status shopping_list_status NOT NULL DEFAULT 'ACTIVE', created_by UUID REFERENCES public.profiles(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 11. Shopping List Items CREATE TABLE IF NOT EXISTS public.shopping_list_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), shopping_list_id UUID NOT NULL REFERENCES public.shopping_lists(id) ON DELETE CASCADE, product_id UUID REFERENCES public.products(id) ON DELETE SET NULL, custom_name TEXT, quantity NUMERIC(10, 3) NOT NULL DEFAULT 1.0 CHECK (quantity > 0), unit_id UUID REFERENCES public.units(id) ON DELETE SET NULL, estimated_unit_price_cents INT CHECK (estimated_unit_price_cents >= 0), selected_store TEXT, checked BOOLEAN NOT NULL DEFAULT false, automatically_added BOOLEAN NOT NULL DEFAULT false, notes TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 12. Purchases CREATE TABLE IF NOT EXISTS public.purchases ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, store TEXT NOT NULL, purchase_date TIMESTAMPTZ NOT NULL DEFAULT NOW(), total_price_cents INT NOT NULL DEFAULT 0 CHECK (total_price_cents >= 0), receipt_image_path TEXT, notes TEXT, created_by UUID REFERENCES public.profiles(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 13. Purchase Items CREATE TABLE IF NOT EXISTS public.purchase_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), purchase_id UUID NOT NULL REFERENCES public.purchases(id) ON DELETE CASCADE, product_id UUID REFERENCES public.products(id) ON DELETE SET NULL, quantity NUMERIC(10, 3) NOT NULL DEFAULT 1.0 CHECK (quantity > 0), unit_price_cents INT NOT NULL CHECK (unit_price_cents >= 0), total_price_cents INT NOT NULL CHECK (total_price_cents >= 0), package_size NUMERIC(10, 3), package_unit TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 14. Price Observations CREATE TABLE IF NOT EXISTS public.price_observations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, product_id UUID REFERENCES public.products(id) ON DELETE SET NULL, product_name TEXT NOT NULL, brand TEXT, package_size NUMERIC(10, 3), package_unit TEXT, price_cents INT NOT NULL CHECK (price_cents >= 0), base_price_cents INT CHECK (base_price_cents >= 0), base_price_unit TEXT, retailer TEXT NOT NULL, price_type price_type NOT NULL DEFAULT 'REGULAR', postal_code TEXT, region TEXT, source_url TEXT, source_title TEXT, observed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), valid_from DATE, valid_until DATE, confidence confidence_level NOT NULL DEFAULT 'MEDIUM', exact_match BOOLEAN NOT NULL DEFAULT true, verified BOOLEAN NOT NULL DEFAULT false, source_kind source_kind NOT NULL DEFAULT 'USER_ESTIMATE', created_by UUID REFERENCES public.profiles(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 15. Budgets CREATE TABLE IF NOT EXISTS public.budgets ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, name TEXT NOT NULL, period budget_period NOT NULL DEFAULT 'MONTHLY', amount_cents INT NOT NULL CHECK (amount_cents >= 0), warning_threshold_percent INT NOT NULL DEFAULT 80 CHECK (warning_threshold_percent BETWEEN 1 AND 100), start_date DATE NOT NULL, end_date DATE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 16. Inventory Sessions CREATE TABLE IF NOT EXISTS public.inventory_sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, location_id UUID REFERENCES public.locations(id) ON DELETE SET NULL, status inventory_session_status NOT NULL DEFAULT 'OPEN', started_by UUID REFERENCES public.profiles(id) ON DELETE SET NULL, started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), completed_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 17. Inventory Session Items CREATE TABLE IF NOT EXISTS public.inventory_session_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), session_id UUID NOT NULL REFERENCES public.inventory_sessions(id) ON DELETE CASCADE, product_id UUID NOT NULL REFERENCES public.products(id) ON DELETE CASCADE, expected_quantity NUMERIC(10, 3) NOT NULL DEFAULT 0.0, counted_quantity NUMERIC(10, 3) NOT NULL DEFAULT 0.0, difference NUMERIC(10, 3) NOT NULL DEFAULT 0.0, confirmed BOOLEAN NOT NULL DEFAULT false, notes TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 18. Dietary Preferences CREATE TABLE IF NOT EXISTS public.dietary_preferences ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL UNIQUE REFERENCES public.households(id) ON DELETE CASCADE, diet_type diet_type NOT NULL DEFAULT 'OMNIVORE', excluded_ingredients TEXT[] NOT NULL DEFAULT '{}', allergens TEXT[] NOT NULL DEFAULT '{}', preferred_cuisines TEXT[] NOT NULL DEFAULT '{}', maximum_preparation_minutes INT, maximum_meal_budget_cents INT, default_servings INT NOT NULL DEFAULT 2, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 19. Recipes CREATE TABLE IF NOT EXISTS public.recipes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, title TEXT NOT NULL, description TEXT, servings INT NOT NULL DEFAULT 2, preparation_minutes INT, difficulty TEXT, estimated_cost_cents INT, inventory_coverage_percent INT NOT NULL DEFAULT 0, ingredients_json JSONB NOT NULL DEFAULT '[]'::jsonb, steps_json JSONB NOT NULL DEFAULT '[]'::jsonb, dietary_tags TEXT[] NOT NULL DEFAULT '{}', allergen_warnings TEXT[] NOT NULL DEFAULT '{}', source_information_json JSONB, created_by_ai BOOLEAN NOT NULL DEFAULT false, created_by UUID REFERENCES public.profiles(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 20. Meal Plans CREATE TABLE IF NOT EXISTS public.meal_plans ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, name TEXT NOT NULL, start_date DATE NOT NULL, end_date DATE NOT NULL, estimated_total_cost_cents INT, plan_json JSONB NOT NULL DEFAULT '[]'::jsonb, created_by UUID REFERENCES public.profiles(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 21. AI Settings CREATE TABLE IF NOT EXISTS public.ai_settings ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL UNIQUE REFERENCES public.households(id) ON DELETE CASCADE, enabled BOOLEAN NOT NULL DEFAULT true, preferred_retailers TEXT[] NOT NULL DEFAULT '{"KAUFLAND", "REWE", "ALDI_NORD", "ALDI_SUED", "LIDL"}', allow_google_search BOOLEAN NOT NULL DEFAULT true, daily_request_limit INT NOT NULL DEFAULT 30 CHECK (daily_request_limit > 0), automatic_price_updates BOOLEAN NOT NULL DEFAULT false, price_update_interval_days INT NOT NULL DEFAULT 14 CHECK (price_update_interval_days > 0), maximum_products_per_price_request INT NOT NULL DEFAULT 20 CHECK (maximum_products_per_price_request > 0), save_chat_history BOOLEAN NOT NULL DEFAULT true, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 22. AI Request Logs CREATE TABLE IF NOT EXISTS public.ai_request_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, user_id UUID REFERENCES public.profiles(id) ON DELETE SET NULL, feature TEXT NOT NULL, model TEXT NOT NULL, grounding_used BOOLEAN NOT NULL DEFAULT false, input_token_count INT, output_token_count INT, latency_ms INT, successful BOOLEAN NOT NULL DEFAULT true, error_code TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- 23. AI Pending Actions CREATE TABLE IF NOT EXISTS public.ai_pending_actions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), household_id UUID NOT NULL REFERENCES public.households(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE, action_type TEXT NOT NULL, payload_json JSONB NOT NULL, status pending_action_status NOT NULL DEFAULT 'PENDING', expires_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '24 hours'), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), confirmed_at TIMESTAMPTZ ); -- 24. Rate Limit Events CREATE TABLE IF NOT EXISTS public.rate_limit_events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE, action TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Triggers for updated_at CREATE TRIGGER trg_profiles_updated_at BEFORE UPDATE ON public.profiles FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER trg_households_updated_at BEFORE UPDATE ON public.households FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER trg_categories_updated_at BEFORE UPDATE ON public.categories FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER trg_locations_updated_at BEFORE UPDATE ON public.locations FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER trg_products_updated_at BEFORE UPDATE ON public.products FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER trg_inventory_batches_updated_at BEFORE UPDATE ON public.inventory_batches FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER trg_shopping_lists_updated_at BEFORE UPDATE ON public.shopping_lists FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER trg_shopping_list_items_updated_at BEFORE UPDATE ON public.shopping_list_items FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER trg_purchases_updated_at BEFORE UPDATE ON public.purchases FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER trg_budgets_updated_at BEFORE UPDATE ON public.budgets FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER trg_inventory_session_items_updated_at BEFORE UPDATE ON public.inventory_session_items FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER trg_dietary_preferences_updated_at BEFORE UPDATE ON public.dietary_preferences FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER trg_recipes_updated_at BEFORE UPDATE ON public.recipes FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER trg_meal_plans_updated_at BEFORE UPDATE ON public.meal_plans FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER trg_ai_settings_updated_at BEFORE UPDATE ON public.ai_settings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- Indexes for optimal query performance CREATE INDEX idx_household_members_user ON public.household_members(user_id); CREATE INDEX idx_products_household ON public.products(household_id); CREATE INDEX idx_products_category ON public.products(category_id); CREATE INDEX idx_products_location ON public.products(default_location_id); CREATE INDEX idx_products_barcode ON public.products(barcode); CREATE INDEX idx_inventory_batches_product ON public.inventory_batches(product_id); CREATE INDEX idx_inventory_batches_household ON public.inventory_batches(household_id); CREATE INDEX idx_inventory_batches_exp ON public.inventory_batches(expiration_date); CREATE INDEX idx_stock_movements_product ON public.stock_movements(product_id); CREATE INDEX idx_stock_movements_household ON public.stock_movements(household_id); CREATE INDEX idx_shopping_list_items_list ON public.shopping_list_items(shopping_list_id); CREATE INDEX idx_purchases_household_date ON public.purchases(household_id, purchase_date); CREATE INDEX idx_price_observations_product ON public.price_observations(product_id); CREATE INDEX idx_price_observations_observed ON public.price_observations(observed_at); CREATE INDEX idx_rate_limit_events_user_action ON public.rate_limit_events(user_id, action, created_at);