593 lines
20 KiB
PHP
593 lines
20 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Api;
|
||
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\MetrikaForm;
|
||
use App\Models\MetrikaGroup;
|
||
use App\Models\MetrikaItem;
|
||
use App\Models\MetrikaResult;
|
||
use App\Models\MetrikaResultValue;
|
||
use App\Models\Report;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\Auth;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Validator;
|
||
|
||
class MetrikaFormController extends Controller
|
||
{
|
||
// Получение формы для заполнения
|
||
public function getForm($groupId)
|
||
{
|
||
$group = MetrikaGroup::findOrFail($groupId);
|
||
|
||
$formData = MetrikaForm::getFormData($groupId);
|
||
$validationSchema = MetrikaForm::getValidationSchema($groupId);
|
||
|
||
// Проверяем существующий отчет
|
||
$existingReport = $this->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
|
||
];
|
||
}
|
||
|
||
}
|