add waiver column to event table

This commit is contained in:
root
2026-04-19 12:32:30 -04:00
parent 596d368b1d
commit 45d7895182
5 changed files with 185 additions and 9 deletions
+1
View File
@@ -537,6 +537,7 @@ $routes->post('payment/event_charges', 'View\EventController::eventUpdate');
$routes->get('administrator/event-charges', 'View\EventController::eventShow');
$routes->post('administrator/event-charges/remove/(:num)', 'View\EventController::removeCharge/$1');
$routes->post('administrator/event-charges/payment/(:num)', 'View\EventController::toggleEventPayment/$1');
$routes->post('administrator/event-charges/waiver/(:num)', 'View\EventController::toggleWaiverStatus/$1');
$routes->get('administrator/get-students-with-charges', 'View\EventController::getStudentsWithCharges');
$routes->get('parent/events', 'View\ParentController::parentEventPage', ['filter' => 'auth:parent']);
+87 -8
View File
@@ -41,6 +41,7 @@ class EventController extends ResourceController
protected $categories;
protected $enrollmentModel;
private ?bool $eventChargesHasCreatedBy = null;
private ?bool $eventChargesHasWaiverSigned = null;
public function __construct()
{
@@ -85,6 +86,41 @@ class EventController extends ResourceController
return $this->eventChargesHasCreatedBy;
}
private function eventChargesSupportsWaiverSigned(): bool
{
if ($this->eventChargesHasWaiverSigned !== null) {
return $this->eventChargesHasWaiverSigned;
}
try {
$db = Database::connect();
$this->eventChargesHasWaiverSigned = $db->fieldExists('waiver_signed', 'event_charges');
} catch (\Throwable $e) {
$this->eventChargesHasWaiverSigned = false;
}
return $this->eventChargesHasWaiverSigned;
}
private function getEventChargesReturnTo(): string
{
$fallback = site_url('administrator/event-charges');
$returnTo = trim((string) ($this->request->getPost('return_to') ?? ''));
if ($returnTo === '') {
return $fallback;
}
$base = rtrim((string) base_url(), '/');
if (str_starts_with($returnTo, '/')) {
return $returnTo;
}
if ($base !== '' && str_starts_with($returnTo, $base)) {
return $returnTo;
}
return $fallback;
}
public function index()
{
$eventModel = new EventModel();
@@ -570,6 +606,7 @@ class EventController extends ResourceController
$parentsForInvoice = [];
$supportsCreatedBy = $this->eventChargesSupportsCreatedBy();
$supportsWaiverSigned = $this->eventChargesSupportsWaiverSigned();
foreach ($participations as $studentId => $value) {
$existing = $this->eventChargesModel->where([
@@ -596,6 +633,9 @@ class EventController extends ResourceController
'updated_by' => $userId,
'class_section_id'=> $classSectionId,
];
if ($supportsWaiverSigned) {
$updateData['waiver_signed'] = (int) ($existing['waiver_signed'] ?? 0);
}
if (isset($event['amount']) && ((float)$event['amount'] !== (float)($existing['charged'] ?? 0))) {
$updateData['charged'] = $event['amount'];
}
@@ -614,6 +654,9 @@ class EventController extends ResourceController
'semester' => $semester,
'updated_by' => $userId
];
if ($supportsWaiverSigned) {
$insertData['waiver_signed'] = 0;
}
if ($supportsCreatedBy) {
$insertData['created_by'] = $userId;
}
@@ -630,6 +673,7 @@ class EventController extends ResourceController
$parentPhone = trim($pieces['parent_phone'] ?? '');
$parentEmail = trim((string)($pieces['parent_email'] ?? ''));
$markPaid = (string)($pieces['paid'] ?? '0') === '1';
$waiverSigned = (string)($pieces['waiver_signed'] ?? '0') === '1';
if (!$firstname && !$lastname) {
continue;
@@ -664,7 +708,7 @@ class EventController extends ResourceController
$existingExternal = $this->eventChargesModel->where($matchConditions)->first();
if ($existingExternal) {
$this->eventChargesModel->update($existingExternal['id'], [
$updateData = [
'participation' => 'yes',
'charged' => $event['amount'],
'updated_by' => $userId,
@@ -674,7 +718,11 @@ class EventController extends ResourceController
'external_parent_lastname' => $parentLast !== '' ? $parentLast : ($existingExternal['external_parent_lastname'] ?? null),
'external_parent_phone' => $parentPhone !== '' ? $parentPhone : ($existingExternal['external_parent_phone'] ?? null),
'external_parent_email' => $parentEmail !== '' ? $parentEmail : ($existingExternal['external_parent_email'] ?? null),
]);
];
if ($supportsWaiverSigned) {
$updateData['waiver_signed'] = $waiverSigned ? 1 : 0;
}
$this->eventChargesModel->update($existingExternal['id'], $updateData);
continue;
}
@@ -696,6 +744,9 @@ class EventController extends ResourceController
'semester' => $semester,
'updated_by' => $userId,
];
if ($supportsWaiverSigned) {
$insertData['waiver_signed'] = $waiverSigned ? 1 : 0;
}
if ($supportsCreatedBy) {
$insertData['created_by'] = $userId;
}
@@ -725,13 +776,14 @@ class EventController extends ResourceController
public function removeCharge($chargeId = null)
{
$returnTo = $this->getEventChargesReturnTo();
if (!$chargeId) {
return redirect()->back()->with('error', 'Invalid charge.');
return redirect()->to($returnTo)->with('error', 'Invalid charge.');
}
$charge = $this->eventChargesModel->find($chargeId);
if (!$charge) {
return redirect()->back()->with('error', 'Charge not found.');
return redirect()->to($returnTo)->with('error', 'Charge not found.');
}
$paymentId = (int)($charge['event_payment_id'] ?? 0);
@@ -744,19 +796,20 @@ class EventController extends ResourceController
$this->invoiceController->generateInvoice((string)$charge['parent_id'], (string)($charge['school_year'] ?? $this->schoolYear), (string)($charge['semester'] ?? $this->semester), false);
}
return redirect()->back()->with('success', 'Participation removed, invoices updated.');
return redirect()->to($returnTo)->with('success', 'Participation removed, invoices updated.');
}
public function toggleEventPayment($chargeId = null)
{
$returnTo = $this->getEventChargesReturnTo();
if (!$chargeId) {
return redirect()->back()->with('error', 'Invalid charge.');
return redirect()->to($returnTo)->with('error', 'Invalid charge.');
}
$isPaid = $this->request->getPost('paid') === '1';
$meta = $this->applyEventPaymentStatus((int)$chargeId, $isPaid);
if (!$meta) {
return redirect()->back()->with('error', 'Charge not found.');
return redirect()->to($returnTo)->with('error', 'Charge not found.');
}
if (!empty($meta['invoice_id'])) {
@@ -764,7 +817,33 @@ class EventController extends ResourceController
$this->fixInvoiceStatusAfterCharge((int)$meta['parent_id'], (string)$meta['school_year']);
}
return redirect()->back()->with('success', 'Event payment status updated.');
return redirect()->to($returnTo)->with('success', 'Event payment status updated.');
}
public function toggleWaiverStatus($chargeId = null)
{
$returnTo = $this->getEventChargesReturnTo();
if (!$chargeId) {
return redirect()->to($returnTo)->with('error', 'Invalid charge.');
}
if (!$this->eventChargesSupportsWaiverSigned()) {
return redirect()->to($returnTo)->with('error', 'Waiver tracking is not available until the database migration is applied.');
}
$charge = $this->eventChargesModel->find($chargeId);
if (!$charge) {
return redirect()->to($returnTo)->with('error', 'Charge not found.');
}
$signed = $this->request->getPost('waiver_signed') === '1';
$this->eventChargesModel->update((int) $chargeId, [
'waiver_signed' => $signed ? 1 : 0,
'updated_by' => session()->get('user_id'),
]);
return redirect()->to($returnTo)->with('success', $signed ? 'Waiver marked as signed.' : 'Waiver marked as unsigned.');
}
private function parentHasEnrollment(int $parentId, string $schoolYear): bool
@@ -0,0 +1,31 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddWaiverSignedToEventCharges extends Migration
{
public function up()
{
$fields = [
'waiver_signed' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
'after' => 'charged',
],
];
if (! $this->db->fieldExists('waiver_signed', 'event_charges')) {
$this->forge->addColumn('event_charges', $fields);
}
}
public function down()
{
if ($this->db->fieldExists('waiver_signed', 'event_charges')) {
$this->forge->dropColumn('event_charges', 'waiver_signed');
}
}
}
+1
View File
@@ -15,6 +15,7 @@ class EventChargesModel extends Model
'student_id',
'participation',
'charged',
'waiver_signed',
'event_paid',
'event_payment_id',
'class_section_id',
@@ -183,6 +183,7 @@
<th>Class Section</th>
<th>Charged Amount</th>
<th>Created</th>
<th>Waiver</th>
<th>Fees Paid</th>
<th>Payment</th>
<th>Actions</th>
@@ -193,6 +194,22 @@
<?php
$isParticipating = ($charge['participation'] === 'yes');
$isEventPaid = !empty($charge['event_paid']);
$returnParams = [];
if (!empty($semester)) {
$returnParams['semester'] = $semester;
}
if (!empty($school_year)) {
$returnParams['school_year'] = $school_year;
}
if (!empty($filterEventId)) {
$returnParams['event_id'] = $filterEventId;
}
if (!empty($filterParentId)) {
$returnParams['parent_id'] = $filterParentId;
}
$returnTo = site_url('administrator/event-charges')
. (!empty($returnParams) ? '?' . http_build_query($returnParams) : '')
. '#eventTable_' . (int) ($charge['event_id'] ?? 0);
?>
<tr>
<td><?= esc($charge['id']) ?></td>
@@ -237,8 +254,22 @@
$feeAmount = (float) ($charge['event_amount'] ?? 0);
$hasBalanceRecord = array_key_exists((int)$charge['parent_id'], $parentBalances);
$parentBalance = $hasBalanceRecord ? (float)$parentBalances[$charge['parent_id']] : null;
$waiverSigned = !empty($charge['waiver_signed']);
$feeIsPaid = $isParticipating && ($isEventPaid || ($hasBalanceRecord && $parentBalance <= 0));
?>
<td class="text-center">
<form method="post" action="<?= site_url('administrator/event-charges/waiver/' . esc($charge['id'])) ?>" class="d-inline-flex align-items-center">
<?= csrf_field() ?>
<input type="hidden" name="return_to" value="<?= esc($returnTo) ?>">
<input type="hidden" name="waiver_signed" value="<?= $waiverSigned ? '1' : '0' ?>">
<input type="checkbox" class="form-check-input" id="eventWaiver_<?= esc($charge['id']) ?>"
<?= $waiverSigned ? 'checked' : '' ?>
onchange="this.form.waiver_signed.value = this.checked ? 1 : 0; this.form.submit();">
<label class="form-check-label ms-2" for="eventWaiver_<?= esc($charge['id']) ?>" aria-hidden="true">
<?= $waiverSigned ? 'Signed' : 'Unsigned' ?>
</label>
</form>
</td>
<td class="text-center">
<?php if ($feeIsPaid): ?>
<span class="badge bg-success">Paid</span>
@@ -249,6 +280,7 @@
<td class="text-center">
<form method="post" action="<?= site_url('administrator/event-charges/payment/' . esc($charge['id'])) ?>" class="d-inline-flex align-items-center">
<?= csrf_field() ?>
<input type="hidden" name="return_to" value="<?= esc($returnTo) ?>">
<input type="hidden" name="paid" value="<?= $feeIsPaid ? '1' : '0' ?>">
<input type="checkbox" class="form-check-input" id="eventPayment_<?= esc($charge['id']) ?>"
<?= $isEventPaid ? 'checked' : '' ?>
@@ -263,6 +295,7 @@
</a>
<form action="<?= site_url('administrator/event-charges/remove/' . esc($charge['id'])) ?>" method="post" onsubmit="return confirm('Remove this participation and update the charge?');">
<?= csrf_field() ?>
<input type="hidden" name="return_to" value="<?= esc($returnTo) ?>">
<button type="submit" class="btn btn-outline-danger btn-sm">Remove</button>
</form>
</div>
@@ -280,7 +313,7 @@
<tr>
<th colspan="4">Totals</th>
<th>$<?= esc(number_format($totalCharged, 2)) ?></th>
<th colspan="5">
<th colspan="6">
<span class="badge bg-secondary">
<?= esc($totalParticipants) ?> participating
</span>
@@ -444,6 +477,7 @@ function loadStudentsWithCharges() {
function buildExternalRow(entry) {
const key = entry.key;
const isPaid = !!entry.paid;
const waiverSigned = !!entry.waiverSigned;
const firstEsc = escapeHtml(entry.firstname);
const lastEsc = escapeHtml(entry.lastname);
const noteEsc = escapeHtml(entry.note);
@@ -472,6 +506,7 @@ function loadStudentsWithCharges() {
<input type="hidden" name="external_participants[${key}][parent_lastname]" value="${parentLastEsc}">
<input type="hidden" name="external_participants[${key}][parent_phone]" value="${parentPhoneEsc}">
<input type="hidden" name="external_participants[${key}][parent_email]" value="${parentEmailEsc}">
<input type="hidden" name="external_participants[${key}][waiver_signed]" value="${waiverSigned ? '1' : '0'}">
<input type="hidden" name="external_participants[${key}][paid]" value="${isPaid ? '1' : '0'}">
</div>
`);
@@ -497,6 +532,12 @@ function loadStudentsWithCharges() {
<td>External</td>
<td>$${amountDisplay}</td>
<td>${createdDisplay}</td>
<td class="text-center">
<div class="form-check d-inline-flex align-items-center gap-2 justify-content-center m-0">
<input class="form-check-input external-waiver-toggle" type="checkbox" data-key="${key}" ${waiverSigned ? 'checked' : ''}>
<span class="small text-muted external-waiver-label">${waiverSigned ? 'Signed' : 'Unsigned'}</span>
</div>
</td>
<td class="text-center">
<span class="badge ${isPaid ? 'bg-success' : 'bg-danger'} external-paid-badge">${isPaid ? 'Paid' : 'Unpaid'}</span>
</td>
@@ -526,6 +567,7 @@ function loadStudentsWithCharges() {
function saveExternalDraft(entry) {
entry.key = entry.key ?? `${Date.now()}_${Math.random().toString(36).slice(2)}`;
entry.paid = !!entry.paid;
entry.waiverSigned = !!entry.waiverSigned;
removeExternalEntryFromDom(entry.key);
externalDrafts = externalDrafts.filter((existing) => existing.key !== entry.key);
externalDrafts.push(entry);
@@ -646,6 +688,7 @@ function loadStudentsWithCharges() {
eventId: (draftEntry && draftEntry.eventId) ? draftEntry.eventId : eventId,
eventAmount: (draftEntry && draftEntry.eventAmount) ? draftEntry.eventAmount : eventAmount,
parentDisplayName: (draftEntry && draftEntry.parentDisplayName) ? draftEntry.parentDisplayName : parentName,
waiverSigned: (draftEntry && typeof draftEntry.waiverSigned !== 'undefined') ? !!draftEntry.waiverSigned : false,
paid: (draftEntry && typeof draftEntry.paid !== 'undefined') ? !!draftEntry.paid : false,
};
if (editingKey) {
@@ -695,6 +738,27 @@ function loadStudentsWithCharges() {
$badge.removeClass('bg-success').addClass('bg-danger').text('Unpaid');
}
});
$(document).on('change', '.external-waiver-toggle', function() {
const key = $(this).data('key');
const signed = !!this.checked;
if (!key) {
return;
}
const entry = getDraftByKey(key);
if (entry) {
entry.waiverSigned = signed;
persistDrafts();
}
$externalHidden
.find(`div[data-key="${key}"] input[name="external_participants[${key}][waiver_signed]"]`)
.val(signed ? '1' : '0');
const $label = $(`tr.external-preview[data-key="${key}"] .external-waiver-label`);
if (signed) {
$label.text('Signed');
} else {
$label.text('Unsigned');
}
});
$('#event-participant-form').on('submit', function() {
localStorage.removeItem(storageKey);
});