-- ============================================================ -- CVPP CONSTRUCTION SUITE - DATABASE SCHEMA -- MySQL 5.7+ / MariaDB 10.3+ -- Designed for cPanel shared hosting (GoDaddy) -- ============================================================ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ============================================================ -- USERS & ACCESS CONTROL -- ============================================================ CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, full_name VARCHAR(100) NOT NULL, phone VARCHAR(30) DEFAULT NULL, role ENUM('master','project_manager','foreman','installer','viewer') NOT NULL DEFAULT 'viewer', permissions TEXT DEFAULT NULL COMMENT 'JSON of module permissions', avatar_color VARCHAR(20) DEFAULT '#f5a524', language VARCHAR(2) DEFAULT 'es', is_active TINYINT(1) DEFAULT 1, last_login DATETIME DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_role (role), INDEX idx_active (is_active) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Sessions for secure login CREATE TABLE IF NOT EXISTS sessions ( id VARCHAR(128) PRIMARY KEY, user_id INT NOT NULL, ip_address VARCHAR(45), user_agent TEXT, last_activity DATETIME DEFAULT CURRENT_TIMESTAMP, expires_at DATETIME NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_expires (expires_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ============================================================ -- COMPANIES (Clients / GCs that hire us) -- ============================================================ CREATE TABLE IF NOT EXISTS companies ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(200) NOT NULL, contact_name VARCHAR(100), email VARCHAR(100), phone VARCHAR(30), address TEXT, city VARCHAR(80), state VARCHAR(2), zip VARCHAR(10), tax_id VARCHAR(30), notes TEXT, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ============================================================ -- PROJECTS -- ============================================================ CREATE TABLE IF NOT EXISTS projects ( id INT AUTO_INCREMENT PRIMARY KEY, project_code VARCHAR(20) NOT NULL UNIQUE COMMENT 'PRJ-2826', name VARCHAR(200) NOT NULL, company_id INT, address TEXT, city VARCHAR(80), state VARCHAR(2), zip VARCHAR(10), total_sf DECIMAL(10,2) DEFAULT 0, contract_amount DECIMAL(12,2) DEFAULT NULL, status ENUM('pending','active','on_hold','done','cancelled') DEFAULT 'pending', start_date DATE, due_date DATE, completed_date DATE, project_manager_id INT, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE SET NULL, FOREIGN KEY (project_manager_id) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_status (status), INDEX idx_company (company_id), INDEX idx_dates (due_date, completed_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Project assignments (which users work on which project) CREATE TABLE IF NOT EXISTS project_assignments ( id INT AUTO_INCREMENT PRIMARY KEY, project_id INT NOT NULL, user_id INT NOT NULL, role_in_project VARCHAR(50) DEFAULT 'installer', assigned_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE KEY uniq_assignment (project_id, user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ============================================================ -- BUILDINGS / FLOORS / AREAS (Project hierarchy) -- ============================================================ CREATE TABLE IF NOT EXISTS buildings ( id INT AUTO_INCREMENT PRIMARY KEY, project_id INT NOT NULL, name VARCHAR(100) NOT NULL, description TEXT, sort_order INT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE, INDEX idx_project (project_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS floors ( id INT AUTO_INCREMENT PRIMARY KEY, building_id INT NOT NULL, name VARCHAR(100) NOT NULL, floor_number INT, sort_order INT DEFAULT 0, FOREIGN KEY (building_id) REFERENCES buildings(id) ON DELETE CASCADE, INDEX idx_building (building_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS areas ( id INT AUTO_INCREMENT PRIMARY KEY, floor_id INT NOT NULL, name VARCHAR(100) NOT NULL, product_type VARCHAR(100) COMMENT 'LVP, Tile, Carpet, etc', product_detail VARCHAR(200) COMMENT 'COREtec Pro Plus 7"', total_sf DECIMAL(10,2) DEFAULT 0, installed_sf DECIMAL(10,2) DEFAULT 0, progress_pct DECIMAL(5,2) DEFAULT 0, status ENUM('not_started','in_progress','completed','on_hold') DEFAULT 'not_started', sort_order INT DEFAULT 0, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (floor_id) REFERENCES floors(id) ON DELETE CASCADE, INDEX idx_floor (floor_id), INDEX idx_status (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Daily progress log per area CREATE TABLE IF NOT EXISTS progress_logs ( id INT AUTO_INCREMENT PRIMARY KEY, area_id INT NOT NULL, user_id INT, log_date DATE NOT NULL, sf_installed DECIMAL(10,2) DEFAULT 0, notes TEXT, photo_path VARCHAR(255), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (area_id) REFERENCES areas(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_area_date (area_id, log_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ============================================================ -- MATERIALS & SUPPLIERS -- ============================================================ CREATE TABLE IF NOT EXISTS suppliers ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(150) NOT NULL, contact_name VARCHAR(100), email VARCHAR(100), phone VARCHAR(30), address TEXT, notes TEXT, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS material_orders ( id INT AUTO_INCREMENT PRIMARY KEY, po_number VARCHAR(20) NOT NULL UNIQUE COMMENT 'PO-2826', project_id INT NOT NULL, supplier_id INT, order_date DATE NOT NULL, eta_date DATE, received_date DATE, status ENUM('draft','ordered','partial','received','delayed','cancelled') DEFAULT 'draft', total_amount DECIMAL(12,2) DEFAULT 0, notes TEXT, created_by INT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE, FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL, FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_status (status), INDEX idx_project (project_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS material_order_items ( id INT AUTO_INCREMENT PRIMARY KEY, order_id INT NOT NULL, product_name VARCHAR(200) NOT NULL, product_code VARCHAR(50), quantity DECIMAL(10,2) NOT NULL, unit VARCHAR(20) DEFAULT 'SF' COMMENT 'SF, BOX, ROLL, BAG, etc', unit_price DECIMAL(10,2) DEFAULT 0, quantity_received DECIMAL(10,2) DEFAULT 0, notes TEXT, FOREIGN KEY (order_id) REFERENCES material_orders(id) ON DELETE CASCADE, INDEX idx_order (order_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ============================================================ -- SITE INVENTORY (where material is stored) -- ============================================================ CREATE TABLE IF NOT EXISTS storage_locations ( id INT AUTO_INCREMENT PRIMARY KEY, location_code VARCHAR(20) NOT NULL UNIQUE COMMENT 'LOC-001', name VARCHAR(150) NOT NULL, project_id INT, description TEXT, is_offsite TINYINT(1) DEFAULT 0, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE SET NULL, INDEX idx_project (project_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS inventory_items ( id INT AUTO_INCREMENT PRIMARY KEY, location_id INT NOT NULL, product_name VARCHAR(200) NOT NULL, quantity DECIMAL(10,2) NOT NULL DEFAULT 0, unit VARCHAR(20) DEFAULT 'SF', source_order_id INT COMMENT 'Optional link to PO that delivered this', last_updated_by INT, last_updated DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, notes TEXT, FOREIGN KEY (location_id) REFERENCES storage_locations(id) ON DELETE CASCADE, FOREIGN KEY (source_order_id) REFERENCES material_orders(id) ON DELETE SET NULL, FOREIGN KEY (last_updated_by) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_location (location_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Inventory movement log (for audit trail) CREATE TABLE IF NOT EXISTS inventory_movements ( id INT AUTO_INCREMENT PRIMARY KEY, item_id INT, from_location_id INT, to_location_id INT, quantity DECIMAL(10,2) NOT NULL, movement_type ENUM('receive','transfer','consume','adjust','return') NOT NULL, reference_id INT COMMENT 'Optional PO/area reference', user_id INT, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_item (item_id), INDEX idx_date (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ============================================================ -- TIMECLOCK / ATTENDANCE -- ============================================================ CREATE TABLE IF NOT EXISTS timeclock ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, project_id INT, check_in DATETIME NOT NULL, check_out DATETIME DEFAULT NULL, hours_worked DECIMAL(5,2) DEFAULT 0, check_in_lat DECIMAL(10,7), check_in_lng DECIMAL(10,7), check_out_lat DECIMAL(10,7), check_out_lng DECIMAL(10,7), location_label VARCHAR(150), is_overtime TINYINT(1) DEFAULT 0, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE SET NULL, INDEX idx_user_date (user_id, check_in), INDEX idx_project (project_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ============================================================ -- BILLING (Ready-to-bill queue + tickets + payments) -- ============================================================ CREATE TABLE IF NOT EXISTS billing_queue ( id INT AUTO_INCREMENT PRIMARY KEY, project_id INT NOT NULL, area_id INT NOT NULL, description VARCHAR(255) COMMENT 'Override or additional detail', quantity DECIMAL(10,2), unit VARCHAR(20) DEFAULT 'SF', unit_price DECIMAL(10,2) DEFAULT NULL COMMENT 'NULL = TBD', amount DECIMAL(12,2) DEFAULT NULL, progress_pct DECIMAL(5,2), is_partial TINYINT(1) DEFAULT 0, ticket_id INT DEFAULT NULL COMMENT 'NULL = still in queue', added_by INT, added_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE, FOREIGN KEY (area_id) REFERENCES areas(id) ON DELETE CASCADE, FOREIGN KEY (added_by) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_ticket (ticket_id), INDEX idx_area (area_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS billing_tickets ( id INT AUTO_INCREMENT PRIMARY KEY, ticket_number VARCHAR(20) NOT NULL UNIQUE COMMENT 'BT-2026-001', project_id INT NOT NULL, company_id INT NOT NULL, issue_date DATE NOT NULL, sent_date DATE DEFAULT NULL, due_date DATE DEFAULT NULL, subtotal DECIMAL(12,2) DEFAULT 0, tbd_lines INT DEFAULT 0, total_amount DECIMAL(12,2) DEFAULT 0, paid_amount DECIMAL(12,2) DEFAULT 0, status ENUM('draft','sent','partial','paid','void') DEFAULT 'draft', notes TEXT, pdf_path VARCHAR(255), created_by INT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE, FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE, FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_status (status), INDEX idx_company (company_id), INDEX idx_date (issue_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS billing_payments ( id INT AUTO_INCREMENT PRIMARY KEY, ticket_id INT NOT NULL, payment_date DATE NOT NULL, amount DECIMAL(12,2) NOT NULL, method VARCHAR(50) COMMENT 'wire, check, zelle, ach', reference VARCHAR(100) COMMENT 'check # or wire ref', notes TEXT, recorded_by INT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (ticket_id) REFERENCES billing_tickets(id) ON DELETE CASCADE, FOREIGN KEY (recorded_by) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_ticket (ticket_id), INDEX idx_date (payment_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Add foreign key from billing_queue to billing_tickets ALTER TABLE billing_queue ADD CONSTRAINT fk_queue_ticket FOREIGN KEY (ticket_id) REFERENCES billing_tickets(id) ON DELETE SET NULL; -- ============================================================ -- ACTIVITY LOG (audit trail) -- ============================================================ CREATE TABLE IF NOT EXISTS activity_log ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT, action VARCHAR(80) NOT NULL, entity_type VARCHAR(50), entity_id INT, description TEXT, ip_address VARCHAR(45), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_user (user_id), INDEX idx_entity (entity_type, entity_id), INDEX idx_date (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; SET FOREIGN_KEY_CHECKS = 1; -- ============================================================ -- DEFAULT MASTER USER (password: ChangeMe2026!) -- IMPORTANT: Change password immediately after first login -- The hash below is for: ChangeMe2026! -- ============================================================ INSERT INTO users (username, email, password_hash, full_name, role, language) VALUES ( 'master', 'master@cvppapps.com', '$2y$10$f.tDVK8wFjIEdsjWsnLrn.2BSLDdP2HWIdyt.UT0fXUplnhZtxPs6', 'Master Admin', 'master', 'es' ) ON DUPLICATE KEY UPDATE id=id;