CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE TABLE IF NOT EXISTS customers ( customer_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), first_name TEXT NOT NULL, last_name TEXT NOT NULL, email TEXT UNIQUE, phone TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS vehicles ( vehicle_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), customer_id UUID NOT NULL REFERENCES customers(customer_id) ON DELETE CASCADE, registration_number TEXT NOT NULL UNIQUE, make TEXT NOT NULL, model TEXT NOT NULL, model_year INTEGER CHECK (model_year BETWEEN 1886 AND EXTRACT(YEAR FROM CURRENT_DATE)::INTEGER + 1), engine TEXT, transmission TEXT, fuel_type TEXT, color TEXT, vin CHAR(17) UNIQUE, mileage INTEGER NOT NULL DEFAULT 0 CHECK (mileage >= 0), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS employees ( employee_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), first_name TEXT NOT NULL, last_name TEXT NOT NULL, role TEXT NOT NULL CHECK (role IN ('technician', 'service_advisor', 'manager')), email TEXT UNIQUE, active BOOLEAN NOT NULL DEFAULT TRUE ); CREATE TABLE IF NOT EXISTS appointments ( appointment_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), vehicle_id UUID NOT NULL REFERENCES vehicles(vehicle_id) ON DELETE CASCADE, employee_id UUID REFERENCES employees(employee_id) ON DELETE SET NULL, scheduled_at TIMESTAMPTZ NOT NULL, reason TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'scheduled' CHECK (status IN ('scheduled', 'confirmed', 'completed', 'cancelled', 'no_show')), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS service_orders ( service_order_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), vehicle_id UUID NOT NULL REFERENCES vehicles(vehicle_id) ON DELETE RESTRICT, assigned_employee_id UUID REFERENCES employees(employee_id) ON DELETE SET NULL, opened_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), closed_at TIMESTAMPTZ, complaint TEXT NOT NULL, service_date DATE NOT NULL DEFAULT CURRENT_DATE, diagnosis TEXT, status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'in_progress', 'waiting_for_parts', 'ready', 'closed', 'cancelled')), CHECK (closed_at IS NULL OR closed_at >= opened_at) ); CREATE TABLE IF NOT EXISTS service_order_items ( item_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), service_order_id UUID NOT NULL REFERENCES service_orders(service_order_id) ON DELETE CASCADE, item_type TEXT NOT NULL CHECK (item_type IN ('labor', 'part')), description TEXT NOT NULL, part_number TEXT, labor_time_minutes INTEGER CHECK (labor_time_minutes IS NULL OR labor_time_minutes > 0), quantity NUMERIC(10, 2) NOT NULL DEFAULT 1 CHECK (quantity > 0), unit_price NUMERIC(12, 2) NOT NULL CHECK (unit_price >= 0), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS invoices ( invoice_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), service_order_id UUID NOT NULL UNIQUE REFERENCES service_orders(service_order_id) ON DELETE RESTRICT, issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), due_at DATE, status TEXT NOT NULL DEFAULT 'unpaid' CHECK (status IN ('draft', 'unpaid', 'paid', 'void')), tax_rate NUMERIC(5, 2) NOT NULL DEFAULT 0 CHECK (tax_rate >= 0 AND tax_rate <= 100) ); CREATE TABLE IF NOT EXISTS app_users ( user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), username TEXT NOT NULL UNIQUE, display_name TEXT NOT NULL, email TEXT UNIQUE, role TEXT NOT NULL DEFAULT 'viewer' CHECK (role IN ('admin', 'service_advisor', 'technician', 'viewer')), can_read BOOLEAN NOT NULL DEFAULT TRUE, can_add BOOLEAN NOT NULL DEFAULT FALSE, can_delete BOOLEAN NOT NULL DEFAULT FALSE, password_hash TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_vehicles_customer_id ON vehicles(customer_id); CREATE INDEX IF NOT EXISTS idx_appointments_scheduled_at ON appointments(scheduled_at); CREATE INDEX IF NOT EXISTS idx_service_orders_vehicle_id ON service_orders(vehicle_id); CREATE INDEX IF NOT EXISTS idx_service_orders_status ON service_orders(status); INSERT INTO customers (first_name, last_name, email, phone) VALUES ('Alex', 'Morgan', 'alex.morgan@example.com', '+1-555-0100') ON CONFLICT (email) DO NOTHING; INSERT INTO app_users (username, display_name, role, can_read, can_add, can_delete) VALUES ('admin', 'Workshop administrator', 'admin', TRUE, TRUE, TRUE) ON CONFLICT (username) DO NOTHING;