setupTenantConnection(); } public function mount() { setlocale(LC_ALL, 'it_IT'); $this->record_assenze = []; $end_day = now()->yesterday(); $limit = $end_day->endOfDay(); $this->year = ($end_day->month >= $this->fiscalStartMonth) ? $end_day->year : $end_day->year - 1; $dayMap = [ 'lun' => 1, 'mar' => 2, 'mer' => 3, 'gio' => 4, 'ven' => 5, 'sab' => 6, 'dom' => 0, ]; try { $courses = \App\Models\Course::whereDate('date_from', '<=', $limit) ->whereDate('date_to', '>=', $limit) ->where('active', true) ->where('enabled', true) ->get(); foreach ($courses as $course) { $course_members = \App\Models\MemberCourse::with(['member' => function ($q) { $q->where(function ($q) { $q->where('is_archived', false)->orWhereNull('is_archived'); })->where(function ($q) { $q->where('is_deleted', false)->orWhereNull('is_deleted'); }); }]) ->where('course_id', $course->id) ->whereHas('member', function ($q) { $q->where(function ($q) { $q->where('is_archived', false)->orWhereNull('is_archived'); })->where(function ($q) { $q->where('is_deleted', false)->orWhereNull('is_deleted'); }); }) ->whereDate('date_from', '<=', $limit) ->whereDate('date_to', '>=', Carbon::parse($course->date_from)->startOfDay()) ->get(); if ($course_members->isEmpty()) { continue; } $courseCalendars = \App\Models\Calendar::where('course_id', $course->id)->where('from', '<=', $limit)->where('status', '<>', 99)->get(); if ($courseCalendars->isEmpty()) { continue; } $calendarIndex = []; foreach ($courseCalendars as $cal) { $fromKey = Carbon::parse($cal->from)->toDateTimeString(); $toKey = Carbon::parse($cal->to)->toDateTimeString(); $key = $fromKey . '|' . $toKey; $calendarIndex[$key] = $cal; } $memberIds = $course_members->pluck('member_id')->unique()->values()->all(); $presences = \App\Models\Presence::query() ->whereIn('calendar_id', $courseCalendars->pluck('id')->all()) ->whereIn('member_id', $memberIds) ->where('status', '<>', 99) ->get(['member_id', 'calendar_id']); $presenceIndex = []; foreach ($presences as $p) { $presenceIndex[$p->member_id . '|' . $p->calendar_id] = true; } $makeups = \App\Models\Presence::query() ->join('calendars', 'presences.calendar_id', '=', 'calendars.id') ->where('presences.motivation_course_id', $course->id) ->whereIn('presences.member_id', $memberIds) ->where('presences.status', '<>', 99) ->where('calendars.from', '<=', $limit) ->where('calendars.status', '<>', 99) ->selectRaw('presences.member_id, MAX(calendars.from) as last_makeup_from') ->groupBy('presences.member_id') ->get(); $lastMakeupByMember = []; foreach ($makeups as $m) { $lastMakeupByMember[$m->member_id] = Carbon::parse($m->last_makeup_from); } $this->record_assenze[$course->id] = [ 'course' => [ 'id' => $course->id, 'name' => $course->getDetailsName(), ], 'members' => [], ]; $courseWhen = json_decode($course->when ?? '[]', true); if (!is_array($courseWhen) || empty($courseWhen)) { unset($this->record_assenze[$course->id]); continue; } foreach ($course_members as $course_member) { $this->record_assenze[$course->id]['members'][$course_member->id] = [ 'member' => [ 'id' => $course_member->member->id, 'first_name' => $course_member->member->first_name, 'last_name' => $course_member->member->last_name, ], 'count' => 0, 'dates' => [], ]; $mid = $course_member->member_id; $rangeStart = Carbon::parse(max($course->date_from, $course_member->date_from))->startOfDay(); $rangeEnd = Carbon::parse(min($course->date_to, $course_member->date_to, $limit->toDateString()))->endOfDay(); if ($rangeStart->gt($rangeEnd)) { unset($this->record_assenze[$course->id]['members'][$course_member->id]); continue; } $memberCalendars = []; foreach ($courseWhen as $period) { $days = $period['day'] ?? []; if (empty($days)) continue; $fromTime = $period['from'] ?? null; $toTime = $period['to'] ?? null; if (!$fromTime || !$toTime) continue; $ranges = $this->generateDateRanges( $rangeStart, $rangeEnd, $days, $dayMap, $fromTime, $toTime ); foreach ($ranges as $range) { $key = $range['from']->toDateTimeString() . '|' . $range['to']->toDateTimeString(); if (isset($calendarIndex[$key])) { $memberCalendars[] = $calendarIndex[$key]; } } } if (empty($memberCalendars)) { unset($this->record_assenze[$course->id]['members'][$course_member->id]); continue; } usort($memberCalendars, fn($a, $b) => $b->to <=> $a->to); $lastAttendance = $lastMakeupByMember[$mid] ?? null; foreach ($memberCalendars as $calendar) { $lessonFrom = Carbon::parse($calendar->from); if ($lastAttendance && $lessonFrom->lte($lastAttendance)) { break; } $pKey = $mid . '|' . $calendar->id; if (isset($presenceIndex[$pKey])) { break; } // assenza vera $this->record_assenze[$course->id]['members'][$course_member->id]['count']++; $this->record_assenze[$course->id]['members'][$course_member->id]['dates'][] = [ 'calendar_id' => $calendar->id, 'date' => $lessonFrom->translatedFormat('d/m'), ]; } if ($this->record_assenze[$course->id]['members'][$course_member->id]['count'] < 2) { unset($this->record_assenze[$course->id]['members'][$course_member->id]); } } if (empty($this->record_assenze[$course->id]['members'])) { unset($this->record_assenze[$course->id]); } else { $members = $this->record_assenze[$course->id]['members']; usort($members, function ($a, $b) { if ($a['count'] !== $b['count']) { return $b['count'] <=> $a['count']; } $last = strcmp($a['member']['last_name'], $b['member']['last_name']); if ($last !== 0) return $last; return strcmp($a['member']['first_name'], $b['member']['first_name']); }); $this->record_assenze[$course->id]['members'] = $members; } } usort($this->record_assenze, fn($a, $b) => $a['course']['name'] <=> $b['course']['name']); } catch (\Throwable $e) { dd($e->getMessage()); } $this->original_record_assenze = $this->record_assenze; } public function render() { setlocale(LC_ALL, 'it_IT'); return view('livewire.absence_report'); } public function applySearch() { $this->search = trim($this->search); $this->filter(); } public function resetSearch() { $this->search = ''; $this->record_assenze = $this->original_record_assenze; } protected function filter() { $this->record_assenze = $this->original_record_assenze; if ($this->search === '') return; $needle = mb_strtolower($this->search); foreach ($this->record_assenze as $courseId => $courseData) { $courseData['members'] = array_values(array_filter($courseData['members'], function ($m) use ($needle) { $full = mb_strtolower((trim($m['member']['last_name']) ?? '') . ' ' . (trim($m['member']['first_name']) ?? '')); $full2 = mb_strtolower((trim($m['member']['first_name']) ?? '') . ' ' . (trim($m['member']['last_name']) ?? '')); return str_contains($full, $needle) || str_contains($full2, $needle); })); if (empty($courseData['members'])) { unset($this->record_assenze[$courseId]); } else { $this->record_assenze[$courseId] = $courseData; } } } protected function generateDateRanges($rangeStart, $rangeEnd, $days, $dayMap, $fromTime, $toTime) { $allowedDow = collect($days) ->map(fn($d) => $dayMap[$d] ?? null) ->filter(fn($v) => $v !== null) ->unique() ->values() ->all(); // parse orari $fromC = Carbon::parse($fromTime); $toC = Carbon::parse($toTime); $fromHour = $fromC->hour; $fromMinute = $fromC->minute; $fromSecond = $fromC->second; $toHour = $toC->hour; $toMinute = $toC->minute; $toSecond = $toC->second; $ranges = []; foreach ($allowedDow as $dow) { $current = $rangeStart->copy()->startOfDay(); $offset = ($dow - $current->dayOfWeek + 7) % 7; $current->addDays($offset); while ($current->lte($rangeEnd)) { $fromDateTime = $current->copy()->setTime($fromHour, $fromMinute, $fromSecond); $toDateTime = $current->copy()->setTime($toHour, $toMinute, $toSecond); if ($fromDateTime->lt($rangeStart)) { $current->addWeek(); continue; } if ($fromDateTime->gt($rangeEnd)) { break; } $ranges[] = [ 'from' => $fromDateTime, 'to' => $toDateTime, ]; $current->addWeek(); } } usort($ranges, fn($a, $b) => $a['from'] <=> $b['from']); return $ranges; } }