update event charge at the invoice

This commit is contained in:
root
2026-04-13 01:38:40 -04:00
parent 4e2d12e524
commit 821e9cdc81
11 changed files with 537 additions and 32 deletions
+5 -2
View File
@@ -750,7 +750,9 @@ class DiscountController extends BaseController
}
} catch (\Throwable $e) {}
$newTotal = round($tuitionSubtotal + $eventSubtotal + $additionalSubtotal, 2);
$discountableTotal = $tuitionSubtotal + $additionalSubtotal;
$nonDiscountableTotal = $eventSubtotal;
$newTotal = round($discountableTotal + $nonDiscountableTotal, 2);
// ---- Payments / Discounts / Refunds ----
$db = $this->db;
@@ -794,7 +796,8 @@ class DiscountController extends BaseController
->get()->getRowArray();
$totalRefundPaid = (float)($refundRow['total_refund_paid'] ?? 0);
$newBalance = max(0.0, $newTotal - $totalDisc - $totalPaid - $totalRefundPaid);
$appliedDiscount = min($totalDisc, $discountableTotal);
$newBalance = max(0.0, $newTotal - $appliedDiscount - $totalPaid - $totalRefundPaid);
$newStatus = ($newBalance <= 0.00001) ? 'Paid' : (($totalPaid > 0) ? 'Partially Paid' : 'Unpaid');
$updateData = [
+255 -8
View File
@@ -6,8 +6,14 @@ use App\Models\EventModel;
use App\Models\EventChargesModel;
use App\Models\UserModel;
use App\Models\StudentModel;
use App\Models\StudentClassModel;
use App\Models\ConfigurationModel;
use App\Models\EnrollmentModel;
use App\Models\InvoiceModel;
use App\Models\InvoiceEventModel;
use App\Models\ClassSectionModel;
use App\Models\PaymentModel;
#use ???
use App\Controllers\View\InvoiceController;
use CodeIgniter\RESTful\ResourceController;
@@ -19,6 +25,11 @@ class EventController extends ResourceController
protected $configModel;
protected $eventModel;
protected $invoiceController;
protected $invoiceModel;
protected $invoiceEventModel;
protected $studentClassModel;
protected $classSectionModel;
protected $paymentModel;
protected $schoolYear;
protected $semester;
protected $categories;
@@ -32,6 +43,11 @@ class EventController extends ResourceController
$this->userModel = new UserModel(); // Add this
$this->eventModel = new EventModel();
$this->invoiceController = new InvoiceController();
$this->invoiceModel = new InvoiceModel();
$this->invoiceEventModel = new InvoiceEventModel();
$this->studentClassModel = new StudentClassModel();
$this->classSectionModel = new ClassSectionModel();
$this->paymentModel = new PaymentModel();
$this->enrollmentModel = new EnrollmentModel();
$this->schoolYear = $this->configModel->getConfig('school_year');
@@ -166,6 +182,19 @@ class EventController extends ResourceController
$parentIds[] = $charge['parent_id'];
}
// Also remove any invoice_event rows tied to this event
$eventName = $event['event_name'] ?? null;
if ($eventName) {
$builder = $this->invoiceEventModel->where('event_name', $eventName);
if (!empty($event['school_year'])) {
$builder->where('school_year', $event['school_year']);
}
if (!empty($event['semester'])) {
$builder->where('semester', $event['semester']);
}
$builder->delete();
}
// Delete event
$this->eventModel->delete($id);
@@ -189,6 +218,7 @@ class EventController extends ResourceController
$parents = $this->userModel->getParents();
$events = $this->eventModel->getActiveEvents($this->schoolYear);
$filterEventId = (int) ($this->request->getGet('event_id') ?? 0);
$filterParentId = (int) ($this->request->getGet('parent_id') ?? 0);
$chargesBuilder = $this->eventChargesModel
->select('event_charges.*,
@@ -209,6 +239,67 @@ class EventController extends ResourceController
->orderBy('event_charges.created_at', 'DESC')
->findAll();
foreach ($charges as &$charge) {
if (empty($charge['class_section_id']) && !empty($charge['student_id'])) {
$sections = $this->studentClassModel->getClassSectionIdsByStudentId(
(int)$charge['student_id'],
$schoolYear
);
if (!empty($sections)) {
$charge['class_section_id'] = $sections[0];
}
}
}
unset($charge);
$parentBalances = [];
try {
$balanceRows = $this->invoiceModel
->select('parent_id, COALESCE(SUM(balance),0) AS total_balance')
->where('school_year', $schoolYear)
->where('semester', $semester)
->groupBy('parent_id')
->findAll();
foreach ($balanceRows as $row) {
$parentBalances[(int)($row['parent_id'] ?? 0)] = (float)($row['total_balance'] ?? 0.0);
}
} catch (\Throwable $e) {
log_message('error', 'Failed to load parent balances: ' . $e->getMessage());
}
$sectionIds = array_unique(array_filter(array_column($charges, 'class_section_id')));
$classSectionNames = [];
if (!empty($sectionIds)) {
$sections = $this->classSectionModel
->select('class_section_id, class_section_name')
->whereIn('class_section_id', $sectionIds)
->findAll();
foreach ($sections as $section) {
$classSectionNames[(int)($section['class_section_id'] ?? 0)] = $section['class_section_name'] ?? '';
}
}
$semesterOptions = (new EventChargesModel())
->select('semester')
->distinct()
->orderBy('semester', 'ASC')
->findColumn('semester');
$semesterOptions = array_values(array_filter(array_unique($semesterOptions ?? [])));
if (empty($semesterOptions)) {
$semesterOptions = [$this->semester];
}
$schoolYearOptions = (new EventChargesModel())
->select('school_year')
->distinct()
->orderBy('school_year', 'ASC')
->findColumn('school_year');
$schoolYearOptions = array_values(array_filter(array_unique($schoolYearOptions ?? [])));
if (empty($schoolYearOptions)) {
$schoolYearOptions = [$this->schoolYear];
}
return view('administrator/events/event_charges', [
'charges' => $charges,
'parents' => $parents,
@@ -216,6 +307,11 @@ class EventController extends ResourceController
'school_year' => $schoolYear,
'semester' => $semester,
'filterEventId' => $filterEventId,
'filterParentId' => $filterParentId,
'parentBalances' => $parentBalances,
'classSectionNames' => $classSectionNames,
'semesterOptions' => $semesterOptions,
'schoolYearOptions' => $schoolYearOptions,
]);
}
@@ -252,13 +348,20 @@ class EventController extends ResourceController
continue;
}
// value is 'yes'
$sectionIds = $this->studentClassModel->getClassSectionIdsByStudentId($studentId, $schoolYear);
$classSectionId = !empty($sectionIds) ? $sectionIds[0] : null;
if ($existing) {
$this->eventChargesModel->update($existing['id'], [
'participation' => 'yes',
'charged' => $event['amount'],
'updated_by' => $userId
]);
$updateData = [
'participation' => 'yes',
'updated_by' => $userId,
'class_section_id'=> $classSectionId,
];
if (isset($event['amount']) && ((float)$event['amount'] !== (float)($existing['charged'] ?? 0))) {
$updateData['charged'] = $event['amount'];
}
$this->eventChargesModel->update($existing['id'], $updateData);
continue;
} else {
$this->eventChargesModel->insert([
'parent_id' => $parentId,
@@ -266,6 +369,8 @@ class EventController extends ResourceController
'event_id' => $eventId,
'participation' => 'yes',
'charged' => $event['amount'],
'event_paid' => 0,
'class_section_id' => $classSectionId,
'school_year' => $schoolYear,
'semester' => $semester,
'created_by' => $userId,
@@ -276,9 +381,90 @@ class EventController extends ResourceController
$this->invoiceController->generateInvoice($parentId);
$this->fixInvoiceStatusAfterCharge($parentId, $schoolYear);
return redirect()->back()->with('success', 'Event charges updated successfully.');
}
public function removeCharge($chargeId = null)
{
if (!$chargeId) {
return redirect()->back()->with('error', 'Invalid charge.');
}
$charge = $this->eventChargesModel->find($chargeId);
if (!$charge) {
return redirect()->back()->with('error', 'Charge not found.');
}
$paymentId = (int)($charge['event_payment_id'] ?? 0);
if ($paymentId > 0) {
$this->paymentModel->delete($paymentId);
}
$this->eventChargesModel->delete($chargeId);
$this->invoiceController->generateInvoice($charge['parent_id']);
return redirect()->back()->with('success', 'Participation removed, invoices updated.');
}
public function toggleEventPayment($chargeId = null)
{
if (!$chargeId) {
return redirect()->back()->with('error', 'Invalid charge.');
}
$charge = $this->eventChargesModel->find($chargeId);
if (!$charge) {
return redirect()->back()->with('error', 'Charge not found.');
}
$isPaid = $this->request->getPost('paid') === '1';
$event = $this->eventModel->find($charge['event_id']);
$eventAmount = max(0.0, (float)($event['amount'] ?? 0));
$paymentId = (int)($charge['event_payment_id'] ?? 0);
$parentId = $charge['parent_id'];
$schoolYear = $charge['school_year'] ?? $this->schoolYear;
if (!empty($eventAmount)) {
$invoice = $this->invoiceModel
->where('parent_id', $parentId)
->where('school_year', $schoolYear)
->orderBy('id', 'DESC')
->first();
if (!$invoice) {
$this->invoiceController->generateInvoice($parentId);
$invoice = $this->invoiceModel
->where('parent_id', $parentId)
->where('school_year', $schoolYear)
->orderBy('id', 'DESC')
->first();
}
if ($invoice) {
if ($isPaid && !$paymentId) {
$paymentId = $this->createEventPayment($parentId, (int)$invoice['id'], $eventAmount, $schoolYear, $charge['semester'] ?? $this->semester);
} elseif (!$isPaid && $paymentId > 0) {
$this->paymentModel->delete($paymentId);
$paymentId = 0;
}
}
}
$this->eventChargesModel->update($chargeId, [
'event_paid' => $isPaid ? 1 : 0,
'charged' => $eventAmount,
'event_payment_id' => $paymentId > 0 ? $paymentId : null,
]);
$this->invoiceController->generateInvoice($parentId);
$this->fixInvoiceStatusAfterCharge($parentId, $schoolYear);
return redirect()->back()->with('success', 'Event payment status updated.');
}
@@ -292,10 +478,18 @@ class EventController extends ResourceController
$students = $this->studentModel->where('parent_id', $parentId)->findAll();
// Get student_ids that already have charges
$chargedStudentIds = $this->eventChargesModel
$eventId = $this->request->getGet('event_id');
$chargesBuilder = $this->eventChargesModel
->where('parent_id', $parentId)
->where('semester', $semester)
->where('school_year', $schoolYear)
->where('school_year', $schoolYear);
if (!empty($eventId)) {
$chargesBuilder->where('event_id', $eventId);
}
$chargedStudentIds = $chargesBuilder
->groupBy('student_id')
->select('student_id')
->findColumn('student_id');
@@ -312,5 +506,58 @@ class EventController extends ResourceController
return $this->response->setJSON($data);
}
private function createEventPayment(int $parentId, int $invoiceId, float $amount, string $schoolYear, ?string $semester): ?int
{
if ($amount <= 0 || $invoiceId <= 0 || $parentId <= 0) {
return null;
}
$monthSemester = $semester ?: $this->semester;
$data = [
'parent_id' => $parentId,
'invoice_id' => $invoiceId,
'total_amount' => $amount,
'paid_amount' => $amount,
'balance' => 0.0,
'number_of_installments' => 1,
'payment_method' => 'Event charge',
'payment_date' => utc_now(),
'school_year' => $schoolYear,
'semester' => $monthSemester,
'status' => 'Paid',
'transaction_id' => $this->paymentModel->generateNewTransactionId(),
'updated_by' => session()->get('user_id'),
];
if (!$this->paymentModel->insert($data)) {
log_message('error', 'Failed to insert event payment: ' . json_encode($this->paymentModel->errors()));
return null;
}
return (int)$this->paymentModel->getInsertID();
}
private function fixInvoiceStatusAfterCharge($parentId, $schoolYear)
{
if (!$parentId || !$schoolYear) {
return;
}
$invoices = $this->invoiceModel
->where('parent_id', $parentId)
->where('school_year', $schoolYear)
->findAll();
foreach ($invoices as $invoice) {
$status = strtolower(trim($invoice['status'] ?? ''));
$balance = (float)($invoice['balance'] ?? 0.0);
if ($balance <= 0.00001 && $status !== 'paid') {
$this->invoiceModel->update($invoice['id'], ['status' => 'Paid']);
} elseif ($status === 'paid' && $balance > 0) {
$this->invoiceModel->update($invoice['id'], ['status' => 'Unpaid']);
}
}
}
}
+14 -2
View File
@@ -678,7 +678,7 @@ class InvoiceController extends ResourceController
log_message('error', 'additional_charges sum failed for discount recalculation invoice ' . (int)$invoice['id'] . ': ' . $e->getMessage());
}
$baseTotal = round($tuitionFee + $eventchargeTotal + $extrasSum, 2);
$baseTotal = round($tuitionFee + $extrasSum, 2);
// Recalculate discount
if ($discountUsage['discount_type'] === 'percent') {
@@ -1612,12 +1612,24 @@ private function getGradeLevel($grade): array
$invoice['last_payment_date'] = $lastPayments[$invoice['id']]['last_payment_date'] ?? null;
}
$invoiceEventCharges = [];
foreach ($invoices as $invoice) {
$year = $invoice['school_year'] ?? $this->schoolYear;
$sem = $invoice['semester'] ?? $this->semester;
$invoiceEventCharges[(int)$invoice['id']] = $this->chargesModel->getChargesWithEventInfo(
$invoice['parent_id'],
$year,
$sem
);
}
return view('/parent/invoice_payment', [
'invoices' => $invoices,
'schoolYears' => $schoolYears,
'selectedYear' => $selectedYear,
'currentSchoolYear' => $currentSchoolYear,
'dueDate' => $this->dueDate
'dueDate' => $this->dueDate,
'invoiceEventCharges' => $invoiceEventCharges,
]);
}