update controllers logic

This commit is contained in:
root
2026-04-23 00:04:35 -04:00
parent 1977a513df
commit ca4ba272fc
353 changed files with 13402 additions and 1301 deletions
@@ -0,0 +1,166 @@
<?php
namespace App\Services\Parents;
use App\Models\AuthorizedUser;
use App\Models\User;
use App\Services\ApplicationUrlService;
use App\Services\Email\EmailDispatchService;
use Illuminate\Support\Facades\DB;
/**
* CodeIgniter {@see \App\Controllers\View\AuthorizedUsersController} parity.
*/
class AuthorizedUsersManagementService
{
private const TOKEN_TTL_HOURS = 24;
public function __construct(
private EmailDispatchService $mail,
private ApplicationUrlService $urls,
) {}
public function hashToken(string $plain): string
{
return hash('sha256', $plain);
}
/**
* CI create() first step — lookup by token after invite email (pending row, created within TTL).
*/
public function findPendingByIncomingToken(string $plainToken): ?AuthorizedUser
{
if ($plainToken === '') {
return null;
}
$hash = $this->hashToken($plainToken);
return AuthorizedUser::query()
->where(function ($q) use ($hash, $plainToken) {
$q->where('token', $hash)->orWhere('token', $plainToken);
})
->where('created_at', '>=', now()->subHours(self::TOKEN_TTL_HOURS))
->first();
}
/**
* CI setPassword / savePassword — active row with rotating token.
*/
public function findActiveSetupRow(int $authorizedUserId, string $plainToken): ?AuthorizedUser
{
if ($plainToken === '') {
return null;
}
$hash = $this->hashToken($plainToken);
return AuthorizedUser::query()
->where(function ($q) use ($hash, $plainToken) {
$q->where('token', $hash)->orWhere('token', $plainToken);
})
->where('authorized_user_id', $authorizedUserId)
->where('status', 'Active')
->where('updated_at', '>=', now()->subHours(self::TOKEN_TTL_HOURS))
->first();
}
/**
* @return array{redirect_url: string}
*/
public function confirmEmailClick(string $plainToken): array
{
$row = $this->findPendingByIncomingToken($plainToken);
if (! $row) {
throw new \InvalidArgumentException('Invalid or expired confirmation link.');
}
$nextPlain = bin2hex(random_bytes(48));
$nextHash = $this->hashToken($nextPlain);
$row->status = 'Active';
$row->token = $nextHash;
$row->save();
$url = $this->urls->inviteSetPasswordFormUrl((int) $row->authorized_user_id, $nextPlain);
return ['redirect_url' => $url];
}
/**
* CI `create` — when email is not a registered user, respond success without leaking (privacy).
*
* @return array{created: bool, record: AuthorizedUser|null}
*/
public function inviteByEmail(int $primaryUserId, string $email): array
{
$email = strtolower(trim($email));
$user = User::query()
->whereRaw('LOWER(email) = ?', [$email])
->first();
if (! $user) {
return ['created' => false, 'record' => null];
}
$plain = bin2hex(random_bytes(48));
$tokenHash = $this->hashToken($plain);
$phone = trim((string) ($user->cellphone ?? ''));
$record = AuthorizedUser::query()->create([
'user_id' => $primaryUserId,
'authorized_user_id' => (int) $user->id,
'firstname' => (string) ($user->firstname ?? ''),
'lastname' => (string) ($user->lastname ?? ''),
'phone_number' => $phone !== '' ? $phone : '-',
'gender' => trim((string) ($user->gender ?? '')) !== '' ? (string) $user->gender : '-',
'email' => $email,
'relation_to_user' => null,
'token' => $tokenHash,
'status' => 'Pending',
]);
$this->sendConfirmationEmail($email, $plain);
return ['created' => true, 'record' => $record];
}
public function sendConfirmationEmail(string $email, string $plainToken): void
{
$confirmLink = $this->urls->inviteConfirmUrl($plainToken);
$body = '<p>You have been added as an authorized user for another account. Click the link below to confirm your access:</p>'
.'<p><a href="'.e($confirmLink).'">Confirm Access</a></p>'
.'<p>If you did not request this, please ignore this email.</p>';
$this->mail->send($email, 'Authorized User Confirmation', $body, 'notifications');
}
/**
* @throws \InvalidArgumentException
*/
public function setAuthorizedUserPassword(int $authorizedUserId, int $postedUserId, string $plainToken, string $password): void
{
if ($postedUserId !== $authorizedUserId || $authorizedUserId <= 0) {
throw new \InvalidArgumentException('Invalid request.');
}
$row = $this->findActiveSetupRow($authorizedUserId, $plainToken);
if (! $row) {
throw new \InvalidArgumentException('Invalid or expired confirmation link.');
}
$user = User::query()->find($authorizedUserId);
if (! $user) {
throw new \InvalidArgumentException('User not found.');
}
DB::transaction(function () use ($user, $row, $password) {
$user->password = $password;
$user->save();
$row->token = null;
$row->save();
});
}
}
@@ -4,10 +4,12 @@ namespace App\Services\Parents;
use App\Models\AttendanceData;
use App\Models\AttendanceRecord;
use App\Models\EarlyDismissalSignature;
use App\Models\ParentAttendanceReport;
use App\Models\Student;
use App\Models\ClassSection;
use App\Services\SemesterRangeService;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
class ParentAttendanceReportService
@@ -254,12 +256,12 @@ class ParentAttendanceReportService
];
}
public function listReports(string $start, ?string $end, ?string $schoolYear, ?string $semester): array
public function listReports(string $start, ?string $end, ?string $schoolYear, ?string $semester, int $parentId): array
{
return ParentAttendanceReport::listForDateRange($start, $end, $schoolYear, $semester);
return ParentAttendanceReport::listForDateRange($start, $end, $schoolYear, $semester, $parentId);
}
public function checkExisting(array $payload): array
public function checkExisting(int $parentId, array $payload): array
{
$datesPost = $payload['dates'] ?? null;
$dateSingle = $payload['date'] ?? null;
@@ -286,6 +288,15 @@ class ParentAttendanceReportService
return [];
}
$uniqueIds = array_values(array_unique($studentIds));
$ownedCount = Student::query()
->where('parent_id', $parentId)
->whereIn('id', $uniqueIds)
->count();
if ($ownedCount !== count($uniqueIds)) {
throw new \RuntimeException('Invalid student selection.');
}
$rows = DB::table('parent_attendance_reports as par')
->select('par.student_id', 'par.type', 'par.report_date', 's.firstname', 's.lastname')
->leftJoin('students as s', 's.id', '=', 'par.student_id')
@@ -371,6 +382,275 @@ class ParentAttendanceReportService
$row->update($update);
}
/**
* Staff index: early dismissal rows grouped by date (+ signature metadata per date).
*
* Parity with CodeIgniter {@see \App\Controllers\View\ParentAttendanceReportController::earlyDismissals}.
*
* @return array{groups: array<string, array<int, array<string, mixed>>>, school_year: string, semester: string, signature_by_date: array<string, array<string, mixed>>}
*/
public function staffEarlyDismissalsIndex(?string $schoolYearParam, ?string $semesterParam): array
{
$context = $this->configService->context();
$schoolYear = filled($schoolYearParam) ? trim((string) $schoolYearParam) : (string) ($context['school_year'] ?? '');
$semester = filled($semesterParam) ? trim((string) $semesterParam) : '';
$b = DB::table('parent_attendance_reports as par')
->select('par.*', 's.firstname', 's.lastname', 'cs.class_section_name')
->leftJoin('students as s', 's.id', '=', 'par.student_id')
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'par.class_section_id')
->where('par.type', '=', 'early_dismissal');
if ($schoolYear !== '') {
$b->where('par.school_year', $schoolYear);
}
if ($semester !== '') {
$b->where('par.semester', $semester);
}
$rows = $b->orderByDesc('par.report_date')
->orderBy('par.dismiss_time')
->get()
->map(fn ($r) => (array) $r)
->all();
$groups = [];
foreach ($rows as $r) {
$d = substr((string) ($r['report_date'] ?? ''), 0, 10);
if ($d === '') {
$d = 'Unknown';
}
$groups[$d][] = $r;
}
uksort($groups, static function ($a, $b) {
if ($a === 'Unknown' && $b === 'Unknown') {
return 0;
}
if ($a === 'Unknown') {
return 1;
}
if ($b === 'Unknown') {
return -1;
}
return strcmp((string) $b, (string) $a);
});
$signatureByDate = [];
$dates = array_values(array_filter(array_keys($groups), static function ($d) {
return preg_match('/^\d{4}-\d{2}-\d{2}$/', (string) $d) === 1;
}));
if ($dates !== []) {
$sigQ = EarlyDismissalSignature::query()->whereIn('report_date', $dates);
if ($schoolYear !== '') {
$sigQ->where('school_year', $schoolYear);
}
if ($semester !== '') {
$sigQ->where('semester', $semester);
}
foreach ($sigQ->orderBy('report_date')->get() as $sig) {
$d = $sig->report_date instanceof \DateTimeInterface
? $sig->report_date->format('Y-m-d')
: substr((string) $sig->report_date, 0, 10);
if ($d !== '') {
$signatureByDate[$d] = $sig->toArray();
}
}
}
return [
'groups' => $groups,
'school_year' => $schoolYear,
'semester' => $semester,
'signature_by_date' => $signatureByDate,
];
}
/**
* Parity with CodeIgniter {@see \App\Controllers\View\ParentAttendanceReportController::addEarlyDismissalForm}.
*
* @return array{students: array<int, array<string, mixed>>, default_date: string, school_year: string, semester: string}
*/
public function staffAddEarlyDismissalFormData(): array
{
$context = $this->configService->context();
$schoolYear = (string) ($context['school_year'] ?? '');
$semester = (string) ($context['semester'] ?? '');
try {
$students = DB::table('students as s')
->select('s.id', 's.firstname', 's.lastname', 's.parent_id', 'cs.class_section_name', 'sc.class_section_id')
->leftJoin('student_class as sc', function ($join) use ($schoolYear) {
$join->on('sc.student_id', '=', 's.id')
->where('sc.school_year', '=', $schoolYear);
})
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
->orderBy('s.lastname')
->orderBy('s.firstname')
->get()
->map(fn ($r) => (array) $r)
->all();
} catch (\Throwable) {
$students = [];
}
[, $defaultDate] = $this->calendarService->computeSundays(0, 26);
return [
'students' => $students,
'default_date' => $defaultDate,
'school_year' => $schoolYear,
'semester' => $semester,
];
}
/**
* Parity with CodeIgniter {@see \App\Controllers\View\ParentAttendanceReportController::saveEarlyDismissal}.
*/
public function saveStaffEarlyDismissal(int $studentId, string $reportDate, string $dismissTime, string $reason): void
{
$context = $this->configService->context();
$schoolYear = (string) ($context['school_year'] ?? '');
$semester = (string) ($context['semester'] ?? '');
$dt = \DateTime::createFromFormat('Y-m-d', $reportDate);
if (! $dt || (int) $dt->format('w') !== 0) {
throw new \RuntimeException('Please select a Sunday date.');
}
$stu = Student::query()->select('id', 'parent_id', 'firstname', 'lastname')->find($studentId);
if (! $stu || empty($stu->parent_id)) {
throw new \RuntimeException('Selected student not found or has no primary parent assigned.');
}
$parentId = (int) $stu->parent_id;
$sectionRow = DB::table('student_class')
->select('class_section_id')
->where('student_id', $studentId)
->where('school_year', $schoolYear)
->orderByDesc('updated_at')
->first();
$classSectionId = $sectionRow ? (int) $sectionRow->class_section_id : null;
$qb = ParentAttendanceReport::query()
->where('student_id', $studentId)
->whereDate('report_date', $reportDate)
->where('school_year', $schoolYear);
$existingAl = (clone $qb)->where(function ($q) {
$q->where('type', 'absent')->orWhere('type', 'late');
})->first();
if ($existingAl) {
throw new \RuntimeException('Absent/Late already submitted for this student on the selected date.');
}
$existingEd = (clone $qb)->where('type', 'early_dismissal')->first();
if ($existingEd) {
$existingEd->update([
'dismiss_time' => $dismissTime !== '' ? $dismissTime : $existingEd->dismiss_time,
'reason' => $reason !== '' ? $reason : $existingEd->reason,
'status' => 'new',
]);
return;
}
ParentAttendanceReport::query()->create([
'parent_id' => $parentId,
'student_id' => $studentId,
'class_section_id' => $classSectionId,
'report_date' => $reportDate,
'type' => 'early_dismissal',
'dismiss_time' => $dismissTime,
'reason' => $reason !== '' ? $reason : null,
'semester' => $semester,
'school_year' => $schoolYear,
'status' => 'new',
]);
}
/**
* Parity with CodeIgniter {@see \App\Controllers\View\ParentAttendanceReportController::uploadEarlyDismissalSignature}.
* Files live under {@see storage_path('uploads/early_dismissal_signatures')} (same as {@see \App\Http\Controllers\Api\Reports\FilesController::earlyDismissalSignature}).
*/
public function storeEarlyDismissalSignature(
UploadedFile $file,
string $reportDate,
?string $schoolYearPost,
?string $semesterPost,
int $uploadedByUserId,
): void {
$dt = \DateTime::createFromFormat('Y-m-d', $reportDate);
if (! $dt || (int) $dt->format('w') !== 0) {
throw new \RuntimeException('Please select a Sunday date.');
}
$context = $this->configService->context();
$schoolYear = $schoolYearPost !== null && $schoolYearPost !== ''
? (string) $schoolYearPost
: (string) ($context['school_year'] ?? '');
$semesterRaw = (string) ($semesterPost ?? '');
$hasSemesterFilter = $semesterRaw !== '';
$semester = $hasSemesterFilter ? $semesterRaw : null;
$existsQ = ParentAttendanceReport::query()
->where('type', 'early_dismissal')
->whereDate('report_date', $reportDate);
if ($schoolYear !== '') {
$existsQ->where('school_year', $schoolYear);
}
if ($hasSemesterFilter) {
$existsQ->where('semester', $semester);
}
if (! $existsQ->exists()) {
throw new \RuntimeException('No early dismissal records found for that date.');
}
$targetDir = storage_path('uploads/early_dismissal_signatures');
if (! is_dir($targetDir) && ! @mkdir($targetDir, 0755, true) && ! is_dir($targetDir)) {
throw new \RuntimeException('Upload directory is not available.');
}
$originalName = $file->getClientOriginalName();
$mimeType = $file->getClientMimeType();
$fileSize = (int) $file->getSize();
$filename = $file->hashName();
$file->move($targetDir, $filename);
$payload = [
'report_date' => $reportDate,
'school_year' => $schoolYear !== '' ? $schoolYear : null,
'semester' => $semester,
'filename' => $filename,
'original_name' => $originalName,
'mime_type' => $mimeType,
'file_size' => $fileSize,
'uploaded_by' => $uploadedByUserId,
];
$existingQ = EarlyDismissalSignature::query()->whereDate('report_date', $reportDate);
if ($schoolYear !== '') {
$existingQ->where('school_year', $schoolYear);
}
if ($hasSemesterFilter) {
$existingQ->where('semester', $semester);
}
$existing = $existingQ->first();
if ($existing) {
$oldFile = (string) ($existing->filename ?? '');
$existing->update($payload);
if ($oldFile !== '' && $oldFile !== $filename) {
@unlink($targetDir . DIRECTORY_SEPARATOR . $oldFile);
}
} else {
EarlyDismissalSignature::query()->create($payload);
}
}
private function resolveClassSectionLabel(?int $classSectionId): string
{
$cid = (int) ($classSectionId ?? 0);
@@ -0,0 +1,191 @@
<?php
namespace App\Services\Parents;
use App\Models\ClassProgressReport;
use App\Services\ClassProgress\ClassProgressQueryService;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
/**
* CodeIgniter {@see \App\Controllers\ParentProgressController} read-side logic.
*/
class ParentProgressQueryService
{
public function __construct(
private ClassProgressQueryService $classProgressQuery,
) {}
/**
* CI `getParentStudents` — one row per distinct student (latest enrollment ordering).
*
* @return list<array<string, mixed>>
*/
public function parentStudents(int $parentId): array
{
if ($parentId <= 0) {
return [];
}
$rows = DB::table('enrollments as e')
->select(
'e.student_id',
'e.class_section_id',
'e.updated_at',
'e.created_at',
's.firstname',
's.lastname',
'cs.class_section_name',
)
->join('students as s', 's.id', '=', 'e.student_id')
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'e.class_section_id')
->where('e.parent_id', $parentId)
->where('e.is_withdrawn', 0)
->orderByDesc('e.updated_at')
->orderByDesc('e.created_at')
->get();
$students = [];
foreach ($rows as $row) {
$arr = (array) $row;
$studentId = (int) ($arr['student_id'] ?? 0);
if ($studentId === 0 || isset($students[$studentId])) {
continue;
}
$students[$studentId] = $arr;
}
return array_values($students);
}
/**
* CI `getParentSectionIds`.
*
* @return list<int>
*/
public function parentSectionIds(int $parentId): array
{
if ($parentId <= 0) {
return [];
}
$ids = DB::table('enrollments')
->where('parent_id', $parentId)
->where('is_withdrawn', 0)
->whereNotNull('class_section_id')
->distinct()
->pluck('class_section_id')
->map(fn ($id) => (int) $id)
->filter(fn ($id) => $id > 0)
->unique()
->values()
->all();
return array_values($ids);
}
public function parentCanAccessSection(int $parentUserId, ?int $classSectionId): bool
{
if (! $classSectionId) {
return false;
}
return in_array((int) $classSectionId, $this->parentSectionIds($parentUserId), true);
}
/**
* Progress reports visible to the parent (all teachers) for allowed sections.
*
* @param list<int> $sectionIds
*/
public function reportsForSections(array $sectionIds): Collection
{
$sectionIds = array_values(array_filter(array_map('intval', $sectionIds)));
if ($sectionIds === []) {
return collect();
}
return ClassProgressReport::query()
->select('class_progress_reports.*')
->addSelect([
'cs.class_section_name',
DB::raw('CONCAT(IFNULL(u.firstname, ""), " ", IFNULL(u.lastname, "")) AS teacher_name'),
])
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'class_progress_reports.class_section_id')
->leftJoin('users as u', 'u.id', '=', 'class_progress_reports.teacher_id')
->whereIn('class_progress_reports.class_section_id', $sectionIds)
->orderByDesc('class_progress_reports.week_start')
->get();
}
/**
* CI `groupReportsByWeek` — keys `"{week_start}:{class_section_id}"`.
*
* @param iterable<int, ClassProgressReport> $rows
* @return array<string, array<string, mixed>>
*/
public function groupReportsByWeek(iterable $rows): array
{
$statusOptions = (array) config('progress.status_options', []);
$reportGroups = [];
foreach ($rows as $row) {
$statusLabel = $statusOptions[$row->status] ?? 'Unknown';
$row->setAttribute('status_label', $statusLabel);
$weekStart = $row->week_start instanceof \Carbon\CarbonInterface
? $row->week_start->format('Y-m-d')
: (string) $row->week_start;
$sectionId = (int) $row->class_section_id;
if ($weekStart === '' || $sectionId === 0) {
continue;
}
$key = $weekStart.':'.$sectionId;
if (! isset($reportGroups[$key])) {
$reportGroups[$key] = [
'week_start' => $weekStart,
'week_end' => $row->week_end instanceof \Carbon\CarbonInterface
? $row->week_end->format('Y-m-d')
: (string) ($row->week_end ?? ''),
'class_section_name' => $row->class_section_name ?? '',
'class_section_id' => $sectionId,
'reports' => [],
];
}
$subject = (string) ($row->subject ?? '');
if ($subject === '') {
continue;
}
$reportGroups[$key]['reports'][$subject] = (new \App\Http\Resources\ClassProgress\ClassProgressReportResource($row))->toArray(request());
}
return $reportGroups;
}
/**
* Weekly batch for a section/week (CI parent `view`, all subjects).
*
* @return list<ClassProgressReport>
*/
public function weeklyReportsForSectionWeek(int $classSectionId, string $weekStartYmd): array
{
return ClassProgressReport::query()
->with(['classSection', 'teacher'])
->where('class_section_id', $classSectionId)
->whereDate('week_start', $weekStartYmd)
->orderBy('subject')
->get()
->all();
}
/**
* @return array<int, list<array{id:int,name:string}>>
*/
public function attachmentMapForReportIds(array $reportIds): array
{
return $this->classProgressQuery->attachmentMap($reportIds);
}
}
@@ -0,0 +1,161 @@
<?php
namespace App\Services\Parents;
use App\Models\Configuration;
use App\Services\ApplicationUrlService;
use App\Models\ReportCardAcknowledgement;
use App\Models\User;
use Illuminate\Support\Facades\DB;
/**
* CodeIgniter {@see \App\Controllers\ParentReportCardController} parity (read + acknowledgement).
*/
class ParentReportCardService
{
public function __construct(
private PrimaryParentUserResolver $primaryParentResolver,
private ApplicationUrlService $urls,
) {}
/**
* Resolve term from query string with configuration fallbacks (CI index/view).
*
* @return array{0: string, 1: string}
*/
public function termFromRequest(?string $schoolYearGet, ?string $semesterGet): array
{
$schoolYear = trim((string) ($schoolYearGet ?? ''));
$semester = trim((string) ($semesterGet ?? ''));
if ($schoolYear === '') {
$schoolYear = trim((string) (Configuration::getConfigValueByKey('school_year') ?? ''));
}
if ($semester === '') {
$semester = trim((string) (Configuration::getConfigValueByKey('semester') ?? ''));
}
return [$schoolYear, $semester];
}
/**
* CI sign() — current configuration term only (no GET overrides).
*
* @return array{0: string, 1: string}
*/
public function termFromConfiguration(): array
{
$schoolYear = trim((string) (Configuration::getConfigValueByKey('school_year') ?? ''));
$semester = trim((string) (Configuration::getConfigValueByKey('semester') ?? ''));
return [$schoolYear, $semester];
}
public function resolvePrimaryParentUserId(?User $user): ?int
{
return $this->primaryParentResolver->resolveFromUser($user);
}
/**
* CI index() student rows for parent + optional student_class term filters.
*
* @return list<array<string, mixed>>
*/
public function studentsForTerm(int $primaryParentUserId, string $schoolYear, string $semester): array
{
if ($primaryParentUserId <= 0) {
return [];
}
$q = DB::table('students as s')
->select('s.id', 's.firstname', 's.lastname', 'cs.class_section_name')
->leftJoin('student_class as sc', 'sc.student_id', '=', 's.id')
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
->where('s.parent_id', $primaryParentUserId)
->orderBy('s.firstname', 'ASC')
->orderBy('s.lastname', 'ASC');
if ($schoolYear !== '') {
$q->where('sc.school_year', $schoolYear);
}
if ($semester !== '') {
$q->where('sc.semester', $semester);
}
return $q->get()->map(static fn ($r) => (array) $r)->all();
}
/**
* @param list<int> $studentIds
* @return array<int, array<string, mixed>>
*/
public function acknowledgementMap(int $primaryParentUserId, array $studentIds, string $schoolYear, string $semester): array
{
if ($primaryParentUserId <= 0 || $studentIds === []) {
return [];
}
$rows = ReportCardAcknowledgement::query()
->where('parent_id', $primaryParentUserId)
->where('school_year', $schoolYear)
->where('semester', $semester)
->whereIn('student_id', $studentIds)
->get();
$map = [];
foreach ($rows as $row) {
$map[(int) $row->student_id] = $row->toArray();
}
return $map;
}
public function studentBelongsToPrimaryParent(int $studentId, int $primaryParentUserId): bool
{
$pid = DB::table('students')->where('id', $studentId)->value('parent_id');
return $pid !== null && (int) $pid === $primaryParentUserId;
}
/**
* @param array<string, mixed> $values viewed_at, signed_at, signed_name, signer_ip, …
*/
public function touchAcknowledgement(
int $primaryParentUserId,
int $studentId,
string $schoolYear,
string $semester,
array $values,
): void {
$criteria = [
'parent_id' => $primaryParentUserId,
'student_id' => $studentId,
'school_year' => $schoolYear,
'semester' => $semester,
];
$existing = ReportCardAcknowledgement::query()->where($criteria)->first();
if ($existing) {
$existing->fill($values);
$existing->save();
return;
}
ReportCardAcknowledgement::query()->create(array_merge($criteria, $values));
}
public function reportCardUrl(int $studentId, string $schoolYear, string $semester): string
{
$query = [];
if ($schoolYear !== '') {
$query['school_year'] = $schoolYear;
}
if ($semester !== '') {
$query['semester'] = $semester;
}
$base = $this->urls->studentReportCardUrl($studentId);
return $query === [] ? $base : $base.'?'.http_build_query($query);
}
}
@@ -0,0 +1,54 @@
<?php
namespace App\Services\Parents;
use App\Models\User;
use Illuminate\Support\Facades\DB;
/**
* Maps the logged-in parent-family account to the primary parent's user id
* (stored on {@see \App\Models\Student::$parent_id}), matching CodeIgniter
* {@see \App\Controllers\ParentReportCardController::resolvePrimaryParentId}.
*/
final class PrimaryParentUserResolver
{
public function resolveFromUser(?User $user): ?int
{
if ($user === null) {
return null;
}
return $this->resolveFromCredentials((int) $user->id, (string) ($user->user_type ?? ''));
}
/**
* @param int $userId Session user id (may be secondary/tertiary login).
*/
public function resolveFromCredentials(int $userId, string $userType): ?int
{
$userType = strtolower(trim($userType));
if ($userType === 'primary') {
return $userId > 0 ? $userId : null;
}
if ($userType === 'secondary') {
$row = DB::table('parents')
->select('parent_id')
->where('secondparent_user_id', $userId)
->first();
return $row ? (int) ($row->parent_id ?? 0) ?: null : null;
}
if ($userType === 'tertiary') {
$row = DB::table('authorized_users')
->select('user_id')
->where('authorized_user_id', $userId)
->first();
return $row ? (int) ($row->user_id ?? 0) ?: null : null;
}
return $userId > 0 ? $userId : null;
}
}