configModel = new ConfigurationModel(); $this->paypalModel = new PayPalPaymentModel(); $this->paymentModel = new PaymentModel(); $this->userModel = new UserModel(); $this->invoiceModel = new InvoiceModel(); $this->studentModel = new StudentModel(); $this->enrollmentModel = new EnrollmentModel(); } public function run(array $params) { $this->semester = (string) ($this->configModel->getConfig('semester') ?? ''); $this->schoolYear = (string) ($this->configModel->getConfig('school_year') ?? ''); $dryRun = CLI::getOption('dry-run'); $reportOnly = CLI::getOption('report-only'); $mode = $reportOnly ? 'REPORT-ONLY' : ($dryRun ? 'DRY-RUN' : 'LIVE'); $paypalEntries = $this->paypalModel ->where('status', 'COMPLETED') ->where('synced', 0) ->where('sync_attempts <', 3) ->where('transaction_id IS NOT NULL') ->findAll(); $syncedCount = 0; $failed = []; foreach ($paypalEntries as $entry) { $parentId = null; $invoiceId = 0; $users = $this->userModel->getUsersBySchoolId($entry['parent_school_id']); $user = $users[0] ?? null; // Always increment sync_attempts unless report-only if (!$reportOnly) { $this->paypalModel->update($entry['id'], [ 'sync_attempts' => $entry['sync_attempts'] + 1 ]); } if ($user) { $parentId = $user['id']; $invoice = $this->invoiceModel->getInvoicesByParentId($parentId, $this->schoolYear); if (!$reportOnly && !$dryRun) { if ($invoice) { $invoiceId = $invoice['id']; $success = $this->processPayment( $invoiceId, $entry['amount'], 'PayPal', null, $entry['transaction_id'], date('Y-m-d', strtotime($entry['created_at'])), $this->schoolYear, $this->semester ); if (!$success) { $failed[] = $entry['transaction_id']; continue; } } else { $this->paymentModel->insert([ 'parent_id' => $parentId, 'invoice_id' => 0, 'total_amount' => $entry['amount'], 'paid_amount' => $entry['amount'], 'balance' => 0.00, 'number_of_installments' => 1, 'transaction_id' => $entry['transaction_id'], 'payment_method' => 'PayPal', 'payment_date' => date('Y-m-d', strtotime($entry['created_at'])), 'school_year' => $this->schoolYear, 'semester' => $this->semester, 'status' => 'Completed', 'updated_by' => null, ]); } // Mark as synced only in LIVE mode $this->paypalModel->update($entry['id'], ['synced' => 1]); } $syncedCount++; } else { log_message('error', "[PAYPAL SYNC FAILED] No user found for parent_school_id: {$entry['parent_school_id']}"); $failed[] = $entry['transaction_id']; } } // === Logging === log_message('info', "[$mode] PAYPAL SYNC: $syncedCount processed."); if (!empty($failed)) { log_message('error', "[$mode] PAYPAL SYNC Failed: " . implode(', ', $failed)); } // === CLI Output === CLI::write("[$mode] $syncedCount PayPal payments processed.", 'green'); if (!empty($failed)) { CLI::error("[$mode] Failed transactions: " . implode(', ', $failed)); } // === Email Report: Only if there's any update === if ($syncedCount > 0 || !empty($failed)) { helper('email'); $email = \Config\Services::email(); $email->setTo('support@alrahmaisgl.org'); $email->setFrom('no-parentsreply@alrahmaisgl.org', 'PayPal Sync Report'); $email->setSubject("[$mode] PayPal Sync Report - " . date('Y-m-d H:i')); $body = "PayPal Sync Mode: $mode\n\n"; $body .= "$syncedCount PayPal payments processed.\n\n"; if (!empty($failed)) { $body .= count($failed) . " failed transactions:\n"; $body .= implode("\n", $failed); } else { $body .= "No failed transactions.\n"; } $email->setMessage(nl2br($body)); if ($email->send()) { CLI::write("[$mode] Email report sent successfully.", 'yellow'); } else { CLI::error("[$mode] Failed to send email report."); log_message('error', 'Email send error: ' . $email->printDebugger(['headers'])); } } else { log_message('info', "[$mode] No PayPal sync updates. Email not sent."); CLI::write("[$mode] No changes to report. Email not sent.", 'blue'); } } private function processPayment($invoiceId, $amount, $paymentMethod, $checkFile = null, $transactionId = null, $paymentDate = null, $schoolYear = null, $semester = null) { $invoice = $this->invoiceModel->find($invoiceId); if (!$invoice) { return false; } $transactionId = $transactionId ?? 'INV-' . $invoiceId . '-' . time(); $paymentDate = $paymentDate ?? date('Y-m-d'); $newPaid = $invoice['paid_amount'] + $amount; $newBalance = $invoice['balance'] - $amount; $invoiceUpdateData = [ 'paid_amount' => $newPaid, 'balance' => $newBalance, 'status' => ($newBalance <= 0) ? 'Paid' : $invoice['status'], ]; if (!$this->invoiceModel->update($invoiceId, $invoiceUpdateData)) { return false; } $this->paymentModel->insert([ 'parent_id' => $invoice['parent_id'], 'invoice_id' => $invoiceId, 'total_amount' => $invoice['total_amount'], 'paid_amount' => $amount, 'balance' => $newBalance, 'number_of_installments' => 1, 'transaction_id' => $transactionId, 'payment_method' => $paymentMethod, 'payment_date' => $paymentDate, 'status' => ($newBalance <= 0) ? 'Full' : 'Partial', 'check_file' => $checkFile, 'updated_by' => null, // Avoid using session()->get() in CLI 'school_year' => $schoolYear, 'semester' => $semester ]); $this->updateEnrollmentStatusIfPaid($invoiceId, $schoolYear); return true; } private function updateEnrollmentStatusIfPaid($invoiceId, $schoolYear) { $invoice = $this->invoiceModel->find($invoiceId); if (!$invoice || $invoice['balance'] > 0) { return; } $students = $this->studentModel->where('parent_id', $invoice['parent_id']) ->where('school_year', $schoolYear) ->findAll(); foreach ($students as $student) { $this->enrollmentModel->set(['enrollment_status' => 'enrolled']) ->where('student_id', $student['id']) ->update(); } } }