add refund logic and fix books inventory logic
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
namespace App\Services;
|
||||
|
||||
use App\Controllers\View\InvoiceController;
|
||||
use App\Libraries\RefundEligibilityService;
|
||||
use App\Models\ClassSectionModel;
|
||||
use App\Models\EnrollmentModel;
|
||||
use App\Models\InvoiceModel;
|
||||
@@ -194,7 +193,6 @@ public function newStudents(string $schoolYear): array
|
||||
//update enrollment status
|
||||
public function updateStatuses(?array $enrollmentStatuses, string $schoolYear, string $semester, ?int $performedBy): array
|
||||
{
|
||||
$refundService = new FeeCalculationService();
|
||||
$enrollmentStatusService = \Config\Services::enrollmentStatus(false);
|
||||
$performedBy = $performedBy ?: ((int) (session()->get('user_id') ?? 0) ?: null);
|
||||
$this->schoolYear = $schoolYear;
|
||||
@@ -212,8 +210,8 @@ public function updateStatuses(?array $enrollmentStatuses, string $schoolYear, s
|
||||
// For batching emails: parent -> status -> [students...]
|
||||
$groupsByParentStatus = []; // [parent_id][status][] = ['student_id'=>, 'student_name'=>]
|
||||
$parentInfo = []; // [parent_id] = ['user_id','email','firstname','lastname']
|
||||
$refundParents = []; // parent_id => true (for refund calc)
|
||||
$refundAmountByParent = []; // parent_id => amount
|
||||
$withdrawalPreviewEnrollmentIds = []; // enrollment_id => parent_id
|
||||
$refundAmountByParent = []; // parent_id => preview amount for notification context
|
||||
|
||||
$validStatuses = EnrollmentStatusService::VALID_STATUSES;
|
||||
|
||||
@@ -287,7 +285,7 @@ public function updateStatuses(?array $enrollmentStatuses, string $schoolYear, s
|
||||
];
|
||||
|
||||
if ($newEnrollmentStatus === 'refund pending') {
|
||||
$refundParents[$parentId] = true;
|
||||
$withdrawalPreviewEnrollmentIds[(int) $result['id']] = $parentId;
|
||||
}
|
||||
|
||||
log_message('info', "Created enrollment for student ID {$studentId} with status {$newEnrollmentStatus} and admission {$admissionStatus}.");
|
||||
@@ -369,98 +367,25 @@ public function updateStatuses(?array $enrollmentStatuses, string $schoolYear, s
|
||||
|
||||
// Mark for refund calc
|
||||
if ($newEnrollmentStatus === 'refund pending') {
|
||||
$refundParents[$parentId] = true;
|
||||
$withdrawalPreviewEnrollmentIds[(int) $enrollmentRow['id']] = (int) $parentId;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute refunds ONCE per parent needing it
|
||||
foreach (array_keys($refundParents) as $pid) {
|
||||
$students = $this->enrollmentModel
|
||||
->where('parent_id', $pid)
|
||||
->where('school_year', $this->schoolYear)
|
||||
->findAll();
|
||||
|
||||
if (empty($students)) {
|
||||
// If a parent is marked for refund but has no enrollments, just log and continue.
|
||||
log_message('info', "No enrollments found for parent ID {$pid} (for refund calc); skipping refund.");
|
||||
continue;
|
||||
}
|
||||
|
||||
$invoice = $this->invoiceModel->where('parent_id', $pid)
|
||||
->where('school_year', $this->schoolYear)
|
||||
->orderBy('created_at', 'DESC')
|
||||
->first();
|
||||
|
||||
if (!$invoice) {
|
||||
$errors[] = "No invoice found for parent ID $pid (for refund calc).";
|
||||
continue;
|
||||
}
|
||||
|
||||
$refundAmount = $refundService->calculateRefund($students, $pid);
|
||||
$refundAmountByParent[$pid] = $refundAmount;
|
||||
|
||||
$existingRefund = $this->refundModel->where('invoice_id', $invoice['id'])->first();
|
||||
|
||||
if ($existingRefund) {
|
||||
$refundId = (int)$existingRefund['id'];
|
||||
$status = strtolower((string)($existingRefund['status'] ?? ''));
|
||||
$isApprovedState = in_array($status, ['approved', 'partial', 'paid', 'partially_paid'], true);
|
||||
$calculatedCents = max(0, (int)round($refundAmount * 100));
|
||||
$paidCents = (new RefundEligibilityService())->getCompletedPayoutTotalCentsForRefund($refundId);
|
||||
$targetCents = $isApprovedState ? max($calculatedCents, $paidCents) : $calculatedCents;
|
||||
$update = [
|
||||
'refund_amount' => $targetCents / 100,
|
||||
'updated_by' => session()->get('user_id') ?? null,
|
||||
];
|
||||
if ($isApprovedState) {
|
||||
$update['approved_amount_cents'] = $targetCents;
|
||||
} else {
|
||||
$update['status'] = 'Pending';
|
||||
$update['requested_amount_cents'] = $targetCents;
|
||||
}
|
||||
if ($isApprovedState && $paidCents > $calculatedCents) {
|
||||
$message = sprintf(
|
||||
'Completed payouts (%0.2f) exceed recalculated refundable credit (%0.2f).',
|
||||
$paidCents / 100,
|
||||
$calculatedCents / 100
|
||||
);
|
||||
$update['reconciliation_status'] = 'requires_review';
|
||||
$update['reconciliation_reason'] = $message;
|
||||
$update['reconciliation_required_at'] = utc_now();
|
||||
log_message('critical', 'Refund reconciliation required for refund #' . $refundId . ': ' . $message);
|
||||
} else {
|
||||
$update['reconciliation_status'] = null;
|
||||
$update['reconciliation_reason'] = null;
|
||||
$update['reconciliation_required_at'] = null;
|
||||
}
|
||||
$this->refundModel->update($refundId, $update);
|
||||
} else {
|
||||
$this->refundModel->insert([
|
||||
'parent_id' => $pid,
|
||||
'school_year' => $invoice['school_year'],
|
||||
'invoice_id' => $invoice['id'],
|
||||
'refund_amount' => $refundAmount,
|
||||
'requested_amount_cents' => (int)round($refundAmount * 100),
|
||||
'approved_amount_cents' => null,
|
||||
'currency' => 'USD',
|
||||
'refund_paid_amount' => 0.0,
|
||||
'status' => 'Pending',
|
||||
'source_type' => 'tuition_withdrawal',
|
||||
'source_id' => (int)$invoice['id'],
|
||||
'requested_at' => utc_now(),
|
||||
'updated_by' => session()->get('user_id') ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
log_message('info', "Refund of $refundAmount created/updated for invoice ID {$invoice['id']} (parent {$pid}).");
|
||||
}
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
if (!$this->db->transStatus()) {
|
||||
return ['ok' => false, 'message' => 'A database error occurred. Changes were rolled back.'];
|
||||
}
|
||||
|
||||
foreach ($withdrawalPreviewEnrollmentIds as $enrollmentId => $pid) {
|
||||
try {
|
||||
$calculation = service('withdrawalFinancial')->preview((int) $enrollmentId, $performedBy);
|
||||
$refundAmountByParent[(int) $pid] = ((int) ($calculation['new_refund_request_cents'] ?? 0)) / 100;
|
||||
} catch (\Throwable $e) {
|
||||
$errors[] = 'Withdrawal calculation preview failed for enrollment #' . (int) $enrollmentId . ': ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// === AFTER COMMIT: fire specific events, batched per parent/status ===
|
||||
$eventMap = [
|
||||
'admission under review' => 'admissionUnderReview',
|
||||
@@ -524,7 +449,7 @@ public function updateStatuses(?array $enrollmentStatuses, string $schoolYear, s
|
||||
|
||||
// === Server-side safety net: generate/update invoices for parents whose statuses require it ===
|
||||
try {
|
||||
$needsInvoiceFor = ['payment pending', 'enrolled', 'withdrawn', 'refund pending'];
|
||||
$needsInvoiceFor = ['payment pending', 'enrolled'];
|
||||
$invCtl = new InvoiceController();
|
||||
foreach ($groupsByParentStatus as $pid => $byStatus) {
|
||||
$statuses = array_keys($byStatus);
|
||||
|
||||
Reference in New Issue
Block a user