-- v2.5 Service Orders (Kanban) for workshop management CREATE TABLE IF NOT EXISTS service_orders ( id SERIAL PRIMARY KEY, tenant_id INTEGER NOT NULL, branch_id INTEGER REFERENCES branches(id), customer_id INTEGER REFERENCES customers(id), vehicle_id INTEGER REFERENCES fleet_vehicles(id), -- optional link to fleet order_number VARCHAR(50) UNIQUE, -- human-readable SO-2026-0001 status VARCHAR(30) DEFAULT 'received', -- received, diagnosis, waiting_parts, repair, quality_check, ready, delivered, cancelled priority VARCHAR(20) DEFAULT 'normal', -- low, normal, high, urgent reception_notes TEXT, diagnosis_notes TEXT, repair_notes TEXT, delivery_notes TEXT, estimated_cost NUMERIC(12,2), final_cost NUMERIC(12,2), estimated_completion TIMESTAMPTZ, actual_completion TIMESTAMPTZ, delivered_at TIMESTAMPTZ, delivered_by INTEGER REFERENCES employees(id), employee_id INTEGER REFERENCES employees(id), -- assigned mechanic/technician mileage_in INTEGER, mileage_out INTEGER, fuel_level VARCHAR(20), -- empty, quarter, half, three_quarters, full created_by INTEGER REFERENCES employees(id), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_so_status ON service_orders(status); CREATE INDEX IF NOT EXISTS idx_so_customer ON service_orders(customer_id); CREATE INDEX IF NOT EXISTS idx_so_branch ON service_orders(branch_id); CREATE INDEX IF NOT EXISTS idx_so_created ON service_orders(created_at DESC); -- Service order items (parts needed/used) CREATE TABLE IF NOT EXISTS service_order_items ( id SERIAL PRIMARY KEY, service_order_id INTEGER NOT NULL REFERENCES service_orders(id) ON DELETE CASCADE, inventory_id INTEGER REFERENCES inventory(id), part_number VARCHAR(50), name VARCHAR(300), quantity NUMERIC(10,2) DEFAULT 1, unit_cost NUMERIC(12,2), unit_price NUMERIC(12,2), status VARCHAR(20) DEFAULT 'pending', -- pending, ordered, received, installed, cancelled notes TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_soi_order ON service_order_items(service_order_id); -- Service order labor / work items CREATE TABLE IF NOT EXISTS service_order_labor ( id SERIAL PRIMARY KEY, service_order_id INTEGER NOT NULL REFERENCES service_orders(id) ON DELETE CASCADE, description TEXT NOT NULL, hours NUMERIC(6,2) DEFAULT 0, hourly_rate NUMERIC(12,2) DEFAULT 0, total_cost NUMERIC(12,2) DEFAULT 0, employee_id INTEGER REFERENCES employees(id), -- mechanic who did the work status VARCHAR(20) DEFAULT 'pending', -- pending, in_progress, completed created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_sol_order ON service_order_labor(service_order_id); -- Status history for audit trail CREATE TABLE IF NOT EXISTS service_order_status_history ( id SERIAL PRIMARY KEY, service_order_id INTEGER NOT NULL REFERENCES service_orders(id) ON DELETE CASCADE, old_status VARCHAR(30), new_status VARCHAR(30) NOT NULL, changed_by INTEGER REFERENCES employees(id), notes TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_sosh_order ON service_order_status_history(service_order_id); -- Trigger to auto-update updated_at CREATE OR REPLACE FUNCTION update_so_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS trg_service_orders_updated_at ON service_orders; CREATE TRIGGER trg_service_orders_updated_at BEFORE UPDATE ON service_orders FOR EACH ROW EXECUTE FUNCTION update_so_updated_at();