getExistingReport($groupId); return response()->json([ 'success' => true, 'group' => [ 'id' => $group->metrika_group_id, 'name' => $group->name, 'description' => $group->description, 'department_type' => $group->department_type, ], 'form' => [ 'fields' => $formData, 'validation' => $validationSchema['schema'], 'defaults' => $validationSchema['defaults'], 'sections' => $this->groupFieldsBySection($formData) ], 'existing_data' => $existingReport, 'created_at' => now()->format('Y-m-d') ]); } // Сохранение данных формы public function saveForm(Request $request, $groupId) { $user = Auth::user(); // Получаем схему валидации $schema = MetrikaForm::getValidationSchema($groupId); // Динамическая валидация $validator = Validator::make($request->all(), $schema['schema']); if ($validator->fails()) { return response()->json([ 'success' => false, 'errors' => $validator->errors(), 'message' => 'Пожалуйста, исправьте ошибки в форме' ], 422); } // Создаем или обновляем отчет $report = Report::create( [ 'rf_user_id' => $user->id, 'created_at' => now()->toDateString(), 'sent_at' => now()->toDateString(), 'rf_department_id' => $user->department->departmentid ] ); // Создаем или обновляем результат $metricResult = MetrikaResult::create( [ 'rf_report_id' => $report->report_id, 'rf_metrika_group_id' => $groupId, 'comment' => $request->input('comment', ''), ] ); // Сохраняем значения метрик foreach ($schema['fields'] as $field) { $fieldName = "model.metrika_item_{$field['metrika_item_id']}"; if ($request->has($fieldName)) { MetrikaResultValue::create( [ 'rf_metrika_result_id' => $metricResult->metrika_result_id, 'rf_metrika_item_id' => $field['metrika_item_id'], 'value' => $request->input($fieldName), ] ); } } return response()->json([ 'success' => true, 'message' => 'Данные успешно сохранены', 'report_id' => $report->report_id, 'result_id' => $metricResult->metrika_result_id ]); } // Получение данных для редактирования public function getEditForm($reportId, $groupId) { $metricResult = MetrikaResult::where('rf_report_id', $reportId) ->where('rf_metrika_group_id', $groupId) ->with(['values.item', 'group']) ->firstOrFail(); $formData = MetrikaForm::getFormData($groupId); // Подготавливаем данные для формы $formValues = []; foreach ($metricResult->values as $value) { $formValues["metric_{$value->rf_metrika_item_id}"] = $value->value; } return response()->json([ 'success' => true, 'group' => $metricResult->group, 'form' => [ 'fields' => $formData, 'values' => $formValues, 'comment' => $metricResult->comment ] ]); } /** * Получение существующих данных для формы */ public function getExistingData($groupId) { $user = Auth::user(); if (!$user) { return response()->json([ 'success' => false, 'message' => 'Пользователь не авторизован' ], 401); } // Находим последний отчет пользователя за сегодня $report = Report::where('rf_user_id', $user->id) ->whereDate('created_at', now()->toDateString()) ->orderBy('created_at', 'desc') ->first(); if (!$report) { return response()->json([ 'success' => true, 'existing_data' => null, 'message' => 'Нет сохраненных данных за сегодня' ]); } // Находим результат для указанной группы $metricResult = MetrikaResult::where('rf_report_id', $report->report_id) ->where('rf_metrika_group_id', $groupId) ->with(['values.item']) ->first(); if (!$metricResult) { return response()->json([ 'success' => true, 'existing_data' => null, 'message' => 'Нет данных для этой группы метрик' ]); } // Подготавливаем данные для формы $formData = []; $comments = []; foreach ($metricResult->values as $value) { $fieldName = "metric_{$value->rf_metrika_item_id}"; // Преобразуем значение в зависимости от типа данных $fieldValue = $this->formatValueForForm( $value->value, $value->item->data_type ?? 'string' ); $formData[$fieldName] = $fieldValue; // Сохраняем информацию о поле $comments[$fieldName] = [ 'item_id' => $value->rf_metrika_item_id, 'item_name' => $value->item->name ?? 'Неизвестно', 'updated_at' => $value->updated_at ? $value->updated_at->format('H:i') : null ]; } return response()->json([ 'success' => true, 'existing_data' => [ 'result_id' => $metricResult->metrika_result_id, 'report_id' => $report->report_id, 'department' => $metricResult->department, 'comment' => $metricResult->comment ?? '', 'data' => $formData, 'comments' => $comments, 'submitted_at' => $metricResult->created_at ? $metricResult->created_at->format('Y-m-d H:i:s') : null, 'last_updated' => $metricResult->updated_at ? $metricResult->updated_at->format('Y-m-d H:i:s') : null, 'status' => $metricResult->status ?? 'submitted' ], 'report_info' => [ 'created_at' => $report->created_at, 'report_status' => $report->status, ] ]); } /** * Получение формы для просмотра отчета по дате */ public function getReportByDate($groupId, Request $request) { $user = Auth::user(); $validator = Validator::make($request->all(), [ 'sent_at' => 'required|string' ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'errors' => $validator->errors() ], 422); } $timestamps = explode(',', $request->sent_at); $startAt = intval($timestamps[0] / 1000); $endAt = intval($timestamps[1] / 1000); // Проверяем период (максимум 1 год) $daysDiff = ($endAt - $startAt) / (60 * 60 * 24); if ($daysDiff > 365) { return response()->json([ 'success' => false, 'message' => 'Период не может превышать 1 год' ], 400); } $dateStart = date('Y-m-d', $startAt) . ' 00:00:00'; $dateEnd = date('Y-m-d', $endAt) . ' 23:59:59'; $group = MetrikaGroup::findOrFail($groupId); // Оптимизированный агрегированный запрос $aggregatedData = DB::table('metrika_results as mr') ->join('metrika_result_values as mv', 'mr.metrika_result_id', '=', 'mv.rf_metrika_result_id') ->join('reports as r', 'mr.rf_report_id', '=', 'r.report_id') ->where('mr.rf_metrika_group_id', $groupId) ->whereBetween('r.sent_at', [$dateStart, $dateEnd]) ->when(!$user->isAdmin() && !$user->isHeadOfDepartment(), function ($query) use ($user) { return $query->where('r.rf_user_id', $user->id); }) ->select([ 'mv.rf_metrika_item_id', DB::raw('SUM(CAST(mv.value AS DECIMAL(10,2))) as total_sum'), DB::raw('COUNT(DISTINCT r.report_id) as reports_count'), DB::raw('AVG(CAST(mv.value AS DECIMAL(10,2))) as avg_value') ]) ->groupBy('mv.rf_metrika_item_id') ->get() ->keyBy('rf_metrika_item_id'); if ($aggregatedData->isEmpty()) { return response()->json([ 'success' => false, 'message' => 'Данные за указанный период не найдены' ], 404); } // Получаем названия метрик одним запросом $itemIds = $aggregatedData->pluck('rf_metrika_item_id')->toArray(); $items = MetrikaItem::whereIn('metrika_item_id', $itemIds) ->pluck('name', 'metrika_item_id'); // Формируем ответ $formValues = []; foreach ($aggregatedData as $itemId => $data) { $formValues["metrika_item_{$itemId}"] = [ 'sum' => (float) $data->total_sum, 'average' => (float) $data->avg_value, 'reports_count' => $data->reports_count, 'item_name' => $items[$itemId] ?? 'Неизвестный показатель' ]; } // Получаем структуру формы $formData = MetrikaForm::getFormData($groupId); return response()->json([ 'success' => true, 'is_view_only' => true, 'period' => [ 'start' => $dateStart, 'end' => $dateEnd, 'days' => $daysDiff + 1 ], 'group' => [ 'id' => $group->metrika_group_id, 'name' => $group->name, 'description' => $group->description, ], 'metrics' => [ 'total_items' => count($formValues), 'total_reports' => $aggregatedData->first()->reports_count ?? 0, 'values' => $formValues, 'aggregation' => 'sum_and_average' ], 'form' => [ 'fields' => $formData, 'sections' => $this->groupFieldsBySection($formData) ] ]); } /** * Получение календаря с отметками о заполненных отчетах */ public function getCalendarWithReports($groupId, Request $request) { $user = Auth::user(); $validator = Validator::make($request->all(), [ 'year' => 'nullable|integer|min:2023|max:2030', 'month' => 'nullable|integer|min:1|max:12' ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'errors' => $validator->errors() ], 422); } $year = $request->input('year', date('Y')); $month = $request->input('month', date('n')); // Получаем отправленные отчеты за указанный месяц $startDate = date("{$year}-{$month}-01"); $endDate = date("{$year}-{$month}-t", strtotime($startDate)); $reports = Report::where('rf_user_id', $user->id) ->whereBetween('sent_at', [$startDate, $endDate]) ->get(); // Создаем календарь $calendar = $this->generateCalendar($year, $month, $reports, $groupId); return response()->json([ 'success' => true, 'calendar' => $calendar, 'month_info' => [ 'year' => $year, 'month' => $month, 'month_name' => $this->getMonthName($month), 'start_date' => $startDate, 'end_date' => $endDate ], 'statistics' => [ 'total_reports' => $reports->count() ] ]); } /** * Генерация календаря */ private function generateCalendar($year, $month, $reports, $groupId) { $firstDay = date("{$year}-{$month}-01"); $daysInMonth = date('t', strtotime($firstDay)); $firstWeekday = date('N', strtotime($firstDay)); $calendar = [ 'year' => $year, 'month' => $month, 'month_name' => $this->getMonthName($month), 'days' => [] ]; // Пустые дни в начале месяца for ($i = 1; $i < $firstWeekday; $i++) { $calendar['days'][] = [ 'day' => null, 'empty' => true ]; } // Дни месяца for ($day = 1; $day <= $daysInMonth; $day++) { $date = date("{$year}-{$month}-" . sprintf('%02d', $day)); $report = $reports->firstWhere('sent_at', $date); $timestamp = strtotime($date) * 1000; // В миллисекундах $dayData = [ 'day' => $day, 'date' => $date, 'timestamp' => $timestamp, 'formatted_date' => date('d.m.Y', strtotime($date)), 'day_of_week' => date('N', strtotime($date)), 'day_name' => $this->getDayName(date('N', strtotime($date))), 'is_today' => $date === now()->toDateString(), 'is_weekend' => date('N', strtotime($date)) >= 6, 'has_report' => !is_null($report), 'report_status' => $report ? $report->status : null, 'sent_at' => $report && $report->sent_at ? $report->sent_at->getTimestamp() * 1000 : $timestamp ]; $calendar['days'][] = $dayData; } // Пустые дни в конце месяца $totalCells = ceil((count($calendar['days']) + $firstWeekday - 1) / 7) * 7; while (count($calendar['days']) < $totalCells) { $calendar['days'][] = [ 'day' => null, 'empty' => true ]; } return $calendar; } /** * Получение названия месяца */ private function getMonthName($month) { $months = [ 1 => 'Январь', 2 => 'Февраль', 3 => 'Март', 4 => 'Апрель', 5 => 'Май', 6 => 'Июнь', 7 => 'Июль', 8 => 'Август', 9 => 'Сентябрь', 10 => 'Октябрь', 11 => 'Ноябрь', 12 => 'Декабрь' ]; return $months[$month] ?? ''; } /** * Получение названия дня недели */ private function getDayName($dayOfWeek) { $days = [ 1 => 'Пн', 2 => 'Вт', 3 => 'Ср', 4 => 'Чт', 5 => 'Пт', 6 => 'Сб', 7 => 'Вс' ]; return $days[$dayOfWeek] ?? ''; } /** * Получение информации о дате */ private function getDateInfo($date) { $timestamp = strtotime($date); $months = [ 1 => 'января', 2 => 'февраля', 3 => 'марта', 4 => 'апреля', 5 => 'мая', 6 => 'июня', 7 => 'июля', 8 => 'августа', 9 => 'сентября', 10 => 'октября', 11 => 'ноября', 12 => 'декабря' ]; $days = [ 1 => 'понедельник', 2 => 'вторник', 3 => 'среда', 4 => 'четверг', 5 => 'пятница', 6 => 'суббота', 7 => 'воскресенье' ]; $dayOfWeek = date('N', $timestamp); $day = date('j', $timestamp); $month = date('n', $timestamp); $year = date('Y', $timestamp); return [ 'formatted' => $days[$dayOfWeek] . ', ' . $day . ' ' . $months[$month] . ' ' . $year . ' г.', 'day_of_week' => $days[$dayOfWeek], 'is_weekend' => $dayOfWeek >= 6, 'is_today' => $date === now()->toDateString() ]; } /** * Форматирование значения для формы */ private function formatValueForForm($value, $dataType) { if ($value === null || $value === '') { return null; } switch ($dataType) { case 'integer': return (int)$value; case 'float': case 'decimal': return (float)$value; case 'boolean': return filter_var($value, FILTER_VALIDATE_BOOLEAN); case 'array': case 'json': return json_decode($value, true) ?? $value; case 'date': return date('Y-m-d', strtotime($value)); case 'datetime': return date('Y-m-d\TH:i', strtotime($value)); default: return (string)$value; } } /** * Группировка полей по секциям */ private function groupFieldsBySection($fields) { $sections = []; foreach ($fields as $field) { $section = $field['section'] ?? 'general'; if (!isset($sections[$section])) { $sections[$section] = [ 'name' => $this->getSectionName($section), 'fields' => [] ]; } $sections[$section]['fields'][] = $field; } return array_values($sections); } private function getSectionName($section) { $names = [ 'general' => 'Основные показатели', 'admissions' => 'Поступления', 'discharges' => 'Выписки', 'additional' => 'Дополнительная информация' ]; return $names[$section] ?? ucfirst($section); } private function getExistingReport($groupId) { $user = Auth::user(); $report = Report::where('rf_user_id', $user->id) ->whereDate('created_at', now()->toDateString()) ->first(); if (!$report) { return null; } $result = MetrikaResult::where('rf_report_id', $report->report_id) ->where('rf_metrika_group_id', $groupId) ->with('values') ->first(); if (!$result) { return null; } $data = []; foreach ($result->values as $value) { $data["metric_{$value->rf_metrika_item_id}"] = $value->value; } return [ 'result_id' => $result->metrika_result_id, 'comment' => $result->comment, 'data' => $data, 'submitted_at' => $result->created_at ]; } }