reportUtil = $reportUtil; $this->mtUtil = $mtUtil; $this->ppBridge = $ppBridge; } protected function authorizeOwn() { if (! auth()->user()->can('managementtools.daily_report_own')) { abort(403, 'Unauthorized action.'); } } protected function authorizeAll() { if (! auth()->user()->can('managementtools.access') || ! auth()->user()->can('managementtools.daily_report_all')) { abort(403, 'Unauthorized action.'); } } protected function authorizeUserTasks() { if (! auth()->user()->can('managementtools.access') || (! auth()->user()->can('managementtools.manage_employee_tasks') && ! auth()->user()->can('managementtools.daily_report_all'))) { abort(403, 'Unauthorized action.'); } } /** * Employee: task-based daily work report. */ public function index(Request $request) { $this->authorizeOwn(); $business_id = (int) $request->session()->get('user.business_id'); $user_id = auth()->id(); $selected_day = $request->query('day', $this->reportUtil->todayDate()); $is_editable = $this->reportUtil->isEditableDay($selected_day); $is_past = $this->reportUtil->isPastDay($selected_day); $day_selected = $request->has('day'); $slot_items = $this->reportUtil->buildSlotReportMap($business_id, $user_id, $selected_day); [$workday_start, $workday_end] = $this->reportUtil->getWorkdayBounds($selected_day); if ($is_past) { $reported_task_ids = MtDailyReport::forBusiness($business_id) ->where('user_id', $user_id) ->whereDate('report_date', $selected_day) ->whereNotNull('mt_user_task_id') ->pluck('mt_user_task_id') ->unique() ->filter(); $my_tasks = MtUserTask::forBusiness($business_id) ->where('user_id', $user_id) ->whereIn('id', $reported_task_ids) ->orderBy('title') ->get(); } else { $my_tasks = MtUserTask::forBusiness($business_id) ->where('user_id', $user_id) ->active() ->assignable() ->orderBy('title') ->get(); } $my_submitted_tasks = MtUserTask::forBusiness($business_id) ->where('user_id', $user_id) ->userSubmitted() ->orderByDesc('created_at') ->get(); $has_projects = $this->ppBridge->isAvailable(); if ($has_projects && $my_submitted_tasks->isNotEmpty()) { $project_names = $this->ppBridge->resolveProjectNames( $my_submitted_tasks->pluck('project_id')->filter()->unique()->values()->all() ); $my_submitted_tasks->each(function ($task) use ($project_names) { $task->project_name = $task->project_id ? ($project_names[$task->project_id] ?? null) : null; }); } $day_timelines = MtUserTimeLine::forBusiness($business_id) ->where('user_id', $user_id) ->whereBetween('starts_at', [$workday_start, $workday_end]) ->with('userTask') ->orderBy('starts_at') ->get(); $slot_task_cache = []; $report_settings = $this->reportUtil->getUserReportSettings($business_id, $user_id); return view('managementtools::daily_reports.index', [ 'slot_items' => $slot_items, 'report_settings' => $report_settings, 'my_tasks' => $my_tasks, 'my_submitted_tasks' => $my_submitted_tasks, 'has_projects' => $has_projects ?? false, 'day_timelines' => $day_timelines, 'days' => $this->reportUtil->generateRecentDays(15), 'selected_day' => $selected_day, 'is_editable' => $is_editable, 'is_past' => $is_past, 'day_selected' => $day_selected, 'submitted_days' => $this->reportUtil->getSubmittedDays($business_id, $user_id), 'task_status_options' => $this->reportUtil->taskStatusOptions(), 'user_status_options' => $this->reportUtil->userStatusOptions(), 'slot_task_cache' => &$slot_task_cache, 'business_id' => $business_id, 'user_id' => $user_id, ]); } /** * Save task-based daily report (today only). */ public function store(Request $request) { $this->authorizeOwn(); $business_id = (int) $request->session()->get('user.business_id'); $user_id = auth()->id(); $report_date = $request->input('report_date', $this->reportUtil->todayDate()); if (! $this->reportUtil->isEditableDay($report_date)) { return response()->json([ 'success' => false, 'msg' => __('managementtools::lang.past_day_locked'), ], 422); } $finalize = $request->boolean('finalize'); $now = Carbon::now(); $saved = 0; // Slot-based submission (CRM format) $descriptions = $request->input('description', []); if (is_array($descriptions) && count($descriptions) > 0) { $tasks_input = $request->input('task', []); $user_statuses = $request->input('user_status', []); $task_statuses = $request->input('task_status', []); foreach ($descriptions as $slot => $body) { $body = trim((string) $body); if ($body === '') { continue; } if (Carbon::parse($slot)->gt($now)) { continue; } $task_id = ! empty($tasks_input[$slot]) ? (int) $tasks_input[$slot] : null; if ($task_id === 0) { $task_id = null; } MtDailyReport::updateOrCreate( [ 'business_id' => $business_id, 'user_id' => $user_id, 'reported_at' => Carbon::parse($slot), ], [ 'body' => $body, 'report_date' => Carbon::parse($slot)->format('Y-m-d'), 'mt_user_task_id' => $task_id, 'user_status' => ! empty($user_statuses[$slot]) ? (int) $user_statuses[$slot] : null, 'task_status' => ! empty($task_statuses[$slot]) ? (int) $task_statuses[$slot] : null, 'created_by' => $user_id, 'submitted_at' => $finalize ? $now : null, ] ); $saved++; } } else { // Legacy task-card batch submission $request->validate([ 'report_date' => 'required|date', 'user_status' => 'nullable|integer|in:1,2,3,4', 'general_notes' => 'nullable|string|max:2000', 'tasks' => 'nullable|array', 'tasks.*.body' => 'nullable|string|max:5000', 'tasks.*.task_status' => 'nullable|integer|in:3,4,5', ]); $reported_at = Carbon::parse($report_date)->startOfDay()->addHours($this->reportUtil->workdayStartHour()); $user_status = $request->input('user_status'); $general_notes = trim((string) $request->input('general_notes', '')); if ($user_status || $general_notes !== '') { MtDailyReport::updateOrCreate( [ 'business_id' => $business_id, 'user_id' => $user_id, 'report_date' => $report_date, 'mt_user_task_id' => null, ], [ 'body' => $general_notes, 'reported_at' => $reported_at, 'user_status' => $user_status ?: null, 'created_by' => $user_id, 'submitted_at' => $finalize ? $now : null, ] ); $saved++; } $tasks_input = $request->input('tasks', []); $valid_task_ids = MtUserTask::forBusiness($business_id) ->where('user_id', $user_id) ->assignable() ->whereIn('id', array_keys($tasks_input)) ->pluck('id') ->toArray(); foreach ($tasks_input as $task_id => $data) { if (! in_array((int) $task_id, $valid_task_ids, true)) { continue; } $body = trim((string) ($data['body'] ?? '')); $task_status = ! empty($data['task_status']) ? (int) $data['task_status'] : null; if ($body === '' && ! $task_status) { continue; } MtDailyReport::updateOrCreate( [ 'business_id' => $business_id, 'user_id' => $user_id, 'report_date' => $report_date, 'mt_user_task_id' => (int) $task_id, ], [ 'body' => $body ?: '—', 'reported_at' => $reported_at, 'task_status' => $task_status, 'user_status' => $user_status, 'created_by' => $user_id, 'submitted_at' => $finalize ? $now : null, ] ); $saved++; } } if ($finalize && $saved === 0) { return response()->json([ 'success' => false, 'msg' => __('managementtools::lang.report_nothing_to_submit'), ], 422); } $msg = $finalize ? __('managementtools::lang.report_day_submitted') : __('managementtools::lang.report_draft_saved', ['count' => $saved]); return response()->json([ 'success' => true, 'msg' => $msg, 'submitted' => $finalize, ]); } /** * Admin: all employees' reports for a day. */ public function employees(Request $request) { $this->authorizeAll(); $business_id = (int) $request->session()->get('user.business_id'); $day = $request->query('day', Carbon::today()->format('Y-m-d')); $reports = MtDailyReport::forBusiness($business_id) ->whereDate('report_date', $day) ->with(['user', 'comments.user', 'userTask']) ->orderBy('reported_at') ->get(); $grouped = $reports->groupBy('user_id')->map(function ($userReports) { return $userReports->groupBy(function ($r) { return ($r->mt_user_task_id ?: 'day').'|'.$r->body; })->map(function ($bodyGroup) { $first = $bodyGroup->first(); return (object) [ 'body' => $first->body, 'body_count' => $bodyGroup->count(), 'first_slot' => $bodyGroup->min('reported_at'), 'last_slot' => $bodyGroup->max('reported_at'), 'id' => $first->id, 'user_id' => $first->user_id, 'score' => $first->score, 'user_status' => $first->user_status, 'task_status' => $first->task_status, 'mt_user_task_id' => $first->mt_user_task_id, 'userTask' => $first->userTask, 'user' => $first->user, 'comments' => $bodyGroup->flatMap->comments->unique('id'), 'created_at' => $first->created_at, 'updated_at' => $bodyGroup->max('updated_at'), 'submitted_at' => $bodyGroup->max('submitted_at'), ]; })->values(); }); $reported_user_ids = $grouped->keys()->toArray(); $users_without_report = User::where('business_id', $business_id) ->user() ->where('allow_login', 1) ->whereNotIn('id', $reported_user_ids) ->get(['id', 'first_name', 'last_name', 'surname']); return view('managementtools::daily_reports.employees', [ 'grouped' => $grouped, 'days' => $this->reportUtil->generateRecentDays(), 'selected_day' => $day, 'users_without_report' => $users_without_report, ]); } /** * Admin: analytics with date range + user filter. */ public function analytics(Request $request) { $this->authorizeAll(); $business_id = (int) $request->session()->get('user.business_id'); $users = User::forDropdown($business_id, true); $grouped = collect(); $start_date_input = $request->input('start_date'); $end_date_input = $request->input('end_date'); $user_id = $request->input('user_id'); $start_gregorian = $this->mtUtil->parseInputDate($start_date_input); $end_gregorian = $this->mtUtil->parseInputDate($end_date_input); if ($start_gregorian && $end_gregorian && $user_id) { $reports = MtDailyReport::forBusiness($business_id) ->where('user_id', $user_id) ->whereBetween('report_date', [$start_gregorian, $end_gregorian]) ->with(['user', 'comments.user', 'userTask']) ->orderBy('report_date') ->orderBy('reported_at') ->get(); $grouped = $reports->groupBy('body')->map(function ($bodyGroup) { $first = $bodyGroup->first(); return (object) [ 'body' => $first->body, 'body_count' => $bodyGroup->count(), 'first_slot' => $bodyGroup->min('reported_at'), 'last_slot' => $bodyGroup->max('reported_at'), 'id' => $first->id, 'score' => $first->score, 'user_status' => $first->user_status, 'userTask' => $first->userTask, 'user' => $first->user, 'comments' => $bodyGroup->flatMap->comments, 'created_at' => $first->created_at, 'updated_at' => $bodyGroup->max('updated_at'), ]; })->values(); } return view('managementtools::daily_reports.analytics', [ 'users' => $users, 'grouped' => $grouped, 'start_date' => $this->mtUtil->formatForInput($start_gregorian ?? $start_date_input), 'end_date' => $this->mtUtil->formatForInput($end_gregorian ?? $end_date_input), 'user_id' => $user_id, ]); } public function feedback(Request $request, $id) { $this->authorizeAll(); $business_id = (int) $request->session()->get('user.business_id'); $score = (int) $request->input('score'); if (! in_array($score, [1, 2, 3, 4, 5], true)) { return response()->json(['success' => false, 'msg' => __('managementtools::lang.something_went_wrong')]); } $report = MtDailyReport::forBusiness($business_id)->findOrFail($id); MtDailyReport::forBusiness($business_id) ->where('user_id', $report->user_id) ->where('body', $report->body) ->whereDate('report_date', $report->report_date ?? $report->reported_at) ->update(['score' => $score]); return response()->json(['success' => true, 'msg' => __('managementtools::lang.score_saved')]); } public function comments($id) { $this->authorizeAll(); $business_id = (int) request()->session()->get('user.business_id'); $report = MtDailyReport::forBusiness($business_id) ->with(['user', 'comments.user', 'userTask']) ->findOrFail($id); return view('managementtools::daily_reports.partials.comments_modal', compact('report')); } public function storeComment(Request $request, $id) { $this->authorizeAll(); $business_id = (int) $request->session()->get('user.business_id'); $report = MtDailyReport::forBusiness($business_id)->findOrFail($id); $request->validate(['body' => 'required|string|max:2000']); MtDailyReportComment::create([ 'business_id' => $business_id, 'daily_report_id' => $report->id, 'user_id' => auth()->id(), 'body' => $request->body, ]); return response()->json(['success' => true, 'msg' => __('managementtools::lang.comment_added')]); } }