copy()->startOfDay()->addHours($this->workdayStartHour()); $end = $base->copy()->addDay()->startOfDay()->addHours($this->workdayStartHour()); return [$start, $end]; } /** * Time slots for the workday (interval in minutes). */ public function generateTimeSlots(Carbon $start, Carbon $end, int $intervalMinutes = 15): array { if ($intervalMinutes >= 1440) { return [$start->format('Y-m-d H:i:s')]; } $period = CarbonPeriod::create($start, $intervalMinutes.' minutes', $end); $slots = []; foreach ($period as $date) { $slots[] = $date->format('Y-m-d H:i:s'); } array_pop($slots); return $slots; } public function generateTimeSlotsForUser(int $business_id, int $user_id, Carbon $start, Carbon $end, ?string $day = null): array { $settings = MtUserReportSetting::resolveForUser($business_id, $user_id); if ($settings->isMonthlyMode()) { if ($day && ! $settings->allowsReportingOnDay($day)) { return []; } return [$start->format('Y-m-d H:i:s')]; } if ($settings->isSingleSlotDay()) { return [$start->format('Y-m-d H:i:s')]; } return $this->generateTimeSlots($start, $end, $settings->slotIntervalMinutes()); } public function generateRecentDays(int $count = 15): array { $start = Carbon::today()->subDays($count); $end = Carbon::today(); $period = CarbonPeriod::create($start, '1 day', $end); $days = []; foreach ($period as $date) { $days[] = $date->format('Y-m-d'); } return $days; } /** * Map slot => report for a user within workday bounds. */ public function getUserSlotReports(int $business_id, int $user_id, Carbon $start, Carbon $end): Collection { return MtDailyReport::forBusiness($business_id) ->where('user_id', $user_id) ->whereBetween('reported_at', [$start, $end]) ->with(['comments', 'userTask']) ->orderBy('reported_at') ->get() ->keyBy(function ($report) { return $report->reported_at->format('Y-m-d H:i:s'); }); } /** * Grouped duplicate bodies for admin views (same logic as Grow CRM). */ public function getGroupedReportsForRange(int $business_id, Carbon $start, Carbon $end, ?int $user_id = null): Collection { $query = MtDailyReport::forBusiness($business_id) ->selectRaw('body, COUNT(body) as body_count, MIN(reported_at) as first_slot, MAX(reported_at) as last_slot, MIN(id) as id, user_id, score, task_id, mt_user_task_id, user_status, MIN(created_at) as first_created, MAX(updated_at) as last_updated') ->whereBetween('reported_at', [$start, $end]) ->groupBy('body', 'user_id', 'score', 'task_id', 'mt_user_task_id', 'user_status') ->havingRaw('COUNT(*) >= 1') ->with(['user', 'comments.user']) ->orderBy('first_slot'); if ($user_id) { $query->where('user_id', $user_id); } return $query->get()->groupBy('user_id'); } public function userStatusLabel(?int $status): string { $map = [ 0 => __('managementtools::lang.status_select'), 1 => __('managementtools::lang.status_absent'), 2 => __('managementtools::lang.status_leave'), 3 => __('managementtools::lang.status_present'), 4 => __('managementtools::lang.status_mission'), ]; return $map[$status ?? 0] ?? '—'; } public function scoreLabel(?int $score): string { $map = [ 1 => __('managementtools::lang.score_very_weak'), 2 => __('managementtools::lang.score_weak'), 3 => __('managementtools::lang.score_average'), 4 => __('managementtools::lang.score_good'), 5 => __('managementtools::lang.score_excellent'), ]; return $map[$score ?? 0] ?? '—'; } public function todayDate(): string { return Carbon::today()->format('Y-m-d'); } public function isEditableDay(?string $day): bool { if (empty($day)) { return true; } return Carbon::parse($day)->isToday(); } public function isPastDay(?string $day): bool { if (empty($day)) { return false; } return Carbon::parse($day)->lt(Carbon::today()); } /** * Task-based reports for a calendar day (excludes day-summary row where task id is null). */ public function getTaskReportsForDay(int $business_id, int $user_id, string $day): Collection { return MtDailyReport::forBusiness($business_id) ->where('user_id', $user_id) ->whereDate('report_date', $day) ->whereNotNull('mt_user_task_id') ->with('userTask') ->get() ->keyBy('mt_user_task_id'); } /** * Day-level attendance / summary (mt_user_task_id is null). */ public function getDaySummary(int $business_id, int $user_id, string $day): ?MtDailyReport { return MtDailyReport::forBusiness($business_id) ->where('user_id', $user_id) ->whereDate('report_date', $day) ->whereNull('mt_user_task_id') ->first(); } /** * Dates in range where user submitted at least one task report. */ public function getSubmittedDays(int $business_id, int $user_id, int $days = 14): array { $start = Carbon::today()->subDays($days); return MtDailyReport::forBusiness($business_id) ->where('user_id', $user_id) ->where('report_date', '>=', $start->format('Y-m-d')) ->whereNotNull('submitted_at') ->distinct() ->pluck('report_date') ->map(fn ($d) => Carbon::parse($d)->format('Y-m-d')) ->toArray(); } public function taskStatusOptions(): array { return [ 3 => __('managementtools::lang.task_in_progress'), 5 => __('managementtools::lang.task_completed'), 4 => __('managementtools::lang.task_beyond_ability'), ]; } public function userStatusOptions(): array { return [ 3 => __('managementtools::lang.status_present'), 1 => __('managementtools::lang.status_absent'), 2 => __('managementtools::lang.status_leave'), 4 => __('managementtools::lang.status_mission'), ]; } /** * Tasks available for linking to a slot on a given calendar date. */ public function getTasksForUserOnDate(int $business_id, int $user_id, string $date): Collection { return \Modules\ManagementTools\Entities\MtUserTask::forBusiness($business_id) ->where('user_id', $user_id) ->assignable() ->where(function ($q) use ($date) { $q->whereHas('timeLines', fn ($tl) => $tl->whereDate('starts_at', $date)) ->orWhereDate('due_date', $date) ->orWhereDate('created_at', $date); }) ->orderBy('title') ->get(); } /** * Build slot => report map for the workday (includes empty slots). */ public function buildSlotReportMap(int $business_id, int $user_id, string $day): array { [$start, $end] = $this->getWorkdayBounds($day); $slots = $this->generateTimeSlotsForUser($business_id, $user_id, $start, $end, $day); $reports = $this->getUserSlotReports($business_id, $user_id, $start, $end); $map = []; foreach ($slots as $slot) { $map[$slot] = $reports->get($slot); } return $map; } public function getUserReportSettings(int $business_id, int $user_id): MtUserReportSetting { return MtUserReportSetting::resolveForUser($business_id, $user_id); } }