Files
onboard/app/Http/Controllers/Api/MetrikaFormController.php
2026-01-04 23:15:06 +09:00

593 lines
20 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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
];
}
}