add school year model

This commit is contained in:
root
2026-06-07 00:52:01 -04:00
parent a192ed433d
commit 6866aedf42
36 changed files with 4771 additions and 88 deletions
@@ -0,0 +1,146 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (!Schema::hasTable('school_years')) {
Schema::create('school_years', function (Blueprint $table) {
$table->id();
$table->string('name', 50)->unique();
$table->date('start_date');
$table->date('end_date');
$table->string('status', 20)->default('draft')->index();
$table->boolean('is_current')->default(false)->index();
$table->timestamp('closed_at')->nullable();
$table->unsignedBigInteger('closed_by')->nullable()->index();
$table->timestamps();
});
}
if (!Schema::hasTable('parent_accounts')) {
Schema::create('parent_accounts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('parent_id')->index();
$table->string('school_year', 50)->index();
$table->decimal('opening_balance', 12, 2)->default(0);
$table->decimal('current_balance', 12, 2)->default(0);
$table->timestamps();
$table->unique(['parent_id', 'school_year'], 'parent_accounts_parent_year_unique');
});
}
if (!Schema::hasTable('parent_balance_transfers')) {
Schema::create('parent_balance_transfers', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('parent_id')->index();
$table->string('from_school_year', 50)->index();
$table->string('to_school_year', 50)->index();
$table->decimal('amount', 12, 2)->default(0);
$table->string('status', 30)->default('pending')->index();
$table->json('source_summary_json')->nullable();
$table->unsignedBigInteger('new_invoice_id')->nullable()->index();
$table->unsignedBigInteger('created_by')->nullable()->index();
$table->timestamps();
$table->unique(['parent_id', 'from_school_year', 'to_school_year'], 'parent_balance_transfers_parent_year_unique');
});
}
if (!Schema::hasTable('audit_logs')) {
Schema::create('audit_logs', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id')->nullable()->index();
$table->string('action', 100)->index();
$table->string('table_name', 100)->nullable()->index();
$table->unsignedBigInteger('record_id')->nullable()->index();
$table->json('old_value')->nullable();
$table->json('new_value')->nullable();
$table->json('metadata')->nullable();
$table->timestamp('created_at')->nullable();
});
}
if (Schema::hasTable('invoices') && !Schema::hasColumn('invoices', 'balance_transfer_id')) {
Schema::table('invoices', function (Blueprint $table) {
$table->unsignedBigInteger('balance_transfer_id')->nullable()->index()->after('semester');
});
}
$this->seedCurrentSchoolYear();
}
public function down(): void
{
if (Schema::hasTable('invoices') && Schema::hasColumn('invoices', 'balance_transfer_id')) {
Schema::table('invoices', function (Blueprint $table) {
$table->dropColumn('balance_transfer_id');
});
}
Schema::dropIfExists('audit_logs');
Schema::dropIfExists('parent_balance_transfers');
Schema::dropIfExists('parent_accounts');
Schema::dropIfExists('school_years');
}
private function seedCurrentSchoolYear(): void
{
if (!Schema::hasTable('configuration') || !Schema::hasTable('school_years')) {
return;
}
$current = DB::table('configuration')
->where('config_key', 'school_year')
->orderByDesc('id')
->value('config_value');
$current = is_string($current) ? trim($current) : '';
if ($current === '') {
return;
}
$exists = DB::table('school_years')->where('name', $current)->exists();
if ($exists) {
DB::table('school_years')->where('name', $current)->update([
'status' => 'active',
'is_current' => true,
'updated_at' => now(),
]);
return;
}
[$startDate, $endDate] = $this->inferDates($current);
DB::table('school_years')->insert([
'name' => $current,
'start_date' => $startDate,
'end_date' => $endDate,
'status' => 'active',
'is_current' => true,
'created_at' => now(),
'updated_at' => now(),
]);
}
private function inferDates(string $schoolYear): array
{
if (preg_match('/^(\\d{4})-(\\d{4})$/', $schoolYear, $matches) === 1) {
return [
sprintf('%s-09-01', $matches[1]),
sprintf('%s-06-30', $matches[2]),
];
}
$year = (int) date('Y');
return [
sprintf('%d-09-01', $year),
sprintf('%d-06-30', $year + 1),
];
}
};
@@ -0,0 +1,165 @@
<?php
use Carbon\CarbonImmutable;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (Schema::hasTable('attendance_management_events')) {
Schema::table('attendance_management_events', function (Blueprint $table) {
if (! Schema::hasColumn('attendance_management_events', 'school_year')) {
$table->string('school_year', 20)->nullable()->after('event_date')->index();
}
if (! Schema::hasColumn('attendance_management_events', 'semester')) {
$table->string('semester', 20)->nullable()->after('school_year')->index();
}
});
DB::table('attendance_management_events')
->select('id', 'event_date', 'school_year', 'semester')
->orderBy('id')
->chunkById(200, function ($rows): void {
foreach ($rows as $row) {
$schoolYear = trim((string) ($row->school_year ?? ''));
$semester = trim((string) ($row->semester ?? ''));
if ($schoolYear !== '' && $semester !== '') {
continue;
}
$date = CarbonImmutable::parse((string) $row->event_date);
DB::table('attendance_management_events')
->where('id', $row->id)
->update([
'school_year' => $schoolYear !== '' ? $schoolYear : $this->deriveSchoolYearForDate($date),
'semester' => $semester !== '' ? $semester : $this->deriveSemesterForDate($date),
]);
}
});
}
if (Schema::hasTable('badge_exceptions')) {
Schema::table('badge_exceptions', function (Blueprint $table) {
if (! Schema::hasColumn('badge_exceptions', 'school_year')) {
$table->string('school_year', 20)->nullable()->after('exception_date')->index();
}
if (! Schema::hasColumn('badge_exceptions', 'semester')) {
$table->string('semester', 20)->nullable()->after('school_year')->index();
}
});
DB::table('badge_exceptions')
->select('id', 'exception_date', 'school_year', 'semester')
->orderBy('id')
->chunkById(200, function ($rows): void {
foreach ($rows as $row) {
$schoolYear = trim((string) ($row->school_year ?? ''));
$semester = trim((string) ($row->semester ?? ''));
if ($schoolYear !== '' && $semester !== '') {
continue;
}
$date = CarbonImmutable::parse((string) $row->exception_date);
DB::table('badge_exceptions')
->where('id', $row->id)
->update([
'school_year' => $schoolYear !== '' ? $schoolYear : $this->deriveSchoolYearForDate($date),
'semester' => $semester !== '' ? $semester : $this->deriveSemesterForDate($date),
]);
}
});
}
if (Schema::hasTable('late_slip_reprints')) {
Schema::table('late_slip_reprints', function (Blueprint $table) {
if (! Schema::hasColumn('late_slip_reprints', 'school_year')) {
$table->string('school_year', 20)->nullable()->after('reprinted_at')->index();
}
if (! Schema::hasColumn('late_slip_reprints', 'semester')) {
$table->string('semester', 20)->nullable()->after('school_year')->index();
}
});
DB::table('late_slip_reprints')
->leftJoin('attendance_management_events as ame', 'ame.id', '=', 'late_slip_reprints.attendance_management_event_id')
->select(
'late_slip_reprints.id',
'late_slip_reprints.reprinted_at',
'late_slip_reprints.school_year',
'late_slip_reprints.semester',
'ame.school_year as event_school_year',
'ame.semester as event_semester',
)
->orderBy('late_slip_reprints.id')
->chunkById(200, function ($rows): void {
foreach ($rows as $row) {
$schoolYear = trim((string) ($row->school_year ?? ''));
$semester = trim((string) ($row->semester ?? ''));
if ($schoolYear !== '' && $semester !== '') {
continue;
}
$date = CarbonImmutable::parse((string) $row->reprinted_at);
DB::table('late_slip_reprints')
->where('id', $row->id)
->update([
'school_year' => $schoolYear !== '' ? $schoolYear : (trim((string) ($row->event_school_year ?? '')) ?: $this->deriveSchoolYearForDate($date)),
'semester' => $semester !== '' ? $semester : (trim((string) ($row->event_semester ?? '')) ?: $this->deriveSemesterForDate($date)),
]);
}
}, 'late_slip_reprints.id', 'id');
}
}
public function down(): void
{
if (Schema::hasTable('late_slip_reprints')) {
Schema::table('late_slip_reprints', function (Blueprint $table) {
if (Schema::hasColumn('late_slip_reprints', 'semester')) {
$table->dropColumn('semester');
}
if (Schema::hasColumn('late_slip_reprints', 'school_year')) {
$table->dropColumn('school_year');
}
});
}
if (Schema::hasTable('badge_exceptions')) {
Schema::table('badge_exceptions', function (Blueprint $table) {
if (Schema::hasColumn('badge_exceptions', 'semester')) {
$table->dropColumn('semester');
}
if (Schema::hasColumn('badge_exceptions', 'school_year')) {
$table->dropColumn('school_year');
}
});
}
if (Schema::hasTable('attendance_management_events')) {
Schema::table('attendance_management_events', function (Blueprint $table) {
if (Schema::hasColumn('attendance_management_events', 'semester')) {
$table->dropColumn('semester');
}
if (Schema::hasColumn('attendance_management_events', 'school_year')) {
$table->dropColumn('school_year');
}
});
}
}
private function deriveSchoolYearForDate(CarbonImmutable $date): string
{
$startYear = $date->month >= 8 ? $date->year : ($date->year - 1);
return sprintf('%d-%d', $startYear, $startYear + 1);
}
private function deriveSemesterForDate(CarbonImmutable $date): string
{
return ($date->month >= 8 || $date->month === 1) ? 'Fall' : 'Spring';
}
};