first commit

This commit is contained in:
brusnitsyn
2026-01-04 23:15:06 +09:00
commit 0ec04cfb4b
104 changed files with 19072 additions and 0 deletions

View File

@@ -0,0 +1,592 @@
<?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
];
}
}