fix financial issues
This commit is contained in:
@@ -3,12 +3,15 @@
|
||||
namespace App\Controllers\View;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Libraries\FinancialAttachmentService;
|
||||
use App\Libraries\InvoiceLedgerService;
|
||||
use App\Models\RefundModel;
|
||||
use App\Models\UserModel;
|
||||
use App\Models\PaymentModel;
|
||||
use App\Models\ConfigurationModel;
|
||||
use App\Models\InvoiceModel;
|
||||
use App\Models\EnrollmentModel;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
|
||||
class RefundController extends BaseController
|
||||
{
|
||||
@@ -18,6 +21,8 @@ class RefundController extends BaseController
|
||||
protected ConfigurationModel $configModel;
|
||||
protected InvoiceModel $invoiceModel;
|
||||
protected EnrollmentModel $enrollmentModel;
|
||||
protected InvoiceLedgerService $invoiceLedgerService;
|
||||
protected FinancialAttachmentService $financialAttachmentService;
|
||||
protected $db;
|
||||
|
||||
// Allowed request types (mapped to your `refunds.request` column)
|
||||
@@ -33,6 +38,8 @@ class RefundController extends BaseController
|
||||
$this->configModel = new ConfigurationModel();
|
||||
$this->invoiceModel = new InvoiceModel();
|
||||
$this->enrollmentModel = new EnrollmentModel();
|
||||
$this->invoiceLedgerService = new InvoiceLedgerService();
|
||||
$this->financialAttachmentService = new FinancialAttachmentService();
|
||||
$this->db = \Config\Database::connect();
|
||||
}
|
||||
|
||||
@@ -445,6 +452,14 @@ class RefundController extends BaseController
|
||||
return $this->response->setJSON(['error' => 'Failed to create refund request.']);
|
||||
}
|
||||
|
||||
if (!empty($invoiceId)) {
|
||||
try {
|
||||
$this->invoiceLedgerService->recalculateInvoice((int) $invoiceId);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'requestRefund recalc failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Fire refundPending notification/event for newly created refunds
|
||||
try {
|
||||
$user = $this->userModel->select('id, email, firstname, lastname')->find($parentId) ?: [];
|
||||
@@ -467,13 +482,36 @@ class RefundController extends BaseController
|
||||
// Approve refund (no money movement)
|
||||
public function approveRefund(int $refundId)
|
||||
{
|
||||
$ok = $this->refundModel->update($refundId, [
|
||||
'status' => 'Approved',
|
||||
'approved_at' => utc_now(),
|
||||
'approved_by' => session()->get('user_id'),
|
||||
'updated_at' => utc_now(),
|
||||
'updated_by' => session()->get('user_id'),
|
||||
]);
|
||||
$refund = $this->refundModel->find($refundId);
|
||||
if (!$refund) {
|
||||
return $this->response->setJSON(['error' => 'Refund not found']);
|
||||
}
|
||||
|
||||
$this->db->transBegin();
|
||||
|
||||
try {
|
||||
if (!empty($refund['invoice_id'])) {
|
||||
$this->db->query('SELECT id FROM invoices WHERE id = ? FOR UPDATE', [(int) $refund['invoice_id']]);
|
||||
}
|
||||
|
||||
$ok = $this->refundModel->update($refundId, [
|
||||
'status' => 'Approved',
|
||||
'approved_at' => utc_now(),
|
||||
'approved_by' => session()->get('user_id'),
|
||||
'updated_at' => utc_now(),
|
||||
'updated_by' => session()->get('user_id'),
|
||||
]);
|
||||
|
||||
if (!empty($refund['invoice_id'])) {
|
||||
$this->invoiceLedgerService->recalculateInvoice((int) $refund['invoice_id']);
|
||||
}
|
||||
|
||||
$this->db->transCommit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->db->transRollback();
|
||||
log_message('error', 'approveRefund failed: ' . $e->getMessage());
|
||||
$ok = false;
|
||||
}
|
||||
|
||||
return $this->response->setJSON($ok ? ['success' => 'Refund approved'] : ['error' => 'Approve failed']);
|
||||
}
|
||||
@@ -481,13 +519,36 @@ class RefundController extends BaseController
|
||||
// Reject refund
|
||||
public function rejectRefund(int $refundId)
|
||||
{
|
||||
$ok = $this->refundModel->update($refundId, [
|
||||
'status' => 'Rejected',
|
||||
'approved_at' => utc_now(),
|
||||
'approved_by' => session()->get('user_id'),
|
||||
'updated_at' => utc_now(),
|
||||
'updated_by' => session()->get('user_id'),
|
||||
]);
|
||||
$refund = $this->refundModel->find($refundId);
|
||||
if (!$refund) {
|
||||
return $this->response->setJSON(['error' => 'Refund not found']);
|
||||
}
|
||||
|
||||
$this->db->transBegin();
|
||||
|
||||
try {
|
||||
if (!empty($refund['invoice_id'])) {
|
||||
$this->db->query('SELECT id FROM invoices WHERE id = ? FOR UPDATE', [(int) $refund['invoice_id']]);
|
||||
}
|
||||
|
||||
$ok = $this->refundModel->update($refundId, [
|
||||
'status' => 'Rejected',
|
||||
'approved_at' => utc_now(),
|
||||
'approved_by' => session()->get('user_id'),
|
||||
'updated_at' => utc_now(),
|
||||
'updated_by' => session()->get('user_id'),
|
||||
]);
|
||||
|
||||
if (!empty($refund['invoice_id'])) {
|
||||
$this->invoiceLedgerService->recalculateInvoice((int) $refund['invoice_id']);
|
||||
}
|
||||
|
||||
$this->db->transCommit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->db->transRollback();
|
||||
log_message('error', 'rejectRefund failed: ' . $e->getMessage());
|
||||
$ok = false;
|
||||
}
|
||||
|
||||
return $this->response->setJSON($ok ? ['success' => 'Refund rejected'] : ['error' => 'Reject failed']);
|
||||
}
|
||||
@@ -526,17 +587,21 @@ class RefundController extends BaseController
|
||||
// Optional file upload for Check
|
||||
$checkFileName = $refund['check_file'] ?? null;
|
||||
if ($refundMethod === 'Check') {
|
||||
$checkFile = $this->request->getFile('check_file');
|
||||
if ($checkFile && $checkFile->isValid() && !$checkFile->hasMoved()) {
|
||||
$checkFileName = $checkFile->getRandomName();
|
||||
$checkFile->move(WRITEPATH . 'uploads/checks/', $checkFileName);
|
||||
try {
|
||||
$checkFile = $this->request->getFile('check_file');
|
||||
$uploaded = $this->financialAttachmentService->saveUploadedFile($checkFile, 'checks');
|
||||
if ($uploaded !== null) {
|
||||
$checkFileName = $uploaded;
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
return $this->response->setJSON(['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
$newStatus = ($total < $target) ? 'Partial' : 'Paid';
|
||||
|
||||
$db = db_connect();
|
||||
$db->transStart();
|
||||
$db->transBegin();
|
||||
|
||||
// 0) If this refund is not tied to an invoice yet, assign it to the most overpaid invoice
|
||||
$assignInvoiceId = null;
|
||||
@@ -595,26 +660,32 @@ class RefundController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
// 1) Update refund row
|
||||
$this->refundModel->update($refundId, [
|
||||
'refund_paid_amount' => $total,
|
||||
'status' => $newStatus,
|
||||
'refunded_at' => utc_now(),
|
||||
'updated_at' => utc_now(),
|
||||
'updated_by' => session()->get('user_id'),
|
||||
'refund_method' => $refundMethod,
|
||||
'check_nbr' => $checkNbr,
|
||||
'check_file' => $checkFileName,
|
||||
// tie to invoice if determined
|
||||
'invoice_id' => $assignInvoiceId ?? $refund['invoice_id'],
|
||||
]);
|
||||
try {
|
||||
$affectedInvoiceId = (int) ($assignInvoiceId ?? $refund['invoice_id'] ?? 0);
|
||||
if ($affectedInvoiceId > 0) {
|
||||
$db->query('SELECT id FROM invoices WHERE id = ? FOR UPDATE', [$affectedInvoiceId]);
|
||||
}
|
||||
|
||||
// 2) Optional: If you want an accounting journal entry for payouts, write to a dedicated table.
|
||||
// We no longer insert a negative row into payments to avoid schema/validation conflicts.
|
||||
$this->refundModel->update($refundId, [
|
||||
'refund_paid_amount' => $total,
|
||||
'status' => $newStatus,
|
||||
'refunded_at' => utc_now(),
|
||||
'updated_at' => utc_now(),
|
||||
'updated_by' => session()->get('user_id'),
|
||||
'refund_method' => $refundMethod,
|
||||
'check_nbr' => $checkNbr,
|
||||
'check_file' => $checkFileName,
|
||||
'invoice_id' => $affectedInvoiceId ?: null,
|
||||
]);
|
||||
|
||||
$db->transComplete();
|
||||
if ($affectedInvoiceId > 0) {
|
||||
$this->invoiceLedgerService->recalculateInvoice($affectedInvoiceId);
|
||||
}
|
||||
|
||||
if ($db->transStatus() === false) {
|
||||
$db->transCommit();
|
||||
} catch (\Throwable $e) {
|
||||
$db->transRollback();
|
||||
log_message('error', 'updatePayment failed: ' . $e->getMessage());
|
||||
return $this->response->setJSON(['error' => 'Failed to update payment.']);
|
||||
}
|
||||
|
||||
@@ -770,16 +841,77 @@ class RefundController extends BaseController
|
||||
return $this->response->setJSON(['error' => 'Refund not found.']);
|
||||
}
|
||||
|
||||
$ok = $this->refundModel->update($refundId, [
|
||||
'status' => $status,
|
||||
'reason' => $reason,
|
||||
'approved_at' => utc_now(),
|
||||
'approved_by' => session()->get('user_id'),
|
||||
'updated_at' => utc_now(),
|
||||
'updated_by' => session()->get('user_id'),
|
||||
]);
|
||||
$this->db->transBegin();
|
||||
|
||||
try {
|
||||
if (!empty($refund['invoice_id'])) {
|
||||
$this->db->query('SELECT id FROM invoices WHERE id = ? FOR UPDATE', [(int) $refund['invoice_id']]);
|
||||
}
|
||||
|
||||
$ok = $this->refundModel->update($refundId, [
|
||||
'status' => $status,
|
||||
'reason' => $reason,
|
||||
'approved_at' => utc_now(),
|
||||
'approved_by' => session()->get('user_id'),
|
||||
'updated_at' => utc_now(),
|
||||
'updated_by' => session()->get('user_id'),
|
||||
]);
|
||||
|
||||
if (!empty($refund['invoice_id'])) {
|
||||
$this->invoiceLedgerService->recalculateInvoice((int) $refund['invoice_id']);
|
||||
}
|
||||
|
||||
$this->db->transCommit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->db->transRollback();
|
||||
log_message('error', 'updateStatus failed: ' . $e->getMessage());
|
||||
$ok = false;
|
||||
}
|
||||
|
||||
return $this->response->setJSON($ok ? ['success' => 'Refund status updated successfully.']
|
||||
: ['error' => 'Failed to update refund status.']);
|
||||
}
|
||||
|
||||
public function serveRefundFile(int $refundId, string $mode = 'download')
|
||||
{
|
||||
$refund = $this->refundModel->find($refundId);
|
||||
if (!$refund || empty($refund['check_file'])) {
|
||||
throw PageNotFoundException::forPageNotFound('Refund file not found.');
|
||||
}
|
||||
|
||||
if (!$this->canViewRefund($refund)) {
|
||||
return $this->response->setStatusCode(403);
|
||||
}
|
||||
|
||||
$path = $this->financialAttachmentService->resolvePath('checks', (string) $refund['check_file']);
|
||||
if ($path === null) {
|
||||
throw PageNotFoundException::forPageNotFound('Refund file not found.');
|
||||
}
|
||||
|
||||
if ($mode === 'inline') {
|
||||
return $this->response
|
||||
->setHeader('Content-Type', $this->financialAttachmentService->detectMime($path))
|
||||
->setHeader('Content-Disposition', 'inline; filename="' . basename($path) . '"')
|
||||
->setBody(file_get_contents($path));
|
||||
}
|
||||
|
||||
return $this->response->download($path, null);
|
||||
}
|
||||
|
||||
private function canViewRefund(array $refund): bool
|
||||
{
|
||||
$roles = array_map('strtolower', (array) (session()->get('roles') ?? []));
|
||||
$activeRole = strtolower((string) (session()->get('role') ?? ''));
|
||||
if ($activeRole !== '' && !in_array($activeRole, $roles, true)) {
|
||||
$roles[] = $activeRole;
|
||||
}
|
||||
|
||||
foreach (['administrator', 'administrative staff', 'principal', 'teacher', 'teacher_assistant'] as $role) {
|
||||
if (in_array($role, $roles, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return in_array('parent', $roles, true) && (int) ($refund['parent_id'] ?? 0) === (int) session()->get('user_id');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user