Files
alrahma_sunday_school/app/Database/Migrations/2026-05-30-000001_FinancialSystemLedgerCleanup.php
T
2026-06-07 00:27:27 -04:00

295 lines
11 KiB
PHP

<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class FinancialSystemLedgerCleanup extends Migration
{
public function up()
{
$this->addInstallmentSequenceColumn();
$this->ensureIndexes();
$this->ensureConfigurationDefaults();
$this->archivePaypalTables();
$this->refreshFinancialNavItems();
}
public function down()
{
$this->restorePaypalTables();
if ($this->db->tableExists('payments') && $this->db->fieldExists('installment_seq', 'payments')) {
$this->forge->dropColumn('payments', 'installment_seq');
}
$this->dropIndexIfExists('payments', 'idx_payments_invoice_id');
$this->dropIndexIfExists('payments', 'idx_payments_parent_year_semester');
$this->dropIndexIfExists('payments', 'uniq_payments_transaction_id');
$this->dropIndexIfExists('invoices', 'idx_invoices_parent_year_semester');
$this->dropIndexIfExists('discount_usages', 'idx_discount_usages_invoice_id');
$this->dropIndexIfExists('discount_usages', 'idx_discount_usages_parent_year_semester');
$this->dropIndexIfExists('refunds', 'idx_refunds_invoice_id');
$this->dropIndexIfExists('refunds', 'idx_refunds_parent_year_semester_status');
$this->dropIndexIfExists('additional_charges', 'idx_additional_charges_invoice_id');
$this->dropIndexIfExists('additional_charges', 'idx_additional_charges_parent_year_semester_status');
$this->dropIndexIfExists('invoice_event', 'idx_invoice_event_invoice_id');
if ($this->db->tableExists('nav_items')) {
$forecastRow = $this->db->table('nav_items')
->select('id')
->where('url', 'administrator/tuition-forecast')
->get()
->getRowArray();
if ($forecastRow && $this->db->tableExists('role_nav_items')) {
$this->db->table('role_nav_items')
->where('nav_item_id', (int) $forecastRow['id'])
->delete();
}
$this->db->table('nav_items')
->where('url', 'administrator/tuition-forecast')
->delete();
}
}
protected function addInstallmentSequenceColumn(): void
{
if ($this->db->tableExists('payments') && !$this->db->fieldExists('installment_seq', 'payments')) {
$this->forge->addColumn('payments', [
'installment_seq' => [
'type' => 'INT',
'constraint' => 11,
'null' => true,
'after' => 'number_of_installments',
],
]);
}
}
protected function ensureIndexes(): void
{
$this->addIndexIfMissing('payments', 'idx_payments_invoice_id', ['invoice_id']);
$this->addIndexIfMissing('payments', 'idx_payments_parent_year_semester', ['parent_id', 'school_year', 'semester']);
$this->addIndexIfMissing('invoices', 'idx_invoices_parent_year_semester', ['parent_id', 'school_year', 'semester']);
$this->addIndexIfMissing('discount_usages', 'idx_discount_usages_invoice_id', ['invoice_id']);
$this->addIndexIfMissing('discount_usages', 'idx_discount_usages_parent_year_semester', ['parent_id', 'school_year', 'semester']);
$this->addIndexIfMissing('refunds', 'idx_refunds_invoice_id', ['invoice_id']);
$this->addIndexIfMissing('refunds', 'idx_refunds_parent_year_semester_status', ['parent_id', 'school_year', 'semester', 'status']);
$this->addIndexIfMissing('additional_charges', 'idx_additional_charges_invoice_id', ['invoice_id']);
$this->addIndexIfMissing('additional_charges', 'idx_additional_charges_parent_year_semester_status', ['parent_id', 'school_year', 'semester', 'status']);
$this->addIndexIfMissing('invoice_event', 'idx_invoice_event_invoice_id', ['invoice_id']);
if ($this->db->tableExists('payments') && $this->db->fieldExists('transaction_id', 'payments') && !$this->hasDuplicateTransactionIds()) {
$this->addIndexIfMissing('payments', 'uniq_payments_transaction_id', ['transaction_id'], true);
}
}
protected function ensureConfigurationDefaults(): void
{
if (!$this->db->tableExists('configuration')) {
return;
}
$defaults = [
'tuition_calculator_version' => 'old',
'youth_fee' => '200.00',
'new_tuition_full_amount' => '370.00',
'new_tuition_youth_amount' => '200.00',
'new_tuition_second_student_discount' => '50.00',
'new_tuition_third_student_discount' => '50.00',
'new_tuition_fourth_plus_discount' => '100.00',
];
foreach ($defaults as $key => $value) {
$row = $this->db->table('configuration')
->select('id, config_value')
->where('config_key', $key)
->orderBy('id', 'ASC')
->get()
->getRowArray();
if (!$row) {
$this->db->table('configuration')->insert([
'config_key' => $key,
'config_value' => $value,
]);
continue;
}
if (trim((string) ($row['config_value'] ?? '')) === '') {
$this->db->table('configuration')
->where('config_key', $key)
->update(['config_value' => $value]);
}
}
}
protected function archivePaypalTables(): void
{
$this->renameTableIfPresent('paypal_payments', 'archived_paypal_payments');
$this->renameTableIfPresent('paypal_transactions', 'archived_paypal_transactions');
}
protected function restorePaypalTables(): void
{
$this->renameTableIfPresent('archived_paypal_payments', 'paypal_payments');
$this->renameTableIfPresent('archived_paypal_transactions', 'paypal_transactions');
}
protected function refreshFinancialNavItems(): void
{
if (!$this->db->tableExists('nav_items')) {
return;
}
$parentColumn = null;
if ($this->db->fieldExists('menu_parent_id', 'nav_items')) {
$parentColumn = 'menu_parent_id';
} elseif ($this->db->fieldExists('parent_id', 'nav_items')) {
$parentColumn = 'parent_id';
}
if ($parentColumn === null) {
return;
}
$this->db->table('nav_items')
->groupStart()
->where('url', 'administrator/paypal_transactions')
->orWhere('url', 'admin/paypal-transactions')
->orWhere('label', 'PaypalTransactions')
->orWhere('label', 'PayPal Transactions')
->groupEnd()
->delete();
$financialRow = $this->db->table('nav_items')
->select('id')
->where('label', 'Financial')
->where($parentColumn, null)
->get()
->getRowArray();
if (!$financialRow) {
return;
}
$forecastUrl = 'administrator/tuition-forecast';
$existing = $this->db->table('nav_items')
->select('id')
->where('url', $forecastUrl)
->get()
->getRowArray();
if ($existing) {
$forecastId = (int) $existing['id'];
} else {
$this->db->table('nav_items')->insert([
$parentColumn => (int) $financialRow['id'],
'label' => 'Tuition Forecast',
'url' => $forecastUrl,
'sort_order' => 4,
'is_enabled' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
$forecastId = (int) $this->db->insertID();
}
if ($forecastId <= 0 || !$this->db->tableExists('role_nav_items') || !$this->db->tableExists('roles')) {
return;
}
$roles = $this->db->table('roles')
->select('id')
->whereIn('name', ['administrator', 'administrative staff', 'principal', 'vice_principal', 'head of department (finance)'])
->get()
->getResultArray();
foreach ($roles as $role) {
$roleId = (int) ($role['id'] ?? 0);
if ($roleId <= 0) {
continue;
}
$exists = $this->db->table('role_nav_items')
->where('role_id', $roleId)
->where('nav_item_id', $forecastId)
->get()
->getRowArray();
if ($exists) {
continue;
}
$this->db->table('role_nav_items')->insert([
'role_id' => $roleId,
'nav_item_id' => $forecastId,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
}
protected function renameTableIfPresent(string $from, string $to): void
{
if (!$this->db->tableExists($from) || $this->db->tableExists($to)) {
return;
}
$this->db->query(sprintf('RENAME TABLE `%s` TO `%s`', $from, $to));
}
protected function addIndexIfMissing(string $table, string $indexName, array $columns, bool $unique = false): void
{
if (!$this->db->tableExists($table) || $this->indexExists($table, $indexName)) {
return;
}
$quotedColumns = implode(', ', array_map(static fn (string $column): string => '`' . $column . '`', $columns));
$type = $unique ? 'UNIQUE INDEX' : 'INDEX';
$sql = sprintf('ALTER TABLE `%s` ADD %s `%s` (%s)', $table, $type, $indexName, $quotedColumns);
$this->db->query($sql);
}
protected function dropIndexIfExists(string $table, string $indexName): void
{
if (!$this->db->tableExists($table) || !$this->indexExists($table, $indexName)) {
return;
}
$this->db->query(sprintf('ALTER TABLE `%s` DROP INDEX `%s`', $table, $indexName));
}
protected function indexExists(string $table, string $indexName): bool
{
if (!$this->db->tableExists($table)) {
return false;
}
$rows = $this->db->query(sprintf('SHOW INDEX FROM `%s`', $table))->getResultArray();
foreach ($rows as $row) {
if (($row['Key_name'] ?? null) === $indexName) {
return true;
}
}
return false;
}
protected function hasDuplicateTransactionIds(): bool
{
$row = $this->db->table('payments')
->select('transaction_id')
->where('transaction_id IS NOT NULL', null, false)
->where('transaction_id !=', '')
->groupBy('transaction_id')
->having('COUNT(*) >', 1, false)
->get(1)
->getRowArray();
return $row !== null;
}
}