From 52a80ccd3b4a320c11e45943df65bea8dfa9ece8 Mon Sep 17 00:00:00 2001 From: brusnitsyn Date: Fri, 20 Feb 2026 17:28:16 +0900 Subject: [PATCH] nothing --- .dockerignore | 1 + Dockerfile | 15 +- .../Commands/FillAverageBedDaysMetric.php | 268 ++++++++++++++ .../Controllers/Api/DepartmentController.php | 17 + app/Http/Controllers/Api/ReportController.php | 29 +- .../Controllers/Api/StatisticController.php | 51 +++ .../Controllers/Web/Admin/AdminController.php | 19 + .../Controllers/Web/Admin/UserController.php | 100 ++++++ app/Http/Controllers/Web/ReportController.php | 16 +- app/Models/MedicalHistorySnapshot.php | 6 + app/Models/MisBed.php | 41 +++ app/Models/MisBedAction.php | 62 ++++ app/Models/MisMedicalHistory.php | 60 +++- app/Models/Report.php | 72 ++++ app/Services/AutoReportService.php | 30 +- app/Services/BedDayService.php | 326 ++++++++++++++++++ app/Services/DateRangeService.php | 4 + app/Services/ReportService.php | 181 ++++++++-- app/Services/StatisticsService.php | 258 ++++++++++++-- database/seeders/TestDepartmentDataSeeder.php | 119 +++++++ docker-compose.yml | 2 +- resources/js/Components/AppContainer.vue | 13 + resources/js/Components/AppPanel.vue | 65 ++++ .../js/Layouts/Components/AppUserButton.vue | 4 +- resources/js/Pages/Admin/Index.vue | 47 +++ resources/js/Pages/Admin/Users/Create.vue | 61 ++++ resources/js/Pages/Admin/Users/Index.vue | 81 +++++ resources/js/Pages/Admin/Users/User.vue | 120 +++++++ resources/js/Pages/Index.vue | 8 +- .../js/Pages/Report/Components/ReportForm.vue | 6 +- .../Report/Components/ReportFormInput.vue | 47 ++- .../Pages/Report/Components/ReportHeader.vue | 6 +- .../Report/Components/ReportSectionHeader.vue | 3 +- .../Report/Components/ReportSectionItem.vue | 1 + .../Report/Components/SelectUserModal.vue | 120 ++++++- .../Components/ModalObservablePatients.vue | 173 ++++++++++ .../Components/ModalUnwantedEvents.vue | 115 ++++++ resources/js/Pages/Statistic/Index.vue | 183 +++++----- resources/js/Stores/report.js | 5 +- routes/api.php | 13 + routes/web.php | 13 + 41 files changed, 2555 insertions(+), 206 deletions(-) create mode 100644 app/Console/Commands/FillAverageBedDaysMetric.php create mode 100644 app/Http/Controllers/Api/DepartmentController.php create mode 100644 app/Http/Controllers/Api/StatisticController.php create mode 100644 app/Http/Controllers/Web/Admin/AdminController.php create mode 100644 app/Http/Controllers/Web/Admin/UserController.php create mode 100644 app/Models/MisBed.php create mode 100644 app/Models/MisBedAction.php create mode 100644 app/Services/BedDayService.php create mode 100644 resources/js/Components/AppContainer.vue create mode 100644 resources/js/Components/AppPanel.vue create mode 100644 resources/js/Pages/Admin/Index.vue create mode 100644 resources/js/Pages/Admin/Users/Create.vue create mode 100644 resources/js/Pages/Admin/Users/Index.vue create mode 100644 resources/js/Pages/Admin/Users/User.vue create mode 100644 resources/js/Pages/Statistic/Components/ModalObservablePatients.vue create mode 100644 resources/js/Pages/Statistic/Components/ModalUnwantedEvents.vue diff --git a/.dockerignore b/.dockerignore index 1a72545..ede6f97 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,7 @@ node_modules vendor *.md .editorconfig +.env .gitignore .gitattributes storage/logs/*.log diff --git a/Dockerfile b/Dockerfile index 989d226..7d76742 100644 --- a/Dockerfile +++ b/Dockerfile @@ -125,13 +125,16 @@ RUN chown -R application:application /var/www/html && \ RUN php artisan storage:link # Кэшируем конфигурации Laravel для production -RUN php artisan config:cache && \ - php artisan route:cache && \ - php artisan view:cache +#RUN php artisan config:cache && \ +# php artisan route:cache && \ +# php artisan view:cache -# Проверяем расширения в финальном образе -RUN php -m | grep -E "(redis|gd|pdo_mysql|pdo_pgsql|zip|intl|opcache)" && \ - echo "✅ Все необходимые расширения установлены" +# Удаляем все тестовые файлы PHP (для docker-squash) +# https://github.com/shinsenter/docker-squash +RUN rm -rf /usr/src/php \ + && rm -f /usr/src/php.tar.xz 2>/dev/null || true \ + && rm -rf /usr/local/lib/php/test \ + && rm -rf /usr/local/lib/php/doc # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ diff --git a/app/Console/Commands/FillAverageBedDaysMetric.php b/app/Console/Commands/FillAverageBedDaysMetric.php new file mode 100644 index 0000000..2aae887 --- /dev/null +++ b/app/Console/Commands/FillAverageBedDaysMetric.php @@ -0,0 +1,268 @@ +info('Starting to fill average bed days metrics...'); + + // Build query + $query = Report::query(); + + // Filter by date range + if ($from = $this->option('from')) { + $query->whereDate('created_at', '>=', $from); + $this->info("Filter: from {$from}"); + } + + if ($to = $this->option('to')) { + $query->whereDate('created_at', '<=', $to); + $this->info("Filter: to {$to}"); + } + + // Filter by department + if ($departmentId = $this->option('department')) { + $query->where('rf_department_id', $departmentId); + $this->info("Filter: department ID {$departmentId}"); + } + + // Filter by specific report + if ($reportId = $this->option('report')) { + $query->where('report_id', $reportId); + $this->info("Filter: report ID {$reportId}"); + } + + // Get total count + $totalReports = $query->count(); + + if ($totalReports === 0) { + $this->warn('No reports found matching criteria.'); + return 0; + } + + $this->info("Found {$totalReports} reports to process"); + + // Confirm if large number + if ($totalReports > 1000 && !$this->confirm("Processing {$totalReports} reports may take a while. Continue?")) { + $this->info('Command cancelled.'); + return 0; + } + + $force = $this->option('force'); + $chunkSize = (int)$this->option('chunk'); + + // Progress bar + $bar = $this->output->createProgressBar($totalReports); + $bar->start(); + + $updated = 0; + $skipped = 0; + $errors = 0; + $reportsWithoutSnapshots = 0; + + // Process in chunks to avoid memory issues + $query->orderBy('report_id')->chunk($chunkSize, function ($reportsChunk) use ($reportService, $force, &$updated, &$skipped, &$errors, &$reportsWithoutSnapshots, $bar) { + foreach ($reportsChunk as $report) { + try { + $result = $this->processReport($report, $reportService, $force); + + if ($result === 'updated') { + $updated++; + } elseif ($result === 'skipped') { + $skipped++; + } elseif ($result === 'no_snapshots') { + $reportsWithoutSnapshots++; + } + + } catch (\Exception $e) { + $errors++; + $this->warn("\nError processing report {$report->report_id}: " . $e->getMessage()); + } + + $bar->advance(); + } + }); + + $bar->finish(); + $this->newLine(); + $this->newLine(); + + // Show summary + $this->table( + ['Status', 'Count'], + [ + ['Updated', $updated], + ['Skipped (already exists)', $skipped], + ['No snapshots found', $reportsWithoutSnapshots], + ['Errors', $errors], + ['Total', $totalReports], + ] + ); + + // Show sample of updated reports if any + if ($updated > 0) { + $this->newLine(); + $this->info('Sample of updated reports:'); + + // Get a sample of recently updated reports + $sampleQuery = Report::whereHas('metrikaResults', function($q) { + $q->where('rf_metrika_item_id', 18); + }) + ->orderBy('report_id', 'desc') + ->limit(5); + + // Apply same filters to sample + if ($from = $this->option('from')) { + $sampleQuery->whereDate('created_at', '>=', $from); + } + if ($to = $this->option('to')) { + $sampleQuery->whereDate('created_at', '<=', $to); + } + if ($departmentId = $this->option('department')) { + $sampleQuery->where('rf_department_id', $departmentId); + } + + $sample = $sampleQuery->get()->map(function($report) { + $metric = $report->metrikaResults + ->where('rf_metrika_item_id', 18) + ->first(); + return [ + 'report_id' => $report->report_id, + 'department' => $report->rf_department_id, + 'date' => $report->created_at, + 'value' => $metric ? $metric->value : 'N/A', + ]; + }); + + if ($sample->isNotEmpty()) { + $this->table(['Report ID', 'Department', 'Date', 'Value'], $sample->toArray()); + } + } + + $this->newLine(); + $this->info('Done!'); + + return 0; + } + + /** + * Process a single report + */ + private function processReport(Report $report, ReportService $reportService, bool $force): string + { + // Check if metric already exists + $existingMetric = MetrikaResult::where('rf_report_id', $report->report_id) + ->where('rf_metrika_item_id', 18) + ->first(); + + if ($existingMetric && !$force) { + return 'skipped'; + } + + // Check if report has snapshots + $snapshotsCount = MedicalHistorySnapshot::where('rf_report_id', $report->report_id) + ->whereIn('patient_type', ['discharged', 'deceased']) + ->count(); + + if ($snapshotsCount === 0) { + // Save 0 for reports without snapshots + MetrikaResult::updateOrCreate( + [ + 'rf_report_id' => $report->report_id, + 'rf_metrika_item_id' => 18, + ], + ['value' => 0] + ); + return 'no_snapshots'; + } + + // Calculate average bed days from snapshots + $avgBedDays = $this->calculateAverageFromSnapshots($report->report_id); + + // Save metric + MetrikaResult::updateOrCreate( + [ + 'rf_report_id' => $report->report_id, + 'rf_metrika_item_id' => 18, + ], + ['value' => $avgBedDays] + ); + + return 'updated'; + } + + /** + * Calculate average bed days from report snapshots + */ + private function calculateAverageFromSnapshots(int $reportId): float + { + $snapshots = MedicalHistorySnapshot::where('rf_report_id', $reportId) + ->whereIn('patient_type', ['discharged', 'deceased']) + ->distinct() + ->with('medicalHistory') + ->get(); + + if ($snapshots->isEmpty()) { + return 0; + } + + $totalDays = 0; + $validCount = 0; + + foreach ($snapshots as $snapshot) { + $history = $snapshot->medicalHistory; + + if ($history && $history->DateRecipient && $history->DateExtract) { + // Check if discharge date is not the special value + if ($history->DateExtract->format('Y-m-d') === '1900-01-01') { + continue; + } + + $start = Carbon::parse($history->DateRecipient); + $end = Carbon::parse($history->DateExtract); + + if ($end->gt($start)) { + $days = $start->diffInDays($end); + $totalDays += $days; + $validCount++; + } + } + } + + return $validCount > 0 ? round($totalDays / $validCount, 1) : 0; + } +} diff --git a/app/Http/Controllers/Api/DepartmentController.php b/app/Http/Controllers/Api/DepartmentController.php new file mode 100644 index 0000000..9748e98 --- /dev/null +++ b/app/Http/Controllers/Api/DepartmentController.php @@ -0,0 +1,17 @@ +json($departments); + } +} diff --git a/app/Http/Controllers/Api/ReportController.php b/app/Http/Controllers/Api/ReportController.php index 3e4993b..6f097f7 100644 --- a/app/Http/Controllers/Api/ReportController.php +++ b/app/Http/Controllers/Api/ReportController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Http\Resources\Mis\FormattedPatientResource; +use App\Models\Department; use App\Models\MedicalHistorySnapshot; use App\Models\MetrikaGroup; use App\Models\MetrikaResult; @@ -659,11 +660,16 @@ class ReportController extends Controller 'status' => 'required|string', 'startAt' => 'nullable', 'endAt' => 'nullable', + 'departmentId' => 'nullable' ]); $dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user); + $departmentId = $request->get('departmentId', $user->department->department_id); + $department = Department::where('department_id', $departmentId)->first(); + $patients = $this->reportService->getPatientsByStatus( + $department, Auth::user(), $validated['status'], $dateRange @@ -680,12 +686,17 @@ class ReportController extends Controller 'status' => 'required|string', 'startAt' => 'nullable', 'endAt' => 'nullable', + 'departmentId' => 'nullable' ]); $dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user); + $departmentId = $request->get('departmentId', $user->department->department_id); + $department = Department::where('department_id', $departmentId)->first(); + $count = $this->reportService->getPatientsCountByStatus( - Auth::user(), + $department, + $user, $validated['status'], $dateRange, ); @@ -965,4 +976,20 @@ class ReportController extends Controller ->orderBy('created_at', 'ASC') ->get(); } + + public function checkReport(Request $request) + { + $request->validate([ + 'department_id' => 'required|integer|exists:departments,department_id' + ]); + + $report = Report::where('rf_department_id', $request->department_id) + ->whereDate('created_at', now()->toDateString()) + ->first(); + + return response()->json([ + 'report_id' => $report?->report_id, + 'exists' => $report->exists + ]); + } } diff --git a/app/Http/Controllers/Api/StatisticController.php b/app/Http/Controllers/Api/StatisticController.php new file mode 100644 index 0000000..c6b2174 --- /dev/null +++ b/app/Http/Controllers/Api/StatisticController.php @@ -0,0 +1,51 @@ +validate([ + 'startAt' => 'required', + 'endAt' => 'required', + 'departmentId' => 'required', + ]); + + $dateRange = $this->dateRangeService->getNormalizedDateRange($user, $validated['startAt'], $validated['endAt']); + $department = Department::findSole($request->departmentId, 'department_id'); + + $unwantedEvents = $this->reportService->getUnwantedEvents($department, $dateRange); + + return response()->json($unwantedEvents); + } + + public function getObservablePatients(Request $request) + { + $user = Auth::user(); + $validated = $request->validate([ + 'startAt' => 'required', + 'endAt' => 'required', + 'departmentId' => 'required' + ]); + + $dateRange = $this->dateRangeService->getNormalizedDateRange($user, $validated['startAt'], $validated['endAt']); + $department = Department::findSole($request->departmentId, 'department_id'); + $observablePatients = $this->reportService->getPatientsByStatus($department, $user, 'observation', $dateRange); + + return response()->json($observablePatients); + } +} diff --git a/app/Http/Controllers/Web/Admin/AdminController.php b/app/Http/Controllers/Web/Admin/AdminController.php new file mode 100644 index 0000000..ad2f289 --- /dev/null +++ b/app/Http/Controllers/Web/Admin/AdminController.php @@ -0,0 +1,19 @@ +get()->map(function ($user) { + return [ + 'id' => $user->id, + 'name' => $user->name, + 'login' => $user->login, + 'is_active' => $user->is_active, + 'created_at' => $user->created_at->format('d.m.Y H:i:s'), + 'updated_at' => $user->updated_at->format('d.m.Y H:i:s'), + ]; + }); + + return Inertia::render('Admin/Users/Index', + [ + 'users' => $users, + ] + ); + } + + public function create() + { + $rolesData = Role::all()->map(function ($role) { + return [ + 'role_id' => $role->role_id, + 'name' => $role->name, + ]; + }); + + $departmentData = Department::all()->map(function (Department $department) { + return [ + 'department_id' => $department->department_id, + 'name_full' => $department->name_full, + ]; + }); + + return Inertia::render('Admin/Users/Create', [ + 'departments' => $departmentData, + 'roles' => $rolesData, + ]); + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'name' => 'required|string', + 'login' => 'required|string', + 'password' => 'required|string', + 'is_active' => 'required|boolean', + ]); + + dd($validated); + } + + public function show(User $user, Request $request) + { + $userData = [ + 'id' => $user->id, + 'name' => $user->name, + 'login' => $user->login, + 'is_active' => $user->is_active, + 'created_at' => $user->created_at->format('d.m.Y H:i:s'), + 'updated_at' => $user->updated_at->format('d.m.Y H:i:s'), + ]; + + $rolesData = $user->roles->map(function ($role) { + return [ + 'role_id' => $role->role_id, + 'name' => $role->name, + ]; + }); + + $departmentData = $user->departments->map(function (UserDepartment $userDepartment) { + return [ + 'department_id' => $userDepartment->department->department_id, + 'name_full' => $userDepartment->department->name_full, + ]; + }); + + return Inertia::render('Admin/Users/User', [ + 'userData' => $userData, + 'roles' => $rolesData, + 'departments' => $departmentData, + ]); + } +} diff --git a/app/Http/Controllers/Web/ReportController.php b/app/Http/Controllers/Web/ReportController.php index f5d8607..7409c67 100644 --- a/app/Http/Controllers/Web/ReportController.php +++ b/app/Http/Controllers/Web/ReportController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Web; use App\Http\Controllers\Controller; use App\Http\Resources\Mis\FormattedPatientResource; +use App\Models\Department; use App\Models\MetrikaGroup; use App\Models\MisLpuDoctor; use App\Models\Report; @@ -25,22 +26,25 @@ class ReportController extends Controller public function index(Request $request) { $user = Auth::user(); - $department = $user->department; + $departmentId = $request->query('departmentId', $user->department->department_id); + $department = Department::where('department_id', $departmentId)->first(); //$user->department; $dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user); // Получаем статистику - $statistics = $this->reportService->getReportStatistics($user, $dateRange); + $statistics = $this->reportService->getReportStatistics($department, $user, $dateRange); // Получаем метрики $metrikaGroup = MetrikaGroup::whereMetrikaGroupId(2)->first(); $metrikaItems = $metrikaGroup->metrikaItems; // Получаем информацию о текущем отчете - $reportInfo = $this->reportService->getCurrentReportInfo($user, $dateRange); + $reportInfo = $this->reportService->getCurrentReportInfo($department, $user, $dateRange); return Inertia::render('Report/Index', [ 'department' => [ + 'department_name' => $department->name_full, + 'department_id' => $department->department_id, 'beds' => $department->beds, 'percentLoadedBeds' => $this->calculateBedOccupancy($department, $user), ...$statistics, @@ -88,7 +92,7 @@ class ReportController extends Controller $patients = $this->reportService->getPatientsByStatus( Auth::user(), $validated['status'], - $dateRange + $dateRange, ); return response()->json(FormattedPatientResource::collection($patients)); @@ -143,10 +147,10 @@ class ReportController extends Controller /** * Рассчитать загруженность коек */ - private function calculateBedOccupancy($department, $user): int + private function calculateBedOccupancy(Department $department, $user): int { $beds = (int)$department->metrikaDefault()->where('rf_metrika_item_id', 1)->first()->value; - $occupiedBeds = optional(Report::where('rf_department_id', $user->rf_department_id) + $occupiedBeds = optional(Report::where('rf_department_id', $department->department_id) ->join('metrika_results', 'reports.report_id', '=', 'metrika_results.rf_report_id') ->where('metrika_results.rf_metrika_item_id', 8) ->orderBy('sent_at', 'desc') diff --git a/app/Models/MedicalHistorySnapshot.php b/app/Models/MedicalHistorySnapshot.php index 75c18d4..c433ef7 100644 --- a/app/Models/MedicalHistorySnapshot.php +++ b/app/Models/MedicalHistorySnapshot.php @@ -14,6 +14,12 @@ class MedicalHistorySnapshot extends Model 'patient_type', ]; + /** + * Типы пациентов + */ + const PATIENT_TYPE_DISCHARGED = 'discharged'; // Выписанные + const PATIENT_TYPE_CURRENT = 'current'; // Текущие + public function report() { return $this->belongsTo(Report::class, 'rf_report_id'); diff --git a/app/Models/MisBed.php b/app/Models/MisBed.php new file mode 100644 index 0000000..aabfa4c --- /dev/null +++ b/app/Models/MisBed.php @@ -0,0 +1,41 @@ + 'datetime', + 'DateWorkE' => 'datetime', + 'UGUID' => 'string', + 'x_Status' => 'integer' + ]; + + public function bedActions() + { + return $this->hasMany(MisBedAction::class, 'rf_BedID', 'BedID'); + } +} diff --git a/app/Models/MisBedAction.php b/app/Models/MisBedAction.php new file mode 100644 index 0000000..404fcf9 --- /dev/null +++ b/app/Models/MisBedAction.php @@ -0,0 +1,62 @@ + 'datetime', + 'SystemDate' => 'datetime', + 'UGUID' => 'string', + 'x_Status' => 'integer' + ]; + + /** + * Типы действий с койками + */ + const ACTION_TYPE_OCCUPIED = 1; // Поступление + const ACTION_TYPE_FREE = 2; // Выписка + + public function bed() + { + return $this->belongsTo(MisBed::class, 'rf_BedID', 'BedID'); + } + + /** + * Получить информацию о пребывании пациента на койке + */ + public static function getPatientStayPeriods($migrationPatientId) + { + $query = self::where('rf_MigrationPatientID', $migrationPatientId) + ->whereIn('rf_ActionTypeID', [ + self::ACTION_TYPE_OCCUPIED, + self::ACTION_TYPE_FREE + ]) + ->orderBy('Date'); + + return $query->get(); + } +} diff --git a/app/Models/MisMedicalHistory.php b/app/Models/MisMedicalHistory.php index cc12b33..8a70da4 100644 --- a/app/Models/MisMedicalHistory.php +++ b/app/Models/MisMedicalHistory.php @@ -3,13 +3,14 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; class MisMedicalHistory extends Model { protected $table = 'stt_medicalhistory'; - protected $primaryKey = 'MedicalHistoryID'; + public $timestamps = false; protected $fillable = [ 'MedicalHistoryID', @@ -21,9 +22,64 @@ class MisMedicalHistory extends Model ]; protected $casts = [ - 'DateRecipient' => 'datetime' + 'DateRecipient' => 'datetime', + 'DateExtract' => 'datetime' ]; + /** + * Проверить, находится ли пациент еще в стационаре + */ + public function isStillInHospital(): bool + { + // Проверяем специальное значение даты + if ($this->DateExtract && $this->DateExtract->format('dmY') === '01012222') { + return true; + } + + // Или проверяем по статусу + return $this->rf_kl_VisitStatusID === 0; + } + + /** + * Получить реальную дату выписки (если пациент выписан) + */ + public function getActualDischargeDate(): ?Carbon + { + if (!$this->DateExtract) { + return null; + } + + // Если это специальное значение - значит не выписан + if ($this->DateExtract->format('dmY') === '01012222') { + return null; + } + + return $this->DateExtract; + } + + /** + * Рассчитать койко-дни для данной истории болезни + */ + public function getBedDays(): int + { + if (!$this->DateRecipient) { + return 0; + } + + $start = Carbon::parse($this->DateRecipient); + + // Определяем дату окончания + if ($this->isStillInHospital()) { + // Пациент еще в стационаре + $end = Carbon::now(); + } else { + // Пациент выписан + $end = Carbon::parse($this->DateExtract); + } + + return $start->diffInDays($end); + } + public function observationPatient() { return $this->hasMany(ObservationPatient::class, 'rf_medicalhistory_id', 'MedicalHistoryID'); diff --git a/app/Models/Report.php b/app/Models/Report.php index 7bc496f..6e229b3 100644 --- a/app/Models/Report.php +++ b/app/Models/Report.php @@ -3,9 +3,15 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Carbon; class Report extends Model { + /** + * ID метрики для среднего койко-дня + */ + const METRIC_BED_DAYS_ID = 18; + protected $primaryKey = 'report_id'; public $timestamps = false; @@ -41,4 +47,70 @@ class Report extends Model { return $this->belongsTo(MisLpuDoctor::class, 'rf_lpudoctor_id'); } + + /** + * Связь со снапшотами + */ + public function snapshots() + { + return $this->hasMany(MedicalHistorySnapshot::class, 'rf_report_id', 'report_id'); + } + + /** + * Получить выписанных пациентов из снапшотов + */ + public function getDischargedPatientsOnSnapshots() + { + return $this->snapshots() + ->where('patient_type', MedicalHistorySnapshot::PATIENT_TYPE_DISCHARGED) + ->distinct() + ->with('medicalHistory') + ->get(); + } + + /** + * Получить текущих пациентов из снапшотов + */ + public function getCurrentPatientsOnSnapshots() + { + return $this->snapshots() + ->where('patient_type', MedicalHistorySnapshot::PATIENT_TYPE_CURRENT) + ->with('medicalHistory') + ->get(); + } + + /** + * Рассчитать и сохранить средний койко-день на основе снапшотов + */ + public function calculateAndSaveAverageBedDays() + { + $dischargedPatients = $this->getDischargedPatientsOnSnapshots(); + + if ($dischargedPatients->isEmpty()) { + $avgBedDays = 0; + } else { + $totalDays = 0; + foreach ($dischargedPatients as $snapshot) { + $history = $snapshot->medicalHistory; + if ($history && $history->DateRecipient && $history->DateExtract) { + $start = Carbon::parse($history->DateRecipient); + $end = Carbon::parse($history->DateExtract); + $days = $start->diffInDays($end); + $totalDays += $days; + } + } + $avgBedDays = round($totalDays / $dischargedPatients->count(), 1); + } + + // Сохраняем результат как метрику + MetrikaResult::updateOrCreate([ + 'rf_report_id' => $this->report_id, + 'rf_metrika_item_id' => self::METRIC_BED_DAYS_ID, + ], + [ + 'value' => $avgBedDays, + ]); + + return $avgBedDays; + } } diff --git a/app/Services/AutoReportService.php b/app/Services/AutoReportService.php index 4657550..cb747fd 100644 --- a/app/Services/AutoReportService.php +++ b/app/Services/AutoReportService.php @@ -37,8 +37,9 @@ class AutoReportService $period = CarbonPeriod::create($startDate, $endDate); foreach ($period as $date) { + $dateRange = $this->dateRangeService->getNormalizedDateRange($user, $date, $date); try { - $reportCreated = $this->createReportForDate($user, $date, $departmentId, $force); + $reportCreated = $this->createReportForDate($user, $dateRange, $departmentId, $force); if ($reportCreated) { $createdCount++; @@ -55,13 +56,13 @@ class AutoReportService /** * Создать отчет для конкретной даты */ - public function createReportForDate(User $user, Carbon $date, $departmentId, bool $force = false): bool + public function createReportForDate(User $user, DateRange $dateRange, $departmentId, bool $force = false): bool { $user->rf_department_id = $departmentId; // Проверяем, существует ли уже отчет на эту дату $existingReport = Report::where('rf_department_id', $departmentId) - ->whereDate('created_at', $date) - ->whereDate('sent_at', $date) + ->whereDate('created_at', $dateRange->endSql()) + ->whereDate('sent_at', $dateRange->endSql()) ->first(); if ($existingReport && !$force) { @@ -75,18 +76,11 @@ class AutoReportService $existingReport->delete(); } - // Создаем DateRange для этой даты - // Приводим к Illuminate\Carbon если нужно - if (!$date instanceof \Illuminate\Support\Carbon) { - $date = \Illuminate\Support\Carbon::instance($date); - } - $dateRange = $this->dateRangeService->createDateRangeForDate($date, $user); - // Получаем данные для отчета - $reportData = $this->prepareReportData($user, $dateRange, $date, $departmentId); + $reportData = $this->prepareReportData($user, $dateRange, $departmentId); // Создаем отчет - DB::transaction(function () use ($user, $reportData, $date) { + DB::transaction(function () use ($user, $reportData) { $this->reportService->storeReport($reportData, $user); }); @@ -96,7 +90,7 @@ class AutoReportService /** * Подготовить данные для отчета */ - private function prepareReportData(User $user, DateRange $dateRange, Carbon $date, $departmentId): array + private function prepareReportData(User $user, DateRange $dateRange, $departmentId): array { $department = Department::where('department_id', $departmentId)->first(); $branchId = $this->getBranchId($department->rf_mis_department_id); @@ -121,10 +115,10 @@ class AutoReportService $dateRange->startTimestamp(), $dateRange->endTimestamp() ], - 'sent_at' => $this->dateRangeService->toSqlFormat($date), - 'created_at' => $this->dateRangeService->toSqlFormat($date), + 'sent_at' => $dateRange->endSql(), + 'created_at' => $dateRange->endSql(), 'metrics' => $this->formatMetrics($metrics), - 'observationPatients' => $this->getObservationPatients($departmentId, $date), + 'observationPatients' => $this->getObservationPatients($departmentId, $dateRange), 'unwantedEvents' => [], ]; } @@ -247,7 +241,7 @@ class AutoReportService /** * Получить пациентов под наблюдением на дату */ - private function getObservationPatients(int $departmentId, Carbon $date): array + private function getObservationPatients(int $departmentId, DateRange $dateRange): array { // Здесь нужно реализовать логику получения пациентов под наблюдением // на конкретную дату. Возможно, из снапшотов или истории. diff --git a/app/Services/BedDayService.php b/app/Services/BedDayService.php new file mode 100644 index 0000000..da14490 --- /dev/null +++ b/app/Services/BedDayService.php @@ -0,0 +1,326 @@ +cache[$cacheKey])) { + return $this->cache[$cacheKey]; + } + + // Для одного дня берем последние 30 дней для статистической значимости + $actualStartDate = $isRangeOneDay + ? Carbon::parse($endDate)->subDays(30)->format('Y-m-d') + : $startDate; + + // Находим отчеты за период + $reports = Report::where('rf_department_id', $departmentId) + ->whereBetween('created_at', [$actualStartDate, $endDate]) + ->pluck('report_id'); + + if ($reports->isEmpty()) { + $this->cache[$cacheKey] = 0; + return 0; + } + + // Получаем все снапшоты выписанных пациентов из этих отчетов + $snapshots = MedicalHistorySnapshot::whereIn('rf_report_id', $reports) + ->where('patient_type', MedicalHistorySnapshot::PATIENT_TYPE_DISCHARGED) + ->distinct() + ->with('medicalHistory') + ->get(); + + if ($snapshots->isEmpty()) { + $this->cache[$cacheKey] = 0; + return 0; + } + + // Рассчитываем средний койко-день + $totalDays = 0; + $validCount = 0; + + foreach ($snapshots as $snapshot) { + $history = $snapshot->medicalHistory; + if ($history && $history->DateRecipient && $history->DateExtract) { + $start = Carbon::parse($history->DateRecipient); + $end = Carbon::parse($history->DateExtract); + + // Проверяем, что дата выписки входит в период отчета + if ($end->between($actualStartDate, $endDate)) { + $days = $start->diffInDays($end); + $totalDays += $days; + $validCount++; + } + } + } + + $avgDays = $validCount > 0 ? round($totalDays / $validCount, 1) : 0; + $this->cache[$cacheKey] = $avgDays; + + return $avgDays; + } + + /** + * Получить средние койко-дни для всех отделений из снапшотов + */ + public function getAverageBedDaysByDepartmentsFromSnapshots(array $departmentIds, string $startDate, string $endDate, bool $isRangeOneDay): array + { + if (empty($departmentIds)) { + return []; + } + + $cacheKey = 'all_snapshots_' . md5(implode(',', $departmentIds) . $startDate . $endDate . ($isRangeOneDay ? '1day' : 'range')); + + if (isset($this->cache[$cacheKey])) { + return $this->cache[$cacheKey]; + } + + $actualStartDate = $isRangeOneDay + ? Carbon::parse($endDate)->subDays(30)->format('Y-m-d') + : $startDate; + + // Находим все отчеты за период по отделениям + $reportsByDepartment = Report::whereIn('rf_department_id', $departmentIds) + ->whereBetween('created_at', [$actualStartDate, $endDate]) + ->select('report_id', 'rf_department_id') + ->get() + ->groupBy('rf_department_id'); + + $averages = []; + + foreach ($departmentIds as $departmentId) { + $departmentReports = $reportsByDepartment->get($departmentId, collect()); + + if ($departmentReports->isEmpty()) { + $averages[$departmentId] = 0; + continue; + } + + $reportIds = $departmentReports->pluck('report_id')->toArray(); + + // Получаем снапшоты для отчетов отделения + $snapshots = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->where('patient_type', MedicalHistorySnapshot::PATIENT_TYPE_DISCHARGED) + ->distinct() + ->with('medicalHistory') + ->get(); + + if ($snapshots->isEmpty()) { + $averages[$departmentId] = 0; + continue; + } + + $totalDays = 0; + $validCount = 0; + + foreach ($snapshots as $snapshot) { + $history = $snapshot->medicalHistory; + if ($history && $history->DateRecipient && $history->DateExtract) { + $end = Carbon::parse($history->DateExtract); + if ($end->between($actualStartDate, $endDate)) { + $start = Carbon::parse($history->DateRecipient); + $days = $start->diffInDays($end); + $totalDays += $days; + $validCount++; + } + } + } + + $averages[$departmentId] = $validCount > 0 ? round($totalDays / $validCount, 1) : 0; + } + + $this->cache[$cacheKey] = $averages; + + return $averages; + } + + /** + * Получить общий средний койко-день из всех снапшотов + */ + public function getOverallAverageBedDaysFromSnapshots(array $departmentIds, string $startDate, string $endDate, bool $isRangeOneDay): float + { + $averages = $this->getAverageBedDaysByDepartmentsFromSnapshots($departmentIds, $startDate, $endDate, $isRangeOneDay); + + $total = 0; + $count = 0; + + foreach ($averages as $avg) { + if ($avg > 0) { + $total += $avg; + $count++; + } + } + + return $count > 0 ? round($total / $count, 1) : 0; + } + + /** + * Получить детальную статистику по койко-дням из снапшотов + */ + public function getDetailedStatsFromSnapshots(int $departmentId, string $startDate, string $endDate): array + { + $reports = Report::where('rf_department_id', $departmentId) + ->whereBetween('created_at', [$startDate, $endDate]) + ->pluck('report_id'); + + if ($reports->isEmpty()) { + return [ + 'department_id' => $departmentId, + 'period' => ['start' => $startDate, 'end' => $endDate], + 'total_patients' => 0, + 'average_bed_days' => 0, + 'distribution' => [], + 'by_month' => [] + ]; + } + + $snapshots = MedicalHistorySnapshot::whereIn('rf_report_id', $reports) + ->where('patient_type', MedicalHistorySnapshot::PATIENT_TYPE_DISCHARGED) + ->distinct() + ->with('medicalHistory') + ->get(); + + if ($snapshots->isEmpty()) { + return [ + 'department_id' => $departmentId, + 'period' => ['start' => $startDate, 'end' => $endDate], + 'total_patients' => 0, + 'average_bed_days' => 0, + 'distribution' => [], + 'by_month' => [] + ]; + } + + $distribution = [ + '1-3' => 0, + '4-7' => 0, + '8-14' => 0, + '15-21' => 0, + '22-30' => 0, + '30+' => 0 + ]; + + $byMonth = []; + $totalDays = 0; + $validCount = 0; + + foreach ($snapshots as $snapshot) { + $history = $snapshot->medicalHistory; + if ($history && $history->DateRecipient && $history->DateExtract) { + $end = Carbon::parse($history->DateExtract); + if ($end->between($startDate, $endDate)) { + $start = Carbon::parse($history->DateRecipient); + $days = $start->diffInDays($end); + + $totalDays += $days; + $validCount++; + + // Распределение + if ($days <= 3) $distribution['1-3']++; + elseif ($days <= 7) $distribution['4-7']++; + elseif ($days <= 14) $distribution['8-14']++; + elseif ($days <= 21) $distribution['15-21']++; + elseif ($days <= 30) $distribution['22-30']++; + else $distribution['30+']++; + + // По месяцам + $month = $end->format('Y-m'); + if (!isset($byMonth[$month])) { + $byMonth[$month] = ['total' => 0, 'count' => 0]; + } + $byMonth[$month]['total'] += $days; + $byMonth[$month]['count']++; + } + } + } + + // Рассчитываем средние по месяцам + $monthlyStats = []; + foreach ($byMonth as $month => $data) { + $monthlyStats[] = [ + 'month' => $month, + 'avg_days' => round($data['total'] / $data['count'], 1), + 'count' => $data['count'] + ]; + } + + return [ + 'department_id' => $departmentId, + 'period' => ['start' => $startDate, 'end' => $endDate], + 'total_patients' => $validCount, + 'average_bed_days' => $validCount > 0 ? round($totalDays / $validCount, 1) : 0, + 'distribution' => $distribution, + 'by_month' => $monthlyStats + ]; + } + + /** + * Обновить метрики для всех отчетов на основе снапшотов + */ + public function updateAllMetricsFromSnapshots(): array + { + $reports = Report::all(); + $results = []; + + foreach ($reports as $report) { + // Для каждого отчета считаем средний койко-день за последние 30 дней до даты отчета + $endDate = $report->created_at; + $startDate = Carbon::startOfYear(); + dd($startDate); + + $avg = $this->getAverageBedDaysFromSnapshots( + $report->rf_department_id, + $startDate, + $endDate, + false + ); + + // Сохраняем в метрики + MetrikaResult::updateOrCreate( + [ + 'rf_report_id' => $report->report_id, + 'rf_metrika_item_id' => self::METRIC_BED_DAYS_ID, + ], + ['value' => $avg] + ); + + $results[$report->report_id] = $avg; + } + + return $results; + } + + /** + * Очистить кэш памяти + */ + public function clearMemoryCache(): void + { + $this->cache = []; + } +} diff --git a/app/Services/DateRangeService.php b/app/Services/DateRangeService.php index 148294b..80605f2 100644 --- a/app/Services/DateRangeService.php +++ b/app/Services/DateRangeService.php @@ -49,6 +49,10 @@ class DateRangeService return $this->getDateRangeForUser($user, $startAt, $endAt); } + if ($user->isDoctor()) { + return $this->getDateRangeForUser($user, $startAt, $endAt); + } + // По умолчанию: с начала года до сегодня $startDate = Carbon::now('Asia/Yakutsk') ->startOfYear() // 1 января текущего года diff --git a/app/Services/ReportService.php b/app/Services/ReportService.php index f4a1fd9..ffebdcd 100644 --- a/app/Services/ReportService.php +++ b/app/Services/ReportService.php @@ -30,17 +30,16 @@ class ReportService /** * Получить статистику для отчета */ - public function getReportStatistics(User $user, DateRange $dateRange): array + public function getReportStatistics(Department $department, User $user, DateRange $dateRange): array { - $department = $user->department; $misDepartmentId = $department->rf_mis_department_id; $branchId = $this->getBranchId($misDepartmentId); // Определяем, используем ли мы снапшоты - $useSnapshots = $this->shouldUseSnapshots($user, $dateRange); + $useSnapshots = $this->shouldUseSnapshots($department, $user, $dateRange); if ($useSnapshots) { - return $this->getStatisticsFromSnapshots($user, $dateRange, $branchId); + return $this->getStatisticsFromSnapshots($department, $dateRange, $branchId); } return $this->getStatisticsFromReplica($user, $dateRange, $branchId); @@ -63,6 +62,9 @@ class ReportService // Сохраняем снапшоты пациентов $this->snapshotService->createPatientSnapshots($report, $user, $data['dates'], $fillableAuto); + // Сохраняем метрику среднего койко-дня из снапшотов + $this->saveAverageBedDaysMetricFromSnapshots($report); + DB::commit(); // ОЧИСТКА КЭША ПОСЛЕ УСПЕШНОГО СОЗДАНИЯ ОТЧЕТА @@ -75,6 +77,76 @@ class ReportService } } + /** + * Сохранить метрику среднего койко-дня из снапшотов отчета + */ + protected function saveAverageBedDaysMetricFromSnapshots(Report $report): void + { + try { + // Получаем все снапшоты выписанных пациентов из этого отчета + $snapshots = MedicalHistorySnapshot::where('rf_report_id', $report->report_id) + ->whereIn('patient_type', ['discharged', 'deceased']) // выписанные и умершие + ->with('medicalHistory') + ->get(); + + if ($snapshots->isEmpty()) { + // Если нет выписанных, сохраняем 0 + MetrikaResult::updateOrCreate( + [ + 'rf_report_id' => $report->report_id, + 'rf_metrika_item_id' => 18, + ], + ['value' => 0] + ); + + \Log::info("No discharged patients in report {$report->report_id}, saved 0"); + return; + } + + // Рассчитываем средний койко-день по снапшотам + $totalDays = 0; + $validCount = 0; + + foreach ($snapshots as $snapshot) { + $history = $snapshot->medicalHistory; + + if ($history && $history->DateRecipient && $history->DateExtract) { + // Проверяем что дата выписки не специальная + if ($history->DateExtract->format('Y-m-d') === '2222-01-01') { + continue; // пропускаем текущих пациентов + } + + $start = Carbon::parse($history->DateRecipient); + $end = Carbon::parse($history->DateExtract); + + // Проверяем что дата выписки позже даты поступления + if ($end->gt($start)) { + $days = $start->diffInDays($end); + $totalDays += $days; + $validCount++; + } + } + } + + $avgBedDays = $validCount > 0 ? round($totalDays / $validCount, 1) : 0; + + // Сохраняем метрику + MetrikaResult::updateOrCreate( + [ + 'rf_report_id' => $report->report_id, + 'rf_metrika_item_id' => 18, + ], + ['value' => $avgBedDays] + ); + + \Log::info("Saved average bed days metric for report {$report->report_id}: {$avgBedDays} (from {$validCount} patients)"); + + } catch (\Exception $e) { + \Log::error("Failed to save average bed days metric: " . $e->getMessage()); + // Не прерываем выполнение, если метрика не сохранилась + } + } + /** * Очистить кэш после создания отчета */ @@ -115,6 +187,7 @@ class ReportService * Получить пациентов по статусу */ public function getPatientsByStatus( + Department $department, User $user, string $status, DateRange $dateRange, @@ -122,34 +195,35 @@ class ReportService bool $beforeCreate = false, ?bool $includeCurrentPatients = null ) { - $branchId = $this->getBranchId($user->department->rf_mis_department_id); + $branchId = $this->getBranchId($department->rf_mis_department_id); - $useSnapshots = $this->shouldUseSnapshots($user, $dateRange, $beforeCreate); + $useSnapshots = $this->shouldUseSnapshots($department, $user, $dateRange, $beforeCreate); if ($useSnapshots) { - return $this->getPatientsFromSnapshots($user, $status, $dateRange, $branchId); + return $this->getPatientsFromSnapshots($department, $status, $dateRange, $branchId); } - return $this->getPatientsFromReplica($user, $status, $dateRange, $branchId, $onlyIds, $includeCurrentPatients); + return $this->getPatientsFromReplica($department, $user, $status, $dateRange, $branchId, $onlyIds, $includeCurrentPatients); } /** * Получить количество пациентов по статусу */ public function getPatientsCountByStatus( + Department $department, User $user, string $status, DateRange $dateRange ): int { - $branchId = $this->getBranchId($user->department->rf_mis_department_id); + $branchId = $this->getBranchId($department->rf_mis_department_id); - $useSnapshots = $this->shouldUseSnapshots($user, $dateRange); + $useSnapshots = $this->shouldUseSnapshots($department, $user, $dateRange); if ($useSnapshots) { - return $this->getPatientsCountFromSnapshots($user, $status, $dateRange); + return $this->getPatientsCountFromSnapshots($department, $status, $dateRange); } - return $this->getPatientsCountFromReplica($user, $status, $dateRange, $branchId); + return $this->getPatientsCountFromReplica($department, $user, $status, $dateRange, $branchId); } /** @@ -164,7 +238,7 @@ class ReportService /** * Определить, нужно ли использовать снапшоты */ - private function shouldUseSnapshots(User $user, DateRange $dateRange, bool $beforeCreate = false): bool + private function shouldUseSnapshots(Department $department, User $user, DateRange $dateRange, bool $beforeCreate = false): bool { if (($user->isAdmin() || $user->isHeadOfDepartment()) && !$beforeCreate) { return true; @@ -173,6 +247,7 @@ class ReportService // Проверяем, есть ли отчет на сегодня $reportToday = Report::whereDate('sent_at', $dateRange->end()) ->whereDate('created_at', $dateRange->end()) + ->where('rf_department_id', $department->department_id) ->first(); return !$dateRange->isEndDateToday() || $reportToday; @@ -237,6 +312,7 @@ class ReportService { if (empty($unwantedEvents)) { $report->unwantedEvents()->delete(); + $this->saveMetrics($report, [16 => 0]); return; } @@ -260,6 +336,9 @@ class ReportService ]); } } + + // Обновить метрику + $this->saveMetrics($report, [16 => count($unwantedEvents)]); } /** @@ -274,6 +353,8 @@ class ReportService ObservationPatient::where('rf_department_id', $departmentId) ->where('rf_report_id', $report->report_id) ->delete(); + // Обновить метрику + $this->saveMetrics($report, [14 => 0]); return; } @@ -290,16 +371,19 @@ class ReportService ] ); } + + // Обновить метрику + $this->saveMetrics($report, [14 => count($observationPatients)]); } /** * Получить информацию о текущем отчете */ - public function getCurrentReportInfo(User $user, DateRange $dateRange): array + public function getCurrentReportInfo(Department $department, User $user, DateRange $dateRange): array { - $department = $user->department; $reportToday = Report::whereDate('sent_at', $dateRange->endSql()) ->whereDate('created_at', $dateRange->endSql()) + ->where('rf_department_id', $department->department_id) ->first(); $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); @@ -313,7 +397,7 @@ class ReportService } // Получаем нежелательные события - $unwantedEvents = $this->getUnwantedEvents($user, $dateRange); + $unwantedEvents = $this->getUnwantedEvents($department, $dateRange); // Определяем активность кнопки отправки $isActiveSendButton = $this->isSendButtonActive($user, $dateRange, $reportToday, $fillableUserId); @@ -367,11 +451,11 @@ class ReportService /** * Получить статистику из снапшотов */ - private function getStatisticsFromSnapshots(User $user, DateRange $dateRange, int $branchId): array + private function getStatisticsFromSnapshots(Department $department, DateRange $dateRange, int $branchId): array { // Получаем отчеты за период $reports = $this->getReportsForDateRange( - $user->rf_department_id, + $department->department_id, $dateRange ); @@ -403,6 +487,13 @@ class ReportService $this->getMetrikaResultCount(11, $reportIds) // плановые операции ]; + if ($snapshotStats['outcome'] == 0) { + $percentDead = 0; + } else { + $percentDead = ($snapshotStats['deceased'] / $snapshotStats['outcome']) * 100; + $percentDead = round($percentDead, 2); + } + return [ 'recipientCount' => $snapshotStats['recipient'] ?? 0, 'extractCount' => $snapshotStats['outcome'] ?? 0, @@ -410,7 +501,8 @@ class ReportService 'deadCount' => $snapshotStats['deceased'] ?? 0, 'surgicalCount' => $surgicalCount, 'recipientIds' => $recipientIds, - 'beds' => $snapshotStats['beds'] ?? 0 + 'beds' => $snapshotStats['beds'] ?? 0, + 'percentDead' => $percentDead, ]; } @@ -501,6 +593,13 @@ class ReportService $beds = Department::where('rf_mis_department_id', $misBranch->rf_DepartmentID) ->first()->metrikaDefault->where('rf_metrika_item_id', 1)->first(); + if ($outcomeCount == 0) { + $percentDead = 0; + } else { + $percentDead = ($deadCount / $outcomeCount) * 100; + $percentDead = round($percentDead, 2); + } + return [ 'recipientCount' => $recipientCount, // только поступившие сегодня 'extractCount' => $outcomeCount, @@ -510,6 +609,7 @@ class ReportService 'recipientIds' => $recipientIds, // ID поступивших сегодня 'planCount' => $planCount, // плановые (поступившие + уже лечащиеся) 'emergencyCount' => $emergencyCount, // экстренные (поступившие + уже лечащиеся) + 'percentDead' => $percentDead, 'beds' => $beds->value ]; } @@ -517,15 +617,15 @@ class ReportService /** * Получить пациентов из снапшотов */ - private function getPatientsFromSnapshots( - User $user, + public function getPatientsFromSnapshots( + Department $department, string $status, DateRange $dateRange, int $branchId, bool $onlyIds = false ) { $reports = $this->getReportsForDateRange( - $user->rf_department_id, + $department->department_id, $dateRange ); @@ -543,7 +643,7 @@ class ReportService $patientType = $patientTypeMap[$status] ?? null; if ($patientType === 'observation') { - return $this->patientQueryService->getObservationPatients($user->rf_department_id, $onlyIds); //$this->getObservationPatientsFromSnapshots($user->rf_department_id, $reportIds, $onlyIds); + return $this->patientQueryService->getObservationPatients($department->department_id, $onlyIds); //$this->getObservationPatientsFromSnapshots($user->rf_department_id, $reportIds, $onlyIds); } return $this->snapshotService->getPatientsFromSnapshots($patientType, $reportIds, $branchId, $onlyIds); @@ -553,6 +653,7 @@ class ReportService * Получить пациентов из реплики БД */ private function getPatientsFromReplica( + Department $department, User $user, string $status, DateRange $dateRange, @@ -575,7 +676,7 @@ class ReportService $onlyIds, $includeCurrent ), - 'observation' => $this->patientQueryService->getObservationPatients($user->rf_department_id, $onlyIds), + 'observation' => $this->patientQueryService->getObservationPatients($department->department_id, $onlyIds), 'outcome' => $this->patientQueryService->getOutcomePatients( $branchId, $dateRange, @@ -623,10 +724,10 @@ class ReportService /** * Получить количество пациентов из снапшотов */ - private function getPatientsCountFromSnapshots(User $user, string $status, DateRange $dateRange): int + private function getPatientsCountFromSnapshots(Department $department, string $status, DateRange $dateRange): int { $reports = $this->getReportsForDateRange( - $user->rf_department_id, + $department->department_id, $dateRange ); @@ -669,7 +770,13 @@ class ReportService /** * Получить количество пациентов из реплики БД */ - private function getPatientsCountFromReplica(User $user, string $status, DateRange $dateRange, int $branchId): int + private function getPatientsCountFromReplica( + Department $department, + User $user, + string $status, + DateRange $dateRange, + int $branchId + ): int { $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); @@ -680,7 +787,7 @@ class ReportService $branchId, $dateRange, ), - 'observation' => ObservationPatient::where('rf_department_id', $user->rf_department_id)->count(), + 'observation' => ObservationPatient::where('rf_department_id', $department->department_id)->count(), 'outcome' => $this->patientQueryService->getOutcomePatients( $branchId, $dateRange, @@ -708,11 +815,11 @@ class ReportService /** * Получить нежелательные события за дату */ - private function getUnwantedEvents(User $user, DateRange $dateRange) + public function getUnwantedEvents(Department $department, DateRange $dateRange) { - return UnwantedEvent::whereHas('report', function ($query) use ($user, $dateRange) { - $query->where('rf_department_id', $user->rf_department_id) - ->whereDate('created_at', $dateRange->endSql()); + return UnwantedEvent::whereHas('report', function ($query) use ($department, $dateRange) { + $query->where('rf_department_id', $department->department_id) + ->whereBetween('sent_at', [$dateRange->startSql(), $dateRange->endSql()]); }) ->get() ->map(function ($item) { @@ -733,8 +840,12 @@ class ReportService return $dateRange->isEndDateToday() && !$reportToday; } - // Для заведующего/админа: если есть отчет и он заполнен текущим пользователем - if ($reportToday && $reportToday->rf_lpudoctor_id === intval($fillableUserId)) { + // Для заведующего/админа: если есть отчет & он заполнен текущим пользователем & диапазон дат = 1 дню + if ( + $reportToday && + $reportToday->rf_lpudoctor_id === intval($fillableUserId) && + $dateRange->isOneDay + ) { return true; } @@ -761,7 +872,7 @@ class ReportService /** * Получить отчеты за диапазон дат */ - private function getReportsForDateRange(int $departmentId, DateRange $dateRange) + public function getReportsForDateRange(int $departmentId, DateRange $dateRange) { if ($dateRange->isOneDay) { return Report::where('rf_department_id', $departmentId) diff --git a/app/Services/StatisticsService.php b/app/Services/StatisticsService.php index 6f45131..0434883 100644 --- a/app/Services/StatisticsService.php +++ b/app/Services/StatisticsService.php @@ -12,6 +12,16 @@ use Carbon\Carbon; class StatisticsService { + /** + * Метрики, которые нужно суммировать (целые числа) + */ + protected array $summableMetrics = [4, 12, 11, 10, 13, 7, 9, 8, 17, 14, 16]; + + /** + * Метрики, которые нужно усреднять (числа с плавающей точкой) + */ + protected array $averageMetrics = [18]; + protected array $metricMapping = [ 4 => 'plan', // Плановые поступления 12 => 'emergency', // Экстренные поступления @@ -21,13 +31,24 @@ class StatisticsService 7 => 'outcome', // Выбыло 9 => 'deceased', // Умерло 8 => 'current', // Состоит + 17 => 'count_staff', // Кол-во мед. персонала + 14 => 'count_observable', // Кол-во пациентов на контроле + 16 => 'count_unwanted', // Кол-во нежелательных событий + 18 => 'average_bed_days' // Средний койко-день ]; + public function __construct( + protected BedDayService $bedDayService + ) { } + /** * Получить статистические данные с оптимизацией */ public function getStatisticsData(User $user, string $startDate, string $endDate, bool $isRangeOneDay): array { + // Очищаем кэш памяти перед началом + $this->bedDayService->clearMemoryCache(); + // Определяем порог для использования оптимизированного метода $daysDiff = Carbon::parse($startDate)->diffInDays(Carbon::parse($endDate)); @@ -45,6 +66,63 @@ class StatisticsService return $this->getDetailedStatistics($user, $startDate, $endDate, $isRangeOneDay); } + /** + * Получить средние койко-дни за период из сохраненных метрик отчетов + */ + protected function getAverageBedDaysFromReports(array $departmentIds, string $startDate, string $endDate): array + { + if (empty($departmentIds)) { + return []; + } + + try { + $results = DB::table('reports as r') + ->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id') + ->whereIn('r.rf_department_id', $departmentIds) + ->where('mr.rf_metrika_item_id', 18) + ->whereBetween('r.created_at', [$startDate, $endDate]) + ->select( + 'r.rf_department_id', + DB::raw('AVG(CAST(mr.value AS DECIMAL)) as avg_value') + ) + ->groupBy('r.rf_department_id') + ->get() + ->keyBy('rf_department_id'); + + $averages = []; + foreach ($departmentIds as $departmentId) { + if (isset($results[$departmentId]) && $results[$departmentId]->avg_value !== null) { + $averages[$departmentId] = round((float)$results[$departmentId]->avg_value, 1); + } else { + $averages[$departmentId] = 0; + } + } + + return $averages; + } catch (\Exception $e) { + \Log::error("Error in getAverageBedDaysFromReports: " . $e->getMessage()); + return array_fill_keys($departmentIds, 0); + } + } + + /** + * Получить общий средний койко-день + */ + protected function getOverallAverageBedDays(array $averages): float + { + $total = 0; + $count = 0; + + foreach ($averages as $avg) { + if ($avg > 0) { + $total += $avg; + $count++; + } + } + + return $count > 0 ? round($total / $count, 1) : 0; + } + /** * Агрегированный метод для очень больших диапазонов (больше 30 дней) */ @@ -60,7 +138,7 @@ class StatisticsService // Загружаем все отделения $departments = Department::select('department_id', 'rf_department_type', 'name_short') ->with(['departmentType']) - ->orderBy('name_short') + ->orderBy('rf_department_type') ->get() ->keyBy('department_id'); @@ -80,12 +158,24 @@ class StatisticsService $isRangeOneDay ? $dateReport : $dateReport[1] ); + // Получаем средние койко-дни из метрик отчетов + $averageBedDays = $this->getAverageBedDaysFromReports( + $departments->pluck('department_id')->toArray(), + $startDate, + $endDate + ); + + // Общий средний койко-день + $overallAverageBedDays = $this->getOverallAverageBedDays($averageBedDays); + return $this->processAggregatedData( $departments, $defaultMetrics, $aggregatedData, $lastReportsData, - $isRangeOneDay + $isRangeOneDay, + $averageBedDays, + $overallAverageBedDays ); } @@ -94,25 +184,52 @@ class StatisticsService */ private function getAggregatedReportData(array $departmentIds, $dateReport, bool $isRangeOneDay): Collection { - $query = DB::table('reports as r') + // Для суммируемых метрик - SUM + $summableQuery = DB::table('reports as r') ->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id') ->select( 'r.rf_department_id', 'mr.rf_metrika_item_id', - DB::raw('SUM(CAST(mr.value AS INTEGER)) as total') + DB::raw('SUM(CAST(mr.value AS DECIMAL)) as total') ) - ->whereIn('r.rf_department_id', $departmentIds); + ->whereIn('r.rf_department_id', $departmentIds) + ->whereIn('mr.rf_metrika_item_id', $this->summableMetrics); if ($isRangeOneDay) { - $query->whereDate('r.created_at', $dateReport); + $summableQuery->whereDate('r.created_at', $dateReport); } else { - $query->whereBetween('r.created_at', $dateReport); + $summableQuery->whereBetween('r.created_at', $dateReport); } - return $query->whereIn('mr.rf_metrika_item_id', array_keys($this->metricMapping)) + $summableResults = $summableQuery ->groupBy('r.rf_department_id', 'mr.rf_metrika_item_id') - ->get() - ->groupBy('rf_department_id'); + ->get(); + + // Для усредняемых метрик - AVG (например, средний койко-день) + $averageQuery = DB::table('reports as r') + ->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id') + ->select( + 'r.rf_department_id', + 'mr.rf_metrika_item_id', + DB::raw('AVG(CAST(mr.value AS DECIMAL)) as total') + ) + ->whereIn('r.rf_department_id', $departmentIds) + ->whereIn('mr.rf_metrika_item_id', $this->averageMetrics); + + if ($isRangeOneDay) { + $averageQuery->whereDate('r.created_at', $dateReport); + } else { + $averageQuery->whereBetween('r.created_at', $dateReport); + } + + $averageResults = $averageQuery + ->groupBy('r.rf_department_id', 'mr.rf_metrika_item_id') + ->get(); + + // Объединяем результаты + $allResults = $summableResults->concat($averageResults); + + return $allResults->groupBy('rf_department_id'); } /** @@ -145,7 +262,9 @@ class StatisticsService Collection $defaultMetrics, Collection $aggregatedData, Collection $lastReportsData, - bool $isRangeOneDay + bool $isRangeOneDay, + array $averageBedDays, + float $overallAverageBedDays ): array { $groupedData = []; $totalsByType = []; @@ -182,14 +301,21 @@ class StatisticsService $allCount = $counters['plan'] + $counters['emergency']; $percentLoadedBeds = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0; + // Получаем средний койко-день для отделения + $departmentAverageBedDays = $averageBedDays[$departmentId] ?? 0; + // Формируем данные $departmentData = $this->createDepartmentData( $department->name_short, + $departmentId, $bedsCount, $allCount, $counters, $percentLoadedBeds, - $departmentType + $departmentType, + null, + $departmentAverageBedDays, + $overallAverageBedDays ); $groupedData[$departmentType][] = $departmentData; @@ -214,7 +340,7 @@ class StatisticsService // Загружаем все отделения $departments = Department::select('department_id', 'rf_department_type', 'name_short') ->with(['departmentType']) - ->orderBy('name_short') + ->orderBy('rf_department_type') ->get() ->keyBy('department_id'); @@ -228,13 +354,25 @@ class StatisticsService $reportIds = $reports->flatMap(fn($items) => $items->pluck('report_id'))->toArray(); $reportMetrics = $this->getReportMetricsBatch($reportIds); + // Получаем средние койко-дни из метрик отчетов + $averageBedDays = $this->getAverageBedDaysFromReports( + $departments->pluck('department_id')->toArray(), + $startDate, + $endDate + ); + + // Общий средний койко-день + $overallAverageBedDays = $this->getOverallAverageBedDays($averageBedDays); + return $this->processOptimizedData( $departments, $defaultMetrics, $reports, $reportMetrics, $dateReport, - $isRangeOneDay + $isRangeOneDay, + $averageBedDays, + $overallAverageBedDays ); } @@ -279,12 +417,26 @@ class StatisticsService return collect(); } - return DB::table('metrika_results') + // Получаем все метрики, но для 18 не преобразуем в integer + $results = DB::table('metrika_results') ->whereIn('rf_report_id', $reportIds) ->whereIn('rf_metrika_item_id', array_keys($this->metricMapping)) ->select('rf_report_id', 'rf_metrika_item_id', 'value') ->get() ->groupBy('rf_report_id'); + + // Преобразуем значения в зависимости от типа метрики + foreach ($results as $reportId => $metrics) { + foreach ($metrics as $metric) { + if (in_array($metric->rf_metrika_item_id, $this->summableMetrics)) { + $metric->value = (int)$metric->value; + } else { + $metric->value = (float)$metric->value; + } + } + } + + return $results; } /** @@ -296,7 +448,9 @@ class StatisticsService Collection $reports, Collection $reportMetrics, $dateReport, - bool $isRangeOneDay + bool $isRangeOneDay, + array $averageBedDays, + float $overallAverageBedDays ): array { $groupedData = []; $totalsByType = []; @@ -362,14 +516,21 @@ class StatisticsService $allCount = $counters['plan'] + $counters['emergency']; $percentLoadedBeds = $bedsCount > 0 ? round($counters['current'] * 100 / $bedsCount) : 0; + // Получаем средний койко-день для отделения + $departmentAverageBedDays = $averageBedDays[$departmentId] ?? 0; + // Формируем данные отделения $departmentData = $this->createDepartmentData( $department->name_short, + $departmentId, $bedsCount, $allCount, $counters, $percentLoadedBeds, - $departmentType + $departmentType, + null, + $departmentAverageBedDays, + $overallAverageBedDays ); $groupedData[$departmentType][] = $departmentData; @@ -403,9 +564,19 @@ class StatisticsService } $query->with('metrikaResults'); }]) - ->orderBy('name_short') + ->orderBy('rf_department_type') ->get(); + // Получаем средние койко-дни из метрик отчетов + $averageBedDays = $this->getAverageBedDaysFromReports( + $departments->pluck('department_id')->toArray(), + $startDate, + $endDate + ); + + // Общий средний койко-день + $overallAverageBedDays = $this->getOverallAverageBedDays($averageBedDays); + foreach ($departments as $department) { $departmentType = $department->departmentType->name_full; @@ -458,14 +629,24 @@ class StatisticsService $allCount = $counters['plan'] + $counters['emergency']; $percentLoadedBeds = $bedsCount > 0 ? round($counters['current'] * 100 / $bedsCount) : 0; + if ($isRangeOneDay) + $isReportToday = !empty($lastReport); + else $isReportToday = null; + + $departmentAverageBedDays = $averageBedDays[$department->department_id] ?? 0; + // Формируем данные отделения $departmentData = $this->createDepartmentData( $department->name_short, + $department->department_id, $bedsCount, $allCount, $counters, $percentLoadedBeds, - $departmentType + $departmentType, + $isReportToday, + $departmentAverageBedDays, + $overallAverageBedDays ); $groupedData[$departmentType][] = $departmentData; @@ -480,14 +661,19 @@ class StatisticsService */ private function createDepartmentData( string $name, + int $departmentId, int $beds, int $allCount, array $counters, int $percentLoadedBeds, - string $type + string $type, + ?bool $isReportToday = null, + float $departmentAverageBedDays = 0, + float $overallAverageBedDays = 0 ): array { return [ 'department' => $name, + 'department_id' => $departmentId, 'beds' => $beds, 'recipients' => [ 'all' => $allCount, @@ -503,8 +689,14 @@ class StatisticsService 'emergency' => $counters['emergency_surgical'] ], 'deceased' => $counters['deceased'], + 'countStaff' => $counters['count_staff'], + 'countObservable' => $counters['count_observable'], + 'countUnwanted' => $counters['count_unwanted'], + 'averageBedDays' => $departmentAverageBedDays, + 'overallAverageBedDays' => $overallAverageBedDays, 'type' => $type, - 'isDepartment' => true + 'isDepartment' => true, + 'isReportToday' => $isReportToday, ]; } @@ -527,6 +719,11 @@ class StatisticsService 'deceased_sum' => 0, 'percentLoadedBeds_total' => 0, 'percentLoadedBeds_count' => 0, + 'staff_sum' => 0, + 'observable_sum' => 0, + 'unwanted_sum' => 0, + 'average_bed_days_total' => 0, + 'average_bed_days_count' => 0, ]; } @@ -548,6 +745,11 @@ class StatisticsService $totals['deceased_sum'] += $departmentData['deceased']; $totals['percentLoadedBeds_total'] += $departmentData['percentLoadedBeds']; $totals['percentLoadedBeds_count']++; + $totals['staff_sum'] += $departmentData['countStaff']; + $totals['observable_sum'] += $departmentData['countObservable']; + $totals['unwanted_sum'] += $departmentData['countUnwanted']; + $totals['average_bed_days_total'] += $departmentData['averageBedDays']; + $totals['average_bed_days_count']++; } /** @@ -563,7 +765,7 @@ class StatisticsService $finalData[] = [ 'isGroupHeader' => true, 'groupName' => $type, - 'colspan' => 12, + 'colspan' => 14, 'type' => $type ]; @@ -592,7 +794,11 @@ class StatisticsService // ? round($grandTotals['percentLoadedBeds_total'] / $grandTotals['percentLoadedBeds_count']) // : 0; // -// $finalData[] = $this->createTotalRow('all', $grandTotals, $avgPercent, true); +// $grandAvgBedDays = $grandTotals['averageBedDays_count'] > 0 +// ? round($grandTotals['averageBedDays_total'] / $grandTotals['averageBedDays_count'], 1) +// : $overallAverageBedDays; +// +// $finalData[] = $this->createTotalRow('all', $grandTotals, $avgPercent, true, $grandAvgBedDays); // } return [ @@ -605,7 +811,7 @@ class StatisticsService /** * Создать строку итогов */ - private function createTotalRow(string $type, array $total, int $avgPercent, bool $isGrandTotal): array + private function createTotalRow(string $type, array $total, int $avgPercent, bool $isGrandTotal, float $avgBedDays = 0): array { $row = [ 'isTotalRow' => !$isGrandTotal, @@ -628,8 +834,12 @@ class StatisticsService 'emergency' => $total['emergency_surgical_sum'] ], 'deceased' => $total['deceased_sum'], + 'averageBedDays' => '—', 'type' => $type, 'departments_count' => $total['departments_count'], + 'countStaff' => $total['staff_sum'], + 'countObservable' => $total['observable_sum'], + 'countUnwanted' => $total['unwanted_sum'], 'isBold' => true ]; diff --git a/database/seeders/TestDepartmentDataSeeder.php b/database/seeders/TestDepartmentDataSeeder.php index ad5a574..74eecd5 100644 --- a/database/seeders/TestDepartmentDataSeeder.php +++ b/database/seeders/TestDepartmentDataSeeder.php @@ -30,6 +30,9 @@ class TestDepartmentDataSeeder extends Seeder 'name_full' => 'Перинатальный центр' ]); + /** + * Хирургические отделения + */ Department::create([ 'name_full' => 'Гинекологическое отделение', 'name_short' => 'Гинекологическое', @@ -97,6 +100,73 @@ class TestDepartmentDataSeeder extends Seeder 'rf_mis_department_id' => 1073 ]); + /** + * Терапевтические отделения + */ + Department::create([ + 'name_full' => 'Гастроэнтерологическое отделение', + 'name_short' => 'Гастроэнтерологическое', + 'rf_department_type' => 2, + 'rf_mis_department_id' => 1052 + ]); + Department::create([ + 'name_full' => 'Гематологическое отделение', + 'name_short' => 'Гематологическое', + 'rf_department_type' => 2, + 'rf_mis_department_id' => 1053 + ]); + Department::create([ + 'name_full' => 'Кардиологическое отделение', + 'name_short' => 'Кардиологическое', + 'rf_department_type' => 2, + 'rf_mis_department_id' => 1056 + ]); + Department::create([ + 'name_full' => 'Неврологическое отделение', + 'name_short' => 'Неврологическое', + 'rf_department_type' => 2, + 'rf_mis_department_id' => 1057 + ]); + Department::create([ + 'name_full' => 'Нефрологическое отделение', + 'name_short' => 'Нефрологическое', + 'rf_department_type' => 2, + 'rf_mis_department_id' => 1058 + ]); + Department::create([ + 'name_full' => 'Пульмонологическое отделение', + 'name_short' => 'Пульмонологическое', + 'rf_department_type' => 2, + 'rf_mis_department_id' => 1066 + ]); + Department::create([ + 'name_full' => 'Ревматологическое отделение', + 'name_short' => 'Ревматологическое', + 'rf_department_type' => 2, + 'rf_mis_department_id' => 1068 + ]); + Department::create([ + 'name_full' => 'РСЦ кардиологическое отделение', + 'name_short' => 'РСЦ кардиологическое', + 'rf_department_type' => 2, + 'rf_mis_department_id' => 1047 + ]); + Department::create([ + 'name_full' => 'РСЦ неврологическое отделение', + 'name_short' => 'РСЦ неврологическое', + 'rf_department_type' => 2, + 'rf_mis_department_id' => 2162 + ]); + Department::create([ + 'name_full' => 'Отделение медицинской реабилитации', + 'name_short' => 'Медицинской реабилитации', + 'rf_department_type' => 2, + 'rf_mis_department_id' => 2042 + ]); + + /** + * Койки по умолчанию для хир. отделений + */ DepartmentMetrikaDefault::create([ 'rf_department_id' => 1, 'rf_metrika_item_id' => 1, @@ -152,5 +222,54 @@ class TestDepartmentDataSeeder extends Seeder 'rf_metrika_item_id' => 1, 'value' => '25' ]); + + /** + * Койки по умолчанию для терапевт. отделений + */ + DepartmentMetrikaDefault::create([ + 'rf_department_id' => 12, + 'rf_metrika_item_id' => 1, + 'value' => '22' + ]); + DepartmentMetrikaDefault::create([ + 'rf_department_id' => 13, + 'rf_metrika_item_id' => 1, + 'value' => '27' + ]); + DepartmentMetrikaDefault::create([ + 'rf_department_id' => 14, + 'rf_metrika_item_id' => 1, + 'value' => '40' + ]); + DepartmentMetrikaDefault::create([ + 'rf_department_id' => 15, + 'rf_metrika_item_id' => 1, + 'value' => '30' + ]); + DepartmentMetrikaDefault::create([ + 'rf_department_id' => 16, + 'rf_metrika_item_id' => 1, + 'value' => '25' + ]); + DepartmentMetrikaDefault::create([ + 'rf_department_id' => 17, + 'rf_metrika_item_id' => 1, + 'value' => '40' + ]); + DepartmentMetrikaDefault::create([ + 'rf_department_id' => 18, + 'rf_metrika_item_id' => 1, + 'value' => '55' + ]); + DepartmentMetrikaDefault::create([ + 'rf_department_id' => 19, + 'rf_metrika_item_id' => 1, + 'value' => '55' + ]); + DepartmentMetrikaDefault::create([ + 'rf_department_id' => 20, + 'rf_metrika_item_id' => 1, + 'value' => '30' + ]); } } diff --git a/docker-compose.yml b/docker-compose.yml index a18e592..a12bae8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ services: #PHP Service app: - image: registry.brusoff.su/aokb-onboard:1.4 + image: registry.brusoff.su/aokb-onboard:1.5-prod build: . container_name: aokb_onboard_app restart: unless-stopped diff --git a/resources/js/Components/AppContainer.vue b/resources/js/Components/AppContainer.vue new file mode 100644 index 0000000..49aac39 --- /dev/null +++ b/resources/js/Components/AppContainer.vue @@ -0,0 +1,13 @@ + + + + + diff --git a/resources/js/Components/AppPanel.vue b/resources/js/Components/AppPanel.vue new file mode 100644 index 0000000..4053335 --- /dev/null +++ b/resources/js/Components/AppPanel.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/resources/js/Layouts/Components/AppUserButton.vue b/resources/js/Layouts/Components/AppUserButton.vue index 4675ceb..163c002 100644 --- a/resources/js/Layouts/Components/AppUserButton.vue +++ b/resources/js/Layouts/Components/AppUserButton.vue @@ -6,7 +6,7 @@ import {router, useForm} from "@inertiajs/vue3"; const authStore = useAuthStore() const userOptions = computed(() => { - return authStore.availableRoles.map(itm => { + return authStore.availableRoles?.map(itm => { return { label: itm.name, value: itm.role_id @@ -15,7 +15,7 @@ const userOptions = computed(() => { }) const formRole = useForm({ - role_id: authStore.user.role.role_id + role_id: authStore.user.role?.role_id }) const onChangeRole = (roleId) => { formRole.post('/user/role/change', { diff --git a/resources/js/Pages/Admin/Index.vue b/resources/js/Pages/Admin/Index.vue new file mode 100644 index 0000000..295f8fc --- /dev/null +++ b/resources/js/Pages/Admin/Index.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/resources/js/Pages/Admin/Users/Create.vue b/resources/js/Pages/Admin/Users/Create.vue new file mode 100644 index 0000000..df4fd9d --- /dev/null +++ b/resources/js/Pages/Admin/Users/Create.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/resources/js/Pages/Admin/Users/Index.vue b/resources/js/Pages/Admin/Users/Index.vue new file mode 100644 index 0000000..ae8863e --- /dev/null +++ b/resources/js/Pages/Admin/Users/Index.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/resources/js/Pages/Admin/Users/User.vue b/resources/js/Pages/Admin/Users/User.vue new file mode 100644 index 0000000..3b8ae05 --- /dev/null +++ b/resources/js/Pages/Admin/Users/User.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/resources/js/Pages/Index.vue b/resources/js/Pages/Index.vue index 40c5fb1..cee30f9 100644 --- a/resources/js/Pages/Index.vue +++ b/resources/js/Pages/Index.vue @@ -7,7 +7,7 @@ import {computed, ref} from "vue"; import {format} from "date-fns"; import {ru} from "date-fns/locale"; import {useNow} from "@vueuse/core"; -import {TbArticle, TbChartTreemap, TbDoorExit} from "vue-icons-plus/tb"; +import {TbArticle, TbChartTreemap, TbDoorExit, TbUserCog} from "vue-icons-plus/tb"; import {useReportStore} from "../Stores/report.js"; import SelectUserModal from "./Report/Components/SelectUserModal.vue"; import {Link} from "@inertiajs/vue3"; @@ -58,6 +58,12 @@ const reportButtonType = computed(() => authStore.isDoctor ? 'button' : Link) :href="`/statistic`" :icon="TbChartTreemap" /> + { - reportStore.sendReportForm({ - departmentId: authStore.userDepartment.department_id + reportStore.reportFormRef?.validate((errors) => { + if (!errors) reportStore.sendReportForm() + else window.$message.error('Ошибка отправки отчета') }) + // reportStore.sendReportForm() } diff --git a/resources/js/Pages/Report/Components/ReportFormInput.vue b/resources/js/Pages/Report/Components/ReportFormInput.vue index 886ffe2..6062cfc 100644 --- a/resources/js/Pages/Report/Components/ReportFormInput.vue +++ b/resources/js/Pages/Report/Components/ReportFormInput.vue @@ -2,16 +2,42 @@ import {NCard, NSkeleton, NSpace, NFlex, NFormItem, NForm, NInputNumber, NStatistic} from "naive-ui"; import {useReportStore} from "../../../Stores/report.js"; import {useAuthStore} from "../../../Stores/auth.js"; +import {computed, onMounted, ref, watch} from "vue"; const reportStore = useReportStore() const authStore = useAuthStore() +const formRef = ref() +const rules = computed(() => { + const rawRules = {} + + for (const metrika of reportStore.reportInfo.metrikaItems) { + const rule = {} + rule.required = metrika.is_required + rule.trigger = ['input', 'blur'] + if (metrika.data_type === 'integer') { + rule.validator = (rule, value) => { + if (!/^\d*$/.test(value)) { + return new Error() + } + return true + } + } + rawRules[`metrika_item_${metrika.metrika_item_id}`] = rule + } + + return rawRules +}) + +watch(() => formRef.value, (nv) => { + reportStore.reportFormRef = nv +})