From 9ee33bc51755860fd3525ef72ec0cdca171147c7 Mon Sep 17 00:00:00 2001 From: brusnitsyn Date: Tue, 3 Feb 2026 17:03:37 +0900 Subject: [PATCH] =?UTF-8?q?*=20=D0=B1=D0=BB=D0=BE=D0=BA=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B0=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BE=D1=82=D1=87=D0=B5=D1=82=D0=B0=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B2=D1=80=D0=B0=D1=87=D0=B0=20*=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B2=D0=BE=D0=B4=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85?= =?UTF-8?q?=20=D0=B8=D0=B7=20=D0=BE=D1=82=D1=87=D0=B5=D1=82=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=80=D0=BE=D0=BB=D0=B5=D0=B9=20=D0=B0?= =?UTF-8?q?=D0=B4=D0=BC=20=D0=B8=20=D0=B7=D0=B0=D0=B2=20*=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20=D1=88=D0=B8=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D1=83=20=D1=81=D1=82=D0=BE=D0=B1=D1=86=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B2=D0=B2=D0=BE=D0=B4=D0=B0=20*=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=BA=D0=B0=D0=BB=D0=B5=D0=BD=D0=B4=D0=B0?= =?UTF-8?q?=D1=80=D1=8C=20=D0=BD=D0=B0=20=D1=81=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=86=D1=83=20=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=B8=D0=BA=D0=B8=20*=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B0=D0=BB=20=D0=BA=D0=B0=D0=BB=D0=B5=D0=BD=D0=B4=D0=B0?= =?UTF-8?q?=D1=80=D1=8C=20=D1=83=20=D0=B7=D0=B0=D0=B2=D0=B5=D0=B4=D1=83?= =?UTF-8?q?=D1=8E=D1=89=D0=B5=D0=B3=D0=BE=20=D0=BD=D0=B0=20=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B5=20=D0=BE=D1=82=D1=87=D0=B5?= =?UTF-8?q?=D1=82=D0=B0=20*=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=B8=20=D0=BF=D1=80=D0=B8=D0=B2=D1=8F=D0=B7=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=BA=D0=B8=20=D0=B2=20=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Api/ReportController.php | 246 +++++++++++++++--- app/Http/Controllers/Web/ReportController.php | 102 +++++++- .../Controllers/Web/StatisticController.php | 149 ++++++++--- app/Models/Department.php | 5 + app/Models/Report.php | 10 + app/Models/User.php | 5 + app/Services/DateRangeService.php | 33 +-- app/Services/MisPatientService.php | 100 +++++++ resources/js/Components/DatePickerQuery.vue | 117 +++++++++ resources/js/Components/ReportSelectDate.vue | 117 ++++++--- resources/js/Layouts/AppLayout.vue | 6 +- resources/js/Layouts/Components/AppHeader.vue | 14 +- resources/js/Pages/Index.vue | 2 +- .../js/Pages/Report/Components/ReportForm.vue | 5 +- .../Report/Components/ReportFormInput.vue | 5 + .../Pages/Report/Components/ReportHeader.vue | 2 +- .../Pages/Report/Components/ReportSection.vue | 2 +- resources/js/Pages/Statistic/Index.vue | 68 +++-- resources/js/Stores/report.js | 14 +- resources/js/Utils/dateFormatter.js | 46 ++++ 20 files changed, 889 insertions(+), 159 deletions(-) create mode 100644 app/Services/MisPatientService.php create mode 100644 resources/js/Components/DatePickerQuery.vue create mode 100644 resources/js/Utils/dateFormatter.js diff --git a/app/Http/Controllers/Api/ReportController.php b/app/Http/Controllers/Api/ReportController.php index 96ffadf..c720cc3 100644 --- a/app/Http/Controllers/Api/ReportController.php +++ b/app/Http/Controllers/Api/ReportController.php @@ -16,6 +16,8 @@ use App\Models\ObservationPatient; use App\Models\Report; use App\Models\UnwantedEvent; use App\Models\User; +use App\Services\DateRangeService; +use App\Services\MisPatientService; use Illuminate\Http\Request; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Auth; @@ -26,6 +28,15 @@ use Inertia\Inertia; class ReportController extends Controller { + protected MisPatientService $misPatientService; + protected DateRangeService $dateService; + + public function __construct(MisPatientService $misPatientService, DateRangeService $dateRangeService) + { + $this->misPatientService = $misPatientService; + $this->dateService = $dateRangeService; + } + public function index(Request $request) { $user = Auth::user(); @@ -35,7 +46,7 @@ class ReportController extends Controller $endDateCarbon = Carbon::now(); // Определяем даты в зависимости от роли - [$startDate, $endDate] = $this->getDateRangeForRole($user, $request->query('startAt'), $request->query('endAt')); + [$startDate, $endDate] = $this->dateService->getDateRangeForUser($user, $request->query('startAt'), $request->query('endAt')); if (Carbon::parse($startDate)->isValid()) { $startDateCarbon = Carbon::parse($startDate)->setTimeZone('Asia/Yakutsk'); } @@ -48,8 +59,11 @@ class ReportController extends Controller $reportIds = $reports->pluck('report_id')->toArray(); // Определяем, используем ли мы снапшоты - $useSnapshots = ($user->isAdmin() || $user->isHeadOfDepartment()) || Carbon::parse($endDate)->isToday() === false; - if ($useSnapshots) { + $reportToday = Report::whereDate('sent_at', $endDate) + ->whereDate('created_at', $endDate) + ->first(); + $useSnapshots = ($user->isAdmin() || $user->isHeadOfDepartment()) || (Carbon::parse($endDate)->isToday() === false || $reportToday); + if ($useSnapshots && ($user->isHeadOfDepartment() || $user->isAdmin())) { $report = Report::whereDate('sent_at', $endDate) ->where('rf_department_id', $department->department_id) ->first(); @@ -58,12 +72,6 @@ class ReportController extends Controller $fillableUserId = $request->query('userId', $user->rf_lpudoctor_id); } - if (Carbon::parse($startDate)->diffInDays(Carbon::parse($endDate)) > 1.0) { - $lpuDoctor = null; - } else { - $lpuDoctor = MisLpuDoctor::where('LPUDoctorID', $fillableUserId)->first(); - } - $beds = (int)$department->metrikaDefault()->where('rf_metrika_item_id', 1)->first()->value; $occupiedBeds = optional(Report::where('rf_department_id', $user->rf_department_id) ->join('metrika_results', 'reports.report_id', '=', 'metrika_results.rf_report_id') @@ -82,7 +90,7 @@ class ReportController extends Controller $unwantedEvents = UnwantedEvent::whereHas('report', function ($query) use ($user, $startDate, $endDate) { $query->where('rf_department_id', $user->rf_department_id) - ->whereBetween('created_at', [$startDate, $endDate]); + ->whereDate('created_at', $endDate); }) ->get()->map(function ($item) { return [ @@ -95,18 +103,19 @@ class ReportController extends Controller $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); // Получаем статистику в зависимости от источника данных - if ($useSnapshots) { + if ($useSnapshots || $reportToday) { // Используем снапшоты для статистики - $plan = $this->getCountFromSnapshots('plan', $reportIds); - $emergency = $this->getCountFromSnapshots('emergency', $reportIds); - $outcomeCount = $this->getCountFromSnapshots('outcome', $reportIds); - $currentCount = $this->getCurrentPatientsFromSnapshots($reportIds, $branchId); - $deadCount = $this->getCountFromSnapshots('deceased', $reportIds); +// $plan = $this->getCountFromSnapshots('plan', $reportIds); +// $emergency = $this->getCountFromSnapshots('emergency', $reportIds); + $recipientCount = $this->getPatientsCountFromSnapshot('recipient', $reportIds); + $outcomeCount = $this->getPatientsCountFromSnapshot('outcome', $reportIds); + $currentCount = $this->getPatientsCountFromSnapshot('current', $reportIds); + $deadCount = $this->getPatientsCountFromSnapshot('deceased', $reportIds); // Для операций все равно используем реплику с фильтрацией по датам $surgicalCount = [ - $this->getSurgicalPatients('plan', $isHeadOrAdmin, $branchId, $startDate, $endDate, true), - $this->getSurgicalPatients('emergency', $isHeadOrAdmin, $branchId, $startDate, $endDate, true) + $this->getSurgicalPatientsFromSnapshot('plan', $reportIds), + $this->getSurgicalPatientsFromSnapshot('emergency', $reportIds) ]; $recipientIds = $this->getRecipientIdsFromSnapshots($reportIds); @@ -157,12 +166,40 @@ class ReportController extends Controller ); } + $isActiveSendButton = Carbon::createFromFormat('Y-m-d H:i:s', $endDate)->isToday() && + (!$user->isHeadOfDepartment() && !$user->isAdmin()) && $reportToday == null; + + $reportDoctor = $reportToday?->lpuDoctor; + $message = null; + if ($reportToday) { + if ($reportDoctor && $reportDoctor->LPUDoctorID === intval($fillableUserId)) { + $isActiveSendButton = true; + } else { + $message = "Отчет уже создан пользователем: $reportDoctor->FAM_V $reportDoctor->IM_V $reportDoctor->OT_V"; + } + + $lpuDoctor = $reportDoctor; + } else { + if (Carbon::parse($startDate)->diffInDays(Carbon::parse($endDate)) > 1.0) { + $lpuDoctor = null; + } else { + $lpuDoctor = MisLpuDoctor::where('LPUDoctorID', $fillableUserId)->first(); + } + } + + $isRangeOneDay = $this->dateService->isRangeOneDay($startDate, $endDate); + + $date = $isHeadOrAdmin ? [ + $this->dateService->parseDate($isRangeOneDay ? $endDate : $startDate)->getTimestampMs(), + $this->dateService->parseDate($endDate)->getTimestampMs() + ] : $this->dateService->parseDate($endDate)->getTimestampMs(); + return response()->json([ 'department' => [ 'beds' => $beds, 'percentLoadedBeds' => $percentLoadedBeds, - 'recipientCount' => $plan + $emergency, //$recipientCount, + 'recipientCount' => $useSnapshots ? $recipientCount : $plan + $emergency, //$recipientCount, 'extractCount' => $outcomeCount, //$extractedCount, 'currentCount' => $currentCount, 'deadCount' => $deadCount, @@ -174,8 +211,13 @@ class ReportController extends Controller 'endAt' => $endDateCarbon->getTimestampMs() ], 'report' => [ + 'report_id' => $reportToday?->report_id, 'unwantedEvents' => $unwantedEvents, - 'isActiveSendButton' => Carbon::createFromFormat('Y-m-d H:i:s', $endDate)->isToday() && (!$user->isHeadOfDepartment() && !$user->isAdmin()), + 'isActiveSendButton' => $isActiveSendButton, + 'message' => $message, + 'isOneDay' => $isRangeOneDay, + 'isHeadOrAdmin' => $isHeadOrAdmin, + 'dates' => $date ], 'metrikaItems' => $metrikaItems, 'userId' => $fillableUserId, @@ -183,6 +225,68 @@ class ReportController extends Controller ]); } + private function getSurgicalPatientsFromSnapshot(string $type, array $reportIds) + { + $count = 0; + switch ($type) { + case 'emergency': + $count = $this->getMetrikaResult(10, $reportIds); + break; + case 'plan': + $count = $this->getMetrikaResult(11, $reportIds); + break; + case 'recipient': + $count = $this->getMetrikaResult(3, $reportIds); + break; + } + + return $count; + } + + private function getPatientsCountFromSnapshot(string $type, array $reportIds) + { + $count = 0; + switch ($type) { + case 'emergency': + $count = $this->getMetrikaResult(10, $reportIds); + break; + case 'plan': + $count = $this->getMetrikaResult(11, $reportIds); + break; + case 'recipient': + $count = $this->getMetrikaResult(3, $reportIds); + break; + case 'outcome': + $count = $this->getMetrikaResult(7, $reportIds); + break; + case 'deceased': + $count = $this->getMetrikaResult(9, $reportIds); + break; + case 'current': + $count = $this->getMetrikaResult(8, $reportIds); + break; + } + + return $count; + } + + private function getMetrikaResult(int $metrikaItemId, array $reportIds) + { + $reports = Report::whereIn('report_id', $reportIds) + ->with('metrikaResults') + ->get(); + + $count = 0; + foreach ($reports as $report) { + foreach ($report->metrikaResults as $metrikaResult) { + if ($metrikaResult->rf_metrika_item_id === $metrikaItemId) + $count += intval($metrikaResult->value) ?? 0; + } + } + + return $count; + } + /** * Получить количество пациентов из снапшотов */ @@ -213,6 +317,10 @@ class ReportController extends Controller ->where('patient_type', 'transferred') ->distinct('rf_medicalhistory_id') ->count('rf_medicalhistory_id'), + 'recipient' => MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->where('patient_type', 'recipient') + ->distinct('rf_medicalhistory_id') + ->count('rf_medicalhistory_id'), default => 0 }; } @@ -250,19 +358,13 @@ class ReportController extends Controller */ private function getRecipientIdsFromSnapshots(array $reportIds) { - $planIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) - ->where('patient_type', 'plan') + $recipientIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->where('patient_type', 'recipient') ->pluck('rf_medicalhistory_id') ->unique() ->toArray(); - $emergencyIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) - ->where('patient_type', 'emergency') - ->pluck('rf_medicalhistory_id') - ->unique() - ->toArray(); - - return array_merge($planIds, $emergencyIds); + return $recipientIds; } // /** @@ -310,6 +412,7 @@ class ReportController extends Controller 'unwantedEvents' => 'nullable|array', 'dates' => 'required|array', 'userId' => 'required|integer', + 'reportId' => 'nullable' ]); $metrics = $data['metrics']; $observationPatients = $data['observationPatients']; @@ -330,13 +433,28 @@ class ReportController extends Controller \DB::beginTransaction(); - $report = Report::create([ - 'rf_department_id' => $data['departmentId'], - 'rf_user_id' => Auth::user()->id, - 'rf_lpudoctor_id' => $data['userId'], - 'created_at' => now(), - 'sent_at' => now() - ]); + if (isset($data['reportId']) && $data['reportId']) { + $report = Report::updateOrCreate( + [ + 'report_id' => $data['reportId'] + ], + [ + 'rf_department_id' => $data['departmentId'], + 'rf_user_id' => Auth::user()->id, + 'rf_lpudoctor_id' => $data['userId'], + 'created_at' => now(), + 'sent_at' => now() + ] + ); + } else { + $report = Report::create([ + 'rf_department_id' => $data['departmentId'], + 'rf_user_id' => Auth::user()->id, + 'rf_lpudoctor_id' => $data['userId'], + 'created_at' => now(), + 'sent_at' => now() + ]); + } if (count($unwantedEvents)) { foreach ($unwantedEvents as $unwantedEvent) { @@ -361,11 +479,25 @@ class ReportController extends Controller ]); } } + } else { + $unwantedEvents = $report->unwantedEvents; + foreach ($unwantedEvents as $unwantedEvent) { + $unwantedEvent->delete(); + } } foreach ($metriks as $metrika) { - $metrika->rf_report_id = $report->report_id; - $metrika->save(); + MetrikaResult::updateOrCreate( + [ + 'rf_report_id' => $report->report_id, + 'rf_metrika_item_id' => $metrika->rf_metrika_item_id + ], + [ + 'rf_report_id' => $report->report_id, + 'rf_metrika_item_id' => $metrika->rf_metrika_item_id, + 'value' => $metrika->value, + ] + ); } if (count($observationPatients)) { @@ -384,6 +516,10 @@ class ReportController extends Controller ] ); } + } else { + foreach ($report->observationPatients as $observationPatient) { + $observationPatient->delete(); + } } // Сохраняем снимок для каждого типа пациентов @@ -438,6 +574,26 @@ class ReportController extends Controller ]); } + // 5. Поступившие + $recipientIds = $this->getPlanOrEmergencyPatients( + null, + $isHeadOrAdmin, + $branchId, + $startDate, + $endDate, + false, + true, + true, + today: true + ); + foreach ($recipientIds as $id) { + MedicalHistorySnapshot::create([ + 'rf_report_id' => $report->report_id, + 'rf_medicalhistory_id' => $id, + 'patient_type' => 'recipient' + ]); + } + // 6. Находящиеся на лечении // $currentIds = $this->getCurrentPatients($branchId, false, true); // foreach ($currentIds as $id) { @@ -1029,10 +1185,16 @@ class ReportController extends Controller */ private function getReportsForDateRange($departmentId, $startDate, $endDate) { - return Report::where('rf_department_id', $departmentId) - ->whereBetween('created_at', [$startDate, $endDate]) - ->orderBy('created_at', 'ASC') - ->get(); + if (Carbon::parse($startDate)->diffInDays(Carbon::parse($endDate)) > 1.0) + return Report::where('rf_department_id', $departmentId) + ->whereBetween('created_at', [$startDate, $endDate]) + ->orderBy('created_at', 'ASC') + ->get(); + else + return Report::where('rf_department_id', $departmentId) + ->whereDate('created_at', $endDate) + ->orderBy('created_at', 'ASC') + ->get(); } /** diff --git a/app/Http/Controllers/Web/ReportController.php b/app/Http/Controllers/Web/ReportController.php index aef8976..437b71d 100644 --- a/app/Http/Controllers/Web/ReportController.php +++ b/app/Http/Controllers/Web/ReportController.php @@ -3,22 +3,118 @@ namespace App\Http\Controllers\Web; use App\Http\Controllers\Controller; +use App\Models\MisStationarBranch; +use App\Models\Report; +use App\Services\DateRangeService; +use App\Services\MisPatientService; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; use Inertia\Inertia; class ReportController extends Controller { + protected DateRangeService $dateService; + protected MisPatientService $misPatientService; + + public function __construct(MisPatientService $misPatientService, DateRangeService $dateRangeService) + { + $this->misPatientService = $misPatientService; + $this->dateService = $dateRangeService; + } + public function index(Request $request) { - $user = $request->user(); + $user = \Auth::user(); $department = $user->department; - $beds = $department->metrikaDefault()->where('rf_metrika_item_id', 1)->first()->value; + $queryStartDate = $request->query('startAt'); + $queryEndDate = $request->query('endAt'); + [$startDate, $endDate] = $this->dateService->getDateRangeForUser($user, $queryStartDate, $queryEndDate); + $isRangeOneDay = $this->dateService->isRangeOneDay($startDate, $endDate); + + // Если диапазон содержит сутки + if ($isRangeOneDay) { + // Устанавливаем дату отчета, как последний день из выборки + $dateReport = $endDate; + } else { + // Устанавливаем дату отчета, как выборку + $dateReport = [$startDate, $endDate]; + } + + if ($isRangeOneDay) { + // Статистика выводится с нарастающим числом + $reports = $department->reports() + ->whereDate('created_at', $dateReport) + ->get(); + } else { + $reports = $department->reports() + ->whereBetween('created_at', $dateReport) + ->get(); + } + + $isReports = $reports->count() > 0; + + $allCount = 0; $outcomeCount = 0; $currentCount = 0; $occupiedBeds = 0; $planCount = 0; + $emergencyCount = 0; $planSurgical = 0; $emergencySurgical = 0; $transferredCount = 0; + $deceasedCount = 0; + if ($isReports) { + foreach ($reports as $report) { + $allCount += $this->getMetrikaResultFromReport($report, 3, $isRangeOneDay); + $currentCount += $this->getMetrikaResultFromReport($report, 8, false); + $occupiedBeds += $this->getMetrikaResultFromReport($report, 8, $isRangeOneDay); + $planCount += $this->getMetrikaResultFromReport($report, 4, $isRangeOneDay); + $emergencyCount += $this->getMetrikaResultFromReport($report, 12, $isRangeOneDay); + $planSurgical += $this->getMetrikaResultFromReport($report, 11, $isRangeOneDay); + $emergencySurgical += $this->getMetrikaResultFromReport($report, 10, $isRangeOneDay); + $transferredCount += $this->getMetrikaResultFromReport($report, 13, $isRangeOneDay); + $outcomeCount += $this->getMetrikaResultFromReport($report, 7, $isRangeOneDay); + $deceasedCount += $this->getMetrikaResultFromReport($report, 9, $isRangeOneDay); + } + } else { + $misDepartmentId = $request->user()->department->rf_mis_department_id; + + $branchId = MisStationarBranch::where('rf_DepartmentID', $misDepartmentId) + ->value('StationarBranchID'); + + $planCount = $this->misPatientService->getInStationarPatients('plan', $branchId, $dateReport)->count(); + } + + $bedsCount = $department->metrikaDefault() + ->where('rf_metrika_item_id', 1)->value('value'); + + $percentLoadedBeds = $bedsCount > 0 ? round($occupiedBeds * 100 / $bedsCount) : 0; return Inertia::render('Report/Index', [ 'department' => [ - 'beds' => $beds + 'beds' => $bedsCount, + 'recipients' => [ + 'all' => $allCount, + 'plan' => $planCount, + 'emergency' => $emergencyCount, + 'transferred' => $transferredCount, + ], + 'outcome' => $outcomeCount, + 'consist' => $currentCount, + 'percentLoadedBeds' => $percentLoadedBeds, + 'surgical' => [ + 'plan' => $planSurgical, + 'emergency' => $emergencySurgical + ], + 'deceased' => $deceasedCount, ], ]); } + + private function getMetrikaResultFromReport(Report $report, int $metrikaItem, bool $sum = true) + { + if ($sum) { + return (int) ($report->metrikaResults() + ->where('rf_metrika_item_id', $metrikaItem) + ->sum(DB::raw('CAST(value AS INTEGER)')) ?: 0); + } + + return (int) ($report->metrikaResults() + ->where('rf_metrika_item_id', $metrikaItem) + ->value('value') ?: 0); + } } diff --git a/app/Http/Controllers/Web/StatisticController.php b/app/Http/Controllers/Web/StatisticController.php index 08e7aa3..783dd77 100644 --- a/app/Http/Controllers/Web/StatisticController.php +++ b/app/Http/Controllers/Web/StatisticController.php @@ -9,6 +9,7 @@ use App\Models\MetrikaGroup; use App\Models\MetrikaItem; use App\Models\MetrikaResult; use App\Models\Report; +use App\Services\DateRangeService; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -18,65 +19,141 @@ use Inertia\Inertia; class StatisticController extends Controller { + protected DateRangeService $dateService; + + public function __construct(DateRangeService $dateRangeService) + { + $this->dateService = $dateRangeService; + } + public function index(Request $request) { $user = $request->user(); - $userDepartment = $user->department; + $queryStartDate = $request->query('startAt'); + $queryEndDate = $request->query('endAt'); + [$startDate, $endDate] = $this->dateService->getDateRangeForUser($user, $queryStartDate, $queryEndDate); + $isRangeOneDay = $this->dateService->isRangeOneDay($startDate, $endDate); - $data = []; + // Если диапазон содержит сутки + if ($isRangeOneDay) { + // Устанавливаем дату отчета, как последний день из выборки + $dateReport = $endDate; + } else { + // Устанавливаем дату отчета, как выборку + $dateReport = [$startDate, $endDate]; + } - $departments = Department::select('department_id', 'name_short') + $groupedData = []; + + $departments = Department::select('department_id', 'rf_department_type', 'name_short') + ->with(['reports']) ->orderBy('name_short')->get(); foreach ($departments as $department) { - $allCount = MetrikaResult::whereHas('report', function ($query) use ($userDepartment, $department) { - $query->where('rf_department_id', $department->department_id); - })->where('rf_metrika_item_id', 3) - ->sum(DB::raw('value::integer')); + $departmentType = $department->departmentType->name_full; - $leaveCount = MetrikaResult::whereHas('report', function ($query) use ($userDepartment, $department) { - $query->where('rf_department_id', $department->department_id); - })->where('rf_metrika_item_id', 7) - ->sum(DB::raw('value::integer')); + if (!isset($groupedData[$departmentType])) { + $groupedData[$departmentType] = []; + } - $consistCount = optional(MetrikaResult::where('rf_metrika_item_id', 8) - ->whereHas('report', function (Builder $query) use ($department) { - $query->where('rf_department_id', $department->department_id); - })->join('reports', 'metrika_results.rf_report_id', '=', 'reports.report_id') - ->select('metrika_results.value') - ->orderBy('reports.sent_at', 'desc') - )->value('value') ?? 0; + if ($isRangeOneDay) { + // Статистика выводится с нарастающим числом + $reports = $department->reports() + ->whereDate('created_at', $dateReport) + ->get(); + } else { + $reports = $department->reports() + ->whereBetween('created_at', $dateReport)->get(); + } + // Метрики зависищие от отчетов + $allCount = 0; $outcomeCount = 0; $currentCount = 0; $occupiedBeds = 0; $planCount = 0; + $emergencyCount = 0; $planSurgical = 0; $emergencySurgical = 0; $transferredCount = 0; + $deceasedCount = 0; + foreach ($reports as $report) { + $allCount += $this->getMetrikaResultFromReport($report, 3, $isRangeOneDay); + $currentCount += $this->getMetrikaResultFromReport($report, 8, false); + $occupiedBeds += $this->getMetrikaResultFromReport($report, 8, $isRangeOneDay); + $planCount += $this->getMetrikaResultFromReport($report, 4, $isRangeOneDay); + $emergencyCount += $this->getMetrikaResultFromReport($report, 12, $isRangeOneDay); + $planSurgical += $this->getMetrikaResultFromReport($report, 11, $isRangeOneDay); + $emergencySurgical += $this->getMetrikaResultFromReport($report, 10, $isRangeOneDay); + $transferredCount += $this->getMetrikaResultFromReport($report, 13, $isRangeOneDay); + $outcomeCount += $this->getMetrikaResultFromReport($report, 7, $isRangeOneDay); + $deceasedCount += $this->getMetrikaResultFromReport($report, 9, $isRangeOneDay); + } - $beds = (int)optional($department->metrikaDefault() - ->where('rf_metrika_item_id', 1) - ->first())->value ?? 0; + // Независимые метрики (установки по умолчанию и т.п.) + $bedsCount = $department->metrikaDefault() + ->where('rf_metrika_item_id', 1)->value('value'); - $occupiedBeds = (int)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('reports.sent_at', 'desc') - ->first())->value ?? 0; + $percentLoadedBeds = $bedsCount > 0 ? round($occupiedBeds * 100 / $bedsCount) : 0; - $percentLoadedBeds = $beds > 0 ? round($occupiedBeds * 100 / $beds) : 0; - - $data[] = [ + $groupedData[$departmentType][] = [ 'department' => $department->name_short, - 'beds' => $beds, - 'all' => $allCount, - 'plan' => '0', - 'emergency' => '0', - 'leave' => $leaveCount, - 'consist' => $consistCount, + 'beds' => $bedsCount, + 'recipients' => [ + 'all' => $allCount, + 'plan' => $planCount, + 'emergency' => $emergencyCount, + 'transferred' => $transferredCount, + ], + 'outcome' => $outcomeCount, + 'consist' => $currentCount, 'percentLoadedBeds' => $percentLoadedBeds, + 'surgical' => [ + 'plan' => $planSurgical, + 'emergency' => $emergencySurgical + ], + 'deceased' => $deceasedCount, + 'type' => $departmentType ]; } + // Преобразуем группированные данные в плоский массив с заголовками групп + $finalData = []; + foreach ($groupedData as $type => $departmentsInType) { + // Добавляем строку-заголовок группы + $finalData[] = [ + 'isGroupHeader' => true, + 'groupName' => $type, + 'colspan' => 12, // Количество колонок в таблице + 'type' => $type + ]; + + // Добавляем отделения этой группы + foreach ($departmentsInType as $department) { + $finalData[] = $department; + } + } + + $isHeadOrAdmin = $user->isAdmin() || $user->isHeadOfDepartment(); + $date = $isHeadOrAdmin ? [ + $this->dateService->parseDate($isRangeOneDay ? $endDate : $startDate)->getTimestampMs(), + $this->dateService->parseDate($endDate)->getTimestampMs() + ] : $this->dateService->parseDate($endDate)->getTimestampMs(); + return Inertia::render('Statistic/Index', [ - 'data' => $data + 'data' => $finalData, + 'isHeadOrAdmin' => $isHeadOrAdmin, + 'date' => $date, + 'isOneDay' => $isRangeOneDay ]); } + private function getMetrikaResultFromReport(Report $report, int $metrikaItem, bool $sum = true) + { + if ($sum) { + return (int) ($report->metrikaResults() + ->where('rf_metrika_item_id', $metrikaItem) + ->sum(DB::raw('CAST(value AS INTEGER)')) ?: 0); + } + + return (int) ($report->metrikaResults() + ->where('rf_metrika_item_id', $metrikaItem) + ->value('value') ?: 0); + } + public function indexOld(Request $request) { $user = Auth::user(); diff --git a/app/Models/Department.php b/app/Models/Department.php index 46e4cb1..f0aa74a 100644 --- a/app/Models/Department.php +++ b/app/Models/Department.php @@ -31,4 +31,9 @@ class Department extends Model { return $this->hasMany(Report::class, 'rf_department_id', 'department_id'); } + + public function departmentType() + { + return $this->belongsTo(DepartmentType::class, 'rf_department_type', 'department_type_id'); + } } diff --git a/app/Models/Report.php b/app/Models/Report.php index db7ce9c..7bc496f 100644 --- a/app/Models/Report.php +++ b/app/Models/Report.php @@ -31,4 +31,14 @@ class Report extends Model { return $this->hasMany(UnwantedEvent::class, 'rf_report_id', 'report_id'); } + + public function user() + { + return $this->belongsTo(User::class, 'rf_user_id'); + } + + public function lpuDoctor() + { + return $this->belongsTo(MisLpuDoctor::class, 'rf_lpudoctor_id'); + } } diff --git a/app/Models/User.php b/app/Models/User.php index a086738..2bb88ca 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -103,6 +103,11 @@ class User extends Authenticatable return $this->currentRole()->slug === 'head_of_department'; } + public function lpuDoctor() + { + return $this->belongsTo(MisLpuDoctor::class, 'rf_lpudoctor_id'); + } + // Получение доступных отделений public function availableDepartments() { diff --git a/app/Services/DateRangeService.php b/app/Services/DateRangeService.php index 089b034..6e0b644 100644 --- a/app/Services/DateRangeService.php +++ b/app/Services/DateRangeService.php @@ -15,6 +15,18 @@ class DateRangeService return $this->getDefaultDateRange($user); } + public function isRangeOneDay($startAt = null, $endAt = null): bool + { + if (!$startAt || !$endAt) return false; + + $startDate = $this->parseDate($startAt); + $endDate = $this->parseDate($endAt); + + if ($startDate->diffInDays($endDate) === 1.0) return true; + + return false; + } + private function getCustomDateRange($startAt, $endAt, $user): array { $startDate = $this->parseDate($startAt); @@ -36,21 +48,12 @@ class DateRangeService private function getDefaultDateRange($user): array { - if ($user->isHeadOfDepartment() || $user->isAdmin()) { - $startDate = Carbon::now('Asia/Yakutsk') - ->firstOfMonth() - ->setTime(6, 0); + $startDate = Carbon::now('Asia/Yakutsk') + ->subDay() + ->setTime(6, 0); - $endDate = Carbon::now('Asia/Yakutsk') - ->setTime(6, 0); - } else { - $startDate = Carbon::now('Asia/Yakutsk') - ->subDay() - ->setTime(6, 0); - - $endDate = Carbon::now('Asia/Yakutsk') - ->setTime(6, 0); - } + $endDate = Carbon::now('Asia/Yakutsk') + ->setTime(6, 0); return [ $startDate->format('Y-m-d H:i:s'), @@ -58,7 +61,7 @@ class DateRangeService ]; } - private function parseDate($dateInput): Carbon + public function parseDate($dateInput): Carbon { if (is_numeric($dateInput)) { return Carbon::createFromTimestampMs($dateInput) diff --git a/app/Services/MisPatientService.php b/app/Services/MisPatientService.php new file mode 100644 index 0000000..d84be2b --- /dev/null +++ b/app/Services/MisPatientService.php @@ -0,0 +1,100 @@ +getPlanPatientsQuery($branchId, $dateRange); + break; + case 'emergency': + $query = $this->getEmergencyPatientsQuery($branchId, $dateRange); + break; + } + } + + /** + * Получение текущих пациентов по MigrationPatient + */ + public function getInStationarPatients(string $status, $branchId, string|array $dateRange) + { + $emerSign = $status === 'plan' ? 1 : [2,4]; + $query = MisMedicalHistory::query(); + $query->when(isset($branchId), function ($query) use ($branchId, $dateRange) { + $query->with(['migrations', 'surgicalOperations']) + ->whereHas('migrations', function ($q) use ($branchId, $dateRange) { + $q->where('rf_StationarBranchID', $branchId) + ->whereDate('DateRecipient', '<', is_array($dateRange) ? $dateRange[1] : $dateRange) + ->whereDate('DateOut', '=', '2222-01-01 00:00:00.000') + ->where('rf_kl_StatCureResultID', 0) + ->where('rf_kl_VisitResultID', 0); + }); + }) + ->where('rf_EmerSignID', $emerSign) + ->where('MedicalHistoryID', '<>', 0); + + return $query; + } + + /** + * Получение запроса плановых пациентов по MigrationPatient + */ + public function getPlanPatientsQuery($branchId, $dateRange) + { + $query = MisMedicalHistory::query(); + $query->when(isset($branchId), function ($query) use ($branchId, $dateRange) { + $query->with('migrations') + ->whereHas('migrations', function ($q) use ($branchId, $dateRange) { + $q->where('rf_StationarBranchID', $branchId) + ->whereDate('DateRecipient', '<', $dateRange[1]); + }); + }) + ->where('rf_EmerSignID', 1) + ->where('MedicalHistoryID', '<>', 0); + + return $query; + } + + /** + * Получение запроса экстренных + неотложных пациентов по MedicalHistory + */ + public function getEmergencyPatientsQuery($branchId, $dateRange) + { + $query = MisMedicalHistory::query(); + $query->when(isset($branchId), function ($query) use ($branchId, $dateRange) { + $query->with('migrations') + ->whereHas('migrations', function ($q) use ($branchId, $dateRange) { + $q->where('rf_StationarBranchID', $branchId) + ->whereBetween('DateIngoing', $dateRange); + }); + }) + ->whereIn('rf_EmerSignID', [2, 4]) + ->where('MedicalHistoryID', '<>', 0); + + return $query; + } + + /** + * Получение запроса пациентов которые попали в отделение в определенную дату + * @param int $branchId Индентификатор ветки стационара + * @param array $dateRange Массив дат (0 - начальная дата, 1 - конечная дата) + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRecipientPatientsQuery(int $branchId, array $dateRange): \Illuminate\Database\Eloquent\Builder + { + $query = MisMedicalHistory::query(); + $query->with('migrations') + ->whereHas('migrations', function ($q) use ($branchId, $dateRange) { + $q->where('rf_StationarBranchID', $branchId) + ->whereBetween('DateIngoing', $dateRange); + })->where('MedicalHistoryID', '<>', 0); + + return $query; + } +} diff --git a/resources/js/Components/DatePickerQuery.vue b/resources/js/Components/DatePickerQuery.vue new file mode 100644 index 0000000..c43760f --- /dev/null +++ b/resources/js/Components/DatePickerQuery.vue @@ -0,0 +1,117 @@ + + + + + diff --git a/resources/js/Components/ReportSelectDate.vue b/resources/js/Components/ReportSelectDate.vue index 51caa40..d4e7017 100644 --- a/resources/js/Components/ReportSelectDate.vue +++ b/resources/js/Components/ReportSelectDate.vue @@ -1,9 +1,17 @@ diff --git a/resources/js/Layouts/AppLayout.vue b/resources/js/Layouts/AppLayout.vue index e74128d..8684e5b 100644 --- a/resources/js/Layouts/AppLayout.vue +++ b/resources/js/Layouts/AppLayout.vue @@ -22,7 +22,11 @@ const themeOverrides = { - + + + diff --git a/resources/js/Layouts/Components/AppHeader.vue b/resources/js/Layouts/Components/AppHeader.vue index f2f850e..ee28895 100644 --- a/resources/js/Layouts/Components/AppHeader.vue +++ b/resources/js/Layouts/Components/AppHeader.vue @@ -4,13 +4,21 @@ import ReportSelectDate from "../../Components/ReportSelectDate.vue"; import AppUserButton from "./AppUserButton.vue"; import {Link} from "@inertiajs/vue3"; import AppHeaderRole from "./AppHeaderRole.vue"; +import {computed, useSlots} from "vue"; + +const slots = useSlots() +const hasHeaderExtra = computed(() => !!slots.headerExtra)