hr dan payroll

This commit is contained in:
Wian Drs
2026-09-15 10:38:43 +07:00
parent 5e1fd11410
commit 8fedd39d8f
47 changed files with 1409 additions and 370 deletions
@@ -0,0 +1,45 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Migration_Expand_company_bank_accounts extends CI_Migration
{
private function column($name, $definition)
{
if (!$this->db->field_exists($name, 'company_bank_accounts')) {
$this->db->query("ALTER TABLE `company_bank_accounts` ADD `{$name}` {$definition}");
}
}
public function up()
{
if (!$this->db->table_exists('company_bank_accounts')) return;
$this->column('product_name', "VARCHAR(120) NULL AFTER `account_holder`");
$this->column('account_type', "ENUM('savings','deposit') NOT NULL DEFAULT 'savings' AFTER `product_name`");
$this->column('interest_rate', "DECIMAL(7,4) NOT NULL DEFAULT 0 AFTER `account_type`");
$this->column('opened_at', "DATE NULL AFTER `interest_rate`");
$this->column('maturity_date', "DATE NULL AFTER `opened_at`");
$this->column('gl_account_id', "INT NULL AFTER `currency`");
if ($this->db->table_exists('cash_accounts')) {
$this->db->query("UPDATE company_bank_accounts b
JOIN cash_accounts ca ON ca.bank_account_no=b.account_number
AND (ca.company_id=b.company_id OR ca.company_id IS NULL)
JOIN accounts a ON a.id=ca.gl_account_id AND a.company_id=b.company_id
SET b.gl_account_id=ca.gl_account_id
WHERE b.gl_account_id IS NULL");
}
if (!$this->db->query("SHOW INDEX FROM company_bank_accounts WHERE Key_name='idx_company_bank_report'")->row()) {
$this->db->query("ALTER TABLE company_bank_accounts ADD KEY idx_company_bank_report(company_id,account_type,is_active,maturity_date)");
}
if (!$this->db->query("SHOW INDEX FROM company_bank_accounts WHERE Key_name='idx_company_bank_gl'")->row()) {
$this->db->query("ALTER TABLE company_bank_accounts ADD KEY idx_company_bank_gl(company_id,gl_account_id)");
}
}
public function down()
{
throw new RuntimeException('Metadata rekening perusahaan dipertahankan untuk audit dan dokumen lama.');
}
}
@@ -0,0 +1,36 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* Memulihkan skema pembayaran pembelian pada database yang metadata
* migrasinya sudah maju, tetapi kolom biaya bank belum ikut terpasang.
*/
class Migration_Repair_purchase_payment_bank_charge_schema extends CI_Migration
{
public function up()
{
if (!$this->db->table_exists('supplier_payments')) {
throw new RuntimeException('Tabel supplier_payments belum tersedia. Jalankan migrasi pembelian sebelumnya terlebih dahulu.');
}
if (!$this->db->field_exists('bank_charge_amount', 'supplier_payments')) {
$this->db->query('ALTER TABLE supplier_payments ADD bank_charge_amount DECIMAL(18,2) NOT NULL DEFAULT 0 AFTER amount');
}
if (!$this->db->field_exists('bank_charge_account_id', 'supplier_payments')) {
$this->db->query('ALTER TABLE supplier_payments ADD bank_charge_account_id INT NULL AFTER bank_charge_amount');
}
if (!$this->db->field_exists('total_cash_out', 'supplier_payments')) {
$this->db->query('ALTER TABLE supplier_payments ADD total_cash_out DECIMAL(18,2) NOT NULL DEFAULT 0 AFTER bank_charge_account_id');
}
// Pembayaran lama tidak mempunyai biaya bank, sehingga total kas
// keluar sama dengan nilai pokok pembayaran yang sudah tersimpan.
$this->db->query('UPDATE supplier_payments SET bank_charge_amount=0 WHERE bank_charge_amount IS NULL');
$this->db->query('UPDATE supplier_payments SET total_cash_out=amount+bank_charge_amount WHERE total_cash_out IS NULL OR total_cash_out=0');
}
public function down()
{
throw new RuntimeException('Kolom pembayaran pembelian dipertahankan untuk menjaga histori dan audit transaksi.');
}
}
@@ -0,0 +1,28 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* Memulihkan ENUM supplier_invoices.status pada database hasil clone yang
* versi migrasinya sudah maju, tetapi definisi kolomnya masih versi lama.
*/
class Migration_Repair_supplier_invoice_status_enum extends CI_Migration
{
public function up()
{
if (!$this->db->table_exists('supplier_invoices')) {
throw new RuntimeException('Tabel supplier_invoices belum tersedia. Jalankan migrasi pembelian sebelumnya terlebih dahulu.');
}
// Gunakan ENUM transisi supaya seluruh nilai legacy tetap valid saat
// dikonversi ke workflow supplier invoice yang berlaku sekarang.
$this->db->query("ALTER TABLE supplier_invoices MODIFY status ENUM('draft','submitted','approved','verified','posted','partial','paid','cancelled','reversed') NOT NULL DEFAULT 'verified'");
$this->db->query("UPDATE supplier_invoices SET status='verified' WHERE status IN('draft','submitted','approved')");
$this->db->query("UPDATE supplier_invoices SET status=IF(COALESCE(balance,0)<=0,'paid','partial') WHERE status='posted'");
$this->db->query("ALTER TABLE supplier_invoices MODIFY status ENUM('verified','partial','paid','cancelled','reversed') NOT NULL DEFAULT 'verified'");
}
public function down()
{
throw new RuntimeException('Status supplier invoice dipertahankan untuk menjaga histori dan workflow pembayaran.');
}
}
@@ -0,0 +1,49 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* Memperbaiki akun sistem yang dibuat setelah fondasi multi-company sehingga
* company_id-nya masih NULL dan ditolak saat jurnal divalidasi.
*/
class Migration_Repair_system_account_company_scope extends CI_Migration
{
public function up()
{
if (!$this->db->table_exists('companies')) return;
$company = $this->db->where('is_active', 1)->order_by('id')->get('companies')->row();
if (!$company) {
throw new RuntimeException('Perusahaan aktif tidak ditemukan untuk memperbaiki scope akun sistem.');
}
$companyId = (int) $company->id;
if ($this->db->table_exists('accounts') && $this->db->field_exists('company_id', 'accounts')) {
$this->db->where('company_id IS NULL OR company_id=0', null, false)
->update('accounts', array('company_id'=>$companyId));
}
if ($this->db->table_exists('system_account_mappings') && $this->db->field_exists('company_id', 'system_account_mappings')) {
$this->db->query(
'UPDATE system_account_mappings m '
.'JOIN accounts a ON a.id=m.account_id '
.'SET m.company_id=COALESCE(NULLIF(a.company_id,0),?) '
.'WHERE m.company_id IS NULL OR m.company_id=0',
array($companyId)
);
}
$mapping = $this->db->select('m.account_id,a.tipe,a.is_active,a.allow_posting,a.company_id')
->from('system_account_mappings m')
->join('accounts a', 'a.id=m.account_id')
->where(array('m.mapping_key'=>'goods_in_transit','m.is_active'=>1))
->get()->row();
if (!$mapping || $mapping->tipe !== 'asset' || !(int)$mapping->is_active || !(int)$mapping->allow_posting || !(int)$mapping->company_id) {
throw new RuntimeException('Mapping Barang Dalam Perjalanan belum menunjuk akun Aktiva aktif milik perusahaan.');
}
}
public function down()
{
throw new RuntimeException('Scope perusahaan pada akun sistem dipertahankan untuk integritas jurnal.');
}
}