Files
accounting_dev_v2/application/migrations/20260906000400_finalize_fixed_assets.php
2026-09-11 16:03:00 +07:00

204 lines
13 KiB
PHP

<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Migration_Finalize_fixed_assets extends CI_Migration
{
private function column($table, $column, $definition)
{
if ($this->db->table_exists($table) && !$this->db->field_exists($column, $table)) {
$this->db->query("ALTER TABLE `{$table}` ADD `{$column}` {$definition}");
}
}
private function indexExists($table, $name)
{
return $this->db->query("SHOW INDEX FROM `{$table}` WHERE Key_name=?", array($name))->num_rows() > 0;
}
private function addIndex($table, $name, $columns, $unique = false)
{
if ($this->db->table_exists($table) && !$this->indexExists($table, $name)) {
$this->db->query('ALTER TABLE `'.$table.'` ADD '.($unique ? 'UNIQUE ' : '').'KEY `'.$name.'` ('.$columns.')');
}
}
public function up()
{
$this->db->query('DROP TRIGGER IF EXISTS trg_asset_event_no_update');
$this->db->query('DROP TRIGGER IF EXISTS trg_asset_event_no_delete');
$assetColumns = array(
'document_no' => 'VARCHAR(60) NULL',
'workflow_status' => "ENUM('draft','submitted','approved','rejected','capitalized') NOT NULL DEFAULT 'capitalized'",
'source_reference_no' => 'VARCHAR(120) NULL',
'supplier_id' => 'BIGINT UNSIGNED NULL',
'source_journal_mode' => "ENUM('post_journal','linked_journal','no_journal') NOT NULL DEFAULT 'no_journal'",
'source_journal_id' => 'INT NULL',
'acquisition_journal_id' => 'INT NULL',
'warehouse_id' => 'INT NULL',
'bin_id' => 'BIGINT UNSIGNED NULL',
'responsible_employee_id' => 'BIGINT NULL',
'department_id' => 'BIGINT NULL',
'project_id' => 'BIGINT UNSIGNED NULL',
'cost_center_id' => 'BIGINT UNSIGNED NULL',
'idempotency_key' => 'VARCHAR(120) NULL',
'created_by' => 'INT NULL',
'submitted_by' => 'INT NULL',
'submitted_at' => 'DATETIME NULL',
'approved_by' => 'INT NULL',
'approved_at' => 'DATETIME NULL',
'rejected_by' => 'INT NULL',
'rejected_at' => 'DATETIME NULL',
'rejection_reason' => 'VARCHAR(500) NULL',
'capitalized_by' => 'INT NULL',
'capitalized_at' => 'DATETIME NULL',
'updated_by' => 'INT NULL',
'updated_at' => 'DATETIME NULL'
);
foreach ($assetColumns as $column => $definition) $this->column('assets', $column, $definition);
$this->db->query("ALTER TABLE assets MODIFY source_type ENUM('purchase','warehouse','manual','opening','donation','other') NOT NULL DEFAULT 'manual'");
$this->db->query("UPDATE assets SET company_id=COALESCE(company_id,1),document_no=COALESCE(NULLIF(document_no,''),CONCAT('LEGACY-AST-',id)),workflow_status=COALESCE(NULLIF(workflow_status,''),'capitalized'),idempotency_key=COALESCE(NULLIF(idempotency_key,''),CONCAT('LEGACY-ASSET-',id)),created_by=COALESCE(created_by,1),capitalized_at=COALESCE(capitalized_at,created_at),capitalized_by=COALESCE(capitalized_by,created_by,1)");
$this->addIndex('assets', 'uq_asset_document_company', '`company_id`,`document_no`', true);
$this->addIndex('assets', 'uq_asset_idempotency', '`idempotency_key`', true);
$this->addIndex('assets', 'idx_asset_register_filter', '`company_id`,`workflow_status`,`lifecycle_status`,`category_id`,`lokasi_asset_id`,`tanggal_perolehan`');
$this->addIndex('assets', 'idx_asset_source_trace', '`company_id`,`source_type`,`source_id`');
$this->addIndex('assets', 'idx_asset_inventory_trace', '`item_id`,`barcode_id`,`warehouse_id`');
foreach (array(
'company_id' => 'BIGINT UNSIGNED NULL',
'updated_at' => 'DATETIME NULL',
'updated_by' => 'INT NULL'
) as $column => $definition) $this->column('asset_categories', $column, $definition);
if ($this->indexExists('asset_categories', 'uq_asset_category_code')) $this->db->query('ALTER TABLE asset_categories DROP INDEX uq_asset_category_code');
$this->db->query('UPDATE asset_categories SET company_id=COALESCE(company_id,1)');
$this->addIndex('asset_categories', 'uq_asset_category_company_code', '`company_id`,`code`', true);
$this->addIndex('asset_categories', 'idx_asset_category_active', '`company_id`,`is_active`,`name`');
foreach (array(
'company_id' => 'BIGINT UNSIGNED NULL',
'code' => 'VARCHAR(30) NULL',
'address' => 'TEXT NULL',
'responsible_employee_id' => 'BIGINT NULL',
'is_active' => 'TINYINT(1) NOT NULL DEFAULT 1',
'updated_at' => 'DATETIME NULL',
'updated_by' => 'INT NULL'
) as $column => $definition) $this->column('lokasi_asset', $column, $definition);
$this->db->query("UPDATE lokasi_asset SET company_id=COALESCE(company_id,1),code=COALESCE(NULLIF(code,''),CONCAT('LOC-',LPAD(id,4,'0'))),is_active=COALESCE(is_active,1)");
$this->addIndex('lokasi_asset', 'uq_asset_location_company_code', '`company_id`,`code`', true);
$this->addIndex('lokasi_asset', 'idx_asset_location_active', '`company_id`,`is_active`,`nama`');
foreach (array(
'company_id' => 'BIGINT UNSIGNED NULL',
'reversal_of_event_id' => 'BIGINT UNSIGNED NULL'
) as $column => $definition) $this->column('asset_events', $column, $definition);
$this->db->query("ALTER TABLE asset_events MODIFY event_type ENUM('acquisition','capitalization','addition','transfer','responsibility_change','maintenance','depreciation','impairment','revaluation','disposal','lost','damaged','opname_adjustment','reversal') NOT NULL");
$this->db->query('UPDATE asset_events e JOIN assets a ON a.id=e.asset_id SET e.company_id=COALESCE(e.company_id,a.company_id,1)');
$this->addIndex('asset_events', 'idx_asset_event_company_date', '`company_id`,`event_date`,`asset_id`');
$this->addIndex('asset_events', 'idx_asset_event_journal', '`journal_id`');
$this->addIndex('asset_events', 'idx_asset_event_reversal', '`reversal_of_event_id`');
$this->column('asset_depreciation_schedule', 'company_id', 'BIGINT UNSIGNED NULL');
$this->column('asset_depreciation_schedule', 'reversal_journal_id', 'INT NULL');
$this->column('asset_depreciation_schedule', 'reversed_at', 'DATETIME NULL');
$this->db->query('UPDATE asset_depreciation_schedule s JOIN assets a ON a.id=s.asset_id SET s.company_id=COALESCE(s.company_id,a.company_id,1)');
$this->addIndex('asset_depreciation_schedule', 'idx_asset_dep_company_period', '`company_id`,`period`,`status`,`asset_id`');
foreach (array(
'company_id' => 'BIGINT UNSIGNED NULL',
'maintenance_no' => 'VARCHAR(60) NULL',
'status' => "ENUM('posted','cancelled') NOT NULL DEFAULT 'posted'",
'journal_id' => 'INT NULL',
'asset_event_id' => 'BIGINT UNSIGNED NULL',
'idempotency_key' => 'VARCHAR(120) NULL'
) as $column => $definition) $this->column('asset_maintenance', $column, $definition);
$this->db->query("UPDATE asset_maintenance m JOIN assets a ON a.id=m.asset_id SET m.company_id=COALESCE(m.company_id,a.company_id,1),m.maintenance_no=COALESCE(NULLIF(m.maintenance_no,''),CONCAT('LEGACY-MNT-',m.id)),m.idempotency_key=COALESCE(NULLIF(m.idempotency_key,''),CONCAT('LEGACY-MNT-',m.id))");
$this->addIndex('asset_maintenance', 'uq_asset_maintenance_no', '`company_id`,`maintenance_no`', true);
$this->addIndex('asset_maintenance', 'uq_asset_maintenance_key', '`idempotency_key`', true);
$this->addIndex('asset_maintenance', 'idx_asset_maintenance_due', '`company_id`,`next_due_date`,`status`');
foreach (array(
'company_id' => 'BIGINT UNSIGNED NULL',
'snapshot_at' => 'DATETIME NULL',
'submitted_by' => 'INT NULL',
'submitted_at' => 'DATETIME NULL',
'rejected_by' => 'INT NULL',
'rejected_at' => 'DATETIME NULL',
'rejection_reason' => 'VARCHAR(500) NULL',
'posted_by' => 'INT NULL',
'posted_at' => 'DATETIME NULL',
'idempotency_key' => 'VARCHAR(120) NULL'
) as $column => $definition) $this->column('asset_opnames', $column, $definition);
$this->db->query("UPDATE asset_opnames SET company_id=COALESCE(company_id,1),snapshot_at=COALESCE(snapshot_at,NOW()),idempotency_key=COALESCE(NULLIF(idempotency_key,''),CONCAT('LEGACY-OPNAME-',id))");
$this->addIndex('asset_opnames', 'uq_asset_opname_key', '`idempotency_key`', true);
$this->addIndex('asset_opnames', 'idx_asset_opname_company_status', '`company_id`,`status`,`opname_date`');
foreach (array(
'company_id' => 'BIGINT UNSIGNED NULL',
'expected_location_id' => 'INT NULL',
'is_found' => 'TINYINT(1) NOT NULL DEFAULT 1',
'checked_at' => 'DATETIME NULL',
'checked_by' => 'INT NULL'
) as $column => $definition) $this->column('asset_opname_lines', $column, $definition);
$this->db->query('UPDATE asset_opname_lines l JOIN asset_opnames o ON o.id=l.asset_opname_id JOIN assets a ON a.id=l.asset_id SET l.company_id=COALESCE(l.company_id,o.company_id,a.company_id,1),l.expected_location_id=COALESCE(l.expected_location_id,a.lokasi_asset_id)');
$this->addIndex('asset_opname_lines', 'idx_asset_opname_line_company', '`company_id`,`asset_opname_id`,`asset_id`');
if ($this->db->table_exists('asset_reconciliations')) {
$this->column('asset_reconciliations', 'company_id', 'BIGINT UNSIGNED NULL');
$this->db->query('UPDATE asset_reconciliations SET company_id=COALESCE(company_id,1)');
$this->addIndex('asset_reconciliations', 'idx_asset_recon_company_date', '`company_id`,`as_of_date`,`status`');
}
$this->db->query("CREATE TABLE IF NOT EXISTS asset_workflow_history (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
company_id BIGINT UNSIGNED NOT NULL,
asset_id INT NOT NULL,
from_status VARCHAR(30) NULL,
to_status VARCHAR(30) NOT NULL,
notes VARCHAR(500) NULL,
user_id INT NULL,
created_at DATETIME NOT NULL,
KEY idx_asset_workflow (company_id,asset_id,id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$this->db->query("CREATE TABLE IF NOT EXISTS asset_responsibility_history (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
company_id BIGINT UNSIGNED NOT NULL,
asset_id INT NOT NULL,
effective_date DATE NOT NULL,
from_employee_id BIGINT NULL,
to_employee_id BIGINT NULL,
notes VARCHAR(500) NOT NULL,
asset_event_id BIGINT UNSIGNED NULL,
created_by INT NULL,
created_at DATETIME NOT NULL,
KEY idx_asset_responsibility (company_id,asset_id,effective_date,id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$this->column('transaction_attachments', 'company_id', 'BIGINT UNSIGNED NULL');
$this->db->query("UPDATE transaction_attachments t JOIN assets a ON t.entity_type='asset' AND t.entity_id=a.id SET t.company_id=COALESCE(t.company_id,a.company_id,1)");
$this->db->query("UPDATE transaction_attachments t JOIN asset_maintenance m ON t.entity_type='asset_maintenance' AND t.entity_id=m.id SET t.company_id=COALESCE(t.company_id,m.company_id,1)");
$this->db->query("UPDATE transaction_attachments t JOIN asset_opnames o ON t.entity_type='asset_opname' AND t.entity_id=o.id SET t.company_id=COALESCE(t.company_id,o.company_id,1)");
$this->addIndex('transaction_attachments', 'idx_attachment_company_entity', '`company_id`,`entity_type`,`entity_id`,`id`');
$this->db->query("CREATE TABLE IF NOT EXISTS asset_report_snapshots (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
company_id BIGINT UNSIGNED NOT NULL,
report_type VARCHAR(40) NOT NULL,
as_of_date DATE NOT NULL,
payload_json LONGTEXT NOT NULL,
created_by INT NULL,
created_at DATETIME NOT NULL,
KEY idx_asset_snapshot (company_id,report_type,as_of_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$this->db->query("INSERT IGNORE INTO asset_workflow_history(company_id,asset_id,from_status,to_status,notes,user_id,created_at) SELECT COALESCE(company_id,1),id,NULL,workflow_status,'Backfill status aset legacy',COALESCE(created_by,1),COALESCE(created_at,NOW()) FROM assets");
$this->db->query("CREATE TRIGGER trg_asset_event_no_update BEFORE UPDATE ON asset_events FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Asset event is immutable'");
$this->db->query("CREATE TRIGGER trg_asset_event_no_delete BEFORE DELETE ON asset_events FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Asset event is immutable'");
}
public function down()
{
throw new RuntimeException('Rollback finalisasi Aset Tetap wajib melalui restore backup database.');
}
}