Fix Laravel Pint formatting

This commit is contained in:
root
2026-06-09 00:03:03 -04:00
parent 8d4d610b82
commit b5fd4a4ca1
1414 changed files with 11317 additions and 10201 deletions
@@ -17,8 +17,7 @@ class AttendanceCaseQueryService
protected AttendanceTracking $attendanceTrackingModel,
protected ViolationRuleEngineService $violationRuleEngine,
protected AttendanceParentLookupService $parentLookupService
) {
}
) {}
public function getStudentCase(
int $studentId,
@@ -32,16 +31,16 @@ class AttendanceCaseQueryService
?string $defaultSemester = null
): array {
$student = $this->studentModel->query()->find($studentId);
if (!$student) {
if (! $student) {
return [
'success' => false,
'message' => 'Student not found',
'status' => 404,
'status' => 404,
];
}
$student = $student->toArray();
$student['name'] = trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? ''));
$student['name'] = trim(($student['firstname'] ?? '').' '.($student['lastname'] ?? ''));
$codeParam = strtoupper((string) ($codeParam ?? ''));
$incidentDate = preg_match('/^\d{4}-\d{2}-\d{2}$/', (string) $incidentDate) ? (string) $incidentDate : '';
@@ -66,13 +65,13 @@ class AttendanceCaseQueryService
}
[$syStart, $syEnd] = $this->violationRuleEngine->deriveSchoolYearBounds($schoolYear ?: date('Y'));
$today = Carbon::today();
$start = Carbon::parse($syStart)->startOfDay();
$today = Carbon::today();
$start = Carbon::parse($syStart)->startOfDay();
$rangeEnd = Carbon::parse(min($syEnd, $today->format('Y-m-d')))->startOfDay();
$windowWeeks = $this->violationRuleEngine->windowWeeksForViolationCode($codeParam);
$endYmd = $incidentDate !== '' ? $incidentDate : date('Y-m-d');
$startYmd = $this->violationRuleEngine->activeWeekWindowStartYmd($endYmd, $windowWeeks, $schoolYear, $semester);
$endYmd = $incidentDate !== '' ? $incidentDate : date('Y-m-d');
$startYmd = $this->violationRuleEngine->activeWeekWindowStartYmd($endYmd, $windowWeeks, $schoolYear, $semester);
$studentIdValues = [(int) $studentId];
$studentCodeRaw = trim((string) ($student['student_code'] ?? $student['school_id'] ?? ''));
@@ -108,7 +107,7 @@ class AttendanceCaseQueryService
])
->where(function ($q) use ($studentIdValues, $studentCodeValues) {
$q->whereIn('student_id', $studentIdValues);
if (!empty($studentCodeValues)) {
if (! empty($studentCodeValues)) {
$q->orWhereIn('student_id', $studentCodeValues);
}
})
@@ -126,7 +125,7 @@ class AttendanceCaseQueryService
if ($pendingOnly) {
$this->violationRuleEngine->applyUnreportedAttendanceFilter($qb, $this->attendanceDataModel->getTable());
if (!$includeNotified) {
if (! $includeNotified) {
$qb->whereRaw("(LOWER(TRIM(COALESCE(is_notified, ''))) NOT IN ('yes','1'))");
}
}
@@ -142,21 +141,21 @@ class AttendanceCaseQueryService
$attendanceRows = $qb->orderByDesc('date')->get()->toArray();
$data = [
'student' => $student,
'attendance' => $attendanceRows,
'violation_code' => $codeParam !== '' ? $codeParam : null,
'violation_date' => $incidentDate,
'violation_notified' => null,
'student' => $student,
'attendance' => $attendanceRows,
'violation_code' => $codeParam !== '' ? $codeParam : null,
'violation_date' => $incidentDate,
'violation_notified' => null,
'show_violation_summary' => true,
'school_year' => $schoolYear,
'semester' => $semester,
'school_year' => $schoolYear,
'semester' => $semester,
];
if ($data['violation_date'] === '' && !empty($data['attendance'][0]['date'])) {
if ($data['violation_date'] === '' && ! empty($data['attendance'][0]['date'])) {
$data['violation_date'] = substr((string) $data['attendance'][0]['date'], 0, 10);
}
if (!empty($data['attendance'][0]['is_notified'])) {
if (! empty($data['attendance'][0]['is_notified'])) {
$data['violation_notified'] = $data['attendance'][0]['is_notified'];
}
@@ -172,7 +171,7 @@ class AttendanceCaseQueryService
return [
'success' => true,
'data' => $data,
'data' => $data,
];
}
@@ -183,7 +182,7 @@ class AttendanceCaseQueryService
?string $defaultSemester = null
): array {
$schoolYear = filled($schoolYearParam) ? (string) $schoolYearParam : (string) $defaultSchoolYear;
$semester = filled($semesterParam) ? (string) $semesterParam : (string) $defaultSemester;
$semester = filled($semesterParam) ? (string) $semesterParam : (string) $defaultSemester;
$trackingData = $this->attendanceTrackingModel->query()
->where('school_year', $schoolYear)
@@ -211,7 +210,7 @@ class AttendanceCaseQueryService
if ($latest) {
$schoolYear = (string) ($latest->school_year ?? $schoolYear);
$semester = (string) ($latest->semester ?? $semester);
$semester = (string) ($latest->semester ?? $semester);
$trackingData = $this->attendanceTrackingModel->query()
->where('school_year', $schoolYear)
@@ -229,9 +228,9 @@ class AttendanceCaseQueryService
if (empty($trackingData)) {
return [
'students' => [],
'school_year' => $schoolYear,
'semester' => $semester,
'students' => [],
'school_year' => $schoolYear,
'semester' => $semester,
'show_actions' => false,
];
}
@@ -258,9 +257,9 @@ class AttendanceCaseQueryService
if (empty($trackingData)) {
return [
'students' => [],
'school_year' => $schoolYear,
'semester' => $semester,
'students' => [],
'school_year' => $schoolYear,
'semester' => $semester,
'show_actions' => false,
];
}
@@ -289,12 +288,12 @@ class AttendanceCaseQueryService
foreach ($rows as $row) {
$sid = (int) ($row->student_id ?? 0);
if ($sid > 0 && !isset($classByStudent[$sid])) {
if ($sid > 0 && ! isset($classByStudent[$sid])) {
$classByStudent[$sid] = [
'class_section_id' => $row->class_section_id ?? null,
'class_section_id' => $row->class_section_id ?? null,
'class_section_name' => (string) ($row->class_section_name ?? ''),
'class_id' => $row->class_id ?? null,
'class_name' => (string) ($row->class_name ?? ''),
'class_id' => $row->class_id ?? null,
'class_name' => (string) ($row->class_name ?? ''),
];
}
}
@@ -304,7 +303,7 @@ class AttendanceCaseQueryService
$mergedData = [];
foreach ($trackingData as $row) {
$sid = (int) ($row['student_id'] ?? 0);
if (!isset($studentMap[$sid])) {
if (! isset($studentMap[$sid])) {
continue;
}
@@ -312,15 +311,15 @@ class AttendanceCaseQueryService
if (isset($classByStudent[$sid])) {
$merged['class_section_name'] = (string) ($classByStudent[$sid]['class_section_name'] ?? '');
$merged['class_name'] = (string) ($classByStudent[$sid]['class_name'] ?? '');
} elseif (!empty($merged['registration_grade'])) {
$merged['class_name'] = (string) ($classByStudent[$sid]['class_name'] ?? '');
} elseif (! empty($merged['registration_grade'])) {
$merged['grade'] = (string) $merged['registration_grade'];
}
try {
$p = $this->parentLookupService->getPrimaryParentForStudent($sid);
if ($p) {
$merged['parent_name'] = $p['parent_name'] ?? '';
$merged['parent_name'] = $p['parent_name'] ?? '';
$merged['parent_email'] = $p['email'] ?? '';
$merged['parent_phone'] = $p['phone'] ?? '';
}
@@ -331,9 +330,9 @@ class AttendanceCaseQueryService
}
return [
'students' => $mergedData,
'students' => $mergedData,
'school_year' => $schoolYear,
'semester' => $semester,
'semester' => $semester,
];
}
}
}
@@ -10,6 +10,7 @@ use Illuminate\Support\Facades\DB;
class AttendanceCommunicationSupportService
{
protected string $semester;
protected string $schoolYear;
public function __construct(
@@ -19,7 +20,7 @@ class AttendanceCommunicationSupportService
protected AttendanceParentLookupService $parentLookupService,
protected AttendanceEmailComposerService $emailComposerService
) {
$this->semester = (string) ($this->configModel->getConfig('semester') ?? '');
$this->semester = (string) ($this->configModel->getConfig('semester') ?? '');
$this->schoolYear = (string) ($this->configModel->getConfig('school_year') ?? '');
}
@@ -33,12 +34,12 @@ class AttendanceCommunicationSupportService
return [
'success' => false,
'message' => 'Missing student_id.',
'status' => 422,
'status' => 422,
];
}
$student = $this->studentModel->query()->find($studentId)?->toArray() ?? [];
$parent = $this->parentLookupService->getPrimaryParentForStudent($studentId);
$student = $this->studentModel->query()->find($studentId)?->toArray() ?? [];
$parent = $this->parentLookupService->getPrimaryParentForStudent($studentId);
$secondary = $this->parentLookupService->getSecondaryParentForStudent($studentId, $this->schoolYear);
$result = $this->emailComposerService->composePreview(
@@ -52,7 +53,7 @@ class AttendanceCommunicationSupportService
);
if (($result['success'] ?? false) && isset($result['data'])) {
$result['data']['semester'] = $this->semester;
$result['data']['semester'] = $this->semester;
$result['data']['school_year'] = $this->schoolYear;
}
@@ -100,4 +101,4 @@ class AttendanceCommunicationSupportService
->values()
->all();
}
}
}
@@ -4,27 +4,25 @@ namespace App\Services\AttendanceTracking;
use App\Models\AttendanceEmailTemplate;
use App\Support\MailHtml;
use Illuminate\Support\Str;
class AttendanceEmailComposerService
{
public function __construct(
protected AttendanceEmailTemplate $attendanceEmailTemplateModel
) {
}
) {}
public function buildTemplateContext(array $violation, ?array $parent = null): array
{
$studentName = (string) ($violation['name'] ?? '');
$incident = (string) ($violation['last_date'] ?? date('Y-m-d'));
$incident = (string) ($violation['last_date'] ?? date('Y-m-d'));
return [
'{{parent_name}}' => (string) ($parent['parent_name'] ?? 'Parent/Guardian'),
'{{student_name}}' => $studentName,
'{{incident_date}}' => $incident,
'{{school_phone}}' => '978-364-0219',
'{{parent_name}}' => (string) ($parent['parent_name'] ?? 'Parent/Guardian'),
'{{student_name}}' => $studentName,
'{{incident_date}}' => $incident,
'{{school_phone}}' => '978-364-0219',
'{{voicemail_phone}}' => (string) ($parent['phone'] ?? '—'),
'{{class_time}}' => '10:00 AM',
'{{class_time}}' => '10:00 AM',
];
}
@@ -42,7 +40,7 @@ class AttendanceEmailComposerService
->where('is_active', 1)
->first();
if (!$row) {
if (! $row) {
$row = $this->attendanceEmailTemplateModel->query()
->where('code', $code)
->where('variant', 'default')
@@ -53,12 +51,12 @@ class AttendanceEmailComposerService
$row = $row?->toArray();
}
if (!$row) {
if (! $row) {
return null;
}
$subject = strtr($row['subject'], $context);
$body = strtr($row['body_html'], $context);
$body = strtr($row['body_html'], $context);
return [$subject, $body];
}
@@ -70,35 +68,35 @@ class AttendanceEmailComposerService
public function pickVariant(string $code, ?string $globalVariantOverride = null): string
{
if (!empty($globalVariantOverride)) {
if (! empty($globalVariantOverride)) {
return $globalVariantOverride;
}
return match ($code) {
'ABS_2', 'ABS_2_IN3W' => 'no_answer',
default => 'default',
default => 'default',
};
}
public function buildFallbackAutoEmail(string $code, array $violation, string $parentName = ''): array
{
$subject = match ($code) {
'ABS_1' => 'Attendance Notice: Unreported Absence',
'ABS_1' => 'Attendance Notice: Unreported Absence',
'LATE_2' => 'Attendance Notice: Repeated Lateness',
default => 'Attendance Notice',
default => 'Attendance Notice',
};
$studentName = (string) ($violation['name'] ?? '');
$date = (string) ($violation['last_date'] ?? date('Y-m-d'));
$date = (string) ($violation['last_date'] ?? date('Y-m-d'));
$body = "<p>Dear " . ($parentName ?: 'Parent/Guardian') . "</p>"
. "<p>We'd like to inform you about your student's recent attendance:</p>"
. "<ul>"
. "<li><strong>Student:</strong> {$studentName}</li>"
. "<li><strong>Issue:</strong> " . (string) ($violation['violation'] ?? '') . "</li>"
. "<li><strong>As of:</strong> {$date}</li>"
. "</ul>"
. "<p>If you have any questions, please contact the school office.</p>";
$body = '<p>Dear '.($parentName ?: 'Parent/Guardian').'</p>'
."<p>We'd like to inform you about your student's recent attendance:</p>"
.'<ul>'
."<li><strong>Student:</strong> {$studentName}</li>"
.'<li><strong>Issue:</strong> '.(string) ($violation['violation'] ?? '').'</li>'
."<li><strong>As of:</strong> {$date}</li>"
.'</ul>'
.'<p>If you have any questions, please contact the school office.</p>';
return [$subject, $body];
}
@@ -134,7 +132,8 @@ class AttendanceEmailComposerService
static function ($m) {
$url = $m[0];
$urlAttr = htmlspecialchars($url, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
return '<a href="' . $urlAttr . '" target="_blank" rel="noopener noreferrer">' . $url . '</a>';
return '<a href="'.$urlAttr.'" target="_blank" rel="noopener noreferrer">'.$url.'</a>';
},
$escaped
);
@@ -142,8 +141,9 @@ class AttendanceEmailComposerService
$paragraphs = preg_split("/\n{2,}/", $escaped);
$paragraphs = array_map(static function ($p) {
$p = preg_replace("/\n/", "<br>", $p);
return '<p>' . $p . '</p>';
$p = preg_replace("/\n/", '<br>', $p);
return '<p>'.$p.'</p>';
}, $paragraphs);
return implode("\n", $paragraphs);
@@ -152,10 +152,10 @@ class AttendanceEmailComposerService
public function renderAutoEmailFor(array $violation, ?array $parent = null): ?array
{
$code = (string) ($violation['violation_code'] ?? $violation['code'] ?? '');
$ctx = $this->buildTemplateContext($violation, $parent);
$tpl = $this->renderTemplate($code, 'default', $ctx);
$ctx = $this->buildTemplateContext($violation, $parent);
$tpl = $this->renderTemplate($code, 'default', $ctx);
if (!$tpl) {
if (! $tpl) {
return $this->buildFallbackAutoEmail($code, $violation, (string) ($parent['parent_name'] ?? ''));
}
@@ -176,21 +176,21 @@ class AttendanceEmailComposerService
: date('Y-m-d');
$violation = [
'id' => $studentId,
'name' => trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')),
'last_date' => $lastDate,
'violation' => $code,
'id' => $studentId,
'name' => trim(($student['firstname'] ?? '').' '.($student['lastname'] ?? '')),
'last_date' => $lastDate,
'violation' => $code,
'violation_code' => $code,
];
$ctx = $this->buildTemplateContext($violation, $primaryParent);
$rendered = $this->renderTemplate($code, $variant, $ctx);
if (!$rendered) {
if (! $rendered) {
return [
'success' => false,
'message' => "Template not found for {$code} ({$variant}).",
'status' => 404,
'status' => 404,
];
}
@@ -198,19 +198,19 @@ class AttendanceEmailComposerService
return [
'success' => true,
'data' => [
'student_id' => $studentId,
'student_name' => $violation['name'],
'parent_email' => (string) ($primaryParent['email'] ?? ''),
'parent_name' => (string) ($primaryParent['parent_name'] ?? ''),
'data' => [
'student_id' => $studentId,
'student_name' => $violation['name'],
'parent_email' => (string) ($primaryParent['email'] ?? ''),
'parent_name' => (string) ($primaryParent['parent_name'] ?? ''),
'secondary_email' => (string) ($secondaryParent['email'] ?? ''),
'secondary_name' => (string) ($secondaryParent['parent_name'] ?? ''),
'code' => $code,
'variant' => $variant,
'subject' => $subject,
'body_html' => $body,
'incident_date' => $lastDate,
'secondary_name' => (string) ($secondaryParent['parent_name'] ?? ''),
'code' => $code,
'variant' => $variant,
'subject' => $subject,
'body_html' => $body,
'incident_date' => $lastDate,
],
];
}
}
}
@@ -7,4 +7,4 @@ interface AttendanceMailerService
public function send(string $to, string $subject, string $html): bool;
public function queueAttendanceEvent(array $payload): void;
}
}
@@ -10,8 +10,7 @@ class AttendanceNotificationLogService
{
public function __construct(
protected ParentNotification $notificationModel
) {
}
) {}
public function notificationAlreadySent(
int $studentId,
@@ -31,13 +30,14 @@ class AttendanceNotificationLogService
->where('incident_date', $ymd)
->where('channel', $channel);
if (!empty($to)) {
if (! empty($to)) {
$query->where('to_address', $to);
}
return $query->exists();
} catch (Throwable $e) {
Log::debug('notificationAlreadySent(): ' . $e->getMessage());
Log::debug('notificationAlreadySent(): '.$e->getMessage());
return false;
}
}
@@ -61,37 +61,37 @@ class AttendanceNotificationLogService
->whereDate('incident_date', $ymd)
->where('channel', $channel);
if (!empty($to)) {
if (! empty($to)) {
$existingQuery->where('to_address', $to);
}
$existing = $existingQuery->orderByDesc('id')->first();
if ($existing && !empty($existing->id)) {
if ($existing && ! empty($existing->id)) {
$existing->update([
'subject' => $subject,
'status' => $status,
'response' => $response,
'semester' => $semester,
'school_year' => $schoolYear,
'updated_at' => now(),
'subject' => $subject,
'status' => $status,
'response' => $response,
'semester' => $semester,
'school_year' => $schoolYear,
'updated_at' => now(),
]);
} else {
$this->notificationModel->query()->create([
'student_id' => $studentId,
'code' => $code,
'student_id' => $studentId,
'code' => $code,
'incident_date' => $ymd,
'channel' => $channel,
'to_address' => $to,
'subject' => $subject,
'status' => $status,
'response' => $response,
'semester' => $semester,
'school_year' => $schoolYear,
'channel' => $channel,
'to_address' => $to,
'subject' => $subject,
'status' => $status,
'response' => $response,
'semester' => $semester,
'school_year' => $schoolYear,
]);
}
} catch (Throwable $e) {
Log::debug('logNotification(): ' . $e->getMessage());
Log::debug('logNotification(): '.$e->getMessage());
}
}
}
@@ -7,7 +7,6 @@ use App\Models\AttendanceTracking;
use App\Models\Configuration;
use App\Models\Student;
use App\Models\StudentClass;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Throwable;
@@ -15,6 +14,7 @@ use Throwable;
class AttendanceNotificationWorkflowService
{
protected string $semester;
protected string $schoolYear;
public function __construct(
@@ -29,33 +29,33 @@ class AttendanceNotificationWorkflowService
protected AttendanceEmailComposerService $emailComposerService,
protected AttendanceNotificationLogService $notificationLogService
) {
$this->semester = (string) ($this->configModel->getConfig('semester') ?? '');
$this->semester = (string) ($this->configModel->getConfig('semester') ?? '');
$this->schoolYear = (string) ($this->configModel->getConfig('school_year') ?? '');
}
public function record(array $data): array
{
$sid = (int) $data['student_id'];
$ymd = substr((string) $data['date'], 0, 10);
$semester = $data['semester'] ?? $this->semester;
$sid = (int) $data['student_id'];
$ymd = substr((string) $data['date'], 0, 10);
$semester = $data['semester'] ?? $this->semester;
$schoolYear = $data['school_year'] ?? $this->schoolYear;
$subject = $data['subject_type'] ?? 'Absent';
$subject = $data['subject_type'] ?? 'Absent';
$student = $this->studentModel->query()->find($sid)?->toArray() ?? [];
$parentEmail = $data['parent_email'] ?? '';
$parentName = $data['parent_name'] ?? '';
$parentName = $data['parent_name'] ?? '';
if (!$parentEmail) {
if (! $parentEmail) {
$parent = $this->parentLookupService->getPrimaryParentForStudent($sid) ?? [];
$parentEmail = $parent['email'] ?? '';
$parentName = $parent['parent_name'] ?? '';
$parentName = $parent['parent_name'] ?? '';
}
if (!$parentEmail) {
if (! $parentEmail) {
return [
'success' => false,
'message' => 'No parent email found for this student.',
'status' => 422,
'status' => 422,
];
}
@@ -87,37 +87,37 @@ class AttendanceNotificationWorkflowService
: [];
$payload = [
'student_id' => $sid,
'student' => $student,
'parent' => ['email' => $parentEmail, 'name' => $parentName],
'semester' => $semester,
'student_id' => $sid,
'student' => $student,
'parent' => ['email' => $parentEmail, 'name' => $parentName],
'semester' => $semester,
'school_year' => $schoolYear,
'class' => $class,
'now' => now()->toDateTimeString(),
'date' => $ymd,
'subject' => $subject,
'class' => $class,
'now' => now()->toDateTimeString(),
'date' => $ymd,
'subject' => $subject,
'consequence' => $consequence ?: 'follow_up',
];
try {
$this->attendanceMailerService->queueAttendanceEvent($payload);
if ($row && !empty($row['id']) && method_exists($this->attendanceTrackingModel, 'markAsNotified')) {
if ($row && ! empty($row['id']) && method_exists($this->attendanceTrackingModel, 'markAsNotified')) {
$this->attendanceTrackingModel->markAsNotified((int) $row['id']);
}
return [
'success' => true,
'message' => 'Notification queued.',
'data' => $payload,
'data' => $payload,
];
} catch (Throwable $e) {
Log::error('Attendance record queue failed: ' . $e->getMessage());
Log::error('Attendance record queue failed: '.$e->getMessage());
return [
'success' => false,
'message' => $e->getMessage(),
'status' => 500,
'status' => 500,
];
}
}
@@ -132,30 +132,32 @@ class AttendanceNotificationWorkflowService
$details = [];
foreach ($toSend as $v) {
$sid = (int) ($v['id'] ?? 0);
$code = (string) ($v['violation_code'] ?? $v['code'] ?? '');
$date = (string) ($v['last_date'] ?? '');
$to = trim((string) ($v['parent_email'] ?? ''));
$sid = (int) ($v['id'] ?? 0);
$code = (string) ($v['violation_code'] ?? $v['code'] ?? '');
$date = (string) ($v['last_date'] ?? '');
$to = trim((string) ($v['parent_email'] ?? ''));
$pname = (string) ($v['parent_name'] ?? '');
if ($sid <= 0 || !$code || !$date || !$to) {
if ($sid <= 0 || ! $code || ! $date || ! $to) {
$errors++;
$details[] = ['student_id' => $sid, 'code' => $code, 'date' => $date, 'result' => 'missing data'];
continue;
}
if ($this->notificationLogService->notificationAlreadySent($sid, $code, $date, 'email', $to)) {
$skipped++;
$details[] = ['student_id' => $sid, 'code' => $code, 'date' => $date, 'result' => 'duplicate'];
continue;
}
$parent = $this->parentLookupService->getPrimaryParentForStudent($sid);
$parent = $this->parentLookupService->getPrimaryParentForStudent($sid);
$context = $this->emailComposerService->buildTemplateContext($v, $parent);
$variant = $this->emailComposerService->pickVariant($code, $variantOverride);
$tpl = $this->emailComposerService->renderTemplate($code, $variant, $context);
$tpl = $this->emailComposerService->renderTemplate($code, $variant, $context);
if (!$tpl) {
if (! $tpl) {
[$subject, $body] = $this->emailComposerService->buildFallbackAutoEmail($code, $v, $pname);
} else {
[$subject, $body] = $tpl;
@@ -212,7 +214,7 @@ class AttendanceNotificationWorkflowService
$this->semester,
$this->schoolYear
);
$details[] = ['student_id' => $sid, 'code' => $code, 'date' => $date, 'result' => 'error: ' . $e->getMessage()];
$details[] = ['student_id' => $sid, 'code' => $code, 'date' => $date, 'result' => 'error: '.$e->getMessage()];
}
try {
@@ -260,7 +262,7 @@ class AttendanceNotificationWorkflowService
}
} catch (Throwable $e) {
$errors++;
$details[] = ['student_id' => $sid, 'code' => $code, 'date' => $date, 'result' => 'cc_error: ' . $e->getMessage()];
$details[] = ['student_id' => $sid, 'code' => $code, 'date' => $date, 'result' => 'cc_error: '.$e->getMessage()];
}
if ($anyOk) {
@@ -277,13 +279,13 @@ class AttendanceNotificationWorkflowService
}
$allDates = [];
$absDates = array_map(fn ($d) => substr((string) $d, 0, 10), (array) ($v['absences'] ?? []));
$absDates = array_map(fn ($d) => substr((string) $d, 0, 10), (array) ($v['absences'] ?? []));
$lateDates = array_map(fn ($d) => substr((string) $d, 0, 10), (array) ($v['lates'] ?? []));
if (!empty($absDates)) {
if (! empty($absDates)) {
$allDates = array_merge($allDates, $absDates);
}
if (!empty($lateDates)) {
if (! empty($lateDates)) {
$allDates = array_merge($allDates, $lateDates);
}
if (empty($allDates)) {
@@ -303,29 +305,29 @@ class AttendanceNotificationWorkflowService
return [
'success' => true,
'sent' => $sent,
'sent' => $sent,
'skipped' => $skipped,
'errors' => $errors,
'errors' => $errors,
'details' => $details,
];
}
public function sendEmailManual(array $data, array $violationDates = []): array
{
$sid = (int) $data['student_id'];
$to = trim((string) $data['to']);
$subject = (string) $data['subject'];
$sid = (int) $data['student_id'];
$to = trim((string) $data['to']);
$subject = (string) $data['subject'];
$bodyInput = (string) $data['body_html'];
$code = (string) $data['code'];
$variant = (string) ($data['variant'] ?? 'default');
$code = (string) $data['code'];
$variant = (string) ($data['variant'] ?? 'default');
$ymdRaw = (string) ($data['incident_date'] ?? '');
$ymd = $ymdRaw !== '' ? substr($ymdRaw, 0, 10) : '';
$ymd = $ymdRaw !== '' ? substr($ymdRaw, 0, 10) : '';
$safeHtmlBody = $this->emailComposerService->normalizeBodyToHtml($bodyInput);
$wrapped = $this->emailComposerService->renderWithEmailLayout($subject, $safeHtmlBody);
$semester = $this->semester ?: (string) ($this->configModel->getConfig('semester') ?? '');
$semester = $this->semester ?: (string) ($this->configModel->getConfig('semester') ?? '');
$schoolYear = $this->schoolYear ?: (string) ($this->configModel->getConfig('school_year') ?? '');
try {
@@ -354,7 +356,7 @@ class AttendanceNotificationWorkflowService
$to2 = trim((string) ($sec['email'] ?? ''));
if ($to2 !== '' && strcasecmp($to2, $to) !== 0) {
if (!$this->notificationLogService->notificationAlreadySent($sid, $code, $ymd, 'email', $to2)) {
if (! $this->notificationLogService->notificationAlreadySent($sid, $code, $ymd, 'email', $to2)) {
$ok2 = $this->attendanceMailerService->send($to2, $subject, $wrapped);
$this->notificationLogService->logNotification(
@@ -376,11 +378,11 @@ class AttendanceNotificationWorkflowService
}
}
if (!$anyOk) {
if (! $anyOk) {
return [
'success' => false,
'message' => 'Failed to send email.',
'status' => 500,
'status' => 500,
];
}
@@ -394,7 +396,7 @@ class AttendanceNotificationWorkflowService
);
}
if (!empty($violationDates) && method_exists($this->attendanceDataModel, 'markReportedAndNotified')) {
if (! empty($violationDates) && method_exists($this->attendanceDataModel, 'markReportedAndNotified')) {
$this->attendanceDataModel->markReportedAndNotified(
$sid,
$violationDates,
@@ -406,12 +408,12 @@ class AttendanceNotificationWorkflowService
return [
'success' => true,
'message' => 'Email sent successfully.',
'data' => [
'student_id' => $sid,
'to' => $to,
'secondary_to' => $to2 ?? null,
'subject' => $subject,
'variant' => $variant,
'data' => [
'student_id' => $sid,
'to' => $to,
'secondary_to' => $to2 ?? null,
'subject' => $subject,
'variant' => $variant,
'incident_date' => $ymd,
],
];
@@ -431,19 +433,19 @@ class AttendanceNotificationWorkflowService
return [
'success' => false,
'message' => 'Error: ' . $e->getMessage(),
'status' => 500,
'message' => 'Error: '.$e->getMessage(),
'status' => 500,
];
}
}
public function saveNotificationNote(array $data): array
{
$sid = (int) $data['student_id'];
$code = (string) $data['code'];
$note = trim((string) $data['note']);
$sid = (int) $data['student_id'];
$code = (string) $data['code'];
$note = trim((string) $data['note']);
$ymdRaw = (string) ($data['incident_date'] ?? '');
$ymd = $ymdRaw !== '' ? substr($ymdRaw, 0, 10) : '';
$ymd = $ymdRaw !== '' ? substr($ymdRaw, 0, 10) : '';
try {
$day = $ymd !== '' ? $ymd : date('Y-m-d');
@@ -455,11 +457,11 @@ class AttendanceNotificationWorkflowService
->where('school_year', $this->schoolYear)
->where('date', '>=', $start)
->where('date', '<', $end)
->where('reason', 'like', '%' . $code . '%')
->where('reason', 'like', '%'.$code.'%')
->orderByDesc('date')
->first();
if (!$row) {
if (! $row) {
$row = $this->attendanceTrackingModel->query()
->where('student_id', $sid)
->where('semester', $this->semester)
@@ -470,28 +472,28 @@ class AttendanceNotificationWorkflowService
->first();
}
if ($row && !empty($row->id)) {
if ($row && ! empty($row->id)) {
DB::table($this->attendanceTrackingModel->getTable())
->where('id', (int) $row->id)
->update([
'notif_counter' => DB::raw('COALESCE(notif_counter,0)+1'),
'note' => $note,
'is_notified' => 1,
'updated_at' => now(),
'note' => $note,
'is_notified' => 1,
'updated_at' => now(),
]);
} else {
$this->attendanceTrackingModel->query()->create([
'student_id' => $sid,
'date' => $start,
'is_reported' => 0,
'reason' => $code,
'is_notified' => 1,
'student_id' => $sid,
'date' => $start,
'is_reported' => 0,
'reason' => $code,
'is_notified' => 1,
'notif_counter' => 1,
'semester' => $this->semester,
'school_year' => $this->schoolYear,
'note' => $note,
'created_at' => now(),
'updated_at' => now(),
'semester' => $this->semester,
'school_year' => $this->schoolYear,
'note' => $note,
'created_at' => now(),
'updated_at' => now(),
]);
}
@@ -502,9 +504,9 @@ class AttendanceNotificationWorkflowService
} catch (Throwable $e) {
return [
'success' => false,
'message' => 'Failed to save note: ' . $e->getMessage(),
'status' => 500,
'message' => 'Failed to save note: '.$e->getMessage(),
'status' => 500,
];
}
}
}
}
@@ -16,15 +16,15 @@ class AttendanceParentLookupService
->where('s.id', $studentId)
->first();
if (!$row || empty($row->user_id)) {
if (! $row || empty($row->user_id)) {
return null;
}
return [
'user_id' => (int) $row->user_id,
'email' => (string) ($row->email ?? ''),
'user_id' => (int) $row->user_id,
'email' => (string) ($row->email ?? ''),
'parent_name' => (string) ($row->parent_name ?? ''),
'phone' => $row->cellphone ?? null,
'phone' => $row->cellphone ?? null,
];
}
@@ -36,11 +36,11 @@ class AttendanceParentLookupService
->where('id', $studentId)
->first();
if (!$stu) {
if (! $stu) {
return null;
}
$primaryId = (int) ($stu->parent_id ?? 0);
$primaryId = (int) ($stu->parent_id ?? 0);
$schoolYear = (string) ($stu->school_year ?? $fallbackSchoolYear ?? '');
$pb = DB::table('parents')->where('firstparent_id', $primaryId);
@@ -49,7 +49,7 @@ class AttendanceParentLookupService
}
$pRow = $pb->orderByDesc('updated_at')->first();
if (!$pRow) {
if (! $pRow) {
return null;
}
@@ -60,12 +60,12 @@ class AttendanceParentLookupService
->where('id', $secondId)
->first();
if ($u2 && !empty($u2->email)) {
if ($u2 && ! empty($u2->email)) {
return [
'user_id' => (int) $u2->id,
'email' => (string) $u2->email,
'parent_name' => trim(($u2->firstname ?? '') . ' ' . ($u2->lastname ?? '')),
'phone' => (string) ($u2->cellphone ?? ''),
'user_id' => (int) $u2->id,
'email' => (string) $u2->email,
'parent_name' => trim(($u2->firstname ?? '').' '.($u2->lastname ?? '')),
'phone' => (string) ($u2->cellphone ?? ''),
];
}
}
@@ -73,18 +73,18 @@ class AttendanceParentLookupService
$fallbackEmail = (string) ($pRow->secondparent_email ?? '');
$fallbackPhone = (string) ($pRow->secondparent_phone ?? '');
$fallbackFirst = (string) ($pRow->secondparent_firstname ?? '');
$fallbackLast = (string) ($pRow->secondparent_lastname ?? '');
$fallbackLast = (string) ($pRow->secondparent_lastname ?? '');
if ($fallbackEmail || $fallbackPhone || $fallbackFirst || $fallbackLast) {
return [
'user_id' => $secondId ?: null,
'email' => $fallbackEmail,
'parent_name' => trim($fallbackFirst . ' ' . $fallbackLast) ?: null,
'phone' => $fallbackPhone,
'user_id' => $secondId ?: null,
'email' => $fallbackEmail,
'parent_name' => trim($fallbackFirst.' '.$fallbackLast) ?: null,
'phone' => $fallbackPhone,
];
}
} catch (Throwable $e) {
Log::error('getSecondaryParentForStudent() failed: ' . $e->getMessage());
Log::error('getSecondaryParentForStudent() failed: '.$e->getMessage());
}
return null;
@@ -96,7 +96,7 @@ class AttendanceParentLookupService
return [
'success' => false,
'message' => 'Missing student_id',
'status' => 422,
'status' => 422,
];
}
@@ -106,15 +106,15 @@ class AttendanceParentLookupService
->where('id', $studentId)
->first();
if (!$stu) {
if (! $stu) {
return [
'success' => false,
'message' => 'Student not found',
'status' => 404,
'status' => 404,
];
}
$primaryId = (int) $stu->parent_id;
$primaryId = (int) $stu->parent_id;
$schoolYear = $stu->school_year ?: $currentSchoolYear;
$u1 = DB::table('users')
@@ -123,39 +123,39 @@ class AttendanceParentLookupService
->first();
$primary = $u1 ? [
'id' => (int) $u1->id,
'id' => (int) $u1->id,
'firstname' => (string) $u1->firstname,
'lastname' => (string) $u1->lastname,
'email' => (string) $u1->email,
'lastname' => (string) $u1->lastname,
'email' => (string) $u1->email,
'cellphone' => (string) $u1->cellphone,
] : null;
$secondaryRaw = $this->getSecondaryParentForStudent($studentId, $schoolYear);
$secondary = $secondaryRaw ? [
'id' => $secondaryRaw['user_id'] ?? null,
'id' => $secondaryRaw['user_id'] ?? null,
'firstname' => null,
'lastname' => null,
'email' => $secondaryRaw['email'] ?? '',
'lastname' => null,
'email' => $secondaryRaw['email'] ?? '',
'cellphone' => $secondaryRaw['phone'] ?? '',
'name' => $secondaryRaw['parent_name'] ?? '',
'name' => $secondaryRaw['parent_name'] ?? '',
] : null;
return [
'success' => true,
'data' => [
'primary' => $primary,
'data' => [
'primary' => $primary,
'secondary' => $secondary,
],
];
} catch (Throwable $e) {
Log::error('parentsInfo() failed: ' . $e->getMessage());
Log::error('parentsInfo() failed: '.$e->getMessage());
return [
'success' => false,
'message' => 'Server error',
'status' => 500,
'status' => 500,
];
}
}
}
}
@@ -13,8 +13,7 @@ class AttendancePendingViolationService
protected AttendanceData $attendanceDataModel,
protected ViolationRuleEngineService $violationRuleEngine,
protected AttendanceViolationStudentResolverService $studentResolverService
) {
}
) {}
public function getPendingViolations(
string $defaultSchoolYear,
@@ -22,52 +21,52 @@ class AttendancePendingViolationService
?string $semesterParam = null
): array {
$schoolYear = filled($schoolYearParam) ? (string) $schoolYearParam : (string) $defaultSchoolYear;
$semester = filled($semesterParam) ? (string) $semesterParam : null;
$semester = filled($semesterParam) ? (string) $semesterParam : null;
$debugInfo = [
'school_year_param' => $schoolYear,
'semester_param' => $semester,
'class_students' => 0,
'student_ids' => 0,
'attendance_rows' => 0,
'filtered_rows' => 0,
'violations' => 0,
'active_weeks' => 0,
'abs_last5' => 0,
'late_last5' => 0,
'semester_param' => $semester,
'class_students' => 0,
'student_ids' => 0,
'attendance_rows' => 0,
'filtered_rows' => 0,
'violations' => 0,
'active_weeks' => 0,
'abs_last5' => 0,
'late_last5' => 0,
];
$resolved = $this->studentResolverService->resolveForSchoolYear($schoolYear, $semester);
$schoolYear = (string) ($resolved['school_year'] ?? $schoolYear);
$semester = $resolved['semester'] ?? $semester;
$studentIds = $resolved['student_ids'] ?? [];
$studentCodes = $resolved['student_codes'] ?? [];
$students = $resolved['students'] ?? [];
$schoolYear = (string) ($resolved['school_year'] ?? $schoolYear);
$semester = $resolved['semester'] ?? $semester;
$studentIds = $resolved['student_ids'] ?? [];
$studentCodes = $resolved['student_codes'] ?? [];
$students = $resolved['students'] ?? [];
$studentCodeToId = $resolved['student_code_to_id'] ?? [];
$existingIds = $resolved['existing_ids'] ?? [];
$resolverDebug = $resolved['debug'] ?? [];
$existingIds = $resolved['existing_ids'] ?? [];
$resolverDebug = $resolved['debug'] ?? [];
$debugInfo['class_students'] = $resolverDebug['class_students'] ?? 0;
$debugInfo['student_ids'] = $resolverDebug['student_ids'] ?? 0;
$debugInfo['student_ids'] = $resolverDebug['student_ids'] ?? 0;
if (empty($studentIds) && empty($studentCodes)) {
return [
'students' => [],
'students' => [],
'school_year' => $schoolYear,
'semester' => $semester,
'error' => 'No student identifiers found for the current school year.',
'debug' => $debugInfo,
'semester' => $semester,
'error' => 'No student identifiers found for the current school year.',
'debug' => $debugInfo,
];
}
if (empty($students)) {
return [
'students' => [],
'students' => [],
'school_year' => $schoolYear,
'semester' => $semester,
'error' => 'No students found matching attendance records.',
'debug' => $debugInfo,
'semester' => $semester,
'error' => 'No students found matching attendance records.',
'debug' => $debugInfo,
];
}
@@ -91,14 +90,14 @@ class AttendancePendingViolationService
'is_reported',
'is_notified',
])
->when(!empty($schoolYear), fn ($q) => $q->where('school_year', $schoolYear))
->when(!empty($semester), fn ($q) => $q->where('semester', $semester))
->when(! empty($schoolYear), fn ($q) => $q->where('school_year', $schoolYear))
->when(! empty($semester), fn ($q) => $q->where('semester', $semester))
->where(function ($q) use ($studentIds, $studentCodes) {
if (!empty($studentIds)) {
if (! empty($studentIds)) {
$q->whereIn('student_id', $studentIds);
}
if (!empty($studentCodes)) {
if (!empty($studentIds)) {
if (! empty($studentCodes)) {
if (! empty($studentIds)) {
$q->orWhereIn('student_id', $studentCodes);
} else {
$q->whereIn('student_id', $studentCodes);
@@ -118,16 +117,16 @@ class AttendancePendingViolationService
if (empty($termRows)) {
$latestAttendanceTerm = DB::table($this->attendanceDataModel->getTable())
->select('school_year', 'semester', 'date')
->when(!empty($studentIds), fn ($q) => $q->whereIn('student_id', $studentIds))
->when(! empty($studentIds), fn ($q) => $q->whereIn('student_id', $studentIds))
->orderByDesc('date')
->first();
if ($latestAttendanceTerm) {
$schoolYear = !empty($latestAttendanceTerm->school_year)
$schoolYear = ! empty($latestAttendanceTerm->school_year)
? (string) $latestAttendanceTerm->school_year
: $schoolYear;
$semester = !empty($latestAttendanceTerm->semester)
$semester = ! empty($latestAttendanceTerm->semester)
? (string) $latestAttendanceTerm->semester
: $semester;
@@ -150,14 +149,14 @@ class AttendancePendingViolationService
'is_reported',
'is_notified',
])
->when(!empty($schoolYear), fn ($q) => $q->where('school_year', $schoolYear))
->when(!empty($semester), fn ($q) => $q->where('semester', $semester))
->when(! empty($schoolYear), fn ($q) => $q->where('school_year', $schoolYear))
->when(! empty($semester), fn ($q) => $q->where('semester', $semester))
->where(function ($q) use ($studentIds, $studentCodes) {
if (!empty($studentIds)) {
if (! empty($studentIds)) {
$q->whereIn('student_id', $studentIds);
}
if (!empty($studentCodes)) {
if (!empty($studentIds)) {
if (! empty($studentCodes)) {
if (! empty($studentIds)) {
$q->orWhereIn('student_id', $studentCodes);
} else {
$q->whereIn('student_id', $studentCodes);
@@ -177,10 +176,10 @@ class AttendancePendingViolationService
if (empty($termRows)) {
return [
'students' => [],
'students' => [],
'school_year' => $schoolYear,
'semester' => $semester,
'debug' => $debugInfo,
'semester' => $semester,
'debug' => $debugInfo,
];
}
}
@@ -188,7 +187,7 @@ class AttendancePendingViolationService
$attendanceRows = [];
foreach ($termRows as $row) {
$dStr = $row['date'] ?? null;
if (!$dStr) {
if (! $dStr) {
continue;
}
@@ -198,7 +197,7 @@ class AttendancePendingViolationService
continue;
}
if ($d->lt($start) || !$d->lt($endEx)) {
if ($d->lt($start) || ! $d->lt($endEx)) {
continue;
}
@@ -222,15 +221,15 @@ class AttendancePendingViolationService
if (empty($attendanceRows)) {
return [
'students' => [],
'students' => [],
'school_year' => $schoolYear,
'semester' => $semester,
'debug' => $debugInfo,
'semester' => $semester,
'debug' => $debugInfo,
];
}
$debugInfo['attendance_rows'] = count($termRows);
$debugInfo['filtered_rows'] = count($attendanceRows);
$debugInfo['filtered_rows'] = count($attendanceRows);
$violations = $this->violationRuleEngine->computeViolations(
$students,
@@ -241,17 +240,17 @@ class AttendancePendingViolationService
);
$ruleDebug = $this->violationRuleEngine->getDebugCompute();
$debugInfo['violations'] = count($violations);
$debugInfo['violations'] = count($violations);
$debugInfo['active_weeks'] = $ruleDebug['active_weeks'] ?? 0;
$debugInfo['by_student'] = $ruleDebug['by_student'] ?? 0;
$debugInfo['abs_last5'] = $ruleDebug['abs_last5'] ?? 0;
$debugInfo['late_last5'] = $ruleDebug['late_last5'] ?? 0;
$debugInfo['by_student'] = $ruleDebug['by_student'] ?? 0;
$debugInfo['abs_last5'] = $ruleDebug['abs_last5'] ?? 0;
$debugInfo['late_last5'] = $ruleDebug['late_last5'] ?? 0;
return [
'students' => $violations,
'students' => $violations,
'school_year' => $schoolYear,
'semester' => $semester,
'debug' => $debugInfo,
'semester' => $semester,
'debug' => $debugInfo,
];
}
}
}
@@ -2,11 +2,13 @@
namespace App\Services\AttendanceTracking;
use App\Http\Controllers\Api\AttendanceTracking\AttendanceTrackingController;
use App\Models\Configuration;
use Carbon\Carbon;
use Illuminate\Support\Facades\Validator;
/**
* Facade used by {@see \App\Http\Controllers\Api\AttendanceTracking\AttendanceTrackingController}.
* Facade used by {@see AttendanceTrackingController}.
*/
class AttendanceTrackingService
{
@@ -15,8 +17,7 @@ class AttendanceTrackingService
protected AttendanceCaseQueryService $caseQueryService,
protected AttendanceNotificationWorkflowService $workflowService,
protected AttendanceCommunicationSupportService $communicationSupportService,
) {
}
) {}
protected function defaultSchoolYear(): string
{
@@ -152,7 +153,7 @@ class AttendanceTrackingService
$ymd = null;
if (! empty($data['date'])) {
try {
$ymd = \Carbon\Carbon::parse((string) $data['date'])->format('Y-m-d');
$ymd = Carbon::parse((string) $data['date'])->format('Y-m-d');
} catch (\Throwable) {
$ymd = null;
}
@@ -13,14 +13,13 @@ class AttendanceViolationStudentResolverService
protected Student $studentModel,
protected StudentClass $studentClassModel,
protected AttendanceData $attendanceDataModel
) {
}
) {}
public function resolveForSchoolYear(string $schoolYear, ?string $semester = null): array
{
$debug = [
'class_students' => 0,
'student_ids' => 0,
'student_ids' => 0,
];
$classStudentsQuery = $this->studentClassModel->query();
@@ -41,9 +40,9 @@ class AttendanceViolationStudentResolverService
->orderByDesc('id')
->first();
if ($latestTerm && !empty($latestTerm->school_year)) {
if ($latestTerm && ! empty($latestTerm->school_year)) {
$schoolYear = (string) $latestTerm->school_year;
if (!empty($latestTerm->semester)) {
if (! empty($latestTerm->semester)) {
$semester = (string) $latestTerm->semester;
}
@@ -73,14 +72,14 @@ class AttendanceViolationStudentResolverService
if (empty($studentIds) && empty($studentCodes)) {
return [
'school_year' => $schoolYear,
'semester' => $semester,
'student_ids' => [],
'student_codes' => [],
'students' => [],
'student_code_to_id'=> [],
'existing_ids' => [],
'debug' => $debug,
'school_year' => $schoolYear,
'semester' => $semester,
'student_ids' => [],
'student_codes' => [],
'students' => [],
'student_code_to_id' => [],
'existing_ids' => [],
'debug' => $debug,
];
}
@@ -91,14 +90,14 @@ class AttendanceViolationStudentResolverService
[$studentCodeToId, $existingIds] = $this->buildLookupMaps($students);
return [
'school_year' => $schoolYear,
'semester' => $semester,
'student_ids' => $studentIds,
'student_codes' => $studentCodes,
'students' => $students,
'school_year' => $schoolYear,
'semester' => $semester,
'student_ids' => $studentIds,
'student_codes' => $studentCodes,
'students' => $students,
'student_code_to_id' => $studentCodeToId,
'existing_ids' => $existingIds,
'debug' => $debug,
'existing_ids' => $existingIds,
'debug' => $debug,
];
}
@@ -114,12 +113,12 @@ class AttendanceViolationStudentResolverService
return null;
}
if (!isset($existingIds[$sid])) {
if (! isset($existingIds[$sid])) {
$students[] = [
'id' => $sid,
'id' => $sid,
'school_id' => (string) $sid,
'firstname' => (string) $sid,
'lastname' => '',
'lastname' => '',
'parent_id' => 0,
];
$existingIds[$sid] = true;
@@ -139,12 +138,12 @@ class AttendanceViolationStudentResolverService
$sid = abs(crc32($code)) ?: random_int(3000000, 3999999);
$studentCodeToId[$code] = $sid;
if (!isset($existingIds[$sid])) {
if (! isset($existingIds[$sid])) {
$students[] = [
'id' => $sid,
'id' => $sid,
'school_id' => $code,
'firstname' => $code,
'lastname' => '',
'lastname' => '',
'parent_id' => 0,
];
$existingIds[$sid] = true;
@@ -185,7 +184,7 @@ class AttendanceViolationStudentResolverService
$attendanceStudents = DB::table($this->attendanceDataModel->getTable())
->selectRaw('DISTINCT student_id')
->when(!empty($schoolYear), fn ($q) => $q->where('school_year', $schoolYear))
->when(! empty($schoolYear), fn ($q) => $q->where('school_year', $schoolYear))
->orderBy('student_id')
->get();
@@ -207,7 +206,7 @@ class AttendanceViolationStudentResolverService
protected function filterInactiveIdentifiers(array $studentIds, array $studentCodes): array
{
if (!empty($studentIds)) {
if (! empty($studentIds)) {
$inactiveRows = $this->studentModel->query()
->select('id')
->whereIn('id', $studentIds)
@@ -222,11 +221,11 @@ class AttendanceViolationStudentResolverService
$studentIds = array_values(array_filter(
$studentIds,
static fn ($id) => !isset($inactiveIdSet[$id])
static fn ($id) => ! isset($inactiveIdSet[$id])
));
}
if (!empty($studentCodes)) {
if (! empty($studentCodes)) {
$inactiveCodeRows = $this->studentModel->query()
->select('school_id')
->whereIn('school_id', $studentCodes)
@@ -241,7 +240,7 @@ class AttendanceViolationStudentResolverService
$studentCodes = array_values(array_filter(
$studentCodes,
static fn ($code) => $code !== '' && !isset($inactiveCodeSet[$code])
static fn ($code) => $code !== '' && ! isset($inactiveCodeSet[$code])
));
}
@@ -252,7 +251,7 @@ class AttendanceViolationStudentResolverService
{
$students = [];
if (!empty($studentIds)) {
if (! empty($studentIds)) {
$students = $this->studentModel->query()
->whereIn('id', $studentIds)
->where('is_active', 1)
@@ -260,7 +259,7 @@ class AttendanceViolationStudentResolverService
->toArray();
}
if (!empty($studentCodes)) {
if (! empty($studentCodes)) {
$byCode = $this->studentModel->query()
->whereIn('school_id', $studentCodes)
->where('is_active', 1)
@@ -271,7 +270,7 @@ class AttendanceViolationStudentResolverService
foreach ($byCode as $s) {
$sid = (int) ($s['id'] ?? 0);
if ($sid > 0 && !isset($seen[$sid])) {
if ($sid > 0 && ! isset($seen[$sid])) {
$students[] = $s;
$seen[$sid] = true;
}
@@ -293,14 +292,14 @@ class AttendanceViolationStudentResolverService
}
foreach ($studentCodes as $code) {
if (!isset($knownCodes[$code])) {
if (! isset($knownCodes[$code])) {
$virtualId = abs(crc32($code)) ?: random_int(1000000, 1999999);
$students[] = [
'id' => $virtualId,
'school_id' => $code,
'firstname' => $code,
'lastname' => '',
'parent_id' => 0,
'id' => $virtualId,
'school_id' => $code,
'firstname' => $code,
'lastname' => '',
'parent_id' => 0,
'class_name' => '',
];
}
@@ -317,13 +316,13 @@ class AttendanceViolationStudentResolverService
if (is_numeric($sidOrCode)) {
$sid = (int) $sidOrCode;
if ($sid > 0 && !isset($existingIds[$sid])) {
if ($sid > 0 && ! isset($existingIds[$sid])) {
$students[] = [
'id' => $sid,
'school_id' => (string) $sid,
'firstname' => (string) $sid,
'lastname' => '',
'parent_id' => 0,
'id' => $sid,
'school_id' => (string) $sid,
'firstname' => (string) $sid,
'lastname' => '',
'parent_id' => 0,
'class_name' => '',
];
$existingIds[$sid] = true;
@@ -331,14 +330,14 @@ class AttendanceViolationStudentResolverService
} else {
$code = (string) $sidOrCode;
if ($code !== '' && !isset($studentCodeToId[$code])) {
if ($code !== '' && ! isset($studentCodeToId[$code])) {
$virtualId = abs(crc32($code)) ?: random_int(2000000, 2999999);
$students[] = [
'id' => $virtualId,
'school_id' => $code,
'firstname' => $code,
'lastname' => '',
'parent_id' => 0,
'id' => $virtualId,
'school_id' => $code,
'firstname' => $code,
'lastname' => '',
'parent_id' => 0,
'class_name' => '',
];
@@ -10,8 +10,7 @@ class DefaultAttendanceMailerService implements AttendanceMailerService
public function __construct(
protected EmailService $emailService,
protected AttendanceEmailComposerService $emailComposerService
) {
}
) {}
public function send(string $to, string $subject, string $html): bool
{
@@ -26,24 +25,24 @@ class DefaultAttendanceMailerService implements AttendanceMailerService
}
$student = (array) ($payload['student'] ?? []);
$studentName = trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? ''));
$studentName = trim((string) ($student['firstname'] ?? '').' '.(string) ($student['lastname'] ?? ''));
$subjectType = (string) ($payload['subject'] ?? 'Attendance');
$date = substr((string) ($payload['date'] ?? now()->toDateString()), 0, 10);
$subject = "Attendance Notice: {$subjectType}";
$body = '<p>Dear ' . e((string) data_get($payload, 'parent.name', 'Parent/Guardian')) . ',</p>'
. '<p>This is an attendance notification for your student.</p>'
. '<ul>'
. '<li><strong>Student:</strong> ' . e($studentName !== '' ? $studentName : 'Student') . '</li>'
. '<li><strong>Date:</strong> ' . e($date) . '</li>'
. '<li><strong>Attendance:</strong> ' . e($subjectType) . '</li>'
. '</ul>'
. '<p>If you have any questions, please contact the school office.</p>';
$body = '<p>Dear '.e((string) data_get($payload, 'parent.name', 'Parent/Guardian')).',</p>'
.'<p>This is an attendance notification for your student.</p>'
.'<ul>'
.'<li><strong>Student:</strong> '.e($studentName !== '' ? $studentName : 'Student').'</li>'
.'<li><strong>Date:</strong> '.e($date).'</li>'
.'<li><strong>Attendance:</strong> '.e($subjectType).'</li>'
.'</ul>'
.'<p>If you have any questions, please contact the school office.</p>';
$html = $this->emailComposerService->renderWithEmailLayout($subject, $body);
if (!$this->send($to, $subject, $html)) {
Log::error('Attendance notification email failed for ' . $to);
if (! $this->send($to, $subject, $html)) {
Log::error('Attendance notification email failed for '.$to);
throw new \RuntimeException('Failed to send attendance notification email.');
}
}
@@ -13,13 +13,13 @@ use Throwable;
class ViolationRuleEngineService
{
protected array $debugCompute = [];
protected ?array $attendanceReportedColumns = null;
public function __construct(
protected AttendanceTracking $attendanceTrackingModel,
protected AttendanceParentLookupService $parentLookupService
) {
}
) {}
public function getDebugCompute(): array
{
@@ -54,10 +54,10 @@ class ViolationRuleEngineService
continue;
}
$ymd = substr((string) ($r['date'] ?? ''), 0, 10);
$ymd = substr((string) ($r['date'] ?? ''), 0, 10);
$stat = strtolower(trim((string) ($r['status'] ?? '')));
if (!in_array($stat, ['absent', 'late'], true)) {
if (! in_array($stat, ['absent', 'late'], true)) {
continue;
}
@@ -69,10 +69,10 @@ class ViolationRuleEngineService
[$syStart, $syEnd] = $this->deriveSchoolYearBounds($schoolYear);
$weekRowsSource = null;
if (!empty($weekRowsFallback)) {
if (! empty($weekRowsFallback)) {
foreach ($weekRowsFallback as $r) {
$st = strtolower(trim((string) ($r['status'] ?? '')));
if ($st !== '' && !in_array($st, ['absent', 'late'], true)) {
if ($st !== '' && ! in_array($st, ['absent', 'late'], true)) {
$weekRowsSource = $weekRowsFallback;
break;
}
@@ -86,9 +86,9 @@ class ViolationRuleEngineService
return [];
}
$weekIndex = array_flip($activeWeekKeys);
$currentWeekIdx = count($activeWeekKeys) - 1;
$windowStartIdx = max(0, $currentWeekIdx - 4);
$weekIndex = array_flip($activeWeekKeys);
$currentWeekIdx = count($activeWeekKeys) - 1;
$windowStartIdx = max(0, $currentWeekIdx - 4);
$requiredWeeksCount = $currentWeekIdx - $windowStartIdx + 1;
$keepLastWeeks = function (array $dates) use ($weekIndex, $windowStartIdx, $currentWeekIdx): array {
@@ -105,17 +105,18 @@ class ViolationRuleEngineService
}
$out = array_values(array_unique($out));
rsort($out);
return $out;
};
$classByStudent = [];
try {
$studentIdsForClass = array_values(array_unique(array_map(
static fn($s) => (int) ($s['id'] ?? 0),
static fn ($s) => (int) ($s['id'] ?? 0),
$students
)));
if (!empty($studentIdsForClass)) {
if (! empty($studentIdsForClass)) {
$rows = DB::table('student_class as sc')
->select([
'sc.student_id',
@@ -137,18 +138,18 @@ class ViolationRuleEngineService
continue;
}
if (!isset($classByStudent[$sid])) {
if (! isset($classByStudent[$sid])) {
$classByStudent[$sid] = [
'class_section_id' => $row->class_section_id ?? null,
'class_section_id' => $row->class_section_id ?? null,
'class_section_name' => (string) ($row->class_section_name ?? ''),
'class_id' => $row->class_id ?? null,
'class_name' => (string) ($row->class_name ?? ''),
'class_id' => $row->class_id ?? null,
'class_name' => (string) ($row->class_name ?? ''),
];
}
}
}
} catch (Throwable $e) {
Log::debug('computeViolations(): class prefetch failed: ' . $e->getMessage());
Log::debug('computeViolations(): class prefetch failed: '.$e->getMessage());
}
$out = [];
@@ -156,26 +157,26 @@ class ViolationRuleEngineService
$lateCountLast5 = 0;
foreach ($students as $stu) {
$sid = (int) ($stu['id'] ?? 0);
$name = trim(($stu['firstname'] ?? '') . ' ' . ($stu['lastname'] ?? ''));
$sid = (int) ($stu['id'] ?? 0);
$name = trim(($stu['firstname'] ?? '').' '.($stu['lastname'] ?? ''));
$absDatesAll = array_values(array_unique($byStudent[$sid]['absent'] ?? []));
$absDatesAll = array_values(array_unique($byStudent[$sid]['absent'] ?? []));
$lateDatesAll = array_values(array_unique($byStudent[$sid]['late'] ?? []));
$absDates = $keepLastWeeks($absDatesAll);
$absDates = $keepLastWeeks($absDatesAll);
$lateDates = $keepLastWeeks($lateDatesAll);
if (empty($absDates) && empty($lateDates)) {
continue;
}
$absWeekIdx = $this->datesToWeekIndices($absDates, $weekIndex);
$absWeekIdx = $this->datesToWeekIndices($absDates, $weekIndex);
$lateWeekIdx = $this->datesToWeekIndices($lateDates, $weekIndex);
$absCountLast5 += count($absDates);
$absCountLast5 += count($absDates);
$lateCountLast5 += count($lateDates);
$absenceViolation = null;
if (!empty($absDates)) {
if (! empty($absDates)) {
$lastAbsDate = $absDates[0];
$A2row = $this->hasNConsecutiveItems($absDates, 2, 7);
@@ -188,47 +189,47 @@ class ViolationRuleEngineService
if ($A4row || $A_in5w4) {
$absenceViolation = [
'type' => 'absence',
'code' => 'ABS_4',
'severity' => 4,
'action' => 'expel_notify',
'color' => '#ff4d4f',
'title' => '4 Absences (in a row or within last 5 active weeks)',
'type' => 'absence',
'code' => 'ABS_4',
'severity' => 4,
'action' => 'expel_notify',
'color' => '#ff4d4f',
'title' => '4 Absences (in a row or within last 5 active weeks)',
'description' => 'Notify team to inform parent about expulsion and send email.',
'last_date' => $lastAbsDate,
'last_date' => $lastAbsDate,
];
} elseif ($A3row || $A_in4w3) {
$absenceViolation = [
'type' => 'absence',
'code' => 'ABS_3',
'severity' => 3,
'action' => 'team_notify',
'color' => '#fa8c16',
'title' => '3 Absences (in a row or within last 4 active weeks)',
'type' => 'absence',
'code' => 'ABS_3',
'severity' => 3,
'action' => 'team_notify',
'color' => '#fa8c16',
'title' => '3 Absences (in a row or within last 4 active weeks)',
'description' => 'Team calls parent and sends email.',
'last_date' => $lastAbsDate,
'last_date' => $lastAbsDate,
];
} elseif ($A2row || $A_in3w2) {
$absenceViolation = [
'type' => 'absence',
'code' => 'ABS_2',
'severity' => 2,
'action' => 'team_notify',
'color' => '#fadb14',
'title' => '2 Absences (in a row or within last 3 active weeks)',
'type' => 'absence',
'code' => 'ABS_2',
'severity' => 2,
'action' => 'team_notify',
'color' => '#fadb14',
'title' => '2 Absences (in a row or within last 3 active weeks)',
'description' => 'Team calls parent and sends email.',
'last_date' => $lastAbsDate,
'last_date' => $lastAbsDate,
];
}
}
$lateViolation = null;
if (!empty($lateDates)) {
if (! empty($lateDates)) {
$lastLateDate = $lateDates[0];
$L2row = $this->hasNConsecutiveItems($lateDates, 2, 7);
$L3row = $this->hasNConsecutiveItems($lateDates, 3, 7);
$L4row = $this->hasNConsecutiveItems($lateDates, 4, 7);
$L2row = $this->hasNConsecutiveItems($lateDates, 2, 7);
$L3row = $this->hasNConsecutiveItems($lateDates, 3, 7);
$L4row = $this->hasNConsecutiveItems($lateDates, 4, 7);
$L_in3w2 = $this->hasNInWActiveWeeks($lateWeekIdx, 2, 3);
$L_in4w3 = $this->hasNInWActiveWeeks($lateWeekIdx, 3, 4);
@@ -238,7 +239,7 @@ class ViolationRuleEngineService
$lateWeeksSet = array_flip($lateWeekIdx);
$lateCoversAllWindowWeeks = true;
for ($i = $windowStartIdx; $i <= $currentWeekIdx; $i++) {
if (!isset($lateWeeksSet[$i])) {
if (! isset($lateWeeksSet[$i])) {
$lateCoversAllWindowWeeks = false;
break;
}
@@ -247,41 +248,41 @@ class ViolationRuleEngineService
if ($L4row || $lateCoversAllWindowWeeks) {
$lateViolation = [
'type' => 'late',
'code' => 'LATE_4',
'severity' => 4,
'action' => 'team_notify',
'color' => '#ff4d4f',
'title' => '4 Lates in a row OR in each of the last 4 active weeks',
'type' => 'late',
'code' => 'LATE_4',
'severity' => 4,
'action' => 'team_notify',
'color' => '#ff4d4f',
'title' => '4 Lates in a row OR in each of the last 4 active weeks',
'description' => 'Team calls parent and sends email.',
'last_date' => $lastLateDate,
'last_date' => $lastLateDate,
];
} else {
$twoLatesOneAbsWithin4 = $this->twoLatesOneAbsInWWeeks($lateWeekIdx, $absWeekIdx, 4);
if ($L3row || $L_in4w3 || $twoLatesOneAbsWithin4) {
$lateViolation = [
'type' => $twoLatesOneAbsWithin4 ? 'mix' : 'late',
'code' => $twoLatesOneAbsWithin4 ? 'MIX_L2A1' : 'LATE_3',
'severity' => 3,
'action' => 'team_notify',
'color' => '#002766',
'title' => $twoLatesOneAbsWithin4
'type' => $twoLatesOneAbsWithin4 ? 'mix' : 'late',
'code' => $twoLatesOneAbsWithin4 ? 'MIX_L2A1' : 'LATE_3',
'severity' => 3,
'action' => 'team_notify',
'color' => '#002766',
'title' => $twoLatesOneAbsWithin4
? '2 Lates + 1 Absence (within last 4 active weeks)'
: '3 Lates (in a row or within last 4 active weeks)',
'description' => 'Team calls parent and sends email.',
'last_date' => $lastLateDate,
'last_date' => $lastLateDate,
];
} elseif ($L2row || $L_in3w2) {
$lateViolation = [
'type' => 'late',
'code' => 'LATE_2',
'severity' => 1,
'action' => 'auto_email',
'color' => '#bae7ff',
'title' => '2 Lates (in a row or within last 3 active weeks)',
'type' => 'late',
'code' => 'LATE_2',
'severity' => 1,
'action' => 'auto_email',
'color' => '#bae7ff',
'title' => '2 Lates (in a row or within last 3 active weeks)',
'description' => 'Send automated email to parent.',
'last_date' => $lastLateDate,
'last_date' => $lastLateDate,
];
}
}
@@ -289,20 +290,20 @@ class ViolationRuleEngineService
$chosen = $this->chooseHigherSeverity($absenceViolation, $lateViolation);
if (!$chosen && count($absDates) === 1) {
if (! $chosen && count($absDates) === 1) {
$chosen = [
'type' => 'absence',
'code' => 'ABS_1',
'severity' => 1,
'action' => 'auto_email',
'color' => '#e6f7ff',
'title' => '1 Unreported Absence',
'type' => 'absence',
'code' => 'ABS_1',
'severity' => 1,
'action' => 'auto_email',
'color' => '#e6f7ff',
'title' => '1 Unreported Absence',
'description' => 'Send automated email to parent.',
'last_date' => $absDates[0],
'last_date' => $absDates[0],
];
}
if (!$chosen) {
if (! $chosen) {
continue;
}
@@ -315,51 +316,51 @@ class ViolationRuleEngineService
->where('school_year', $schoolYear)
->where('date', '>=', $start)
->where('date', '<', $end)
->where('reason', 'like', '%' . $chosen['code'] . '%');
->where('reason', 'like', '%'.$chosen['code'].'%');
if ($semester !== null) {
$trackingQ->where('semester', $semester);
}
$tracking = $trackingQ->orderByDesc('date')->first();
$tracking = $tracking?->toArray() ?? [];
$tracking = $trackingQ->orderByDesc('date')->first();
$tracking = $tracking?->toArray() ?? [];
$isNotified = (bool) ($tracking['is_notified'] ?? 0);
$notifCnt = (int) ($tracking['notif_counter'] ?? 0);
$notifCnt = (int) ($tracking['notif_counter'] ?? 0);
$cls = $classByStudent[$sid] ?? [];
$classLabel = (string) ($cls['class_section_name'] ?? '');
if ($classLabel === '' && !empty($cls['class_name'])) {
if ($classLabel === '' && ! empty($cls['class_name'])) {
$classLabel = (string) $cls['class_name'];
}
$out[] = [
'id' => $sid,
'name' => $name,
'class_name' => $classLabel,
'id' => $sid,
'name' => $name,
'class_name' => $classLabel,
'class_section_name' => (string) ($cls['class_section_name'] ?? ''),
'className' => (string) ($cls['class_name'] ?? ''),
'class_id' => $cls['class_id'] ?? null,
'className' => (string) ($cls['class_name'] ?? ''),
'class_id' => $cls['class_id'] ?? null,
'class_section_id' => $cls['class_section_id'] ?? null,
'parent_email' => $parent['email'] ?? '',
'parent_name' => $parent['parent_name'] ?? '',
'parent_phone' => $parent['phone'] ?? null,
'violation' => $chosen['title'],
'violation_code' => $chosen['code'],
'type' => $chosen['type'],
'severity' => $chosen['severity'],
'action' => $chosen['action'],
'color' => $chosen['color'],
'last_absence' => $absDates[0] ?? null,
'last_late' => $lateDates[0] ?? null,
'last_date' => $chosen['last_date'],
'is_notified' => $isNotified,
'notif_counter' => $notifCnt,
'absences' => $absDates,
'lates' => $lateDates,
'parent_email' => $parent['email'] ?? '',
'parent_name' => $parent['parent_name'] ?? '',
'parent_phone' => $parent['phone'] ?? null,
'violation' => $chosen['title'],
'violation_code' => $chosen['code'],
'type' => $chosen['type'],
'severity' => $chosen['severity'],
'action' => $chosen['action'],
'color' => $chosen['color'],
'last_absence' => $absDates[0] ?? null,
'last_late' => $lateDates[0] ?? null,
'last_date' => $chosen['last_date'],
'is_notified' => $isNotified,
'notif_counter' => $notifCnt,
'absences' => $absDates,
'lates' => $lateDates,
];
}
$this->debugCompute['abs_last5'] = $absCountLast5;
$this->debugCompute['abs_last5'] = $absCountLast5;
$this->debugCompute['late_last5'] = $lateCountLast5;
return $out;
@@ -370,8 +371,10 @@ class ViolationRuleEngineService
if ($a && $b) {
if ($a['severity'] === $b['severity']) {
$prio = ['expel_notify' => 3, 'team_notify' => 2, 'auto_email' => 1];
return ($prio[$a['action']] ?? 0) >= ($prio[$b['action']] ?? 0) ? $a : $b;
}
return $a['severity'] > $b['severity'] ? $a : $b;
}
@@ -386,7 +389,7 @@ class ViolationRuleEngineService
$this->attendanceReportedColumns = [
'is_reported' => Schema::hasColumn($table, 'is_reported'),
'reported' => Schema::hasColumn($table, 'reported'),
'reported' => Schema::hasColumn($table, 'reported'),
];
return $this->attendanceReportedColumns;
@@ -396,15 +399,15 @@ class ViolationRuleEngineService
{
$cols = $this->attendanceReportedColumns($table);
if (!empty($cols['is_reported'])) {
if (! empty($cols['is_reported'])) {
$qb->whereRaw("(LOWER(TRIM(COALESCE(is_reported, ''))) NOT IN ('yes','1','true','y','on'))");
}
if (!empty($cols['reported'])) {
if (! empty($cols['reported'])) {
$qb->whereRaw("(LOWER(TRIM(COALESCE(reported, ''))) NOT IN ('yes','1','true','y','on'))");
}
$qb->whereRaw("(LOWER(COALESCE(reason, '')) NOT LIKE '%parent-reported%')")
->whereRaw("(LOWER(COALESCE(reason, '')) NOT LIKE '%parent reported%')");
->whereRaw("(LOWER(COALESCE(reason, '')) NOT LIKE '%parent reported%')");
if (Schema::hasTable('parent_attendance_reports')) {
$qb->whereRaw("NOT EXISTS (
@@ -418,14 +421,16 @@ class ViolationRuleEngineService
public function deriveSchoolYearBounds(string $schoolYear): array
{
if (!preg_match('/^(\d{4})-(\d{4})$/', $schoolYear, $m)) {
$y = (int) date('Y');
if (! preg_match('/^(\d{4})-(\d{4})$/', $schoolYear, $m)) {
$y = (int) date('Y');
$startY = (int) (date('n') >= 8 ? $y : $y - 1);
return [sprintf('%d-08-01', $startY), sprintf('%d-07-31', $startY + 1)];
}
$startY = (int) $m[1];
$endY = (int) $m[2];
$endY = (int) $m[2];
return [sprintf('%d-08-01', $startY), sprintf('%d-07-31', $endY)];
}
@@ -446,9 +451,10 @@ class ViolationRuleEngineService
public function dayBounds(string $ymd): array
{
$d = substr($ymd, 0, 10);
return [
$d . ' 00:00:00',
date('Y-m-d', strtotime($d . ' +1 day')) . ' 00:00:00',
$d.' 00:00:00',
date('Y-m-d', strtotime($d.' +1 day')).' 00:00:00',
];
}
@@ -461,10 +467,10 @@ class ViolationRuleEngineService
): array {
$weeks = [];
if (!empty($weekRowsFallback)) {
if (! empty($weekRowsFallback)) {
foreach ($weekRowsFallback as $r) {
$d = $r['date'] ?? null;
if (!$d) {
if (! $d) {
continue;
}
$ts = strtotime($d);
@@ -490,10 +496,10 @@ class ViolationRuleEngineService
$q->whereNull('reason')
->orWhere(function ($q2) {
$q2->where('reason', 'not like', '%break%')
->where('reason', 'not like', '%cancel%');
->where('reason', 'not like', '%cancel%');
});
})
->when($semester !== null && $semester !== '', fn($q) => $q->where('semester', $semester))
->when($semester !== null && $semester !== '', fn ($q) => $q->where('semester', $semester))
->groupBy(DB::raw('DATE(date)'))
->orderBy('ymd')
->get();
@@ -521,6 +527,7 @@ class ViolationRuleEngineService
}
$idx = array_keys($set);
sort($idx);
return $idx;
}
@@ -542,14 +549,16 @@ class ViolationRuleEngineService
if (Str::startsWith($code, 'ABS_4')) {
return 5;
}
return 5;
}
public function isoWeekStartYmd(string $weekKey): string
{
if (preg_match('/^(\d{4})-W(\d{2})$/', $weekKey, $m)) {
return date('Y-m-d', strtotime($m[1] . '-W' . $m[2] . '-1'));
return date('Y-m-d', strtotime($m[1].'-W'.$m[2].'-1'));
}
return date('Y-m-d');
}
@@ -571,14 +580,15 @@ class ViolationRuleEngineService
if (empty($activeWeekKeys)) {
$days = ($windowWeeks * 7) - 1;
return date('Y-m-d', strtotime($endBound . ' -' . $days . ' days'));
return date('Y-m-d', strtotime($endBound.' -'.$days.' days'));
}
$activeWeekKeys = array_values($activeWeekKeys);
$weekIndex = array_flip($activeWeekKeys);
$endWeekKey = date('o-\WW', strtotime($endBound));
if (!isset($weekIndex[$endWeekKey])) {
if (! isset($weekIndex[$endWeekKey])) {
$endWeekKey = null;
for ($i = count($activeWeekKeys) - 1; $i >= 0; $i--) {
$wkStart = $this->isoWeekStartYmd($activeWeekKeys[$i]);
@@ -662,11 +672,12 @@ class ViolationRuleEngineService
while ($iL2 < count($L)) {
$windowStart = $L[$iL1];
$windowEnd = $windowStart + ($w - 1);
$windowEnd = $windowStart + ($w - 1);
if ($L[$iL2] > $windowEnd) {
$iL1++;
$iL2 = $iL1 + 1;
continue;
}
@@ -687,4 +698,4 @@ class ViolationRuleEngineService
return false;
}
}
}