add more controller
This commit is contained in:
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Teachers;
|
||||
|
||||
use App\Libraries\StaffTimeOffLinkService;
|
||||
use App\Models\StaffAttendance;
|
||||
use App\Models\TeacherClass;
|
||||
use App\Models\User;
|
||||
use App\Services\SemesterRangeService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class TeacherAbsenceService
|
||||
{
|
||||
public function __construct(
|
||||
private TeacherConfigService $configService,
|
||||
private SemesterRangeService $semesterRangeService,
|
||||
private StaffTimeOffLinkService $staffTimeOffLinkService
|
||||
) {
|
||||
}
|
||||
|
||||
public function formData(int $userId): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$semester = (string) ($context['semester'] ?? '');
|
||||
$schoolYear = (string) ($context['school_year'] ?? '');
|
||||
|
||||
$teacher = User::query()->find($userId);
|
||||
$displayName = $teacher
|
||||
? trim(($teacher->firstname ?? '') . ' ' . ($teacher->lastname ?? ''))
|
||||
: 'Teacher';
|
||||
|
||||
$existing = StaffAttendance::query()
|
||||
->where('user_id', $userId)
|
||||
->when($semester !== '', fn ($q) => $q->where('semester', $semester))
|
||||
->when($schoolYear !== '', fn ($q) => $q->where('school_year', $schoolYear))
|
||||
->orderByDesc('date')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
return [
|
||||
'teacher_name' => $displayName,
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'existing' => $existing,
|
||||
'available_dates' => $this->allowedAbsenceDates($schoolYear),
|
||||
];
|
||||
}
|
||||
|
||||
public function submit(int $userId, array $payload): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$semester = (string) ($context['semester'] ?? '');
|
||||
$schoolYear = (string) ($context['school_year'] ?? '');
|
||||
|
||||
if ($schoolYear === '') {
|
||||
return [
|
||||
'ok' => false,
|
||||
'message' => 'Semester or school year not configured.',
|
||||
'status' => 422,
|
||||
];
|
||||
}
|
||||
|
||||
$dates = (array) ($payload['dates'] ?? []);
|
||||
$reasonType = trim((string) ($payload['reason_type'] ?? ''));
|
||||
$reasonText = trim((string) ($payload['reason'] ?? ''));
|
||||
|
||||
if ($reasonText === '') {
|
||||
return [
|
||||
'ok' => false,
|
||||
'message' => 'Reason is required.',
|
||||
'status' => 422,
|
||||
];
|
||||
}
|
||||
|
||||
$reasonBase = $reasonType !== '' ? strtolower($reasonType) : '';
|
||||
$reason = $reasonBase !== '' ? ($reasonBase . ': ' . $reasonText) : $reasonText;
|
||||
|
||||
$allowedSet = array_fill_keys($this->allowedAbsenceDates($schoolYear), true);
|
||||
$roleName = User::getUserRoleName($userId) ?: null;
|
||||
|
||||
$saved = 0;
|
||||
$invalid = [];
|
||||
$savedDates = [];
|
||||
|
||||
$dates = array_values(array_unique(array_map('strval', $dates)));
|
||||
|
||||
foreach ($dates as $date) {
|
||||
$date = trim($date);
|
||||
if ($date === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$parsed = Carbon::createFromFormat('Y-m-d', $date);
|
||||
if ($parsed->format('Y-m-d') !== $date || empty($allowedSet[$date])) {
|
||||
$invalid[] = $date;
|
||||
continue;
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
$invalid[] = $date;
|
||||
continue;
|
||||
}
|
||||
|
||||
$semesterForDate = $this->semesterRangeService->getSemesterForDate($date);
|
||||
if ($semesterForDate === '') {
|
||||
$semesterForDate = $semester;
|
||||
}
|
||||
|
||||
$record = StaffAttendance::upsertOne(
|
||||
userId: $userId,
|
||||
roleName: $roleName,
|
||||
date: $date,
|
||||
semester: $semesterForDate,
|
||||
schoolYear: $schoolYear,
|
||||
status: StaffAttendance::STATUS_ABSENT,
|
||||
reason: $reason,
|
||||
editorId: $userId
|
||||
);
|
||||
|
||||
if ($record) {
|
||||
$saved++;
|
||||
$savedDates[] = $date;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($invalid)) {
|
||||
return [
|
||||
'ok' => false,
|
||||
'message' => 'Invalid dates: ' . implode(', ', $invalid),
|
||||
'status' => 422,
|
||||
];
|
||||
}
|
||||
|
||||
$this->sendPrincipalNotification(
|
||||
userId: $userId,
|
||||
roleName: $roleName,
|
||||
semester: $semester,
|
||||
schoolYear: $schoolYear,
|
||||
reasonType: $reasonType,
|
||||
reasonText: $reasonText,
|
||||
dates: $dates,
|
||||
savedDates: $savedDates
|
||||
);
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'message' => $saved . ' day(s) saved as absent.',
|
||||
'saved' => $saved,
|
||||
'dates' => $savedDates,
|
||||
'status' => 200,
|
||||
];
|
||||
}
|
||||
|
||||
private function allowedAbsenceDates(string $schoolYear): array
|
||||
{
|
||||
$today = Carbon::today();
|
||||
$startYear = null;
|
||||
$endYear = null;
|
||||
|
||||
if (preg_match('/^(\d{4})\D+(\d{4})$/', $schoolYear, $m)) {
|
||||
$startYear = (int) $m[1];
|
||||
$endYear = (int) $m[2];
|
||||
} else {
|
||||
$cy = (int) now()->format('Y');
|
||||
$cm = (int) now()->format('n');
|
||||
if ($cm >= 9) {
|
||||
$startYear = $cy;
|
||||
$endYear = $cy + 1;
|
||||
} else {
|
||||
$startYear = $cy - 1;
|
||||
$endYear = $cy;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$start = Carbon::create($startYear, 9, 1)->startOfDay();
|
||||
$end = Carbon::create($endYear, 5, 31)->startOfDay();
|
||||
} catch (\Throwable) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($start->lt($today)) {
|
||||
$start = $today->copy();
|
||||
}
|
||||
|
||||
$dates = [];
|
||||
$cursor = $start->copy();
|
||||
|
||||
while ($cursor->lte($end)) {
|
||||
if ($cursor->dayOfWeek === Carbon::SUNDAY) {
|
||||
$dates[] = $cursor->format('Y-m-d');
|
||||
}
|
||||
$cursor->addDay();
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
private function sendPrincipalNotification(
|
||||
int $userId,
|
||||
?string $roleName,
|
||||
string $semester,
|
||||
string $schoolYear,
|
||||
string $reasonType,
|
||||
string $reasonText,
|
||||
array $dates,
|
||||
array $savedDates
|
||||
): void {
|
||||
try {
|
||||
$user = User::query()->find($userId);
|
||||
$fullName = trim(($user->firstname ?? '') . ' ' . ($user->lastname ?? '')) ?: 'Teacher';
|
||||
$userEmail = $user->email ?? '';
|
||||
$role = $roleName ?: 'teacher';
|
||||
$dateList = !empty($savedDates) ? implode(', ', $savedDates) : implode(', ', $dates);
|
||||
|
||||
$subject = sprintf(
|
||||
'TimeOff Request: %s (%s) — %s',
|
||||
$fullName,
|
||||
ucfirst((string) $role),
|
||||
$dateList ?: 'No dates'
|
||||
);
|
||||
|
||||
$submittedAt = now()->toDateTimeString();
|
||||
$assignedText = $this->formatAssignedClasses($userId, $schoolYear, $semester);
|
||||
|
||||
$body = '<div style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:1.5;">'
|
||||
. '<h3 style="margin:0 0 8px;">New TimeOff Request</h3>'
|
||||
. '<p style="margin:0 0 12px;">A staff time-off request was submitted from the teacher portal.</p>'
|
||||
. '<table cellpadding="6" cellspacing="0" style="border-collapse:collapse;">'
|
||||
. '<tr><td><strong>Name</strong></td><td>' . e($fullName) . '</td></tr>'
|
||||
. '<tr><td><strong>Email</strong></td><td>' . e($userEmail) . '</td></tr>'
|
||||
. '<tr><td><strong>Role</strong></td><td>' . e((string) $role) . '</td></tr>'
|
||||
. '<tr><td><strong>Semester</strong></td><td>' . e($semester) . '</td></tr>'
|
||||
. '<tr><td><strong>School Year</strong></td><td>' . e($schoolYear) . '</td></tr>'
|
||||
. '<tr><td><strong>Reason Type</strong></td><td>' . e($reasonType ?: '-') . '</td></tr>'
|
||||
. '<tr><td><strong>Reason</strong></td><td>' . e($reasonText) . '</td></tr>'
|
||||
. '<tr><td><strong>Dates</strong></td><td>' . e($dateList ?: '-') . '</td></tr>'
|
||||
. '<tr><td><strong>Assigned Class(es)</strong></td><td>' . e($assignedText) . '</td></tr>'
|
||||
. '<tr><td><strong>Submitted At</strong></td><td>' . e($submittedAt) . '</td></tr>'
|
||||
. '</table>'
|
||||
. '</div>';
|
||||
|
||||
$token = $this->staffTimeOffLinkService->createToken([
|
||||
'uid' => $userId,
|
||||
'email' => $userEmail,
|
||||
'name' => $fullName,
|
||||
'role' => $role,
|
||||
'dates' => $dateList ?: '-',
|
||||
'reason' => $reasonText,
|
||||
'reason_type' => $reasonType,
|
||||
'submitted_at' => $submittedAt,
|
||||
'origin' => 'teacher portal',
|
||||
]);
|
||||
|
||||
$notifyUrl = url('/timeoff/notify/' . rawurlencode($token));
|
||||
$body .= '<p style="margin-top:16px;">Click <a href="' . e($notifyUrl) . '">Send confirmation email to '
|
||||
. e($fullName)
|
||||
. '</a> so the staff member is notified automatically. This link expires in 14 days.</p>';
|
||||
|
||||
$principalEmail = env('PRINCIPAL_EMAIL', 'principal@alrahmaisgl.org');
|
||||
|
||||
Mail::html($body, function ($message) use ($principalEmail, $subject) {
|
||||
$message->to($principalEmail)
|
||||
->subject($subject);
|
||||
});
|
||||
} catch (\Throwable) {
|
||||
// Email failures should not block absence submissions.
|
||||
}
|
||||
}
|
||||
|
||||
private function formatAssignedClasses(int $userId, string $schoolYear, string $semester): string
|
||||
{
|
||||
$assignments = TeacherClass::getClassAssignmentsByUserId($userId, $schoolYear, $semester);
|
||||
if (empty($assignments)) {
|
||||
return 'No assigned class';
|
||||
}
|
||||
|
||||
$parts = [];
|
||||
foreach ($assignments as $assignment) {
|
||||
$name = (string) ($assignment['class_section_name'] ?? '');
|
||||
$role = (string) ($assignment['teacher_role'] ?? '');
|
||||
if ($name !== '') {
|
||||
$parts[] = $role !== '' ? ($name . ' (' . $role . ')') : $name;
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($parts) ? implode(', ', $parts) : 'No assigned class';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user