fix school year for all tables
Tests / PHPUnit (push) Failing after 1m20s

This commit is contained in:
root
2026-07-18 14:45:38 -04:00
parent 4db8334a3c
commit 716cc8b8d3
125 changed files with 2315 additions and 81 deletions
+1
View File
@@ -1146,6 +1146,7 @@ $routes->group('administrator/school-years', ['filter' => 'auth:admin'], static
$routes->post('(:num)/archive', 'Administrator\SchoolYearController::archive/$1');
$routes->post('(:num)/reopen', 'Administrator\SchoolYearController::reopen/$1');
$routes->get('(:num)/closing/preview', 'Administrator\SchoolYearClosingController::preview/$1');
$routes->get('(:segment)/closing/preview', 'Administrator\SchoolYearClosingController::previewByName/$1');
$routes->post('(:num)/closing/start', 'Administrator\SchoolYearClosingController::start/$1');
$routes->post('(:num)/closing/execute', 'Administrator\SchoolYearClosingController::execute/$1');
$routes->post('(:num)/closing/complete', 'Administrator\SchoolYearClosingController::complete/$1');
@@ -14,11 +14,13 @@ class SchoolYearClosingController extends BaseController
$targetId = $this->normalizeInt($this->request->getGet('target_school_year_id'));
$preview = service('schoolYearClosing')->preview($id, $targetId);
$promotionTable = $this->promotionTablePayload($preview['promotion']['rows'] ?? []);
$latestBatch = service('schoolYearClosing')->latestBatch($id);
return view('school_years/closing_preview', [
'preview' => $preview,
'promotionTable' => $promotionTable,
'latestBatch' => service('schoolYearClosing')->latestBatch($id),
'latestBatch' => $latestBatch,
'missingCarryForwardInvoices' => $this->missingCarryForwardInvoiceCount($latestBatch),
'schoolYears' => (new SchoolYearModel())->orderBy('name', 'DESC')->findAll(),
]);
} catch (Throwable $e) {
@@ -26,6 +28,23 @@ class SchoolYearClosingController extends BaseController
}
}
public function previewByName(string $name)
{
try {
$year = (new SchoolYearModel())
->where('name', rawurldecode($name))
->first();
if ($year === null) {
return redirect()->to('/administrator/school-years')->with('error', 'School year was not found.');
}
return $this->preview((int) $year['id']);
} catch (Throwable $e) {
return redirect()->to('/administrator/school-years')->with('error', $e->getMessage());
}
}
public function start(int $id)
{
try {
@@ -76,6 +95,26 @@ class SchoolYearClosingController extends BaseController
return is_numeric($value) && (int) $value > 0 ? (int) $value : null;
}
private function missingCarryForwardInvoiceCount(?array $batch): int
{
if ($batch === null || ! in_array((string) ($batch['status'] ?? ''), ['executed', 'completed'], true)) {
return 0;
}
$db = \Config\Database::connect();
if (! $db->tableExists('school_year_closing_items')) {
return 0;
}
return $db->table('school_year_closing_items')
->where('closing_batch_id', (int) $batch['id'])
->groupStart()
->where('target_invoice_id', null)
->orWhere('target_invoice_id', 0)
->groupEnd()
->countAllResults();
}
private function promotionTablePayload(array $rows): array
{
$allowedSorts = ['student', 'school_id', 'class', 'year_score', 'decision', 'source', 'queue', 'target', 'status'];
@@ -860,8 +860,13 @@ class LandingPageController extends BaseController
->get()
->getResultArray();
// Fetch latest invoice balance
$paymentBalance = $this->invoiceModel->getLatestInvoiceTotalAmount($parentId);
$paymentRow = $this->db->table('invoices')
->select('COALESCE(SUM(balance), 0) AS account_balance')
->where('parent_id', $parentId)
->where('school_year', $this->schoolYear)
->get()
->getRowArray();
$paymentBalance = (float) ($paymentRow['account_balance'] ?? 0);
// Pass data to the view, including the deadlines
return view('/landing_page/parent_dashboard', [
@@ -297,6 +297,8 @@ class PaymentController extends ResourceController
$payments = [];
$invoices = [];
$pager = null;
$carryForwardPaymentRequired = false;
$carryForwardPaymentMessage = '';
// Read the search term (email or phone)
$searchTerm = trim((string) $this->request->getGet('search_term'));
@@ -351,6 +353,10 @@ class PaymentController extends ResourceController
if ($parent && !empty($parent['id'])) {
$parentData = $parent;
$parentId = (int) $parent['id'];
$carryForwardPaymentRequired = $this->parentHasActiveCarryForwardBalance($parentId, $this->schoolYear);
if ($carryForwardPaymentRequired) {
$carryForwardPaymentMessage = 'This parent has a balance carried over from a previous school year. Manual payments must be paid in full; installments are not allowed.';
}
// Students
$students = $this->studentModel
@@ -461,6 +467,8 @@ class PaymentController extends ResourceController
'searchTermUsedInSearch' => $searchTerm,
'todayYmd' => utc_now(),
'installmentEndYmd' => $installmentEndYmd, // SELECT will use this in data-end-date
'carryForwardPaymentRequired' => $carryForwardPaymentRequired,
'carryForwardPaymentMessage' => $carryForwardPaymentMessage,
]);
}
@@ -952,6 +960,15 @@ class PaymentController extends ResourceController
// Recompute invoice totals from tuition + events + additional charges
$currentBalance = (float) $this->invoiceLedgerService->recalculateInvoice($invoiceId)['balance'];
$carryForwardPaymentRequired = $this->parentHasActiveCarryForwardBalance($parentId, $invYear);
if ($carryForwardPaymentRequired && $paymentType === 'installment') {
$this->db->transRollback();
return redirect()->back()->withInput()->with(
'error',
'This parent has a balance carried over from a previous school year. Installments are not allowed; payment must be made in full.'
);
}
if ($amount > $currentBalance + 0.00001) {
$this->db->transRollback();
return redirect()->back()->withInput()->with(
@@ -960,6 +977,14 @@ class PaymentController extends ResourceController
);
}
if ($carryForwardPaymentRequired && (float)round($amount, 2) !== (float)round($currentBalance, 2)) {
$this->db->transRollback();
return redirect()->back()->withInput()->with(
'error',
'This parent has a balance carried over from a previous school year. Payment must equal the full remaining balance (' . number_format($currentBalance, 2) . ').'
);
}
if ($paymentMethod === 'card' && (float)round($amount, 2) !== (float)round($currentBalance, 2)) {
$this->db->transRollback();
return redirect()->back()->withInput()->with(
@@ -1158,6 +1183,36 @@ class PaymentController extends ResourceController
}
}
private function parentHasActiveCarryForwardBalance(int $parentId, ?string $schoolYear = null): bool
{
if ($parentId <= 0 || ! $this->db->tableExists('invoices')) {
return false;
}
$builder = $this->db->table('invoices')
->where('parent_id', $parentId)
->where('balance >', 0);
if ($schoolYear !== null && $schoolYear !== '') {
$builder->where('school_year', $schoolYear);
}
$builder->groupStart()
->like('invoice_number', 'CF-', 'after')
->orWhere('semester', 'Opening Balance');
if ($this->db->fieldExists('description', 'invoices')) {
$builder
->orLike('description', 'carried over')
->orLike('description', 'carry-forward')
->orLike('description', 'previous school year');
}
$builder->groupEnd();
return $builder->countAllResults() > 0;
}
/**
* 🔄 Helper: Recalculate invoice totals and status based on all payments for current school year
@@ -8,6 +8,10 @@ class AddEventCategoryToEvents extends Migration
{
public function up()
{
if (! $this->db->tableExists('events')) {
return;
}
$fields = $this->db->getFieldNames('events');
if (!in_array('event_category', $fields, true)) {
@@ -25,6 +29,10 @@ class AddEventCategoryToEvents extends Migration
public function down()
{
if (! $this->db->tableExists('events')) {
return;
}
$fields = $this->db->getFieldNames('events');
if (in_array('event_category', $fields, true)) {
@@ -8,6 +8,10 @@ class AddBatchNumberToReimbursements extends Migration
{
public function up()
{
if (! $this->db->tableExists('reimbursements') || $this->db->fieldExists('batch_number', 'reimbursements')) {
return;
}
$fields = [
'batch_number' => [
'type' => 'INT',
@@ -24,7 +28,10 @@ class AddBatchNumberToReimbursements extends Migration
public function down()
{
if (! $this->db->tableExists('reimbursements') || ! $this->db->fieldExists('batch_number', 'reimbursements')) {
return;
}
$this->forge->dropColumn('reimbursements', 'batch_number');
}
}
@@ -8,6 +8,10 @@ class AddYearlyBatchNumberToReimbursementBatches extends Migration
{
public function up()
{
if (! $this->db->tableExists('reimbursement_batches') || $this->db->fieldExists('yearly_batch_number', 'reimbursement_batches')) {
return;
}
$fields = [
'yearly_batch_number' => [
'type' => 'INT',
@@ -26,6 +30,10 @@ class AddYearlyBatchNumberToReimbursementBatches extends Migration
public function down()
{
if (! $this->db->tableExists('reimbursement_batches') || ! $this->db->fieldExists('yearly_batch_number', 'reimbursement_batches')) {
return;
}
$this->forge->dropColumn('reimbursement_batches', 'yearly_batch_number');
}
@@ -24,7 +24,9 @@ class CreateStaffAttendance extends Migration
]);
$this->forge->addKey('id', true);
$this->forge->addUniqueKey(['user_id','date','semester','school_year']);
$this->forge->addForeignKey('user_id','users','id','CASCADE','CASCADE');
if ($this->db->tableExists('users')) {
$this->forge->addForeignKey('user_id','users','id','CASCADE','CASCADE');
}
$this->forge->createTable('staff_attendance', true);
}
@@ -43,7 +43,9 @@ class CreateFamiliesTables extends Migration
$this->forge->addKey('id', true);
$this->forge->addUniqueKey(['family_id', 'student_id']);
$this->forge->addForeignKey('family_id', 'families', 'id', 'CASCADE', 'CASCADE');
$this->forge->addForeignKey('student_id', 'students', 'id', 'CASCADE', 'CASCADE');
if ($this->db->tableExists('students')) {
$this->forge->addForeignKey('student_id', 'students', 'id', 'CASCADE', 'CASCADE');
}
$this->forge->createTable('family_students', true);
// family_guardians
@@ -62,7 +64,9 @@ class CreateFamiliesTables extends Migration
$this->forge->addKey('id', true);
$this->forge->addUniqueKey(['family_id', 'user_id']);
$this->forge->addForeignKey('family_id', 'families', 'id', 'CASCADE', 'CASCADE');
$this->forge->addForeignKey('user_id', 'users', 'id', 'CASCADE', 'CASCADE');
if ($this->db->tableExists('users')) {
$this->forge->addForeignKey('user_id', 'users', 'id', 'CASCADE', 'CASCADE');
}
$this->forge->createTable('family_guardians', true);
// family_comm_prefs
@@ -90,4 +94,3 @@ class CreateFamiliesTables extends Migration
$this->forge->dropTable('families', true);
}
}
@@ -9,6 +9,10 @@ class AddNoSchoolToCalendarEvents extends Migration
public function up()
{
$db = \Config\Database::connect();
if (! $db->tableExists('calendar_events')) {
return;
}
$cols = [];
try {
$cols = $db->getFieldNames('calendar_events');
@@ -31,6 +35,10 @@ class AddNoSchoolToCalendarEvents extends Migration
public function down()
{
if (! $this->db->tableExists('calendar_events')) {
return;
}
try {
$this->forge->dropColumn('calendar_events', 'no_school');
} catch (\Throwable $e) {
@@ -38,4 +46,3 @@ class AddNoSchoolToCalendarEvents extends Migration
}
}
}
@@ -85,8 +85,12 @@ class CreateParentAttendanceReports extends Migration
$this->forge->addKey('id', true);
$this->forge->addKey(['report_date']);
$this->forge->addKey(['student_id', 'report_date']);
$this->forge->addForeignKey('parent_id', 'users', 'id', 'CASCADE', 'CASCADE');
$this->forge->addForeignKey('student_id', 'students', 'id', 'CASCADE', 'CASCADE');
if ($this->db->tableExists('users')) {
$this->forge->addForeignKey('parent_id', 'users', 'id', 'CASCADE', 'CASCADE');
}
if ($this->db->tableExists('students')) {
$this->forge->addForeignKey('student_id', 'students', 'id', 'CASCADE', 'CASCADE');
}
$this->forge->createTable('parent_attendance_reports', true);
}
@@ -97,4 +101,3 @@ class CreateParentAttendanceReports extends Migration
}
}
}
@@ -11,6 +11,10 @@ class AddDonationCategoryToExpenses extends Migration
*/
public function up()
{
if (! $this->db->tableExists('expenses')) {
return;
}
$this->forge->modifyColumn('expenses', [
'category' => [
'type' => "ENUM('Expense','Purchase','Reimbursement','Donation')",
@@ -24,6 +28,10 @@ class AddDonationCategoryToExpenses extends Migration
*/
public function down()
{
if (! $this->db->tableExists('expenses')) {
return;
}
// Convert any donation rows to Expense to keep rollback safe.
$this->db->table('expenses')
->where('category', 'Donation')
@@ -61,9 +61,13 @@ class CreatePrintRequests extends Migration
],
]);
$this->forge->addKey('id', true);
$this->forge->addForeignKey('teacher_id', 'users', 'id', 'CASCADE', 'CASCADE');
$this->forge->addForeignKey('admin_id', 'users', 'id', 'CASCADE', 'SET NULL');
$this->forge->addForeignKey('class_id', 'classes', 'id', 'CASCADE', 'CASCADE');
if ($this->db->tableExists('users')) {
$this->forge->addForeignKey('teacher_id', 'users', 'id', 'CASCADE', 'CASCADE');
$this->forge->addForeignKey('admin_id', 'users', 'id', 'CASCADE', 'SET NULL');
}
if ($this->db->tableExists('classes')) {
$this->forge->addForeignKey('class_id', 'classes', 'id', 'CASCADE', 'CASCADE');
}
$this->forge->createTable('print_requests');
}
@@ -8,8 +8,16 @@ class FixPrintRequestsForeignKey extends Migration
{
public function up()
{
if (! $this->db->tableExists('print_requests')) {
return;
}
// Drop the old foreign key if it exists
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
if (! $this->db->tableExists('class_sections')) {
return;
@@ -24,8 +32,20 @@ class FixPrintRequestsForeignKey extends Migration
public function down()
{
if (! $this->db->tableExists('print_requests')) {
return;
}
// Drop the new foreign key
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
if (! $this->db->tableExists('classes')) {
return;
}
// Re-add the old foreign key
$this->forge->addForeignKey('class_id', 'classes', 'id', 'CASCADE', 'CASCADE');
@@ -8,16 +8,28 @@ class RevertPrintRequestsForeignKey extends Migration
{
public function up()
{
if (! $this->db->tableExists('print_requests')) {
return;
}
// Drop the old foreign key if it exists
$db = \Config\Database::connect();
$keys = $db->getForeignKeyData('print_requests');
foreach ($keys as $key) {
if ($key->constraint_name === 'print_requests_class_id_foreign') {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
break;
}
}
if (! $this->db->tableExists('classes')) {
return;
}
// Add the new foreign key
$this->forge->addForeignKey('class_id', 'classes', 'id', 'CASCADE', 'CASCADE');
@@ -27,8 +39,20 @@ class RevertPrintRequestsForeignKey extends Migration
public function down()
{
if (! $this->db->tableExists('print_requests')) {
return;
}
// Drop the new foreign key
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
if (! $this->db->tableExists('class_sections')) {
return;
}
// Re-add the old foreign key
$this->forge->addForeignKey('class_id', 'class_sections', 'id', 'CASCADE', 'CASCADE');
@@ -8,12 +8,24 @@ class DropPrintRequestsForeignKey extends Migration
{
public function up()
{
if (! $this->db->tableExists('print_requests')) {
return;
}
// Drop the old foreign key if it exists
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
}
public function down()
{
if (! $this->db->tableExists('print_requests') || ! $this->db->tableExists('class_sections')) {
return;
}
// Re-add the old foreign key
$this->forge->addForeignKey('class_id', 'class_sections', 'id', 'CASCADE', 'CASCADE');
@@ -8,21 +8,41 @@ class FixPrintRequestsForeignKeyOnceAndForAll extends Migration
{
public function up()
{
if (! $this->db->tableExists('print_requests')) {
return;
}
$db = \Config\Database::connect();
$keys = $db->getForeignKeyData('print_requests');
foreach ($keys as $key) {
if ($key->constraint_name === 'print_requests_class_id_foreign') {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
break;
}
}
if (! $this->db->tableExists('classSection')) {
return;
}
$this->forge->addForeignKey('class_id', 'classSection', 'id', 'CASCADE', 'CASCADE');
$this->forge->processIndexes('print_requests');
}
public function down()
{
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
if (! $this->db->tableExists('print_requests')) {
return;
}
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
}
}
@@ -8,6 +8,10 @@ class FixPrintRequestsForeignKeyAgain extends Migration
{
public function up()
{
if (! $this->db->tableExists('print_requests') || ! $this->db->tableExists('classSection')) {
return;
}
$db = \Config\Database::connect();
$keys = $db->getForeignKeyData('print_requests');
foreach ($keys as $key) {
@@ -22,6 +26,14 @@ class FixPrintRequestsForeignKeyAgain extends Migration
public function down()
{
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
if (! $this->db->tableExists('print_requests')) {
return;
}
try {
$this->forge->dropForeignKey('print_requests', 'print_requests_class_id_foreign');
} catch (\Throwable $e) {
// Already absent.
}
}
}
@@ -9,6 +9,9 @@ class AddEventTypeToCalendarEvents extends Migration
public function up()
{
$db = \Config\Database::connect();
if (! $db->tableExists('calendar_events')) {
return;
}
if (!$db->fieldExists('event_type', 'calendar_events')) {
$this->forge->addColumn('calendar_events', [
@@ -26,6 +29,10 @@ class AddEventTypeToCalendarEvents extends Migration
public function down()
{
$db = \Config\Database::connect();
if (! $db->tableExists('calendar_events')) {
return;
}
if ($db->fieldExists('event_type', 'calendar_events')) {
$this->forge->dropColumn('calendar_events', 'event_type');
}
@@ -8,6 +8,10 @@ class AddEventPaidFlagToCharges extends Migration
{
public function up()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
$fields = [
'event_paid' => [
'type' => 'TINYINT',
@@ -17,11 +21,15 @@ class AddEventPaidFlagToCharges extends Migration
],
];
$this->forge->addColumn('event_charges', $fields);
if (! $this->db->fieldExists('event_paid', 'event_charges')) {
$this->forge->addColumn('event_charges', $fields);
}
}
public function down()
{
$this->forge->dropColumn('event_charges', 'event_paid');
if ($this->db->tableExists('event_charges') && $this->db->fieldExists('event_paid', 'event_charges')) {
$this->forge->dropColumn('event_charges', 'event_paid');
}
}
}
@@ -8,6 +8,10 @@ class AddClassSectionToEventCharges extends Migration
{
public function up()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
$fields = [
'class_section_id' => [
'type' => 'INT',
@@ -25,7 +29,7 @@ class AddClassSectionToEventCharges extends Migration
public function down()
{
if ($this->db->fieldExists('class_section_id', 'event_charges')) {
if ($this->db->tableExists('event_charges') && $this->db->fieldExists('class_section_id', 'event_charges')) {
$this->forge->dropColumn('event_charges', 'class_section_id');
}
}
@@ -8,6 +8,10 @@ class AddEventPaymentRefToEventCharges extends Migration
{
public function up()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
$fields = [
'event_payment_id' => [
'type' => 'INT',
@@ -25,7 +29,7 @@ class AddEventPaymentRefToEventCharges extends Migration
public function down()
{
if ($this->db->fieldExists('event_payment_id', 'event_charges')) {
if ($this->db->tableExists('event_charges') && $this->db->fieldExists('event_payment_id', 'event_charges')) {
$this->forge->dropColumn('event_charges', 'event_payment_id');
}
}
@@ -8,6 +8,10 @@ class AddExternalParticipantFieldsToEventCharges extends Migration
{
public function up()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
$fields = [
'external_firstname' => [
'type' => 'VARCHAR',
@@ -34,6 +38,10 @@ class AddExternalParticipantFieldsToEventCharges extends Migration
public function down()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
foreach (['external_firstname', 'external_lastname', 'external_note'] as $field) {
if ($this->db->fieldExists($field, 'event_charges')) {
$this->forge->dropColumn('event_charges', $field);
@@ -8,6 +8,10 @@ class AddExternalParentInfoToEventCharges extends Migration
{
public function up()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
$fields = [
'external_parent_firstname' => [
'type' => 'VARCHAR',
@@ -34,6 +38,10 @@ class AddExternalParentInfoToEventCharges extends Migration
public function down()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
foreach (['external_parent_firstname', 'external_parent_lastname', 'external_parent_phone'] as $field) {
if ($this->db->fieldExists($field, 'event_charges')) {
$this->forge->dropColumn('event_charges', $field);
@@ -8,6 +8,10 @@ class AddExternalParentEmailToEventCharges extends Migration
{
public function up()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
$fields = [
'external_parent_email' => [
'type' => 'VARCHAR',
@@ -24,9 +28,8 @@ class AddExternalParentEmailToEventCharges extends Migration
public function down()
{
if ($this->db->fieldExists('external_parent_email', 'event_charges')) {
if ($this->db->tableExists('event_charges') && $this->db->fieldExists('external_parent_email', 'event_charges')) {
$this->forge->dropColumn('event_charges', 'external_parent_email');
}
}
}
@@ -8,6 +8,10 @@ class AddWaiverSignedToEventCharges extends Migration
{
public function up()
{
if (! $this->db->tableExists('event_charges')) {
return;
}
$fields = [
'waiver_signed' => [
'type' => 'TINYINT',
@@ -24,7 +28,7 @@ class AddWaiverSignedToEventCharges extends Migration
public function down()
{
if ($this->db->fieldExists('waiver_signed', 'event_charges')) {
if ($this->db->tableExists('event_charges') && $this->db->fieldExists('waiver_signed', 'event_charges')) {
$this->forge->dropColumn('event_charges', 'waiver_signed');
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddDescriptionToInvoices extends Migration
{
public function up(): void
{
if (! $this->db->tableExists('invoices') || $this->db->fieldExists('description', 'invoices')) {
return;
}
$this->forge->addColumn('invoices', [
'description' => [
'type' => 'TEXT',
'null' => true,
'after' => 'status',
],
]);
}
public function down(): void
{
if ($this->db->tableExists('invoices') && $this->db->fieldExists('description', 'invoices')) {
$this->forge->dropColumn('invoices', 'description');
}
}
}
@@ -0,0 +1,336 @@
<?php
declare(strict_types=1);
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
use RuntimeException;
final class AlignSchemaToScoolViewDump extends Migration
{
/**
* Columns present in school_prod.sql.zip but absent from scool_view.sql.zip.
*
* This migration intentionally makes the live database match the scool_view
* dump captured on 2026-07-17.
*/
private array $columnsToDrop = [
'classSection' => ['semester'],
'contactus' => ['semester'],
'discount_vouchers' => ['semester'],
'emergency_contacts' => ['semester', 'school_year'],
'ip_attempts' => ['semester'],
'login_activity' => ['semester'],
'notification_recipients' => ['semester'],
'notifications' => ['semester'],
'parents' => ['semester', 'school_year'],
'payment_error' => ['semester'],
'payment_transactions' => ['semester'],
'payments' => ['semester'],
'paypal_transactions' => ['semester'],
'preferences' => ['school_year'],
'refunds' => ['semester'],
'reimbursement_batch_items' => ['semester'],
'reimbursement_batches' => ['semester'],
'reimbursements' => ['semester'],
'students' => ['semester', 'school_year'],
'support_requests' => ['semester'],
'user_notifications' => ['semester'],
'users' => ['semester', 'school_year'],
'whatsapp_group_links' => ['semester'],
'whatsapp_group_memberships' => ['semester'],
];
/**
* Tables that should own a required school_year value after aligning the schema.
*
* @var list<string>
*/
private array $schoolYearTables = [
'admin_notification_subjects',
'attendance_comment_template',
'class_progress_attachments',
'competition_class_winners',
'competition_scores',
'competition_winners',
'inventory_categories',
'paypal_payments',
'placement_scores',
'promotion_queue',
'qcmquestions',
'school_year_closing_batches',
'school_year_closing_items',
'school_year_transition_logs',
'staff',
'whatsapp_group_links',
'whatsapp_invites_log',
];
public function up(): void
{
if ($this->db->DBDriver !== 'MySQLi') {
throw new RuntimeException('This migration requires MySQL/MariaDB through the MySQLi driver.');
}
$this->dropIndexIfExists('competition_winners', 'competition_id_class_section_id_rank');
$this->dropIndexIfExists('refunds', 'idx_refunds_parent_year_semester_status');
$this->dropIndexIfExists('teacher_class', 'idx_sy_teacher_class_school_year_223511c0');
$this->dropIndexIfExists('whatsapp_group_links', 'uq_section_term');
$this->dropIndexIfExists('whatsapp_group_memberships', 'uniq_whatsapp_membership');
$this->dropIndexIfExists('whatsapp_group_memberships', 'class_section_id_school_year_semester');
foreach ($this->columnsToDrop as $table => $columns) {
foreach ($columns as $column) {
$this->dropColumnIfExists($table, $column);
}
}
$this->db->resetDataCache();
foreach ($this->schoolYearTables as $table) {
$this->ensureRequiredSchoolYearColumn($table);
}
$this->db->resetDataCache();
$this->ensureIndex(
'refunds',
'idx_refunds_parent_year_semester_status',
['parent_id', 'school_year', 'status']
);
$this->ensureIndex(
'teacher_class',
'unique_teacher_assignment',
['teacher_id', 'class_section_id', 'school_year'],
true
);
$this->ensureIndex(
'whatsapp_group_links',
'uq_section_term',
['class_section_id'],
true
);
$this->ensureIndex(
'whatsapp_group_memberships',
'uniq_whatsapp_membership',
['class_section_id', 'school_year', 'subject_type', 'subject_id'],
true
);
$this->ensureIndex(
'whatsapp_group_memberships',
'class_section_id_school_year_semester',
['class_section_id', 'school_year']
);
if ($this->db->tableExists('whatsapp_group_links')) {
$this->db->query(
'ALTER TABLE `whatsapp_group_links` ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci'
);
}
}
public function down(): void
{
throw new RuntimeException(
'This migration is intentionally irreversible because matching scool_view drops columns and their data.'
);
}
private function ensureRequiredSchoolYearColumn(string $table): void
{
if (! $this->db->tableExists($table)) {
return;
}
if (! $this->db->fieldExists('school_year', $table)) {
$this->forge->addColumn($table, [
'school_year' => [
'type' => 'VARCHAR',
'constraint' => 9,
'null' => true,
],
]);
}
$year = $this->currentSchoolYear();
$this->db->query(
sprintf(
'UPDATE %s SET `school_year` = ? WHERE `school_year` IS NULL OR TRIM(`school_year`) = \'\'',
$this->quoteIdentifier($table)
),
[$year]
);
$this->db->query(sprintf(
'ALTER TABLE %s MODIFY `school_year` VARCHAR(9) NOT NULL',
$this->quoteIdentifier($table)
));
}
private function currentSchoolYear(): string
{
if ($this->db->tableExists('configuration')) {
$row = $this->db->table('configuration')
->select('config_value')
->whereIn('config_key', ['school_year', 'current_school_year'])
->where('config_value IS NOT NULL', null, false)
->where('TRIM(config_value) <>', '')
->orderBy("FIELD(config_key, 'school_year', 'current_school_year')", '', false)
->get(1)
->getRowArray();
if (isset($row['config_value']) && preg_match('/^\d{4}-\d{4}$/', (string) $row['config_value'])) {
return (string) $row['config_value'];
}
}
$year = (int) date('Y');
return $year . '-' . ($year + 1);
}
private function dropColumnIfExists(string $table, string $column): void
{
if (! $this->db->tableExists($table) || ! $this->db->fieldExists($column, $table)) {
return;
}
$this->dropForeignKeysContainingColumn($table, $column);
$this->dropIndexesContainingColumn($table, $column);
$this->dropChecksContainingColumn($table, $column);
$this->forge->dropColumn($table, $column);
}
private function ensureIndex(string $table, string $index, array $columns, bool $unique = false): void
{
if (! $this->db->tableExists($table) || ! $this->hasColumns($table, $columns)) {
return;
}
if ($this->indexExists($table, $index)) {
return;
}
$keyword = $unique ? 'UNIQUE INDEX' : 'INDEX';
$columnList = implode(', ', array_map([$this, 'quoteIdentifier'], $columns));
$this->db->query(sprintf(
'ALTER TABLE %s ADD %s %s (%s)',
$this->quoteIdentifier($table),
$keyword,
$this->quoteIdentifier($index),
$columnList
));
}
private function dropIndexIfExists(string $table, string $index): void
{
if (! $this->db->tableExists($table) || ! $this->indexExists($table, $index)) {
return;
}
$this->db->query(sprintf(
'ALTER TABLE %s DROP INDEX %s',
$this->quoteIdentifier($table),
$this->quoteIdentifier($index)
));
}
private function dropIndexesContainingColumn(string $table, string $column): void
{
$indexes = $this->db->query(
'SELECT DISTINCT INDEX_NAME
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND COLUMN_NAME = ?
AND INDEX_NAME <> \'PRIMARY\'',
[$table, $column]
)->getResult();
foreach ($indexes as $index) {
$this->dropIndexIfExists($table, $index->INDEX_NAME);
}
}
private function dropForeignKeysContainingColumn(string $table, string $column): void
{
$constraints = $this->db->query(
'SELECT DISTINCT CONSTRAINT_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND COLUMN_NAME = ?
AND REFERENCED_TABLE_NAME IS NOT NULL',
[$table, $column]
)->getResult();
foreach ($constraints as $constraint) {
$this->db->query(sprintf(
'ALTER TABLE %s DROP FOREIGN KEY %s',
$this->quoteIdentifier($table),
$this->quoteIdentifier($constraint->CONSTRAINT_NAME)
));
}
}
private function dropChecksContainingColumn(string $table, string $column): void
{
$checks = $this->db->query(
'SELECT tc.CONSTRAINT_NAME
FROM information_schema.TABLE_CONSTRAINTS tc
INNER JOIN information_schema.CHECK_CONSTRAINTS cc
ON cc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
AND cc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
WHERE tc.CONSTRAINT_SCHEMA = DATABASE()
AND tc.TABLE_NAME = ?
AND tc.CONSTRAINT_TYPE = \'CHECK\'
AND cc.CHECK_CLAUSE LIKE ?',
[$table, '%' . $column . '%']
)->getResult();
foreach ($checks as $check) {
$this->db->query(sprintf(
'ALTER TABLE %s DROP CHECK %s',
$this->quoteIdentifier($table),
$this->quoteIdentifier($check->CONSTRAINT_NAME)
));
}
}
private function indexExists(string $table, string $index): bool
{
$row = $this->db->query(
'SELECT COUNT(*) AS aggregate
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND INDEX_NAME = ?',
[$table, $index]
)->getRow();
return (int) ($row->aggregate ?? 0) > 0;
}
private function hasColumns(string $table, array $columns): bool
{
foreach ($columns as $column) {
if (! $this->db->fieldExists($column, $table)) {
return false;
}
}
return true;
}
private function quoteIdentifier(string $identifier): string
{
return '`' . str_replace('`', '``', $identifier) . '`';
}
}
+27 -3
View File
@@ -74,9 +74,15 @@ class InvoiceLedgerService
$paidCents = $this->toCents($paidTotal);
$refundPaidCents = $this->toCents($refundPaidTotal);
$discountBaseCents = max(0, $tuitionCents + $additionalCents);
$discountCents = min($discountRawCents, $discountBaseCents);
$totalAmountCents = $tuitionCents + $eventCents + $additionalCents;
if ($this->isCarryForwardInvoice($invoice)) {
$totalAmountCents = $this->toCents((float) ($invoice['total_amount'] ?? 0));
$discountCents = 0;
} else {
$discountBaseCents = max(0, $tuitionCents + $additionalCents);
$discountCents = min($discountRawCents, $discountBaseCents);
$totalAmountCents = $tuitionCents + $eventCents + $additionalCents;
}
$balanceCents = max(0, $totalAmountCents - $discountCents - $paidCents - $refundPaidCents);
if ($balanceCents === 0) {
@@ -129,6 +135,24 @@ class InvoiceLedgerService
return $this->invoiceModel->find($invoiceId);
}
protected function isCarryForwardInvoice(array $invoice): bool
{
$invoiceNumber = (string) ($invoice['invoice_number'] ?? '');
if (str_starts_with($invoiceNumber, 'CF-')) {
return true;
}
if (strcasecmp((string) ($invoice['semester'] ?? ''), 'Opening Balance') === 0) {
return true;
}
$description = strtolower((string) ($invoice['description'] ?? ''));
return str_contains($description, 'carried over')
|| str_contains($description, 'carry-forward')
|| str_contains($description, 'previous school year');
}
protected function calculateTuitionTotal(array $invoice): float
{
$parentId = (int) ($invoice['parent_id'] ?? 0);
+4 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class AdditionalChargeModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'additional_charges';
protected $primaryKey = 'id';
protected $returnType = 'array';
@@ -28,7 +31,7 @@ class AdditionalChargeModel extends Model
protected $validationRules = [
'parent_id' => 'permit_empty|integer',
'invoice_id' => 'permit_empty|integer',
'school_year' => 'required|string|max_length[20]',
'school_year' => 'required|string|max_length[9]',
'semester' => 'required|string|max_length[20]',
'charge_type' => 'required|in_list[add,deduct]',
'title' => 'required|string|min_length[2]|max_length[255]',
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class AdminNotificationSubjectModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'admin_notification_subjects';
protected $primaryKey = 'id';
protected $returnType = 'array';
@@ -15,7 +18,12 @@ class AdminNotificationSubjectModel extends Model
'subject',
'created_at',
'updated_at',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+4 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class AttendanceDataModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'attendance_data';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
@@ -42,7 +45,7 @@ class AttendanceDataModel extends Model
'is_notified' => 'required|in_list[no,yes]',
'date' => 'required|valid_date[Y-m-d]',
'semester' => 'required',
'school_year' => 'required',
'school_year' => 'required|string|max_length[9]',
];
/**
* Retrieve attendance by class and section for a specific date.
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class AttendanceDayModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'attendance_day';
protected $primaryKey = 'id';
protected $returnType = 'array';
@@ -28,6 +31,10 @@ class AttendanceDayModel extends Model
'created_at',
'updated_at',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
/**
* Legacy helper kept for compatibility.
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class AttendanceRecordModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'attendance_record';
protected $primaryKey = 'id';
protected $allowedFields = [
@@ -22,6 +25,10 @@ class AttendanceRecordModel extends Model
'created_at',
'updated_at'
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+7
View File
@@ -3,10 +3,13 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
use CodeIgniter\I18n\Time;
class AttendanceTrackingModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'attendance_tracking';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
@@ -24,6 +27,10 @@ class AttendanceTrackingModel extends Model
'school_year',
'note',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class BadgePrintLogModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'badge_print_logs';
protected $primaryKey = 'id';
protected $useTimestamps = false;
@@ -19,6 +22,10 @@ class BadgePrintLogModel extends Model
'class_section_name',
'copies',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
/**
* Insert a batch of print logs. Swallows errors if table is missing.
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class BelowSixtyDecisionModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'below_sixty_decisions';
protected $primaryKey = 'id';
@@ -17,6 +20,10 @@ class BelowSixtyDecisionModel extends Model
'notes',
'decided_by',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class CalendarModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'calendar_events';
protected $primaryKey = 'id';
protected ?bool $hasEventTypeColumn = null;
@@ -22,6 +25,10 @@ class CalendarModel extends Model
'notify_teacher',
'no_school'
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class CertificateRecordModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'certificate_records';
protected $primaryKey = 'id';
protected $useTimestamps = true;
@@ -22,6 +25,10 @@ class CertificateRecordModel extends Model
'issued_by',
'issued_at',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
/**
* Generates the next certificate number for a given school year.
+10 -2
View File
@@ -3,17 +3,25 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ClassModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'classes'; // The table name
protected $primaryKey = 'id'; // The correct primary key for the classes table
// Fields that are allowed to be manipulated.
protected $allowedFields = [
'class_name',
'schedule',
'capacity'
]; // Fields that are allowed to be manipulated
'capacity',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = false;
+9 -1
View File
@@ -2,11 +2,19 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ClassPrepAdjustmentModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'class_prep_adjustments';
protected $allowedFields = [
'class_section_id', 'item_name', 'adjustment', 'adjustable', 'created_at'
'class_section_id', 'item_name', 'adjustment', 'adjustable', 'created_at',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[16]',
];
}
+8
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ClassPreparationLogModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'class_preparation_log';
protected $primaryKey = 'id';
protected $allowedFields = [
@@ -13,6 +16,11 @@ class ClassPreparationLogModel extends Model
'class_section',
'prep_data',
'created_at',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
public $useTimestamps = false;
}
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ClassProgressAttachmentModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'class_progress_attachments';
protected $primaryKey = 'id';
protected $returnType = 'array';
@@ -16,6 +19,11 @@ class ClassProgressAttachmentModel extends Model
'mime_type',
'file_size',
'created_at',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = false;
}
+4 -1
View File
@@ -3,6 +3,7 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
/**
* Weekly class progress reports (one row per subject per week).
@@ -15,6 +16,8 @@ use CodeIgniter\Model;
*/
class ClassProgressReportModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'class_progress_reports';
protected $primaryKey = 'id';
protected $returnType = 'array';
@@ -51,7 +54,7 @@ class ClassProgressReportModel extends Model
protected $validationRules = [
'class_section_id' => 'required|integer',
'teacher_id' => 'required|integer',
'school_year' => 'permit_empty|string|max_length[9]',
'school_year' => 'required|string|max_length[9]',
'week_start' => 'required|valid_date[Y-m-d]',
'week_end' => 'required|valid_date[Y-m-d]',
'subject' => 'required|string|max_length[160]',
+8
View File
@@ -2,9 +2,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ClassSectionModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'classSection'; // Correct table name
protected $primaryKey = 'id'; // Specify the primary key field
@@ -14,7 +17,12 @@ class ClassSectionModel extends Model
'class_section_name',
'created_at',
'updated_at',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class CompetitionClassWinnerModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'competition_class_winners';
protected $primaryKey = 'id';
@@ -20,7 +23,12 @@ class CompetitionClassWinnerModel extends Model
'prize_4',
'prize_5',
'prize_6',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class CompetitionModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'competitions';
protected $primaryKey = 'id';
protected $useSoftDeletes = true;
@@ -26,6 +29,10 @@ class CompetitionModel extends Model
'locked_by',
'created_by',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+8
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class CompetitionScoreModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'competition_scores';
protected $primaryKey = 'id';
@@ -15,7 +18,12 @@ class CompetitionScoreModel extends Model
'class_section_id',
'score',
'notes',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+8
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class CompetitionWinnerModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'competition_winners';
protected $primaryKey = 'id';
@@ -17,7 +20,12 @@ class CompetitionWinnerModel extends Model
'score',
'prize_amount',
'created_at',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = false;
}
@@ -0,0 +1,178 @@
<?php
namespace App\Models\Concerns;
trait SchoolYearAutoFillTrait
{
public function insert($row = null, bool $returnID = true)
{
if (isset($this->tempData['data'])) {
if ($row === null) {
$row = $this->tempData['data'];
} else {
$row = array_merge($this->tempData['data'], $this->transformDataToArray($row, 'insert'));
}
$this->escape = $this->tempData['escape'] ?? [];
$this->tempData = [];
}
return parent::insert($this->withDefaultSchoolYear($row), $returnID);
}
public function insertBatch(?array $set = null, ?bool $escape = null, int $batchSize = 100, bool $testing = false)
{
if (is_array($set)) {
foreach ($set as $index => $row) {
$set[$index] = $this->withDefaultSchoolYear($row);
}
}
return parent::insertBatch($set, $escape, $batchSize, $testing);
}
protected function withDefaultSchoolYear($row)
{
if (! is_array($row)) {
return $row;
}
if ($this->hasNonEmptySchoolYear($row)) {
return $row;
}
$schoolYear = $this->deriveSchoolYearForRow($row) ?? $this->currentSchoolYearName();
if ($schoolYear !== '') {
$row['school_year'] = $schoolYear;
}
return $row;
}
protected function deriveSchoolYearForRow(array $row): ?string
{
foreach (['school_year_from', 'school_year_to', 'previous_school_year'] as $field) {
$value = trim((string)($row[$field] ?? ''));
if ($this->isValidSchoolYear($value)) {
return $value;
}
}
foreach (['source_school_year_id', 'target_school_year_id', 'school_year_id'] as $field) {
$id = (int)($row[$field] ?? 0);
if ($id <= 0) {
continue;
}
$year = $this->schoolYearNameById($id);
if ($year !== null) {
return $year;
}
}
$batchId = (int)($row['closing_batch_id'] ?? 0);
if ($batchId > 0) {
return $this->schoolYearNameByClosingBatchId($batchId);
}
return null;
}
private function hasNonEmptySchoolYear(array $row): bool
{
return array_key_exists('school_year', $row) && trim((string)$row['school_year']) !== '';
}
private function currentSchoolYearName(): string
{
try {
if ($this->db->tableExists('school_years')) {
$row = $this->db->table('school_years')
->select('name')
->where('status', 'active')
->orderBy('id', 'DESC')
->get(1)
->getRowArray();
$name = trim((string)($row['name'] ?? ''));
if ($this->isValidSchoolYear($name)) {
return $name;
}
}
if ($this->db->tableExists('configuration')) {
foreach (['school_year', 'current_school_year'] as $key) {
$row = $this->db->table('configuration')
->select('config_value')
->where('config_key', $key)
->orderBy('id', 'DESC')
->get(1)
->getRowArray();
$value = trim((string)($row['config_value'] ?? ''));
if ($this->isValidSchoolYear($value)) {
return $value;
}
}
}
} catch (\Throwable) {
return $this->calendarYearFallback();
}
return $this->calendarYearFallback();
}
private function schoolYearNameById(int $id): ?string
{
try {
if (! $this->db->tableExists('school_years')) {
return null;
}
$row = $this->db->table('school_years')
->select('name')
->where('id', $id)
->get(1)
->getRowArray();
$name = trim((string)($row['name'] ?? ''));
return $this->isValidSchoolYear($name) ? $name : null;
} catch (\Throwable) {
return null;
}
}
private function schoolYearNameByClosingBatchId(int $id): ?string
{
try {
if (! $this->db->tableExists('school_year_closing_batches')) {
return null;
}
$row = $this->db->table('school_year_closing_batches')
->select('school_year')
->where('id', $id)
->get(1)
->getRowArray();
$year = trim((string)($row['school_year'] ?? ''));
return $this->isValidSchoolYear($year) ? $year : null;
} catch (\Throwable) {
return null;
}
}
private function calendarYearFallback(): string
{
$year = (int) date('Y');
return $year . '-' . ($year + 1);
}
private function isValidSchoolYear(string $value): bool
{
return preg_match('/^\d{4}-\d{4}$/', $value) === 1;
}
}
+6 -1
View File
@@ -4,9 +4,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ContactUsModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'contactus';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
@@ -20,7 +23,8 @@ class ContactUsModel extends Model
'subject',
'message',
'created_at',
'updated_at'
'updated_at',
'school_year',
];
// Timestamps (set manually or use auto timestamps below)
@@ -33,6 +37,7 @@ class ContactUsModel extends Model
'reciever_id' => 'required|integer',
'subject' => 'required|string|max_length[255]',
'message' => 'required|string',
'school_year' => 'required|string|max_length[9]',
];
protected $validationMessages = [];
+7
View File
@@ -4,9 +4,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class CurrentFlagModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'current_flag';
protected $primaryKey = 'id';
protected $allowedFields = [
@@ -28,6 +31,10 @@ class CurrentFlagModel extends Model
'created_at',
'updated_at'
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
// Fields that are allowed to be inserted or updated
+5 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class DiscountUsageModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'discount_usages';
protected $primaryKey = 'id';
@@ -30,7 +33,8 @@ class DiscountUsageModel extends Model
'voucher_id' => 'required|is_natural_no_zero',
'invoice_id' => 'required|is_natural_no_zero',
'user_id' => 'permit_empty|is_natural_no_zero',
'used_at' => 'valid_date'
'used_at' => 'valid_date',
'school_year' => 'required|string|max_length[9]',
];
protected $validationMessages = [
+5 -1
View File
@@ -2,9 +2,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class DiscountVoucherModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'discount_vouchers';
protected $primaryKey = 'id';
@@ -38,7 +41,8 @@ class DiscountVoucherModel extends Model
'valid_from' => 'permit_empty|valid_date[Y-m-d]',
'valid_until' => 'permit_empty|valid_date[Y-m-d]',
'is_active' => 'in_list[0,1]',
'description' => 'permit_empty|string|max_length[1000]', // <-- NEW
'description' => 'permit_empty|string|max_length[1000]', // <-- NEW,
'school_year' => 'required|string|max_length[9]',
];
protected $validationMessages = [
+4 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class EarlyDismissalSignatureModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'early_dismissal_signatures';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
@@ -28,7 +31,7 @@ class EarlyDismissalSignatureModel extends Model
protected $validationRules = [
'report_date' => 'required|valid_date[Y-m-d]',
'filename' => 'required|string',
'school_year' => 'permit_empty|string',
'school_year' => 'required|string|max_length[9]',
'semester' => 'permit_empty|string',
];
}
+5 -2
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class EnrollmentModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'enrollments';
protected $primaryKey = 'id';
@@ -32,7 +35,7 @@ class EnrollmentModel extends Model
'student_id' => 'required|integer',
'class_section_id' => 'permit_empty|integer',
'parent_id' => 'required|integer',
'school_year' => 'required|string|max_length[25]',
'school_year' => 'required|string|max_length[9]',
'enrollment_date' => 'required|valid_date',
'withdrawal_date' => 'permit_empty|valid_date',
'is_withdrawn' => 'permit_empty|in_list[0,1]',
@@ -56,7 +59,7 @@ class EnrollmentModel extends Model
'school_year' => [
'required' => 'School year is required',
'string' => 'School year must be a string',
'max_length' => 'School year must not exceed 25 characters',
'max_length' => 'School year must not exceed 9 characters',
],
'enrollment_date' => [
'required' => 'Enrollment date is required',
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class EventChargesModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'event_charges';
protected $primaryKey = 'id';
@@ -32,6 +35,10 @@ class EventChargesModel extends Model
'created_at',
'updated_at'
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class EventModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'events';
protected $primaryKey = 'id';
@@ -22,6 +25,10 @@ class EventModel extends Model
'created_at',
'updated_at'
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+4
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ExamDraftModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'exam_drafts';
protected $primaryKey = 'id';
protected $returnType = 'array';
@@ -65,6 +68,7 @@ class ExamDraftModel extends Model
'author_id' => 'if_exist|is_natural_no_zero',
'class_section_id' => 'if_exist|is_natural_no_zero',
'version' => 'if_exist|is_natural_no_zero',
'school_year' => 'required|string|max_length[9]',
];
protected $validationMessages = [
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ExamModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'exams';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
@@ -18,6 +21,10 @@ class ExamModel extends Model
'school_year',
'created_at'
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
// Timestamps
protected $useTimestamps = true;
+5 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ExpenseModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'expenses';
protected $primaryKey = 'id';
@@ -32,7 +35,8 @@ class ExpenseModel extends Model
protected $updatedField = 'updated_at';
protected $validationRules = [
// Add your validation rules here as needed
// Add your validation rules here as needed,
'school_year' => 'required|string|max_length[9]',
];
public function getReimbursedExpensesWithDetails(array $filters = [])
+5 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class FinalExamModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'final_exam';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
@@ -38,7 +41,8 @@ class FinalExamModel extends Model
protected $deletedField = 'deleted_at';
// Validation
protected $validationRules = [];
protected $validationRules = [ 'school_year' => 'required|string|max_length[9]',
];
protected $validationMessages = [];
protected $skipValidation = false;
protected $cleanValidationRules = true;
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class FinalScoreModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'final_score';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
@@ -23,6 +26,10 @@ class FinalScoreModel extends Model
'created_at',
'updated_at'
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
// Function to get the final exam score for a specific student, semester, and school year
public function getFinalExamScore($studentId, $semester, $schoolYear)
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class FlagModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'flag';
protected $primaryKey = 'id';
protected $allowedFields = [
@@ -27,4 +30,8 @@ class FlagModel extends Model
'created_at',
'updated_at'
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
}
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class GradingLockModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'grading_locks';
protected $primaryKey = 'id';
protected $returnType = 'array';
@@ -20,6 +23,10 @@ class GradingLockModel extends Model
'created_at',
'updated_at',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
public function getLock(int $classSectionId, string $semester, string $schoolYear): ?array
{
+5 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class HomeworkModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'homework';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
@@ -40,7 +43,8 @@ class HomeworkModel extends Model
protected $deletedField = 'deleted_at';
// Validation
protected $validationRules = [];
protected $validationRules = [ 'school_year' => 'required|string|max_length[9]',
];
protected $validationMessages = [];
protected $skipValidation = false;
protected $cleanValidationRules = true;
+8
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class InventoryCategoryModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'inventory_categories';
protected $primaryKey = 'id';
protected $returnType = 'array';
@@ -19,7 +22,12 @@ class InventoryCategoryModel extends Model
'grade_max',
'created_at',
'updated_at',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
public function optionsForType(string $type): array
{
+5
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class InventoryItemModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'inventory_items';
protected $primaryKey = 'id';
@@ -38,6 +41,7 @@ protected $table = 'inventory_items';
'needs_repair_qty',
'need_replace_qty',
'cannot_find_qty',
'school_year',
];
@@ -46,5 +50,6 @@ protected $table = 'inventory_items';
'name' => 'required|min_length[2]',
'quantity' => 'permit_empty|integer',
'unit_price' => 'permit_empty|decimal',
'school_year' => 'required|string|max_length[16]',
];
}
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class InventoryMovementModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'inventory_movements';
protected $primaryKey = 'id';
protected $returnType = 'array';
@@ -16,4 +19,8 @@ class InventoryMovementModel extends Model
'semester','school_year',
'performed_by','teacher_id','student_id','class_section_id',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
}
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class InvoiceEventModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'invoice_event';
protected $primaryKey = 'id';
@@ -20,6 +23,10 @@ class InvoiceEventModel extends Model
'created_at',
'updated_at'
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+4
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class InvoiceModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'invoices';
protected $primaryKey = 'id';
protected $returnType = 'array';
@@ -40,6 +43,7 @@ class InvoiceModel extends Model
'issue_date' => 'required|valid_date[Y-m-d H:i:s]',
'due_date' => 'permit_empty|valid_date[Y-m-d H:i:s]',
'status' => 'required|string|max_length[50]',
'school_year' => 'required|string|max_length[9]',
];
+9 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class InvoiceStudentListModel extends Model
{
use SchoolYearAutoFillTrait;
// Specify the table name
protected $table = 'invoice_students_list';
@@ -21,8 +24,13 @@ class InvoiceStudentListModel extends Model
'school_id',
'enrolled',
'created_at',
'updated_at'
'updated_at',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
// Enable auto-incrementing primary key
protected $useAutoIncrement = true;
+7 -2
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class IpAttemptModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'ip_attempts';
protected $primaryKey = 'id';
@@ -13,7 +16,8 @@ class IpAttemptModel extends Model
'ip_address',
'attempts',
'last_attempt_at',
'blocked_until'
'blocked_until',
'school_year',
];
// Automatically handle timestamps
@@ -26,7 +30,8 @@ class IpAttemptModel extends Model
'ip_address' => 'required|valid_ip|max_length[45]',
'attempts' => 'required|integer',
'last_attempt_at' => 'required|valid_date',
'blocked_until' => 'permit_empty|valid_date'
'blocked_until' => 'permit_empty|valid_date',
'school_year' => 'required|string|max_length[20]',
];
// Method to get IP attempt data by IP address
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class LateSlipLogModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'late_slip_logs';
protected $primaryKey = 'id';
protected $useTimestamps = false; // we control printed_at explicitly
@@ -22,6 +25,10 @@ class LateSlipLogModel extends Model
'printed_by',
'printed_at',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
/**
* Best-effort insert of a single late slip print log.
+9 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class LoginActivityModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'login_activity';
protected $primaryKey = 'id';
protected $allowedFields = [
@@ -17,8 +20,13 @@ class LoginActivityModel extends Model
'user_agent',
'semester',
'created_at',
'updated_at'
'updated_at',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ManualPaymentModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'manual_payments';
protected $allowedFields = [
'invoice_number',
@@ -16,6 +19,10 @@ class ManualPaymentModel extends Model
'semester',
'school_year',
'created_at'];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = false;
}
+9 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class MessageModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'messages';
protected $primaryKey = 'id';
@@ -21,8 +24,13 @@ class MessageModel extends Model
'priority',
'attachment',
'status',
'semester'
'semester',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = false; // Since you're manually handling date fields
protected $returnType = 'array';
+5 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class MidtermExamModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'midterm_exam';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
@@ -39,7 +42,8 @@ class MidtermExamModel extends Model
protected $deletedField = 'deleted_at';
// Validation
protected $validationRules = [];
protected $validationRules = [ 'school_year' => 'required|string|max_length[9]',
];
protected $validationMessages = [];
protected $skipValidation = false;
protected $cleanValidationRules = true;
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class MissingScoreOverrideModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'missing_score_overrides';
protected $primaryKey = 'id';
protected $returnType = 'array';
@@ -22,6 +25,10 @@ class MissingScoreOverrideModel extends Model
'created_at',
'updated_at',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
public function getOverridesMap(int $classSectionId, string $semester, string $schoolYear, string $itemType): array
{
+9 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class NotificationModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'notifications';
protected $primaryKey = 'id';
protected $allowedFields = [
@@ -20,8 +23,13 @@ class NotificationModel extends Model
'expires_at',
'sent_at',
'scheduled_at',
'deleted_at'
'deleted_at',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[20]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
+4 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ParentAttendanceReportModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'parent_attendance_reports';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
@@ -36,7 +39,7 @@ class ParentAttendanceReportModel extends Model
'arrival_time' => 'permit_empty|regex_match[/^\d{2}:\d{2}(:\d{2})?$/]',
'dismiss_time' => 'permit_empty|regex_match[/^\d{2}:\d{2}(:\d{2})?$/]',
'semester' => 'permit_empty|string',
'school_year' => 'permit_empty|string',
'school_year' => 'required|string|max_length[9]',
'status' => 'permit_empty|in_list[new,seen,processed]',
];
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ParentMeetingScheduleModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'parent_meeting_schedules';
protected $primaryKey = 'id';
@@ -23,6 +26,10 @@ class ParentMeetingScheduleModel extends Model
'status',
'created_by',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ParentNotificationModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'parent_notifications';
protected $primaryKey = 'id';
protected $useTimestamps = true;
@@ -16,6 +19,10 @@ class ParentNotificationModel extends Model
'student_id','code','incident_date','channel','to_address','subject',
'status','response','semester','school_year'
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
public function hasSent(int $studentId, string $code, string $incidentYmd, string $channel='email', ?string $to=null): bool
{
+5 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ParticipationModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'participation';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
@@ -39,7 +42,8 @@ class ParticipationModel extends Model
protected $deletedField = 'deleted_at';
// Validation
protected $validationRules = [];
protected $validationRules = [ 'school_year' => 'required|string|max_length[9]',
];
protected $validationMessages = [];
protected $skipValidation = false;
protected $cleanValidationRules = true;
+5 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class PaymentErrorModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'payment_error'; // The DB table
protected $primaryKey = 'id'; // Primary key
@@ -28,7 +31,8 @@ class PaymentErrorModel extends Model
'wrong_paid_amount' => 'required|decimal',
'payment_id' => 'required|integer',
'invoice_id' => 'required|integer',
'parent_id' => 'required|integer'
'parent_id' => 'required|integer',
'school_year' => 'required|string|max_length[9]',
];
}
+4 -1
View File
@@ -4,10 +4,13 @@ namespace App\Models;
use CodeIgniter\Database\ConnectionInterface;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
use CodeIgniter\Validation\ValidationInterface;
class PaymentModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'payments';
protected $primaryKey = 'id';
protected $returnType = 'array';
@@ -69,7 +72,7 @@ class PaymentModel extends Model
'transaction_id' => 'permit_empty|max_length[100]',
'payment_method' => 'required|in_list[cash,check,card]',
'payment_date' => 'required|valid_date',
'school_year' => 'permit_empty|max_length[20]',
'school_year' => 'required|string|max_length[9]',
'status' => 'required|max_length[50]',
'check_file' => 'permit_empty|max_length[255]',
'check_number' => 'permit_empty|max_length[100]',
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class PaymentNotificationLogModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'payment_notification_logs';
protected $primaryKey = 'id';
protected $returnType = 'array';
@@ -29,6 +32,10 @@ class PaymentNotificationLogModel extends Model
'created_at',
'sent_at',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = false;
+4 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class PaymentTransactionModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'payment_transactions'; // Table name
protected $primaryKey = 'id'; // Primary key
@@ -32,7 +35,7 @@ class PaymentTransactionModel extends Model
'payment_method' => 'required|string|max_length[50]',
'payment_status' => 'required|string|max_length[50]',
'semester' => 'permit_empty|string|max_length[30]',
'school_year' => 'permit_empty|string|max_length[9]',
'school_year' => 'required|string|max_length[9]',
];
// Custom error messages
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class PlacementBatchModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'placement_batches';
protected $primaryKey = 'id';
@@ -17,6 +20,10 @@ class PlacementBatchModel extends Model
'created_at',
'updated_at',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+8
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class PlacementLevelModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'placement_levels';
protected $primaryKey = 'id';
@@ -16,7 +19,12 @@ class PlacementLevelModel extends Model
'updated_by',
'created_at',
'updated_at',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[20]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+8
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class PlacementScoreModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'placement_scores';
protected $primaryKey = 'id';
@@ -17,7 +20,12 @@ class PlacementScoreModel extends Model
'updated_by',
'created_at',
'updated_at',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
+5 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class PrintRequestModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'print_requests';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
@@ -31,7 +34,8 @@ class PrintRequestModel extends Model
protected $updatedField = 'updated_at';
// Validation
protected $validationRules = [];
protected $validationRules = [ 'school_year' => 'required|string|max_length[9]',
];
protected $validationMessages = [];
protected $skipValidation = false;
protected $cleanValidationRules = true;
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ProjectModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'project';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
@@ -23,6 +26,10 @@ class ProjectModel extends Model
'created_at',
'updated_at'
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
/**
* Calculate the average project score for a given student, semester, and school year.
+10 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class PromotionQueueModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'promotion_queue';
protected $primaryKey = 'id';
@@ -20,7 +23,12 @@ class PromotionQueueModel extends Model
'created_at',
'updated_at',
'updated_by',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
@@ -32,6 +40,8 @@ class PromotionQueueModel extends Model
return false;
}
$data['school_year'] = (string)($data['school_year'] ?? $data['school_year_from'] ?? $data['school_year_to']);
$existing = $this->where('student_id', (int)$data['student_id'])
->where('school_year_to', (string)$data['school_year_to'])
->first();
@@ -43,4 +53,3 @@ class PromotionQueueModel extends Model
return (bool) $this->insert($data);
}
}
+5 -1
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class QuizModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'quiz';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
@@ -40,7 +43,8 @@ class QuizModel extends Model
protected $deletedField = 'deleted_at';
// Validation
protected $validationRules = [];
protected $validationRules = [ 'school_year' => 'required|string|max_length[9]',
];
protected $validationMessages = [];
protected $skipValidation = false;
protected $cleanValidationRules = true;
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class RefundModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'refunds';
protected $primaryKey = 'id';
@@ -30,6 +33,10 @@ class RefundModel extends Model
'check_nbr',
'check_file'
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ReimbursementBatchAdminFileModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'reimbursement_batch_admin_files';
protected $primaryKey = 'id';
protected $allowedFields = [
@@ -15,6 +18,11 @@ class ReimbursementBatchAdminFileModel extends Model
'original_filename',
'uploaded_at',
'uploaded_by',
'school_year',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = false;
}
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ReimbursementBatchItemModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'reimbursement_batch_items';
protected $primaryKey = 'id';
protected $allowedFields = [
@@ -19,6 +22,10 @@ class ReimbursementBatchItemModel extends Model
'school_year',
'semester',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = false;
}
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ReimbursementBatchModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'reimbursement_batches';
protected $primaryKey = 'id';
protected $allowedFields = [
@@ -20,6 +23,10 @@ class ReimbursementBatchModel extends Model
'semester',
'yearly_batch_number',
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
protected $useTimestamps = false;
}
+7
View File
@@ -3,9 +3,12 @@
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Concerns\SchoolYearAutoFillTrait;
class ReimbursementModel extends Model
{
use SchoolYearAutoFillTrait;
protected $table = 'reimbursements';
protected $primaryKey = 'id';
protected $allowedFields = [
@@ -23,5 +26,9 @@ class ReimbursementModel extends Model
'reimbursement_method',
'batch_number'
];
protected $validationRules = [
'school_year' => 'required|string|max_length[9]',
];
}

Some files were not shown because too many files have changed in this diff Show More