This commit is contained in:
@@ -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
@@ -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');
|
||||
|
||||
|
||||
+22
-2
@@ -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
@@ -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) . '`';
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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]',
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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]',
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = [])
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]',
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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]',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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]',
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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]',
|
||||
];
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user