fix the event print and certificate qr
This commit is contained in:
@@ -545,6 +545,7 @@ $routes->post('payment/event_charges', 'View\EventController::eventUpdate');
|
|||||||
|
|
||||||
// Parent event participation
|
// Parent event participation
|
||||||
$routes->get('administrator/event-charges', 'View\EventController::eventShow');
|
$routes->get('administrator/event-charges', 'View\EventController::eventShow');
|
||||||
|
$routes->get('administrator/event-charges/pdf', 'View\EventController::eventChargesPdf');
|
||||||
$routes->post('administrator/event-charges/remove/(:num)', 'View\EventController::removeCharge/$1');
|
$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/payment/(:num)', 'View\EventController::toggleEventPayment/$1');
|
||||||
$routes->post('administrator/event-charges/waiver/(:num)', 'View\EventController::toggleWaiverStatus/$1');
|
$routes->post('administrator/event-charges/waiver/(:num)', 'View\EventController::toggleWaiverStatus/$1');
|
||||||
|
|||||||
@@ -308,11 +308,11 @@ class CertificateController extends BaseController
|
|||||||
$pdf->Image($imgDir . 'background.png', 280, 176, 291, 340);
|
$pdf->Image($imgDir . 'background.png', 280, 176, 291, 340);
|
||||||
$pdf->Image($imgDir . 'signature.png', 140, 399, 90, 90);
|
$pdf->Image($imgDir . 'signature.png', 140, 399, 90, 90);
|
||||||
|
|
||||||
// ── QR code — small, bottom-right, just above the certificate number
|
// ── QR code — bottom-right corner: 1.5 cm from bottom, 2.5 cm from right
|
||||||
$qrSize = 42; // pt
|
$qrSize = 42; // pt
|
||||||
if ($qrFile !== null && file_exists($qrFile)) {
|
if ($qrFile !== null && file_exists($qrFile)) {
|
||||||
$qrX = $W - 10 - $qrSize; // right-aligned with the cert number text
|
$qrX = $W - 70.87 - $qrSize; // 2.5 cm from right edge
|
||||||
$qrY = $H - 14 - 3 - $qrSize; // 3 pt gap above the cert number line
|
$qrY = $H - 42.52 - $qrSize; // 1.5 cm from bottom edge
|
||||||
$pdf->Image($qrFile, $qrX, $qrY, $qrSize, $qrSize);
|
$pdf->Image($qrFile, $qrX, $qrY, $qrSize, $qrSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,12 +371,12 @@ class CertificateController extends BaseController
|
|||||||
$pdf->SetXY(106, 492);
|
$pdf->SetXY(106, 492);
|
||||||
$pdf->Cell(168, 20, 'Signature', 0, 0, 'C');
|
$pdf->Cell(168, 20, 'Signature', 0, 0, 'C');
|
||||||
|
|
||||||
// ── Certificate number — bottom-right, small gray
|
// ── Certificate number — 1.4 cm from bottom, 2.5 cm from left
|
||||||
if ($certNumber !== '') {
|
if ($certNumber !== '') {
|
||||||
$pdf->SetFont('helvetica', '', 8);
|
$pdf->SetFont('helvetica', '', 8);
|
||||||
$pdf->SetTextColor(150, 150, 150);
|
$pdf->SetTextColor(150, 150, 150);
|
||||||
$pdf->SetXY(0, $H - 14);
|
$pdf->SetXY(70.87, $H - 39.68);
|
||||||
$pdf->Cell($W - 10, 12, 'Certificate No. ' . $certNumber, 0, 0, 'R');
|
$pdf->Cell(200, 12, 'Certificate No. ' . $certNumber, 0, 0, 'L');
|
||||||
$pdf->SetTextColor(0, 0, 0);
|
$pdf->SetTextColor(0, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ use App\Models\PaymentModel;
|
|||||||
use App\Models\CalendarModel;
|
use App\Models\CalendarModel;
|
||||||
use App\Services\EmailService;
|
use App\Services\EmailService;
|
||||||
use Config\Database;
|
use Config\Database;
|
||||||
#use ???
|
|
||||||
use App\Controllers\View\InvoiceController;
|
use App\Controllers\View\InvoiceController;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use CodeIgniter\RESTful\ResourceController;
|
use CodeIgniter\RESTful\ResourceController;
|
||||||
|
use Dompdf\Dompdf;
|
||||||
|
use Dompdf\Options;
|
||||||
|
|
||||||
class EventController extends ResourceController
|
class EventController extends ResourceController
|
||||||
{
|
{
|
||||||
@@ -121,6 +123,194 @@ class EventController extends ResourceController
|
|||||||
return $fallback;
|
return $fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function loadEventChargesListingData(bool $includeParents = true): array
|
||||||
|
{
|
||||||
|
$schoolYear = $this->request->getGet('school_year') ?? $this->schoolYear;
|
||||||
|
$semester = $this->request->getGet('semester') ?? $this->semester;
|
||||||
|
$filterEventId = (int) ($this->request->getGet('event_id') ?? 0);
|
||||||
|
$filterParentId = (int) ($this->request->getGet('parent_id') ?? 0);
|
||||||
|
|
||||||
|
$parents = [];
|
||||||
|
if ($includeParents) {
|
||||||
|
$parents = $this->userModel->getParents();
|
||||||
|
usort($parents, static function (array $a, array $b): int {
|
||||||
|
$aLabel = trim(preg_replace('/\s+/', ' ', (string) ($a['firstname'] ?? '') . ' ' . (string) ($a['lastname'] ?? '')));
|
||||||
|
$bLabel = trim(preg_replace('/\s+/', ' ', (string) ($b['firstname'] ?? '') . ' ' . (string) ($b['lastname'] ?? '')));
|
||||||
|
|
||||||
|
$nameCmp = strnatcasecmp($aLabel, $bLabel);
|
||||||
|
if ($nameCmp !== 0) {
|
||||||
|
return $nameCmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strnatcasecmp((string) ($a['school_id'] ?? ''), (string) ($b['school_id'] ?? ''));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$events = $this->eventModel->getActiveEvents($schoolYear);
|
||||||
|
|
||||||
|
$chargesBuilder = $this->eventChargesModel
|
||||||
|
->select('event_charges.*,
|
||||||
|
users.firstname AS parent_firstname, users.lastname AS parent_lastname, users.cellphone AS parent_cellphone,
|
||||||
|
students.firstname AS student_firstname, students.lastname AS student_lastname,
|
||||||
|
events.event_name, events.description AS event_description, events.amount AS event_amount')
|
||||||
|
->join('users', 'users.id = event_charges.parent_id', 'left')
|
||||||
|
->join('students', 'students.id = event_charges.student_id', 'left')
|
||||||
|
->join('events', 'events.id = event_charges.event_id', 'left')
|
||||||
|
->where('event_charges.school_year', $schoolYear)
|
||||||
|
->where('event_charges.semester', $semester);
|
||||||
|
|
||||||
|
if ($filterEventId > 0) {
|
||||||
|
$chargesBuilder->where('event_charges.event_id', $filterEventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($filterParentId > 0) {
|
||||||
|
$chargesBuilder->where('event_charges.parent_id', $filterParentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$charges = $chargesBuilder
|
||||||
|
->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'] ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'charges' => $charges,
|
||||||
|
'parents' => $parents,
|
||||||
|
'events' => $events,
|
||||||
|
'school_year' => $schoolYear,
|
||||||
|
'semester' => $semester,
|
||||||
|
'filterEventId' => $filterEventId,
|
||||||
|
'filterParentId' => $filterParentId,
|
||||||
|
'parentBalances' => $parentBalances,
|
||||||
|
'classSectionNames' => $classSectionNames,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildGroupedEventCharges(array $charges): array
|
||||||
|
{
|
||||||
|
$grouped = [];
|
||||||
|
foreach ($charges as $charge) {
|
||||||
|
$eventId = (int) ($charge['event_id'] ?? 0);
|
||||||
|
if (!isset($grouped[$eventId])) {
|
||||||
|
$grouped[$eventId] = [
|
||||||
|
'label' => $charge['event_name'] ?? 'N/A',
|
||||||
|
'rows' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$grouped[$eventId]['rows'][] = $charge;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $grouped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyGroupedEventChargeRowOrder(array $groupedCharges): array
|
||||||
|
{
|
||||||
|
$rowOrder = $this->request->getGet('row_order');
|
||||||
|
if (!is_array($rowOrder) || empty($rowOrder)) {
|
||||||
|
return $groupedCharges;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($groupedCharges as $eventId => &$group) {
|
||||||
|
$rawOrder = $rowOrder[$eventId] ?? $rowOrder[(string) $eventId] ?? null;
|
||||||
|
if (!is_string($rawOrder) || trim($rawOrder) === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$orderedIds = array_values(array_filter(array_map(
|
||||||
|
static fn ($value): int => (int) trim((string) $value),
|
||||||
|
explode(',', $rawOrder)
|
||||||
|
)));
|
||||||
|
if (empty($orderedIds) || empty($group['rows']) || !is_array($group['rows'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rowsById = [];
|
||||||
|
foreach ($group['rows'] as $row) {
|
||||||
|
$rowsById[(int) ($row['id'] ?? 0)] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
$reorderedRows = [];
|
||||||
|
foreach ($orderedIds as $chargeId) {
|
||||||
|
if (isset($rowsById[$chargeId])) {
|
||||||
|
$reorderedRows[] = $rowsById[$chargeId];
|
||||||
|
unset($rowsById[$chargeId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($group['rows'] as $row) {
|
||||||
|
$chargeId = (int) ($row['id'] ?? 0);
|
||||||
|
if (isset($rowsById[$chargeId])) {
|
||||||
|
$reorderedRows[] = $row;
|
||||||
|
unset($rowsById[$chargeId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$group['rows'] = $reorderedRows;
|
||||||
|
}
|
||||||
|
unset($group);
|
||||||
|
|
||||||
|
return $groupedCharges;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildEventChargesPdfFilename(array $data): string
|
||||||
|
{
|
||||||
|
$label = 'event_participant_lists';
|
||||||
|
if (!empty($data['filterEventId'])) {
|
||||||
|
foreach (($data['events'] ?? []) as $event) {
|
||||||
|
if ((int) ($event['id'] ?? 0) === (int) $data['filterEventId']) {
|
||||||
|
$label = (string) ($event['event_name'] ?? $label);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$safeLabel = trim((string) preg_replace('/[^A-Za-z0-9]+/', '_', strtolower($label)), '_');
|
||||||
|
if ($safeLabel === '') {
|
||||||
|
$safeLabel = 'event_participant_lists';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $safeLabel . '_' . date('Ymd_His') . '.pdf';
|
||||||
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$eventModel = new EventModel();
|
$eventModel = new EventModel();
|
||||||
@@ -475,85 +665,7 @@ class EventController extends ResourceController
|
|||||||
// Optionally keep your eventShow / eventUpdate for legacy administrator event charges
|
// Optionally keep your eventShow / eventUpdate for legacy administrator event charges
|
||||||
public function eventShow()
|
public function eventShow()
|
||||||
{
|
{
|
||||||
|
$data = $this->loadEventChargesListingData();
|
||||||
$schoolYear = $this->request->getGet('school_year') ?? $this->schoolYear;
|
|
||||||
$semester = $this->request->getGet('semester') ?? $this->semester;
|
|
||||||
|
|
||||||
$parents = $this->userModel->getParents();
|
|
||||||
usort($parents, static function (array $a, array $b): int {
|
|
||||||
$aLabel = trim(preg_replace('/\s+/', ' ', (string) ($a['firstname'] ?? '') . ' ' . (string) ($a['lastname'] ?? '')));
|
|
||||||
$bLabel = trim(preg_replace('/\s+/', ' ', (string) ($b['firstname'] ?? '') . ' ' . (string) ($b['lastname'] ?? '')));
|
|
||||||
|
|
||||||
$nameCmp = strnatcasecmp($aLabel, $bLabel);
|
|
||||||
if ($nameCmp !== 0) {
|
|
||||||
return $nameCmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return strnatcasecmp((string) ($a['school_id'] ?? ''), (string) ($b['school_id'] ?? ''));
|
|
||||||
});
|
|
||||||
$events = $this->eventModel->getActiveEvents($schoolYear);
|
|
||||||
$filterEventId = (int) ($this->request->getGet('event_id') ?? 0);
|
|
||||||
$filterParentId = (int) ($this->request->getGet('parent_id') ?? 0);
|
|
||||||
|
|
||||||
$chargesBuilder = $this->eventChargesModel
|
|
||||||
->select('event_charges.*,
|
|
||||||
users.firstname AS parent_firstname, users.lastname AS parent_lastname, users.cellphone AS parent_cellphone,
|
|
||||||
students.firstname AS student_firstname, students.lastname AS student_lastname,
|
|
||||||
events.event_name, events.description AS event_description, events.amount AS event_amount')
|
|
||||||
->join('users', 'users.id = event_charges.parent_id', 'left')
|
|
||||||
->join('students', 'students.id = event_charges.student_id', 'left')
|
|
||||||
->join('events', 'events.id = event_charges.event_id', 'left')
|
|
||||||
->where('event_charges.school_year', $schoolYear)
|
|
||||||
->where('event_charges.semester', $semester);
|
|
||||||
|
|
||||||
if ($filterEventId > 0) {
|
|
||||||
$chargesBuilder->where('event_charges.event_id', $filterEventId);
|
|
||||||
}
|
|
||||||
|
|
||||||
$charges = $chargesBuilder
|
|
||||||
->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())
|
$semesterOptions = (new EventChargesModel())
|
||||||
->select('semester')
|
->select('semester')
|
||||||
@@ -575,19 +687,37 @@ class EventController extends ResourceController
|
|||||||
$schoolYearOptions = [$this->schoolYear];
|
$schoolYearOptions = [$this->schoolYear];
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('administrator/events/event_charges', [
|
return view('administrator/events/event_charges', array_merge($data, [
|
||||||
'charges' => $charges,
|
|
||||||
'parents' => $parents,
|
|
||||||
'events' => $events,
|
|
||||||
'school_year' => $schoolYear,
|
|
||||||
'semester' => $semester,
|
|
||||||
'filterEventId' => $filterEventId,
|
|
||||||
'filterParentId' => $filterParentId,
|
|
||||||
'parentBalances' => $parentBalances,
|
|
||||||
'classSectionNames' => $classSectionNames,
|
|
||||||
'semesterOptions' => $semesterOptions,
|
'semesterOptions' => $semesterOptions,
|
||||||
'schoolYearOptions' => $schoolYearOptions,
|
'schoolYearOptions' => $schoolYearOptions,
|
||||||
]);
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function eventChargesPdf(): ResponseInterface
|
||||||
|
{
|
||||||
|
$data = $this->loadEventChargesListingData(false);
|
||||||
|
$groupedCharges = $this->buildGroupedEventCharges($data['charges']);
|
||||||
|
$groupedCharges = $this->applyGroupedEventChargeRowOrder($groupedCharges);
|
||||||
|
$generatedAt = date('m-d-Y h:i A');
|
||||||
|
|
||||||
|
$html = view('administrator/events/event_charges_pdf', array_merge($data, [
|
||||||
|
'groupedCharges' => $groupedCharges,
|
||||||
|
'generatedAt' => $generatedAt,
|
||||||
|
]));
|
||||||
|
|
||||||
|
$options = new Options();
|
||||||
|
$options->set('isRemoteEnabled', true);
|
||||||
|
$options->set('defaultFont', 'DejaVu Sans');
|
||||||
|
|
||||||
|
$dompdf = new Dompdf($options);
|
||||||
|
$dompdf->loadHtml($html, 'UTF-8');
|
||||||
|
$dompdf->setPaper('A4', 'landscape');
|
||||||
|
$dompdf->render();
|
||||||
|
|
||||||
|
return $this->response
|
||||||
|
->setHeader('Content-Type', 'application/pdf')
|
||||||
|
->setHeader('Content-Disposition', 'inline; filename="' . $this->buildEventChargesPdfFilename($data) . '"')
|
||||||
|
->setBody($dompdf->output());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -224,23 +224,51 @@
|
|||||||
<?php if (empty($grouped)): ?>
|
<?php if (empty($grouped)): ?>
|
||||||
<div class="alert alert-info">No charges found.</div>
|
<div class="alert alert-info">No charges found.</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
|
<?php
|
||||||
|
$printBaseParams = array_filter([
|
||||||
|
'school_year' => $school_year,
|
||||||
|
'semester' => $semester,
|
||||||
|
'parent_id' => $filterParentId > 0 ? $filterParentId : null,
|
||||||
|
'event_id' => $filterEventId > 0 ? $filterEventId : null,
|
||||||
|
], static fn ($value) => $value !== null && $value !== '');
|
||||||
|
$printAllUrl = site_url('administrator/event-charges/pdf');
|
||||||
|
if (!empty($printBaseParams)) {
|
||||||
|
$printAllUrl .= '?' . http_build_query($printBaseParams);
|
||||||
|
}
|
||||||
|
?>
|
||||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3 no-print">
|
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3 no-print">
|
||||||
<p class="text-muted mb-0">Click a column header to sort the participants list.</p>
|
<p class="text-muted mb-0">Click a column header to sort the participants list.</p>
|
||||||
<button type="button" class="btn btn-outline-secondary" id="printAllEventCharges">
|
<a href="<?= esc($printAllUrl) ?>"
|
||||||
|
class="btn btn-outline-secondary pdf-print-link"
|
||||||
|
data-base-href="<?= esc($printAllUrl) ?>"
|
||||||
|
data-print-scope="all">
|
||||||
Print All Participant Lists
|
Print All Participant Lists
|
||||||
</button>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="eventChargeCards">
|
<div id="eventChargeCards">
|
||||||
<?php foreach ($grouped as $eventId => $data): ?>
|
<?php foreach ($grouped as $eventId => $data): ?>
|
||||||
<?php $eventLabel = $data['label']; ?>
|
<?php $eventLabel = $data['label']; ?>
|
||||||
<?php $rows = $data['rows']; ?>
|
<?php $rows = $data['rows']; ?>
|
||||||
|
<?php
|
||||||
|
$singlePrintParams = array_filter([
|
||||||
|
'school_year' => $school_year,
|
||||||
|
'semester' => $semester,
|
||||||
|
'parent_id' => $filterParentId > 0 ? $filterParentId : null,
|
||||||
|
'event_id' => $eventId,
|
||||||
|
], static fn ($value) => $value !== null && $value !== '');
|
||||||
|
$singlePrintUrl = site_url('administrator/event-charges/pdf') . '?' . http_build_query($singlePrintParams);
|
||||||
|
?>
|
||||||
<div class="card mb-4 event-card" data-event-id="<?= esc($eventId) ?>">
|
<div class="card mb-4 event-card" data-event-id="<?= esc($eventId) ?>">
|
||||||
<div class="card-header bg-light fw-semibold event-card-header">
|
<div class="card-header bg-light fw-semibold event-card-header">
|
||||||
<span><?= esc($eventLabel) ?></span>
|
<span><?= esc($eventLabel) ?></span>
|
||||||
<div class="event-card-header-actions no-print">
|
<div class="event-card-header-actions no-print">
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm print-event-card" data-event-id="<?= esc($eventId) ?>">
|
<a href="<?= esc($singlePrintUrl) ?>"
|
||||||
|
class="btn btn-outline-secondary btn-sm pdf-print-link"
|
||||||
|
data-base-href="<?= esc($singlePrintUrl) ?>"
|
||||||
|
data-print-scope="single"
|
||||||
|
data-event-id="<?= esc($eventId) ?>">
|
||||||
Print This List
|
Print This List
|
||||||
</button>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
@@ -287,7 +315,7 @@
|
|||||||
. (!empty($returnParams) ? '?' . http_build_query($returnParams) : '')
|
. (!empty($returnParams) ? '?' . http_build_query($returnParams) : '')
|
||||||
. '#eventTable_' . (int) ($charge['event_id'] ?? 0);
|
. '#eventTable_' . (int) ($charge['event_id'] ?? 0);
|
||||||
?>
|
?>
|
||||||
<tr>
|
<tr data-charge-id="<?= esc((string) ($charge['id'] ?? '')) ?>">
|
||||||
<td data-sort-value="<?= esc((string) ($charge['id'] ?? '')) ?>"><?= esc($charge['id']) ?></td>
|
<td data-sort-value="<?= esc((string) ($charge['id'] ?? '')) ?>"><?= esc($charge['id']) ?></td>
|
||||||
<?php
|
<?php
|
||||||
$studentName = $charge['student_firstname']
|
$studentName = $charge['student_firstname']
|
||||||
@@ -593,15 +621,6 @@ function loadStudentsWithCharges() {
|
|||||||
form.appendChild(input);
|
form.appendChild(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(value) {
|
|
||||||
const s = String(value ?? '');
|
|
||||||
return s.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''');
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitExternalParticipant(payload) {
|
function submitExternalParticipant(payload) {
|
||||||
const form = document.createElement('form');
|
const form = document.createElement('form');
|
||||||
form.method = 'post';
|
form.method = 'post';
|
||||||
@@ -690,89 +709,41 @@ function loadStudentsWithCharges() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPrintableCard(card) {
|
function getTableRowOrder(table) {
|
||||||
const clone = card.cloneNode(true);
|
if (!table || !table.tBodies.length) {
|
||||||
clone.querySelectorAll('.no-print').forEach((node) => node.remove());
|
return [];
|
||||||
clone.querySelectorAll('.sortable-header').forEach((button) => {
|
|
||||||
const headerText = button.textContent || '';
|
|
||||||
const textNode = document.createTextNode(headerText);
|
|
||||||
button.parentNode.replaceChild(textNode, button);
|
|
||||||
});
|
|
||||||
clone.querySelectorAll('form').forEach((form) => {
|
|
||||||
const cell = form.closest('td');
|
|
||||||
if (!cell) {
|
|
||||||
form.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const label = form.querySelector('.form-check-label');
|
|
||||||
const checkbox = form.querySelector('input[type="checkbox"]');
|
|
||||||
const text = label
|
|
||||||
? label.textContent.trim()
|
|
||||||
: (checkbox && checkbox.checked ? 'Paid' : 'Unpaid');
|
|
||||||
cell.textContent = text || '—';
|
|
||||||
});
|
|
||||||
clone.querySelectorAll('tr').forEach((row) => {
|
|
||||||
if (row.children.length > 10) {
|
|
||||||
row.removeChild(row.lastElementChild);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return clone.outerHTML;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openPrintWindow(title, contentHtml) {
|
return Array.from(table.tBodies[0].rows)
|
||||||
const printWindow = window.open('', '_blank', 'noopener,noreferrer,width=1200,height=900');
|
.map((row) => String(row.dataset.chargeId || '').trim())
|
||||||
if (!printWindow) {
|
.filter(Boolean);
|
||||||
alert('Unable to open the print preview window. Please allow pop-ups for this site.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const style = `
|
|
||||||
<style>
|
|
||||||
body { font-family: Arial, sans-serif; margin: 24px; color: #212529; }
|
|
||||||
h1 { font-size: 1.5rem; margin-bottom: 0.25rem; }
|
|
||||||
p { margin-top: 0; color: #6c757d; }
|
|
||||||
.card { border: 1px solid #dee2e6; border-radius: 0.5rem; margin-bottom: 1.5rem; overflow: hidden; }
|
|
||||||
.card-header { background: #f8f9fa; padding: 0.9rem 1rem; font-weight: 700; }
|
|
||||||
.table { width: 100%; border-collapse: collapse; }
|
|
||||||
.table th, .table td { border: 1px solid #dee2e6; padding: 0.55rem 0.7rem; text-align: left; vertical-align: top; }
|
|
||||||
.table thead th, .table tfoot th { background: #f8f9fa; }
|
|
||||||
.badge { display: inline-block; padding: 0.2rem 0.45rem; border-radius: 999px; font-size: 0.8rem; font-weight: 700; }
|
|
||||||
.bg-success { background: #198754; color: #fff; }
|
|
||||||
.bg-danger { background: #dc3545; color: #fff; }
|
|
||||||
.bg-secondary { background: #6c757d; color: #fff; }
|
|
||||||
.text-muted { color: #6c757d; }
|
|
||||||
.small { font-size: 0.875rem; }
|
|
||||||
@media print {
|
|
||||||
body { margin: 0; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
|
||||||
printWindow.document.open();
|
|
||||||
printWindow.document.write(`
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>${escapeHtml(title)}</title>
|
|
||||||
${style}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>${escapeHtml(title)}</h1>
|
|
||||||
<p>Generated on ${escapeHtml(new Date().toLocaleString())}</p>
|
|
||||||
${contentHtml}
|
|
||||||
<script>
|
|
||||||
window.onload = function() {
|
|
||||||
window.print();
|
|
||||||
};
|
|
||||||
<\/script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`);
|
|
||||||
printWindow.document.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function printEventCards(cards, title) {
|
function buildPdfUrl(link) {
|
||||||
const printable = cards.map((card) => buildPrintableCard(card)).join('');
|
const baseHref = String(link.dataset.baseHref || link.getAttribute('href') || '');
|
||||||
openPrintWindow(title, printable);
|
const url = new URL(baseHref, window.location.origin);
|
||||||
|
const scope = String(link.dataset.printScope || 'single');
|
||||||
|
|
||||||
|
if (scope === 'all') {
|
||||||
|
document.querySelectorAll('.event-card').forEach((card) => {
|
||||||
|
const eventId = String(card.dataset.eventId || '').trim();
|
||||||
|
const table = card.querySelector('.event-charges-table');
|
||||||
|
const rowOrder = getTableRowOrder(table);
|
||||||
|
if (eventId && rowOrder.length) {
|
||||||
|
url.searchParams.set(`row_order[${eventId}]`, rowOrder.join(','));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventId = String(link.dataset.eventId || '').trim();
|
||||||
|
const table = eventId ? document.getElementById(`eventTable_${eventId}`) : null;
|
||||||
|
const rowOrder = getTableRowOrder(table);
|
||||||
|
if (eventId && rowOrder.length) {
|
||||||
|
url.searchParams.set(`row_order[${eventId}]`, rowOrder.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
$('.sortable-header').on('click', function() {
|
$('.sortable-header').on('click', function() {
|
||||||
@@ -786,22 +757,8 @@ function loadStudentsWithCharges() {
|
|||||||
sortEventTable(table, columnIndex, sortType, direction);
|
sortEventTable(table, columnIndex, sortType, direction);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.print-event-card').on('click', function() {
|
$('.pdf-print-link').on('click', function() {
|
||||||
const eventId = String($(this).data('eventId'));
|
this.href = buildPdfUrl(this);
|
||||||
const card = document.querySelector(`.event-card[data-event-id="${eventId}"]`);
|
|
||||||
if (!card) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const title = $(card).find('.card-header span').first().text().trim() || 'Event Participants';
|
|
||||||
printEventCards([card], `${title} Participant List`);
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#printAllEventCharges').on('click', function() {
|
|
||||||
const cards = Array.from(document.querySelectorAll('.event-card'));
|
|
||||||
if (!cards.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
printEventCards(cards, 'All Event Participant Lists');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#externalParticipantSave').on('click', function() {
|
$('#externalParticipantSave').on('click', function() {
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Event Participant Lists</title>
|
||||||
|
<style>
|
||||||
|
@page {
|
||||||
|
margin: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: DejaVu Sans, sans-serif;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #1f2933;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
margin: 0 0 18px;
|
||||||
|
color: #52606d;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-card {
|
||||||
|
margin-bottom: 22px;
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-title {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border: 1px solid #d9e2ec;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border: 1px solid #d9e2ec;
|
||||||
|
padding: 6px 7px;
|
||||||
|
vertical-align: top;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: #f5f7fa;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
tfoot th {
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
color: #52606d;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-paid {
|
||||||
|
color: #166534;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-unpaid {
|
||||||
|
color: #b42318;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-neutral {
|
||||||
|
color: #52606d;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<?php
|
||||||
|
$title = !empty($filterEventId) ? 'Event Participant List' : 'All Event Participant Lists';
|
||||||
|
if (!empty($filterParentId)) {
|
||||||
|
$title .= ' for Selected Parent';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<h1><?= esc($title) ?></h1>
|
||||||
|
<p class="meta">
|
||||||
|
Generated on <?= esc($generatedAt) ?>
|
||||||
|
| School Year: <?= esc($school_year ?: 'N/A') ?>
|
||||||
|
| Semester: <?= esc($semester ?: 'N/A') ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<?php if (empty($groupedCharges)): ?>
|
||||||
|
<p>No charges found for the selected filters.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($groupedCharges as $eventId => $data): ?>
|
||||||
|
<?php
|
||||||
|
$rows = $data['rows'] ?? [];
|
||||||
|
$totalParticipants = 0;
|
||||||
|
$totalCharged = 0.0;
|
||||||
|
?>
|
||||||
|
<section class="event-card">
|
||||||
|
<h2 class="event-title"><?= esc($data['label'] ?? 'N/A') ?></h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 4%;">ID</th>
|
||||||
|
<th style="width: 15%;">Parent Name</th>
|
||||||
|
<th style="width: 14%;">Student Name</th>
|
||||||
|
<th style="width: 15%;">External Info</th>
|
||||||
|
<th style="width: 11%;">Class Section</th>
|
||||||
|
<th style="width: 8%;">Charged Amount</th>
|
||||||
|
<th style="width: 10%;">Created</th>
|
||||||
|
<th style="width: 7%;">Waiver</th>
|
||||||
|
<th style="width: 7%;">Fees Paid</th>
|
||||||
|
<th style="width: 9%;">Payment</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($rows as $charge): ?>
|
||||||
|
<?php
|
||||||
|
$isParticipating = ($charge['participation'] ?? '') === 'yes';
|
||||||
|
$isEventPaid = !empty($charge['event_paid']);
|
||||||
|
$feeAmount = (float) ($charge['event_amount'] ?? 0);
|
||||||
|
$hasBalanceRecord = array_key_exists((int) ($charge['parent_id'] ?? 0), $parentBalances);
|
||||||
|
$parentBalance = $hasBalanceRecord ? (float) $parentBalances[(int) $charge['parent_id']] : null;
|
||||||
|
$waiverSigned = !empty($charge['waiver_signed']);
|
||||||
|
$feeIsPaid = $isParticipating && ($isEventPaid || ($hasBalanceRecord && $parentBalance <= 0));
|
||||||
|
|
||||||
|
$studentName = !empty($charge['student_firstname'])
|
||||||
|
? trim(($charge['student_firstname'] ?? '') . ' ' . ($charge['student_lastname'] ?? ''))
|
||||||
|
: '';
|
||||||
|
$externalName = trim(($charge['external_firstname'] ?? '') . ' ' . ($charge['external_lastname'] ?? ''));
|
||||||
|
$displayName = $studentName ?: ($externalName ?: '—');
|
||||||
|
$externalNote = trim((string) ($charge['external_note'] ?? ''));
|
||||||
|
$externalParentLabel = trim(($charge['external_parent_firstname'] ?? '') . ' ' . ($charge['external_parent_lastname'] ?? ''));
|
||||||
|
$externalParentPhone = trim((string) ($charge['external_parent_phone'] ?? ''));
|
||||||
|
$externalParentEmail = trim((string) ($charge['external_parent_email'] ?? ''));
|
||||||
|
$parentPhone = trim((string) ($charge['parent_cellphone'] ?? ''));
|
||||||
|
$standardParentName = trim(($charge['parent_firstname'] ?? '') . ' ' . ($charge['parent_lastname'] ?? ''));
|
||||||
|
$parentColumnName = $externalParentLabel ?: ($standardParentName ?: '—');
|
||||||
|
$isExternalParticipant = $externalName !== '';
|
||||||
|
$infoPhone = $isExternalParticipant ? $externalParentPhone : $parentPhone;
|
||||||
|
$infoEmail = $isExternalParticipant ? $externalParentEmail : '';
|
||||||
|
$classSectionName = $classSectionNames[$charge['class_section_id'] ?? ''] ?? '—';
|
||||||
|
|
||||||
|
if ($isParticipating) {
|
||||||
|
$totalParticipants++;
|
||||||
|
$totalCharged += $feeAmount;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td><?= esc($charge['id'] ?? '') ?></td>
|
||||||
|
<td><?= esc($parentColumnName) ?></td>
|
||||||
|
<td>
|
||||||
|
<?= esc($displayName) ?>
|
||||||
|
<?php if ($externalNote !== ''): ?>
|
||||||
|
<div class="small"><?= esc($externalNote) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php if ($infoPhone !== ''): ?>
|
||||||
|
<div class="small"><?= esc($infoPhone) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($infoEmail !== ''): ?>
|
||||||
|
<div class="small"><?= esc($infoEmail) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($infoPhone === '' && $infoEmail === ''): ?>
|
||||||
|
<span class="small">—</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?= esc($classSectionName) ?></td>
|
||||||
|
<td>$<?= esc(number_format($feeAmount, 2)) ?></td>
|
||||||
|
<td><?= esc(!empty($charge['created_at']) ? local_datetime($charge['created_at'], 'm-d-Y H:i') : '') ?></td>
|
||||||
|
<td class="<?= $waiverSigned ? 'status-paid' : 'status-neutral' ?>">
|
||||||
|
<?= $waiverSigned ? 'Signed' : 'Unsigned' ?>
|
||||||
|
</td>
|
||||||
|
<td class="<?= $feeIsPaid ? 'status-paid' : 'status-unpaid' ?>">
|
||||||
|
<?= $feeIsPaid ? 'Paid' : 'Unpaid' ?>
|
||||||
|
</td>
|
||||||
|
<td class="<?= $isEventPaid ? 'status-paid' : 'status-unpaid' ?>">
|
||||||
|
<?= $isEventPaid ? 'Paid' : 'Unpaid' ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<th colspan="5">Totals</th>
|
||||||
|
<th>$<?= esc(number_format($totalCharged, 2)) ?></th>
|
||||||
|
<th colspan="4"><?= esc($totalParticipants) ?> participating</th>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `ip_attempts` (
|
|
||||||
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
`ip_address` VARCHAR(45) NOT NULL,
|
|
||||||
`attempts` INT DEFAULT 0 NOT NULL,
|
|
||||||
`last_attempt_at` DATETIME NOT NULL,
|
|
||||||
`blocked_until` DATETIME DEFAULT NULL,
|
|
||||||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
UNIQUE KEY (`ip_address`)
|
|
||||||
);
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
CREATE TABLE
|
|
||||||
IF NOT EXISTS `users`
|
|
||||||
(
|
|
||||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
||||||
`password` varchar
|
|
||||||
(255) NOT NULL,
|
|
||||||
`lastname` varchar
|
|
||||||
(255) NOT NULL,
|
|
||||||
`firstname` varchar
|
|
||||||
(255) NOT NULL,
|
|
||||||
`gender` enum
|
|
||||||
('Male','Female') CHARACTER
|
|
||||||
SET utf8mb3
|
|
||||||
COLLATE utf8mb3_general_ci DEFAULT NULL,
|
|
||||||
`cellphone` varchar
|
|
||||||
(25) NOT NULL,
|
|
||||||
`email` varchar
|
|
||||||
(255) NOT NULL,
|
|
||||||
`address_street` varchar
|
|
||||||
(255) NOT NULL,
|
|
||||||
`apt` varchar
|
|
||||||
(25) DEFAULT NULL,
|
|
||||||
`city` varchar
|
|
||||||
(255) NOT NULL,
|
|
||||||
`state` varchar
|
|
||||||
(25) NOT NULL,
|
|
||||||
`zip` varchar
|
|
||||||
(25) NOT NULL,
|
|
||||||
`accept_school_policy` tinyint
|
|
||||||
(1) NOT NULL,
|
|
||||||
`created_at` datetime DEFAULT NULL,
|
|
||||||
`updated_at` datetime DEFAULT NULL,
|
|
||||||
`token` varchar
|
|
||||||
(255) DEFAULT NULL,
|
|
||||||
`is_verified` tinyint
|
|
||||||
(1) NOT NULL DEFAULT '0',
|
|
||||||
`account_id` varchar
|
|
||||||
(255) DEFAULT NULL,
|
|
||||||
`status` varchar
|
|
||||||
(10) NOT NULL DEFAULT 'Inactive',
|
|
||||||
`user_type` enum
|
|
||||||
('primary','secondary','tertiary') CHARACTER
|
|
||||||
SET utf8mb3
|
|
||||||
COLLATE utf8mb3_general_ci DEFAULT 'primary',
|
|
||||||
`semester` varchar
|
|
||||||
(255) NOT NULL,
|
|
||||||
`school_year` varchar
|
|
||||||
(9) DEFAULT NULL,
|
|
||||||
`rfid_tag` varchar
|
|
||||||
(100) CHARACTER
|
|
||||||
SET utf8mb3
|
|
||||||
COLLATE utf8mb3_general_ci DEFAULT NULL,
|
|
||||||
`failed_attempts` INT DEFAULT 0 NOT NULL,
|
|
||||||
`last_failed_at` DATETIME DEFAULT NULL,
|
|
||||||
`is_suspended` TINYINT
|
|
||||||
(1) DEFAULT 0 NOT NULL,
|
|
||||||
PRIMARY KEY
|
|
||||||
(`id`)
|
|
||||||
);
|
|
||||||
Reference in New Issue
Block a user