-- ============================================================ -- NEXT-ERP.PRO -- Migration complete v4.0 -- ============================================================ CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS pgcrypto; -- 1. TRIAL ACCOUNTS CREATE TABLE IF NOT EXISTS trial_accounts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, email TEXT NOT NULL, ip_address TEXT, ip_hash TEXT, trial_start TIMESTAMPTZ DEFAULT NOW(), trial_end TIMESTAMPTZ DEFAULT (NOW() + INTERVAL '7 days'), status TEXT DEFAULT 'active' CHECK (status IN ('active','expired','converted','blocked')), reminder_day2_sent BOOLEAN DEFAULT FALSE, reminder_day4_sent BOOLEAN DEFAULT FALSE, reminder_day5_sent BOOLEAN DEFAULT FALSE, reminder_day7_sent BOOLEAN DEFAULT FALSE, reminder_day10_sent BOOLEAN DEFAULT FALSE, converted_at TIMESTAMPTZ, converted_plan TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_trial_email ON trial_accounts(email); CREATE INDEX IF NOT EXISTS idx_trial_ip ON trial_accounts(ip_hash); CREATE INDEX IF NOT EXISTS idx_trial_status ON trial_accounts(status); -- 2. SUBSCRIPTIONS CREATE TABLE IF NOT EXISTS subscriptions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, stripe_customer_id TEXT, stripe_subscription_id TEXT UNIQUE, stripe_price_id TEXT, interval TEXT, status TEXT DEFAULT 'active', current_period_start TIMESTAMPTZ, current_period_end TIMESTAMPTZ, cancel_at_period_end BOOLEAN DEFAULT FALSE, promo_code TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_sub_user ON subscriptions(user_id); CREATE INDEX IF NOT EXISTS idx_sub_stripe ON subscriptions(stripe_subscription_id); -- 3. BLOCKED DOMAINS CREATE TABLE IF NOT EXISTS blocked_domains ( id SERIAL PRIMARY KEY, domain TEXT UNIQUE NOT NULL, reason TEXT, blocked_at TIMESTAMPTZ DEFAULT NOW() ); INSERT INTO blocked_domains (domain, reason) VALUES ('mailinator.com', 'disposable'), ('guerrillamail.com', 'disposable'), ('tempmail.com', 'disposable'), ('yopmail.com', 'disposable'), ('trashmail.com', 'disposable'), ('10minutemail.com', 'disposable'), ('throwaway.email', 'disposable'), ('spam4.me', 'disposable') ON CONFLICT (domain) DO NOTHING; -- 4. SECURITY LOGS CREATE TABLE IF NOT EXISTS security_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), event_type TEXT NOT NULL, email TEXT, ip_hash TEXT, device_hash TEXT, metadata JSONB, created_at TIMESTAMPTZ DEFAULT NOW() ); -- 5. COMPANIES CREATE TABLE IF NOT EXISTS companies ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, name TEXT NOT NULL, legal_form TEXT, vat_number TEXT, registration_number TEXT, address TEXT, city TEXT, zip TEXT, country TEXT DEFAULT 'BE', phone TEXT, email TEXT, website TEXT, logo_url TEXT, iban TEXT, bic TEXT, default_currency TEXT DEFAULT 'EUR', default_vat_rate NUMERIC(5,2) DEFAULT 21.00, invoice_prefix TEXT DEFAULT 'FAC', invoice_counter INTEGER DEFAULT 1, quote_prefix TEXT DEFAULT 'DEV', quote_counter INTEGER DEFAULT 1, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- 6. CLIENTS CREATE TABLE IF NOT EXISTS clients ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, type TEXT DEFAULT 'company', name TEXT NOT NULL, contact_name TEXT, email TEXT, phone TEXT, address TEXT, city TEXT, zip TEXT, country TEXT DEFAULT 'BE', vat_number TEXT, payment_terms INTEGER DEFAULT 30, credit_limit NUMERIC(12,2), status TEXT DEFAULT 'active', notes TEXT, tags TEXT[], created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_clients_company ON clients(company_id); CREATE INDEX IF NOT EXISTS idx_clients_status ON clients(status); CREATE INDEX IF NOT EXISTS idx_clients_name ON clients(name); -- 7. INVOICES CREATE TABLE IF NOT EXISTS invoices ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, client_id UUID REFERENCES clients(id), type TEXT DEFAULT 'invoice', number TEXT UNIQUE, status TEXT DEFAULT 'draft', issue_date DATE DEFAULT CURRENT_DATE, due_date DATE, paid_date DATE, currency TEXT DEFAULT 'EUR', subtotal NUMERIC(12,2) DEFAULT 0, vat_rate NUMERIC(5,2) DEFAULT 21.00, vat_amount NUMERIC(12,2) DEFAULT 0, discount_percent NUMERIC(5,2) DEFAULT 0, discount_amount NUMERIC(12,2) DEFAULT 0, total NUMERIC(12,2) DEFAULT 0, paid_amount NUMERIC(12,2) DEFAULT 0, notes TEXT, footer_text TEXT, pdf_url TEXT, stripe_payment_link TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_invoices_company ON invoices(company_id); CREATE INDEX IF NOT EXISTS idx_invoices_client ON invoices(client_id); CREATE INDEX IF NOT EXISTS idx_invoices_status ON invoices(status); -- 8. INVOICE LINES CREATE TABLE IF NOT EXISTS invoice_lines ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), invoice_id UUID REFERENCES invoices(id) ON DELETE CASCADE, description TEXT NOT NULL, quantity NUMERIC(10,3) DEFAULT 1, unit_price NUMERIC(12,2) DEFAULT 0, vat_rate NUMERIC(5,2) DEFAULT 21.00, discount_percent NUMERIC(5,2) DEFAULT 0, line_total NUMERIC(12,2) DEFAULT 0, sort_order INTEGER DEFAULT 0 ); CREATE INDEX IF NOT EXISTS idx_lines_invoice ON invoice_lines(invoice_id); -- 9. EMPLOYEES CREATE TABLE IF NOT EXISTS employees ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, first_name TEXT NOT NULL, last_name TEXT NOT NULL, email TEXT, phone TEXT, national_id TEXT, birth_date DATE, hire_date DATE NOT NULL, end_date DATE, contract_type TEXT DEFAULT 'cdi', job_title TEXT, department TEXT, gross_salary NUMERIC(10,2) DEFAULT 0, net_salary NUMERIC(10,2) DEFAULT 0, onss_employee NUMERIC(10,2) DEFAULT 0, onss_employer NUMERIC(10,2) DEFAULT 0, professional_tax NUMERIC(10,2) DEFAULT 0, iban TEXT, bic TEXT, status TEXT DEFAULT 'active', avatar_url TEXT, address TEXT, city TEXT, zip TEXT, country TEXT DEFAULT 'BE', notes TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_employees_company ON employees(company_id); CREATE INDEX IF NOT EXISTS idx_employees_status ON employees(status); -- 10. PAYSLIPS CREATE TABLE IF NOT EXISTS payslips ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, employee_id UUID REFERENCES employees(id) ON DELETE CASCADE, period_month INTEGER, period_year INTEGER, gross_salary NUMERIC(10,2) DEFAULT 0, net_salary NUMERIC(10,2) DEFAULT 0, onss_employee NUMERIC(10,2) DEFAULT 0, onss_employer NUMERIC(10,2) DEFAULT 0, professional_tax NUMERIC(10,2) DEFAULT 0, meal_vouchers NUMERIC(10,2) DEFAULT 0, eco_vouchers NUMERIC(10,2) DEFAULT 0, bonuses NUMERIC(10,2) DEFAULT 0, deductions NUMERIC(10,2) DEFAULT 0, advance_deducted NUMERIC(10,2) DEFAULT 0, total_employer_cost NUMERIC(10,2) DEFAULT 0, pdf_url TEXT, status TEXT DEFAULT 'draft', paid_date DATE, created_at TIMESTAMPTZ DEFAULT NOW() ); -- 11. LEAVES CREATE TABLE IF NOT EXISTS leaves ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, employee_id UUID REFERENCES employees(id) ON DELETE CASCADE, type TEXT DEFAULT 'annual', start_date DATE NOT NULL, end_date DATE NOT NULL, days_count NUMERIC(4,1) DEFAULT 1, status TEXT DEFAULT 'pending', reason TEXT, approved_by UUID, approved_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW() ); -- 12. SALARY ADVANCES CREATE TABLE IF NOT EXISTS salary_advances ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, employee_id UUID REFERENCES employees(id) ON DELETE CASCADE, amount NUMERIC(10,2) NOT NULL, request_date DATE DEFAULT CURRENT_DATE, payment_date DATE, repayment_date DATE, status TEXT DEFAULT 'pending', notes TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); -- 13. STOCK CATEGORIES CREATE TABLE IF NOT EXISTS stock_categories ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, name TEXT NOT NULL, description TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); -- 14. STOCK ITEMS CREATE TABLE IF NOT EXISTS stock_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, category_id UUID REFERENCES stock_categories(id), reference TEXT, name TEXT NOT NULL, description TEXT, unit TEXT DEFAULT 'piece', quantity_in_stock NUMERIC(12,3) DEFAULT 0, quantity_min NUMERIC(12,3) DEFAULT 0, quantity_max NUMERIC(12,3), unit_price_buy NUMERIC(12,2) DEFAULT 0, unit_price_sell NUMERIC(12,2) DEFAULT 0, vat_rate NUMERIC(5,2) DEFAULT 21.00, location TEXT, supplier TEXT, barcode TEXT, image_url TEXT, status TEXT DEFAULT 'active', created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_stock_company ON stock_items(company_id); -- 15. STOCK MOVEMENTS CREATE TABLE IF NOT EXISTS stock_movements ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, item_id UUID REFERENCES stock_items(id) ON DELETE CASCADE, type TEXT, quantity NUMERIC(12,3) NOT NULL, quantity_before NUMERIC(12,3), quantity_after NUMERIC(12,3), unit_price NUMERIC(12,2), reference TEXT, notes TEXT, created_by UUID, created_at TIMESTAMPTZ DEFAULT NOW() ); -- 16. VEHICLES CREATE TABLE IF NOT EXISTS vehicles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, brand TEXT NOT NULL, model TEXT NOT NULL, license_plate TEXT NOT NULL, year INTEGER, fuel_type TEXT, vin TEXT, purchase_date DATE, purchase_price NUMERIC(12,2), current_value NUMERIC(12,2), mileage INTEGER DEFAULT 0, status TEXT DEFAULT 'active', assigned_to UUID REFERENCES employees(id), insurance_company TEXT, insurance_policy TEXT, insurance_expiry DATE, ct_date DATE, ct_expiry DATE, notes TEXT, image_url TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- 17. VEHICLE MAINTENANCES CREATE TABLE IF NOT EXISTS vehicle_maintenances ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, vehicle_id UUID REFERENCES vehicles(id) ON DELETE CASCADE, type TEXT, description TEXT, date DATE NOT NULL, mileage INTEGER, cost NUMERIC(10,2) DEFAULT 0, provider TEXT, invoice_ref TEXT, next_due_date DATE, next_due_mileage INTEGER, created_at TIMESTAMPTZ DEFAULT NOW() ); -- 18. PROJECTS CREATE TABLE IF NOT EXISTS projects ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, client_id UUID REFERENCES clients(id), name TEXT NOT NULL, description TEXT, status TEXT DEFAULT 'planning', priority TEXT DEFAULT 'medium', start_date DATE, end_date DATE, budget NUMERIC(12,2), spent NUMERIC(12,2) DEFAULT 0, progress INTEGER DEFAULT 0, manager_id UUID REFERENCES employees(id), color TEXT DEFAULT '#1D9E75', tags TEXT[], created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- 19. PROJECT TASKS CREATE TABLE IF NOT EXISTS project_tasks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id UUID REFERENCES projects(id) ON DELETE CASCADE, title TEXT NOT NULL, description TEXT, status TEXT DEFAULT 'todo', priority TEXT DEFAULT 'medium', assigned_to UUID REFERENCES employees(id), due_date DATE, estimated_hours NUMERIC(6,2), actual_hours NUMERIC(6,2), sort_order INTEGER DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- 20. AI CREDITS CREATE TABLE IF NOT EXISTS ai_credits ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, total_credits INTEGER DEFAULT 500, used_credits INTEGER DEFAULT 0, reset_date TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- 21. AI MESSAGES CREATE TABLE IF NOT EXISTS ai_messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, role TEXT, content TEXT NOT NULL, credits_used INTEGER DEFAULT 1, model TEXT DEFAULT 'gpt-4o-mini', created_at TIMESTAMPTZ DEFAULT NOW() ); -- 22. EXPENSES CREATE TABLE IF NOT EXISTS expenses ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, category TEXT NOT NULL, description TEXT NOT NULL, amount NUMERIC(12,2) NOT NULL, vat_amount NUMERIC(12,2) DEFAULT 0, date DATE DEFAULT CURRENT_DATE, supplier TEXT, invoice_ref TEXT, payment_method TEXT DEFAULT 'bank', status TEXT DEFAULT 'pending', receipt_url TEXT, notes TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); -- 23. USER PROFILES CREATE TABLE IF NOT EXISTS user_profiles ( id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, email TEXT NOT NULL, full_name TEXT, avatar_url TEXT, role TEXT DEFAULT 'owner', language TEXT DEFAULT 'fr', timezone TEXT DEFAULT 'Europe/Brussels', notifications_email BOOLEAN DEFAULT TRUE, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================================ -- FUNCTIONS & TRIGGERS -- ============================================================ CREATE OR REPLACE FUNCTION public.handle_new_user() RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN INSERT INTO public.user_profiles (id, email, full_name) VALUES (NEW.id, NEW.email, COALESCE(NEW.raw_user_meta_data->>'full_name', NEW.email)) ON CONFLICT (id) DO NOTHING; INSERT INTO public.ai_credits (user_id) VALUES (NEW.id) ON CONFLICT DO NOTHING; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users; CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); CREATE OR REPLACE FUNCTION public.set_updated_at() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$; DROP TRIGGER IF EXISTS set_companies_updated_at ON companies; CREATE TRIGGER set_companies_updated_at BEFORE UPDATE ON companies FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); DROP TRIGGER IF EXISTS set_clients_updated_at ON clients; CREATE TRIGGER set_clients_updated_at BEFORE UPDATE ON clients FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); DROP TRIGGER IF EXISTS set_invoices_updated_at ON invoices; CREATE TRIGGER set_invoices_updated_at BEFORE UPDATE ON invoices FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); DROP TRIGGER IF EXISTS set_employees_updated_at ON employees; CREATE TRIGGER set_employees_updated_at BEFORE UPDATE ON employees FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); DROP TRIGGER IF EXISTS set_stock_items_updated_at ON stock_items; CREATE TRIGGER set_stock_items_updated_at BEFORE UPDATE ON stock_items FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); DROP TRIGGER IF EXISTS set_vehicles_updated_at ON vehicles; CREATE TRIGGER set_vehicles_updated_at BEFORE UPDATE ON vehicles FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); DROP TRIGGER IF EXISTS set_projects_updated_at ON projects; CREATE TRIGGER set_projects_updated_at BEFORE UPDATE ON projects FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); -- ============================================================ -- ROW LEVEL SECURITY -- ============================================================ ALTER TABLE user_profiles ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS user_profiles_policy ON user_profiles; CREATE POLICY user_profiles_policy ON user_profiles FOR ALL USING (auth.uid() = id); ALTER TABLE ai_credits ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS ai_credits_policy ON ai_credits; CREATE POLICY ai_credits_policy ON ai_credits FOR ALL USING (auth.uid() = user_id); ALTER TABLE companies ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS companies_policy ON companies; CREATE POLICY companies_policy ON companies FOR ALL USING (auth.uid() = user_id); ALTER TABLE clients ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS clients_policy ON clients; CREATE POLICY clients_policy ON clients FOR ALL USING ( company_id IN (SELECT id FROM companies WHERE user_id = auth.uid()) ); ALTER TABLE invoices ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS invoices_policy ON invoices; CREATE POLICY invoices_policy ON invoices FOR ALL USING ( company_id IN (SELECT id FROM companies WHERE user_id = auth.uid()) ); ALTER TABLE employees ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS employees_policy ON employees; CREATE POLICY employees_policy ON employees FOR ALL USING ( company_id IN (SELECT id FROM companies WHERE user_id = auth.uid()) ); ALTER TABLE projects ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS projects_policy ON projects; CREATE POLICY projects_policy ON projects FOR ALL USING ( company_id IN (SELECT id FROM companies WHERE user_id = auth.uid()) ); -- ============================================================ -- VERIFICATION -- ============================================================ SELECT 'Migration Next-ERP.PRO v4.0 complete' AS result;