first commit
This commit is contained in:
592
app/Http/Controllers/Api/MetrikaFormController.php
Normal file
592
app/Http/Controllers/Api/MetrikaFormController.php
Normal 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
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user