add refund logic and fix books inventory logic
This commit is contained in:
@@ -16,7 +16,7 @@ use App\Models\PaymentModel;
|
||||
use App\Models\ConfigurationModel;
|
||||
use App\Models\InvoiceModel;
|
||||
use App\Models\EnrollmentModel;
|
||||
use App\Services\FeeCalculationService;
|
||||
use App\Services\EnrollmentStatusService;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
|
||||
class RefundController extends BaseController
|
||||
@@ -32,7 +32,6 @@ class RefundController extends BaseController
|
||||
protected ParentLedgerService $parentLedgerService;
|
||||
protected RefundEligibilityService $refundEligibilityService;
|
||||
protected FinancialAttachmentService $financialAttachmentService;
|
||||
protected FeeCalculationService $feeCalculationService;
|
||||
protected $db;
|
||||
|
||||
// Allowed request types (mapped to your `refunds.request` column)
|
||||
@@ -54,7 +53,6 @@ class RefundController extends BaseController
|
||||
$this->parentLedgerService = new ParentLedgerService();
|
||||
$this->refundEligibilityService = new RefundEligibilityService();
|
||||
$this->financialAttachmentService = new FinancialAttachmentService();
|
||||
$this->feeCalculationService = new FeeCalculationService();
|
||||
$this->db = \Config\Database::connect();
|
||||
}
|
||||
|
||||
@@ -723,6 +721,10 @@ class RefundController extends BaseController
|
||||
throw new FinancialPersistenceException('REFUND_PROJECTION_UPDATE_FAILED', $this->refundModel->errors());
|
||||
}
|
||||
|
||||
if (!$isOnline && $newStatus === FinancialStatus::REFUND_PAID) {
|
||||
$this->markWithdrawalEnrollmentComplete($lockedRefund);
|
||||
}
|
||||
|
||||
if (!$isOnline && $affectedInvoiceId > 0) {
|
||||
$this->invoiceLedgerService->recalculateInvoice($affectedInvoiceId);
|
||||
}
|
||||
@@ -774,6 +776,49 @@ class RefundController extends BaseController
|
||||
return $this->response->setJSON($payload);
|
||||
}
|
||||
|
||||
private function markWithdrawalEnrollmentComplete(array $refund): void
|
||||
{
|
||||
if ((string)($refund['source_type'] ?? '') !== 'tuition_withdrawal') {
|
||||
return;
|
||||
}
|
||||
|
||||
$calculationId = (int)($refund['withdrawal_calculation_id'] ?? 0);
|
||||
if ($calculationId <= 0 || ! $this->db->tableExists('withdrawal_financial_calculations')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$calculation = $this->db->query(
|
||||
'SELECT enrollment_id FROM withdrawal_financial_calculations WHERE id = ? FOR UPDATE',
|
||||
[$calculationId]
|
||||
)->getRowArray();
|
||||
$enrollmentId = (int)($calculation['enrollment_id'] ?? 0);
|
||||
if ($enrollmentId <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$enrollment = $this->db->query(
|
||||
'SELECT * FROM enrollments WHERE id = ? FOR UPDATE',
|
||||
[$enrollmentId]
|
||||
)->getRowArray();
|
||||
if (! $enrollment) {
|
||||
return;
|
||||
}
|
||||
|
||||
$statusService = new EnrollmentStatusService($this->db);
|
||||
$statusService->upsertStatus([
|
||||
'id' => (int)$enrollment['id'],
|
||||
'student_id' => (int)$enrollment['student_id'],
|
||||
'parent_id' => (int)$enrollment['parent_id'],
|
||||
'school_year' => (string)$enrollment['school_year'],
|
||||
'semester' => (string)($enrollment['semester'] ?? ''),
|
||||
'enrollment_status' => 'withdrawn',
|
||||
'admission_status' => (string)($enrollment['admission_status'] ?? 'pending'),
|
||||
'is_withdrawn' => 1,
|
||||
'withdrawal_date' => $enrollment['withdrawal_date'] ?? null,
|
||||
'updated_at' => utc_now(),
|
||||
], (int)(session()->get('user_id') ?? 0) ?: null, 'withdrawal_refund_paid');
|
||||
}
|
||||
|
||||
public function reversePayout(?int $routePayoutId = null)
|
||||
{
|
||||
$payoutId = (int)($routePayoutId ?: $this->request->getPost('payout_id'));
|
||||
@@ -933,6 +978,7 @@ class RefundController extends BaseController
|
||||
{
|
||||
// Repair legacy withdrawal placeholders only; overpayment recalculation remains explicit.
|
||||
$this->repairPendingWithdrawalRefunds();
|
||||
$withdrawalReviews = $this->pendingWithdrawalReviews();
|
||||
|
||||
// 2) List refunds with joins
|
||||
$refunds = $this->refundModel
|
||||
@@ -987,10 +1033,41 @@ class RefundController extends BaseController
|
||||
|
||||
return view('refunds/list', [
|
||||
'refunds' => $refunds,
|
||||
'parentId' => $parentId
|
||||
'parentId' => $parentId,
|
||||
'withdrawalReviews' => $withdrawalReviews,
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return list<array<string,mixed>> */
|
||||
private function pendingWithdrawalReviews(): array
|
||||
{
|
||||
try {
|
||||
if (! $this->db->tableExists('withdrawal_financial_calculations')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->db->table('withdrawal_financial_calculations wfc')
|
||||
->select('wfc.*,
|
||||
i.invoice_number,
|
||||
u.firstname AS parent_firstname,
|
||||
u.lastname AS parent_lastname,
|
||||
s.firstname AS student_firstname,
|
||||
s.lastname AS student_lastname')
|
||||
->join('invoices i', 'wfc.invoice_id = i.id', 'left')
|
||||
->join('users u', 'wfc.parent_id = u.id', 'left')
|
||||
->join('students s', 'wfc.student_id = s.id', 'left')
|
||||
->whereIn('wfc.status', ['preview', 'requires_review'])
|
||||
->where('wfc.posted_at', null)
|
||||
->orderBy('wfc.calculated_at', 'DESC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Pending withdrawal review lookup failed: ' . $e->getMessage());
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private function repairPendingWithdrawalRefunds(): void
|
||||
{
|
||||
try {
|
||||
@@ -1021,24 +1098,48 @@ class RefundController extends BaseController
|
||||
continue;
|
||||
}
|
||||
|
||||
$students = $this->enrollmentModel
|
||||
->where('parent_id', $parentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->findAll();
|
||||
$refundAmount = $this->feeCalculationService->calculateRefund($students, $parentId);
|
||||
$refundCents = max(0, (int)round($refundAmount * 100));
|
||||
|
||||
$this->refundModel->update((int)$refund['id'], [
|
||||
'invoice_id' => (int)$invoice['id'],
|
||||
'refund_amount' => $refundAmount,
|
||||
'requested_amount_cents' => $refundCents,
|
||||
'currency' => 'USD',
|
||||
'request' => 'tuition',
|
||||
'source_type' => 'tuition_withdrawal',
|
||||
'source_id' => (int)$invoice['id'],
|
||||
'reconciliation_status' => 'requires_review',
|
||||
'reconciliation_reason' => 'Legacy pending withdrawal refund requires recalculation through the withdrawal financial review workflow.',
|
||||
'reconciliation_required_at' => utc_now(),
|
||||
'updated_at' => utc_now(),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->db->tableExists('withdrawal_financial_calculations')) {
|
||||
$postedRows = $this->db->table('refunds r')
|
||||
->select('r.id, r.refund_amount, r.requested_amount_cents, wfc.posted_at, wfc.posted_by')
|
||||
->join('withdrawal_financial_calculations wfc', 'wfc.id = r.withdrawal_calculation_id', 'inner')
|
||||
->where('r.source_type', 'tuition_withdrawal')
|
||||
->whereIn('r.status', ['Pending', 'pending', 'requested'])
|
||||
->where('wfc.status', 'posted')
|
||||
->where('wfc.posted_at IS NOT NULL', null, false)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
foreach ($postedRows as $refund) {
|
||||
$amountCents = (int)($refund['requested_amount_cents'] ?? 0);
|
||||
if ($amountCents <= 0) {
|
||||
$amountCents = (int)round(((float)($refund['refund_amount'] ?? 0)) * 100);
|
||||
}
|
||||
if ($amountCents <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->refundModel->update((int)$refund['id'], [
|
||||
'status' => $this->refundStatusForStorage(FinancialStatus::REFUND_APPROVED),
|
||||
'approved_amount_cents' => $amountCents,
|
||||
'approved_at' => $refund['posted_at'] ?? utc_now(),
|
||||
'approved_by' => !empty($refund['posted_by']) ? (int)$refund['posted_by'] : null,
|
||||
'updated_at' => utc_now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Pending withdrawal refund repair failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user