db->table_exists($table))return;foreach($columns as$c)if(!$this->db->field_exists($c,$table))return;if(!$this->db->query("SHOW INDEX FROM `$table` WHERE Key_name=".$this->db->escape($name))->row())$this->db->query("ALTER TABLE `$table` ADD INDEX `$name` (`".implode('`,`',$columns)."`)");} public function up(){ $tables=array( 'ci_sessions'=>"id VARCHAR(128) NOT NULL,ip_address VARCHAR(45) NOT NULL,timestamp INT UNSIGNED NOT NULL DEFAULT 0,data BLOB NOT NULL,PRIMARY KEY(id),KEY idx_ci_sessions_timestamp(timestamp)", 'dashboard_summaries'=>"id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,company_id BIGINT UNSIGNED NOT NULL,period CHAR(7) NOT NULL,total_revenue DECIMAL(18,2) NOT NULL DEFAULT 0,total_expense DECIMAL(18,2) NOT NULL DEFAULT 0,net_profit DECIMAL(18,2) NOT NULL DEFAULT 0,journal_count BIGINT UNSIGNED NOT NULL DEFAULT 0,computed_at DATETIME NOT NULL,expires_at DATETIME NOT NULL,UNIQUE KEY uq_dashboard_summary(company_id,period),KEY idx_dashboard_expiry(expires_at)", 'app_jobs'=>"id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,company_id BIGINT UNSIGNED NULL,queue_name VARCHAR(40) NOT NULL DEFAULT 'default',job_type VARCHAR(60) NOT NULL,payload LONGTEXT NOT NULL,status ENUM('queued','processing','completed','retry','dead') NOT NULL DEFAULT 'queued',priority TINYINT NOT NULL DEFAULT 5,attempts INT NOT NULL DEFAULT 0,max_attempts INT NOT NULL DEFAULT 3,available_at DATETIME NOT NULL,locked_at DATETIME NULL,locked_by VARCHAR(100) NULL,last_error TEXT NULL,idempotency_key VARCHAR(140) NOT NULL,created_at DATETIME NOT NULL,completed_at DATETIME NULL,UNIQUE KEY uq_app_job_key(idempotency_key),KEY idx_app_job_claim(status,queue_name,available_at,priority,id)", 'app_dead_letters'=>"id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,job_id BIGINT UNSIGNED NOT NULL,company_id BIGINT UNSIGNED NULL,job_type VARCHAR(60) NOT NULL,payload LONGTEXT NOT NULL,attempts INT NOT NULL,last_error TEXT NULL,failed_at DATETIME NOT NULL,KEY idx_dead_letter_date(failed_at),KEY idx_dead_letter_type(job_type)", 'database_health_metrics'=>"id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,checked_at DATETIME NOT NULL,connection_ms DECIMAL(12,3) NOT NULL,query_ms DECIMAL(12,3) NOT NULL,threads_connected INT NULL,threads_running INT NULL,slow_queries BIGINT NULL,db_size_mb DECIMAL(14,2) NULL,status ENUM('healthy','warning','critical') NOT NULL,message VARCHAR(255) NULL,KEY idx_db_health_date(checked_at,status)" );foreach($tables as$t=>$sql)$this->db->query("CREATE TABLE IF NOT EXISTS `$t`($sql) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); $indexes=array( array('journals','idx_journal_company_status_date',array('company_id','status','tanggal','id')), array('journal_details','idx_jd_company_account_journal',array('company_id','account_id','journal_id')), array('invoices','idx_invoice_company_workflow_due',array('company_id','workflow_status','jatuh_tempo','customer_id')), array('payments','idx_payment_company_customer_date',array('company_id','customer_id','tanggal')), array('supplier_invoices','idx_bill_company_status_due',array('company_id','status','due_date','supplier_id')), array('supplier_payments','idx_spay_company_supplier_date',array('company_id','supplier_id','payment_date')), array('inventory_ledger','idx_ledger_item_wh_date',array('item_id','warehouse_id','movement_date','id')), array('stock_documents','idx_stock_doc_status_date',array('status','document_date','item_id')), array('items','idx_items_company_active_name',array('company_id','is_active','nama_barang')), array('customers','idx_customer_company_name',array('company_id','nama')), array('suppliers','idx_supplier_company_active_name',array('company_id','is_active','name')), array('accounts','idx_account_company_active_code',array('company_id','is_active','kode_akun')), array('k_employees','idx_employee_company_active_name',array('company_id','is_active','nama')), array('activity_logs','idx_activity_created_user',array('created_at','user_id')), array('approval_requests','idx_approval_status_created',array('status','created_at')), array('report_export_jobs','idx_report_job_claim',array('status','requested_at','id')) );foreach($indexes as$i)$this->index($i[0],$i[1],$i[2]); foreach(array('report_export_jobs')as$t){if($this->db->table_exists($t)){if(!$this->db->field_exists('attempts',$t))$this->db->query("ALTER TABLE `$t` ADD attempts INT NOT NULL DEFAULT 0");if(!$this->db->field_exists('max_attempts',$t))$this->db->query("ALTER TABLE `$t` ADD max_attempts INT NOT NULL DEFAULT 3");if(!$this->db->field_exists('available_at',$t))$this->db->query("ALTER TABLE `$t` ADD available_at DATETIME NULL");}} } public function down(){throw new RuntimeException('Gunakan backup untuk rollback fondasi skalabilitas.');} }