295 lines
11 KiB
PHP
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;
|
|
}
|
|
}
|