@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class CreateSchoolYears extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! $this->db->tableExists('school_years')) {
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'name' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 9,
|
||||
'null' => false,
|
||||
],
|
||||
'status' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 20,
|
||||
'null' => false,
|
||||
'default' => 'draft',
|
||||
],
|
||||
'starts_on' => [
|
||||
'type' => 'DATE',
|
||||
'null' => true,
|
||||
],
|
||||
'ends_on' => [
|
||||
'type' => 'DATE',
|
||||
'null' => true,
|
||||
],
|
||||
'description' => [
|
||||
'type' => 'TEXT',
|
||||
'null' => true,
|
||||
],
|
||||
'registration_starts_on' => [
|
||||
'type' => 'DATE',
|
||||
'null' => true,
|
||||
],
|
||||
'registration_ends_on' => [
|
||||
'type' => 'DATE',
|
||||
'null' => true,
|
||||
],
|
||||
'previous_school_year_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'next_school_year_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'activated_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'closing_started_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'closed_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'archived_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'created_by' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'updated_by' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'created_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'updated_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey('name', false, true);
|
||||
$this->forge->addKey('status');
|
||||
$this->forge->createTable('school_years');
|
||||
} else {
|
||||
$this->ensureSchoolYearColumns();
|
||||
}
|
||||
|
||||
$this->createClosingTables();
|
||||
|
||||
$configuredYear = $this->configuredSchoolYear();
|
||||
|
||||
if ($configuredYear !== null && $this->isValidYearName($configuredYear)) {
|
||||
$existing = $this->db->table('school_years')
|
||||
->where('name', $configuredYear)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if ($existing === null) {
|
||||
$this->db->table('school_years')->insert([
|
||||
'name' => $configuredYear,
|
||||
'status' => 'active',
|
||||
'activated_at' => date('Y-m-d H:i:s'),
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('school_year_transition_logs', true);
|
||||
$this->forge->dropTable('school_year_closing_items', true);
|
||||
$this->forge->dropTable('school_year_closing_batches', true);
|
||||
$this->forge->dropTable('school_years', true);
|
||||
}
|
||||
|
||||
private function ensureSchoolYearColumns(): void
|
||||
{
|
||||
$fields = $this->db->getFieldNames('school_years');
|
||||
$add = [];
|
||||
|
||||
$definitions = [
|
||||
'description' => ['type' => 'TEXT', 'null' => true],
|
||||
'registration_starts_on' => ['type' => 'DATE', 'null' => true],
|
||||
'registration_ends_on' => ['type' => 'DATE', 'null' => true],
|
||||
'previous_school_year_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
|
||||
'next_school_year_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
|
||||
'activated_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'closing_started_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'closed_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'archived_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'created_by' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
|
||||
'updated_by' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
|
||||
];
|
||||
|
||||
foreach ($definitions as $field => $definition) {
|
||||
if (! in_array($field, $fields, true)) {
|
||||
$add[$field] = $definition;
|
||||
}
|
||||
}
|
||||
|
||||
if ($add !== []) {
|
||||
$this->forge->addColumn('school_years', $add);
|
||||
}
|
||||
}
|
||||
|
||||
private function createClosingTables(): void
|
||||
{
|
||||
if (! $this->db->tableExists('school_year_closing_batches')) {
|
||||
$this->forge->addField([
|
||||
'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
|
||||
'source_school_year_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
|
||||
'target_school_year_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
|
||||
'status' => ['type' => 'VARCHAR', 'constraint' => 30, 'null' => false, 'default' => 'preview'],
|
||||
'preview_hash' => ['type' => 'CHAR', 'constraint' => 64, 'null' => true],
|
||||
'total_families' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => false, 'default' => 0],
|
||||
'total_positive_balance' => ['type' => 'DECIMAL', 'constraint' => '12,2', 'null' => false, 'default' => '0.00'],
|
||||
'total_credit_balance' => ['type' => 'DECIMAL', 'constraint' => '12,2', 'null' => false, 'default' => '0.00'],
|
||||
'started_by' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
|
||||
'completed_by' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
|
||||
'started_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'completed_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'failed_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'failure_message' => ['type' => 'TEXT', 'null' => true],
|
||||
'created_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'updated_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey(['source_school_year_id', 'status']);
|
||||
$this->forge->createTable('school_year_closing_batches');
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('school_year_closing_items')) {
|
||||
$this->forge->addField([
|
||||
'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
|
||||
'closing_batch_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
|
||||
'family_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
|
||||
'source_balance' => ['type' => 'DECIMAL', 'constraint' => '12,2', 'null' => false, 'default' => '0.00'],
|
||||
'credit_amount' => ['type' => 'DECIMAL', 'constraint' => '12,2', 'null' => false, 'default' => '0.00'],
|
||||
'adjustment_amount' => ['type' => 'DECIMAL', 'constraint' => '12,2', 'null' => false, 'default' => '0.00'],
|
||||
'carry_forward_amount' => ['type' => 'DECIMAL', 'constraint' => '12,2', 'null' => false, 'default' => '0.00'],
|
||||
'target_invoice_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
|
||||
'target_adjustment_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
|
||||
'status' => ['type' => 'VARCHAR', 'constraint' => 30, 'null' => false, 'default' => 'pending'],
|
||||
'error_message' => ['type' => 'TEXT', 'null' => true],
|
||||
'created_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'updated_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey(['closing_batch_id', 'family_id'], false, true);
|
||||
$this->forge->createTable('school_year_closing_items');
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('school_year_transition_logs')) {
|
||||
$this->forge->addField([
|
||||
'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
|
||||
'school_year_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
|
||||
'from_status' => ['type' => 'VARCHAR', 'constraint' => 20, 'null' => true],
|
||||
'to_status' => ['type' => 'VARCHAR', 'constraint' => 20, 'null' => true],
|
||||
'action' => ['type' => 'VARCHAR', 'constraint' => 80, 'null' => false],
|
||||
'performed_by' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
|
||||
'metadata_json' => ['type' => 'TEXT', 'null' => true],
|
||||
'created_at' => ['type' => 'DATETIME', 'null' => false],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey(['school_year_id', 'created_at']);
|
||||
$this->forge->createTable('school_year_transition_logs');
|
||||
}
|
||||
}
|
||||
|
||||
private function configuredSchoolYear(): ?string
|
||||
{
|
||||
if (! $this->db->tableExists('configuration')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = $this->db->table('configuration')
|
||||
->select('config_value')
|
||||
->where('config_key', 'school_year')
|
||||
->orderBy('id', 'DESC')
|
||||
->get(1)
|
||||
->getRowArray();
|
||||
|
||||
$value = trim((string) ($row['config_value'] ?? ''));
|
||||
|
||||
return $value !== '' ? $value : null;
|
||||
}
|
||||
|
||||
private function isValidYearName(string $value): bool
|
||||
{
|
||||
if (! preg_match('/^(\d{4})-(\d{4})$/', $value, $matches)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (int) $matches[2] === (int) $matches[1] + 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user