-- Users table CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, username VARCHAR(100) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, role VARCHAR(20) NOT NULL DEFAULT 'user' CHECK (role IN ('admin', 'user')), mfa_secret VARCHAR(255), mfa_enabled BOOLEAN DEFAULT FALSE, must_change_password BOOLEAN DEFAULT TRUE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Field definitions table (dynamic fields configured by admin) CREATE TABLE IF NOT EXISTS field_definitions ( id SERIAL PRIMARY KEY, field_key VARCHAR(100) UNIQUE NOT NULL, -- internal key e.g. "first_name" label VARCHAR(200) NOT NULL, -- display label e.g. "First Name" field_type VARCHAR(20) NOT NULL DEFAULT 'text' CHECK (field_type IN ('text', 'number', 'phone', 'email')), required BOOLEAN DEFAULT FALSE, sort_order INTEGER DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Contracts table with JSONB data (fully dynamic) CREATE TABLE IF NOT EXISTS mobile_contracts ( id SERIAL PRIMARY KEY, data JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Audit log table CREATE TABLE IF NOT EXISTS audit_log ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id), action VARCHAR(100) NOT NULL, details TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Seed default fields INSERT INTO field_definitions (field_key, label, field_type, required, sort_order) VALUES ('first_name', 'First Name', 'text', TRUE, 1), ('last_name', 'Last Name', 'text', TRUE, 2), ('phone_number', 'Phone Number', 'phone', FALSE, 3), ('mobile_contract_number', 'Mobile Contract Number', 'text', FALSE, 4), ('monthly_fixed_cost', 'Monthly Fixed Cost', 'number', FALSE, 5), ('monthly_available_data', 'Monthly Available Data', 'number', FALSE, 6) ON CONFLICT (field_key) DO NOTHING;