fix financial and certificates
This commit is contained in:
@@ -0,0 +1,682 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Certificates;
|
||||
|
||||
use App\Models\CertificateRecord;
|
||||
use App\Models\Configuration;
|
||||
use App\Models\StudentDecision;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CertificateAdminService
|
||||
{
|
||||
private bool $hasVerificationTokenColumn;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->hasVerificationTokenColumn = Schema::hasTable('certificate_records')
|
||||
&& Schema::hasColumn('certificate_records', 'verification_token');
|
||||
}
|
||||
|
||||
public function dashboard(?string $schoolYear = null): array
|
||||
{
|
||||
$selectedYear = $this->resolveSchoolYear($schoolYear);
|
||||
|
||||
$allEnrolled = DB::table('student_class as sc')
|
||||
->select(
|
||||
's.id as student_id',
|
||||
's.firstname',
|
||||
's.lastname',
|
||||
'sc.class_section_id',
|
||||
'cs.class_section_name'
|
||||
)
|
||||
->join('students as s', 's.id', '=', 'sc.student_id')
|
||||
->join('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
|
||||
->where('s.is_active', 1)
|
||||
->where('sc.school_year', $selectedYear)
|
||||
->orderBy('s.firstname', 'ASC')
|
||||
->orderBy('s.lastname', 'ASC')
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
$studentIds = array_values(array_unique(array_map(
|
||||
static fn (array $row): int => (int) ($row['student_id'] ?? 0),
|
||||
$allEnrolled
|
||||
)));
|
||||
$studentIds = array_values(array_filter($studentIds, static fn (int $id): bool => $id > 0));
|
||||
|
||||
$decisionsByStudent = [];
|
||||
if ($studentIds !== []) {
|
||||
$decisionRows = StudentDecision::query()
|
||||
->whereIn('student_id', $studentIds)
|
||||
->where('school_year', $selectedYear)
|
||||
->get();
|
||||
|
||||
foreach ($decisionRows as $decisionRow) {
|
||||
$sid = (int) ($decisionRow->student_id ?? 0);
|
||||
if ($sid <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$decision = trim((string) ($decisionRow->decision ?? ''));
|
||||
$source = trim((string) ($decisionRow->source ?? ''));
|
||||
if ($source === '') {
|
||||
$source = $decision === '' ? 'pending' : 'manual';
|
||||
}
|
||||
|
||||
$decisionsByStudent[$sid][] = [
|
||||
'decision' => $decision,
|
||||
'source' => $source,
|
||||
'notes' => (string) ($decisionRow->notes ?? ''),
|
||||
'year_score' => is_numeric($decisionRow->year_score) ? round((float) $decisionRow->year_score, 2) : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$certsByStudent = [];
|
||||
if ($studentIds !== []) {
|
||||
$certRows = DB::table('certificate_records')
|
||||
->select('student_id', 'certificate_number', 'issued_at')
|
||||
->whereIn('student_id', $studentIds)
|
||||
->where('school_year', $selectedYear)
|
||||
->orderByDesc('issued_at')
|
||||
->get();
|
||||
|
||||
foreach ($certRows as $record) {
|
||||
$sid = (int) ($record->student_id ?? 0);
|
||||
if ($sid > 0 && ! isset($certsByStudent[$sid])) {
|
||||
$certsByStudent[$sid] = (string) ($record->certificate_number ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$statsPerClass = [];
|
||||
$studentsByClass = [];
|
||||
|
||||
foreach ($allEnrolled as $row) {
|
||||
$sid = (int) ($row['student_id'] ?? 0);
|
||||
$classSectionId = (int) ($row['class_section_id'] ?? 0);
|
||||
$classSectionName = (string) ($row['class_section_name'] ?? ('Section '.$classSectionId));
|
||||
|
||||
if (! isset($statsPerClass[$classSectionId])) {
|
||||
$statsPerClass[$classSectionId] = [
|
||||
'name' => $classSectionName,
|
||||
'total' => 0,
|
||||
'pass' => 0,
|
||||
'cert' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$statsPerClass[$classSectionId]['total']++;
|
||||
|
||||
$studentDecisionRows = $decisionsByStudent[$sid] ?? [];
|
||||
$decisionState = $this->normalizeDecisionState($studentDecisionRows);
|
||||
|
||||
if ($decisionState['eligible']) {
|
||||
$statsPerClass[$classSectionId]['pass']++;
|
||||
}
|
||||
|
||||
$certificateNumber = $certsByStudent[$sid] ?? null;
|
||||
if ($certificateNumber !== null && $certificateNumber !== '') {
|
||||
$statsPerClass[$classSectionId]['cert']++;
|
||||
}
|
||||
|
||||
$studentsByClass[$classSectionId][] = [
|
||||
'student_id' => $sid,
|
||||
'firstname' => (string) ($row['firstname'] ?? ''),
|
||||
'lastname' => (string) ($row['lastname'] ?? ''),
|
||||
'class_section_id' => $classSectionId,
|
||||
'class_section_name' => $classSectionName,
|
||||
'year_score' => $decisionState['year_score'],
|
||||
'eligible' => $decisionState['eligible'],
|
||||
'decision_state' => $decisionState['status'],
|
||||
'decision_labels' => $decisionState['display_decisions'],
|
||||
'decision_rows' => $studentDecisionRows,
|
||||
'certificate_number' => $certificateNumber,
|
||||
];
|
||||
}
|
||||
|
||||
$gradeGroups = $this->buildGradeGroups($statsPerClass, $studentsByClass);
|
||||
|
||||
return [
|
||||
'school_year' => $selectedYear,
|
||||
'cert_date' => date('m/d/Y'),
|
||||
'grade_groups' => array_values($gradeGroups),
|
||||
'stats_per_class' => $statsPerClass,
|
||||
'default_group_key' => array_key_first($gradeGroups),
|
||||
];
|
||||
}
|
||||
|
||||
public function auditLog(?string $schoolYear = null): array
|
||||
{
|
||||
$selectedYear = trim((string) ($schoolYear ?? ''));
|
||||
|
||||
$recordsQuery = DB::table('certificate_records as cr')
|
||||
->select(
|
||||
'cr.*',
|
||||
'u.firstname as admin_firstname',
|
||||
'u.lastname as admin_lastname'
|
||||
)
|
||||
->leftJoin('users as u', 'u.id', '=', 'cr.issued_by')
|
||||
->orderByDesc('cr.issued_at');
|
||||
|
||||
if ($selectedYear !== '') {
|
||||
$recordsQuery->where('cr.school_year', $selectedYear);
|
||||
}
|
||||
|
||||
$records = $recordsQuery->get()->map(fn ($row) => (array) $row)->all();
|
||||
|
||||
$yearSummary = DB::table('certificate_records')
|
||||
->select('school_year', DB::raw('COUNT(*) as total'))
|
||||
->groupBy('school_year')
|
||||
->orderBy('school_year', 'DESC')
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
return [
|
||||
'school_year' => $selectedYear,
|
||||
'records' => $records,
|
||||
'year_summary' => $yearSummary,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int> $studentIds
|
||||
* @return array{students: list<array<string, mixed>>, cert_date_display: string, school_year: string}
|
||||
*/
|
||||
public function issueCertificates(
|
||||
array $studentIds,
|
||||
string $certDate,
|
||||
?int $classSectionId,
|
||||
?string $schoolYear,
|
||||
?int $issuedBy
|
||||
): array {
|
||||
$studentIds = array_values(array_filter(array_map('intval', $studentIds), static fn (int $id): bool => $id > 0));
|
||||
if ($studentIds === []) {
|
||||
throw new \InvalidArgumentException('Please select at least one student.');
|
||||
}
|
||||
|
||||
$selectedYear = $this->resolveSchoolYear($schoolYear);
|
||||
$certDateDisplay = $this->normalizeDisplayCertDate($certDate);
|
||||
$certDateStorage = $this->parseCertDate($certDateDisplay);
|
||||
$students = $this->fetchStudentsForIssuance($studentIds, $classSectionId, $selectedYear);
|
||||
|
||||
if ($students === []) {
|
||||
throw new \RuntimeException('No valid students found.');
|
||||
}
|
||||
|
||||
$rosterMap = [];
|
||||
foreach ($students as $student) {
|
||||
$rosterMap[(int) $student['id']] = $student;
|
||||
}
|
||||
|
||||
$existingByStudent = DB::table('certificate_records')
|
||||
->whereIn('student_id', array_keys($rosterMap))
|
||||
->where('school_year', $selectedYear)
|
||||
->orderByDesc('issued_at')
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->groupBy('student_id')
|
||||
->map(fn ($rows) => $rows[0])
|
||||
->all();
|
||||
|
||||
$baseCount = (int) DB::table('certificate_records')
|
||||
->where('school_year', $selectedYear)
|
||||
->count();
|
||||
$nextOffset = 0;
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($students as &$student) {
|
||||
$sid = (int) ($student['id'] ?? 0);
|
||||
$existing = $existingByStudent[$sid] ?? null;
|
||||
|
||||
if (is_array($existing)) {
|
||||
$student['cert_number'] = (string) ($existing['certificate_number'] ?? '');
|
||||
$student['verify_token'] = $this->ensureVerificationToken($existing);
|
||||
} else {
|
||||
$nextOffset++;
|
||||
$certificateNumber = $this->formatCertificateNumber($selectedYear, $baseCount + $nextOffset);
|
||||
$verificationToken = $this->hasVerificationTokenColumn ? $this->generateVerificationToken() : null;
|
||||
$grade = $this->formatGrade((string) ($student['grade'] ?? ''));
|
||||
$studentName = trim((string) ($student['firstname'] ?? '').' '.(string) ($student['lastname'] ?? ''));
|
||||
|
||||
$payload = [
|
||||
'certificate_number' => $certificateNumber,
|
||||
'student_id' => $sid,
|
||||
'student_name' => $studentName,
|
||||
'grade' => $grade,
|
||||
'cert_date' => $certDateStorage,
|
||||
'school_year' => $selectedYear,
|
||||
'class_section_id' => $classSectionId,
|
||||
'issued_by' => $issuedBy,
|
||||
'issued_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
|
||||
if ($this->hasVerificationTokenColumn) {
|
||||
$payload['verification_token'] = $verificationToken;
|
||||
}
|
||||
|
||||
DB::table('certificate_records')->insert($payload);
|
||||
|
||||
$student['cert_number'] = $certificateNumber;
|
||||
$student['verify_token'] = $verificationToken;
|
||||
}
|
||||
|
||||
$student['grade'] = $this->formatGrade((string) ($student['grade'] ?? ''));
|
||||
$student['verify_url'] = ! empty($student['verify_token'])
|
||||
? url('/api/certificates/verify/'.rawurlencode((string) $student['verify_token']))
|
||||
: null;
|
||||
}
|
||||
unset($student);
|
||||
|
||||
DB::commit();
|
||||
} catch (\Throwable $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return [
|
||||
'students' => $students,
|
||||
'cert_date_display' => $certDateDisplay,
|
||||
'school_year' => $selectedYear,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{student: array<string, mixed>, cert_date_display: string}|null
|
||||
*/
|
||||
public function reprintPayload(string $certificateNumber): ?array
|
||||
{
|
||||
$record = DB::table('certificate_records')
|
||||
->where('certificate_number', strtoupper(trim($certificateNumber)))
|
||||
->first();
|
||||
|
||||
if (! $record) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = (array) $record;
|
||||
$studentName = trim((string) ($row['student_name'] ?? ''));
|
||||
[$firstname, $lastname] = $this->splitStudentName($studentName);
|
||||
$verifyToken = $this->ensureVerificationToken($row);
|
||||
|
||||
return [
|
||||
'student' => [
|
||||
'id' => (int) ($row['student_id'] ?? 0),
|
||||
'firstname' => $firstname,
|
||||
'lastname' => $lastname,
|
||||
'grade' => (string) ($row['grade'] ?? ''),
|
||||
'cert_number' => (string) ($row['certificate_number'] ?? ''),
|
||||
'verify_token' => $verifyToken,
|
||||
'verify_url' => $verifyToken !== '' ? url('/api/certificates/verify/'.rawurlencode($verifyToken)) : null,
|
||||
],
|
||||
'cert_date_display' => $this->displayDateFromStorage($row['cert_date'] ?? null),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
public function verifyPayload(string $tokenOrNumber): ?array
|
||||
{
|
||||
$lookup = strtoupper(trim($tokenOrNumber));
|
||||
|
||||
$query = DB::table('certificate_records as cr')
|
||||
->select(
|
||||
'cr.certificate_number',
|
||||
'cr.student_name',
|
||||
'cr.grade',
|
||||
'cr.cert_date',
|
||||
'cr.school_year',
|
||||
'cr.issued_at',
|
||||
'u.firstname as admin_firstname',
|
||||
'u.lastname as admin_lastname'
|
||||
)
|
||||
->leftJoin('users as u', 'u.id', '=', 'cr.issued_by');
|
||||
|
||||
if ($this->hasVerificationTokenColumn) {
|
||||
$query->where(function ($builder) use ($tokenOrNumber, $lookup): void {
|
||||
$builder->where('cr.verification_token', trim($tokenOrNumber))
|
||||
->orWhere('cr.certificate_number', $lookup);
|
||||
});
|
||||
} else {
|
||||
$query->where('cr.certificate_number', $lookup);
|
||||
}
|
||||
|
||||
$record = $query->first();
|
||||
if (! $record) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (array) $record;
|
||||
}
|
||||
|
||||
private function resolveSchoolYear(?string $schoolYear): string
|
||||
{
|
||||
$selectedYear = trim((string) ($schoolYear ?? ''));
|
||||
|
||||
if ($selectedYear !== '') {
|
||||
return $selectedYear;
|
||||
}
|
||||
|
||||
return trim((string) (Configuration::getConfig('school_year') ?? ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array{name: string, total: int, pass: int, cert: int}> $statsPerClass
|
||||
* @param array<int, list<array<string, mixed>>> $studentsByClass
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
private function buildGradeGroups(array $statsPerClass, array $studentsByClass): array
|
||||
{
|
||||
$gradeGroups = [];
|
||||
|
||||
foreach ($statsPerClass as $classSectionId => $stats) {
|
||||
[$label, $sortKey, $slug] = $this->classGroupMeta((string) ($stats['name'] ?? ''));
|
||||
|
||||
if (! isset($gradeGroups[$sortKey])) {
|
||||
$gradeGroups[$sortKey] = [
|
||||
'key' => $sortKey,
|
||||
'label' => $label,
|
||||
'slug' => $slug,
|
||||
'total' => 0,
|
||||
'pass' => 0,
|
||||
'cert' => 0,
|
||||
'sections' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$total = (int) ($stats['total'] ?? 0);
|
||||
$pass = (int) ($stats['pass'] ?? 0);
|
||||
$cert = (int) ($stats['cert'] ?? 0);
|
||||
|
||||
$gradeGroups[$sortKey]['total'] += $total;
|
||||
$gradeGroups[$sortKey]['pass'] += $pass;
|
||||
$gradeGroups[$sortKey]['cert'] += $cert;
|
||||
$gradeGroups[$sortKey]['sections'][] = [
|
||||
'section_id' => (int) $classSectionId,
|
||||
'section_name' => (string) ($stats['name'] ?? ''),
|
||||
'student_count' => $total,
|
||||
'pass_count' => $pass,
|
||||
'cert_count' => $cert,
|
||||
'remaining_count' => max(0, $pass - $cert),
|
||||
'students' => $studentsByClass[$classSectionId] ?? [],
|
||||
];
|
||||
}
|
||||
|
||||
ksort($gradeGroups);
|
||||
|
||||
foreach ($gradeGroups as &$group) {
|
||||
$group['fully_done'] = $group['pass'] > 0 && $group['cert'] >= $group['pass'];
|
||||
$group['has_pass'] = $group['pass'] > 0;
|
||||
}
|
||||
unset($group);
|
||||
|
||||
return $gradeGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $studentDecisionRows
|
||||
* @return array{eligible: bool, status: string, display_decisions: list<string>, year_score: float|null}
|
||||
*/
|
||||
private function normalizeDecisionState(array $studentDecisionRows): array
|
||||
{
|
||||
if ($studentDecisionRows === []) {
|
||||
return [
|
||||
'eligible' => false,
|
||||
'status' => 'pending',
|
||||
'display_decisions' => [],
|
||||
'year_score' => null,
|
||||
];
|
||||
}
|
||||
|
||||
$allDecisions = array_map(
|
||||
static fn (array $row): string => trim((string) ($row['decision'] ?? '')),
|
||||
$studentDecisionRows
|
||||
);
|
||||
|
||||
$hasPending = in_array('', $allDecisions, true);
|
||||
$unique = array_values(array_unique(array_filter(
|
||||
$allDecisions,
|
||||
static fn (string $decision): bool => $decision !== ''
|
||||
)));
|
||||
|
||||
$eligible = ! $hasPending && $unique === ['Pass'];
|
||||
$displayDecisions = $eligible
|
||||
? ['Pass']
|
||||
: array_values(array_filter($unique, static fn (string $decision): bool => $decision !== 'Pass'));
|
||||
|
||||
$yearScore = null;
|
||||
foreach ($studentDecisionRows as $row) {
|
||||
if (array_key_exists('year_score', $row) && $row['year_score'] !== null && $row['year_score'] !== '') {
|
||||
$yearScore = round((float) $row['year_score'], 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'eligible' => $eligible,
|
||||
'status' => ($hasPending || $displayDecisions === []) ? 'pending' : ($eligible ? 'pass' : 'decision'),
|
||||
'display_decisions' => $displayDecisions,
|
||||
'year_score' => $yearScore,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int> $studentIds
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
private function fetchStudentsForIssuance(array $studentIds, ?int $classSectionId, string $schoolYear): array
|
||||
{
|
||||
$dashboard = $this->dashboard($schoolYear);
|
||||
$eligibleMap = [];
|
||||
|
||||
foreach ($dashboard['grade_groups'] as $group) {
|
||||
foreach (($group['sections'] ?? []) as $section) {
|
||||
foreach (($section['students'] ?? []) as $student) {
|
||||
$sid = (int) ($student['student_id'] ?? 0);
|
||||
if ($sid > 0) {
|
||||
$eligibleMap[$sid] = $student;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$filtered = [];
|
||||
foreach ($studentIds as $studentId) {
|
||||
$student = $eligibleMap[$studentId] ?? null;
|
||||
if (! is_array($student) || empty($student['eligible'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($classSectionId !== null && (int) ($student['class_section_id'] ?? 0) !== $classSectionId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filtered[] = [
|
||||
'id' => $studentId,
|
||||
'firstname' => (string) ($student['firstname'] ?? ''),
|
||||
'lastname' => (string) ($student['lastname'] ?? ''),
|
||||
'grade' => (string) ($student['class_section_name'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
private function formatGrade(string $raw): string
|
||||
{
|
||||
$clean = trim($raw);
|
||||
$lower = strtolower($clean);
|
||||
|
||||
if (preg_match('/^grade\s*(\d+)/i', $clean, $matches)) {
|
||||
return 'Grade '.(int) $matches[1];
|
||||
}
|
||||
|
||||
if ($lower === 'youth') {
|
||||
return 'Youth';
|
||||
}
|
||||
|
||||
if ($lower === 'kg' || $lower === 'kindergarten') {
|
||||
return 'Kindergarten';
|
||||
}
|
||||
|
||||
if (preg_match('/^(\d+)([- ][A-Za-z0-9]+)?$/', $clean, $matches)) {
|
||||
return 'Grade '.(int) $matches[1];
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
private function normalizeDisplayCertDate(string $certDate): string
|
||||
{
|
||||
$clean = trim($certDate);
|
||||
if ($clean === '') {
|
||||
return date('m/d/Y');
|
||||
}
|
||||
|
||||
if (preg_match('#^\d{4}-\d{2}-\d{2}$#', $clean) === 1) {
|
||||
$timestamp = strtotime($clean);
|
||||
if ($timestamp !== false) {
|
||||
return date('m/d/Y', $timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match('#^\d{2}/\d{2}/\d{4}$#', $clean) === 1) {
|
||||
return $clean;
|
||||
}
|
||||
|
||||
return date('m/d/Y');
|
||||
}
|
||||
|
||||
private function parseCertDate(string $certDate): ?string
|
||||
{
|
||||
if (preg_match('#^(\d{2})/(\d{2})/(\d{4})$#', $certDate, $matches) === 1) {
|
||||
return $matches[3].'-'.$matches[1].'-'.$matches[2];
|
||||
}
|
||||
|
||||
if (preg_match('#^\d{4}-\d{2}-\d{2}$#', $certDate) === 1) {
|
||||
return $certDate;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function displayDateFromStorage(mixed $value): string
|
||||
{
|
||||
if ($value === null || trim((string) $value) === '') {
|
||||
return date('m/d/Y');
|
||||
}
|
||||
|
||||
$timestamp = strtotime((string) $value);
|
||||
|
||||
return $timestamp !== false ? date('m/d/Y', $timestamp) : date('m/d/Y');
|
||||
}
|
||||
|
||||
private function formatCertificateNumber(string $schoolYear, int $sequence): string
|
||||
{
|
||||
return 'ARSS-'.$schoolYear.'-'.str_pad((string) $sequence, 4, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
private function generateVerificationToken(): string
|
||||
{
|
||||
if (! $this->hasVerificationTokenColumn) {
|
||||
return '';
|
||||
}
|
||||
|
||||
do {
|
||||
$token = bin2hex(random_bytes(16));
|
||||
$exists = DB::table('certificate_records')
|
||||
->where('verification_token', $token)
|
||||
->exists();
|
||||
} while ($exists);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $record
|
||||
*/
|
||||
private function ensureVerificationToken(array $record): string
|
||||
{
|
||||
if (! $this->hasVerificationTokenColumn) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$token = trim((string) ($record['verification_token'] ?? ''));
|
||||
if ($token !== '') {
|
||||
return $token;
|
||||
}
|
||||
|
||||
$recordId = (int) ($record['id'] ?? 0);
|
||||
if ($recordId <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$token = $this->generateVerificationToken();
|
||||
DB::table('certificate_records')
|
||||
->where('id', $recordId)
|
||||
->update([
|
||||
'verification_token' => $token,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: string, 2: string}
|
||||
*/
|
||||
private function classGroupMeta(string $name): array
|
||||
{
|
||||
$lower = strtolower(trim($name));
|
||||
|
||||
if (preg_match('/grade\s*(\d+)/i', $name, $matches) === 1) {
|
||||
$grade = (int) $matches[1];
|
||||
return [
|
||||
'Grade '.$grade,
|
||||
'1_'.str_pad((string) $grade, 3, '0', STR_PAD_LEFT),
|
||||
'grade'.$grade,
|
||||
];
|
||||
}
|
||||
|
||||
if (str_contains($lower, 'kg') || str_contains($lower, 'kindergarten')) {
|
||||
return ['KG', '0_kg', 'kg'];
|
||||
}
|
||||
|
||||
if (str_contains($lower, 'arabic')) {
|
||||
return ['Arabic', '2_arabic', 'arabic'];
|
||||
}
|
||||
|
||||
if (str_contains($lower, 'youth')) {
|
||||
return ['Youth', '5_youth', 'youth'];
|
||||
}
|
||||
|
||||
return [
|
||||
$name,
|
||||
'3_'.$lower,
|
||||
(string) preg_replace('/[^a-z0-9]+/', '-', $lower),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: string}
|
||||
*/
|
||||
private function splitStudentName(string $studentName): array
|
||||
{
|
||||
$parts = preg_split('/\s+/', trim($studentName), 2) ?: [];
|
||||
|
||||
return [
|
||||
$parts[0] ?? '',
|
||||
$parts[1] ?? '',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user