From 87e21f0e08af966b4c4087adf8e14551fd8598c5 Mon Sep 17 00:00:00 2001 From: brusnitsyn Date: Thu, 29 Jan 2026 16:42:42 +0900 Subject: [PATCH] =?UTF-8?q?*=20=D0=B2=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D0=BA=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B0=D0=B1=D0=BB=D1=8E=D0=B4=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20*=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B1=D1=8B=D0=B2=D1=88=D0=B8=D1=85=20*=20=D1=84=D0=B8?= =?UTF-8?q?=D0=BB=D1=8C=D1=82=D1=80=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B1=D1=8B=D0=B2=D1=88=D0=B8=D1=85=20=D0=BF=D0=BE=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B7=D1=83=D0=BB=D1=8C=D1=82=D0=B0=D1=82=D0=B0=D0=BC=20?= =?UTF-8?q?=D0=BB=D0=B5=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20*=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=BF=D0=BE=D0=B4=D1=81=D0=BA?= =?UTF-8?q?=D0=B0=D0=B7=D0=BA=D1=83=20=D0=BF=D1=80=D0=B8=20=D0=BD=D0=B0?= =?UTF-8?q?=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20*=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=B2=D1=8B=D0=B2=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=BF=D1=80=D0=B8=D1=87=D0=B8=D0=BD=D1=8B=20=D0=BD?= =?UTF-8?q?=D0=B0=D0=B1=D0=BB=D1=8E=D0=B4=D0=B5=D0=BD=D0=B8=D1=8F=20*=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=B2=D0=BA=D0=BB?= =?UTF-8?q?=D0=B0=D0=B4=D0=BA=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B1=D1=8B=D0=B2=D1=88=D0=B8=D1=85=20*=20=D0=B8=D0=B7=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=BB=20=D1=81=D0=B2=D1=8F=D0=B7=D1=8C=20?= =?UTF-8?q?=D0=B8=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BF=D0=B0=D1=86=D0=B8=D0=B5=D0=BD=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=BD=D0=B0=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE=D0=BB?= =?UTF-8?q?=D0=B5=20*=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D1=8C=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BF=D1=80=D0=B8=D1=87=D0=B8?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE=D0=BB=D1=8F?= =?UTF-8?q?=20*=20=D0=BF=D0=BE=D0=BB=D0=BD=D0=BE=D0=B5=20=D0=B8=D0=B7?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D0=BA=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=81=20=D0=BD=D0=B5=D0=B6=D0=B5=D0=BB=D0=B0=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=BC=D0=B8=20=D1=81=D0=BE=D0=B1?= =?UTF-8?q?=D1=8B=D1=82=D0=B8=D1=8F=D0=BC=D0=B8=20*=20=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=BF=D1=80=D0=BE=D1=81=D0=BC?= =?UTF-8?q?=D0=BE=D1=82=D1=80=20=D0=BF=D1=80=D0=B8=D1=87=D0=B8=D0=BD=D1=8B?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE=D0=BB=D1=8F=20*=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D0=BD=D0=B0=D0=B4=20?= =?UTF-8?q?=D0=BE=D0=BA=D0=BD=D0=BE=D0=BC=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D1=87=D0=B8=D0=BD=D1=8B=20=D0=BA=D0=BE=D0=BD=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=BB=D1=8F=20=D0=B2=20=D1=82=D0=B0=D0=B1=D0=BB?= =?UTF-8?q?=D0=B8=D1=86=D0=B5=20*=20=D0=B2=D0=B8=D0=B7=D1=83=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=BE=D0=B5=20=D0=B2=D1=8B=D0=B4=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=83=D0=BC=D0=B5=D1=80=D1=88=D0=B8=D1=85?= =?UTF-8?q?=20=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B9=20*=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20=D0=B4=D0=B0=D1=82=D1=8B=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=80=D0=BE=D0=BB=D0=B8=20=D0=B2=D1=80?= =?UTF-8?q?=D0=B0=D1=87=20*=20=D1=86=D0=B5=D0=BD=D1=82=D1=80=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B1=D0=BB=D0=BE=D0=BA?= =?UTF-8?q?=D0=BE=D0=B2=20=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B8=20*=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B2=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B9=20=D0=BD=D0=B0=20=D1=81=D1=80=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20*=20=D0=BF=D0=BE=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20=D0=BE?= =?UTF-8?q?=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D1=82=D0=B5=D0=BA=D1=83=D1=89=D0=B5=D0=B3=D0=BE=20=D0=B4=D0=BD?= =?UTF-8?q?=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=BE=D0=BB=D0=B8=20=D0=B2?= =?UTF-8?q?=D1=80=D0=B0=D1=87=20*=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B1=D0=BB=D0=BE=D0=BA=D0=B8=D1=80=D0=BE=D0=B2=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BF=D1=80=D0=B8=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=D0=B5=20=D0=B4=D1=80=D1=83=D0=B3=D0=BE=D0=B9=20=D0=B4=D0=B0?= =?UTF-8?q?=D1=82=D1=8B=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=BE=D0=BB=D0=B8=20?= =?UTF-8?q?=D0=B2=D1=80=D0=B0=D1=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Api/ReportController.php | 1024 +++++++++++------ .../Controllers/Web/StatisticController.php | 2 +- .../Mis/FormattedPatientResource.php | 4 +- app/Models/MedicalHistorySnapshot.php | 21 + app/Models/MisMedicalHistory.php | 20 +- app/Models/MisMigrationPatient.php | 95 +- app/Models/Report.php | 5 + app/Models/UnwantedEvent.php | 9 +- app/Services/DateRangeService.php | 70 ++ app/Services/PatientService.php | 449 ++++++++ app/Services/ReportService.php | 125 ++ ...isible_column_in_unwanted_events_table.php | 30 + ...create_medical_history_snapshots_table.php | 30 + resources/js/Components/ReportSelectDate.vue | 166 ++- .../Report/Components/MoveModalComment.vue | 56 + .../js/Pages/Report/Components/ReportForm.vue | 7 +- .../Report/Components/ReportFormInput.vue | 73 +- .../Pages/Report/Components/ReportHeader.vue | 26 +- .../Pages/Report/Components/ReportSection.vue | 37 +- .../Report/Components/ReportSectionHeader.vue | 24 +- .../Report/Components/ReportSectionItem.vue | 88 +- .../Report/Components/UnwantedEventModal.vue | 187 ++- resources/js/Stores/report.js | 16 +- routes/api.php | 2 + 24 files changed, 2065 insertions(+), 501 deletions(-) create mode 100644 app/Models/MedicalHistorySnapshot.php create mode 100644 app/Services/DateRangeService.php create mode 100644 app/Services/PatientService.php create mode 100644 app/Services/ReportService.php create mode 100644 database/migrations/2026_01_27_102348_add_title_and_visible_column_in_unwanted_events_table.php create mode 100644 database/migrations/2026_01_28_151157_create_medical_history_snapshots_table.php create mode 100644 resources/js/Pages/Report/Components/MoveModalComment.vue diff --git a/app/Http/Controllers/Api/ReportController.php b/app/Http/Controllers/Api/ReportController.php index 487f0e6..e9a6acf 100644 --- a/app/Http/Controllers/Api/ReportController.php +++ b/app/Http/Controllers/Api/ReportController.php @@ -4,11 +4,13 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Http\Resources\Mis\FormattedPatientResource; +use App\Models\MedicalHistorySnapshot; use App\Models\MetrikaGroup; use App\Models\MetrikaResult; use App\Models\MisMedicalHistory; use App\Models\MisMigrationPatient; use App\Models\MisStationarBranch; +use App\Models\MisSurgicalOperation; use App\Models\ObservationPatient; use App\Models\Report; use App\Models\UnwantedEvent; @@ -28,20 +30,15 @@ class ReportController extends Controller $department = $user->department; $startDateCarbon = Carbon::now()->firstOfMonth(); - $startDate = $request->query('startAt', $startDateCarbon->format('Y-m-d')); $endDateCarbon = Carbon::now(); - $endDate = $request->query('endAt', $endDateCarbon->format('Y-m-d')); - $doctorStartDate = Carbon::now()->addDays(-1)->format('Y-m-d'); - $doctorEndDate = Carbon::now()->format('Y-m-d'); - - if (is_numeric($startDate)) { - $startDateCarbon = Carbon::createFromTimestampMs($startDate); - $startDate = Carbon::createFromTimestampMs($startDate)->setTimezone('Asia/Yakutsk')->format('Y-m-d'); + // Определяем даты в зависимости от роли + [$startDate, $endDate] = $this->getDateRangeForRole($user, $request->query('startAt'), $request->query('endAt')); + if (Carbon::parse($startDate)->isValid()) { + $startDateCarbon = Carbon::parse($startDate)->setTimeZone('Asia/Yakutsk'); } - if (is_numeric($endDate)) { - $endDateCarbon = Carbon::createFromTimestampMs($endDate); - $endDate = Carbon::createFromTimestampMs($endDate)->setTimezone('Asia/Yakutsk')->format('Y-m-d'); + if (Carbon::parse($endDate)->isValid()) { + $endDateCarbon = Carbon::parse($endDate)->setTimeZone('Asia/Yakutsk'); } $beds = (int)$department->metrikaDefault()->where('rf_metrika_item_id', 1)->first()->value; @@ -50,7 +47,7 @@ class ReportController extends Controller ->where('metrika_results.rf_metrika_item_id', 8) ->orderBy('sent_at', 'desc')->first())->value ?? 0; - $percentLoadedBeds = intval($occupiedBeds) * 100 / $beds; + $percentLoadedBeds = round(intval($occupiedBeds) * 100 / $beds); //intval($occupiedBeds) * 100 / $beds; $metrikaGroup = MetrikaGroup::whereMetrikaGroupId(2)->first(); $metrikaItems = $metrikaGroup->metrikaItems; @@ -59,115 +56,110 @@ class ReportController extends Controller $branchId = MisStationarBranch::where('rf_DepartmentID', $misDepartmentId) ->value('StationarBranchID'); - if ($user->isHeadOfDepartment()) - { - $medicalHistoryIds = MisMigrationPatient::whereInDepartment($branchId) - ->pluck('rf_MedicalHistoryID') - ->unique() - ->toArray(); - $extractedMedicalHistoryIds = MisMigrationPatient::extractedToday($branchId, $startDate, $endDate) - ->pluck('rf_MedicalHistoryID') - ->unique() - ->toArray(); + $unwantedEvents = UnwantedEvent::whereHas('report', function ($query) use ($user, $startDate, $endDate) { + $query->where('rf_department_id', $user->rf_department_id) + ->whereBetween('created_at', [$startDate, $endDate]); + }) + ->get()->map(function ($item) { + return [ + ...$item->toArray(), + 'created_at' => Carbon::parse($item->created_at)->format('Создано d.m.Y в H:i'), + ]; + }); - $recipientCount = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) - ->when($user->isHeadOfDepartment(), function($query) use ($startDate, $endDate) { - return $query->whereBetween('DateRecipient', [$startDate, $endDate]); - }) - ->when(!$user->isHeadOfDepartment(), function($query) use ($doctorStartDate, $doctorEndDate) { - return $query->whereBetween('DateRecipient', [$doctorStartDate, $doctorEndDate]); - }) - ->orderBy('DateRecipient', 'DESC') - ->count(); + // Определяем, является ли пользователь заведующим/администратором + $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); - $extractedCount = MisMedicalHistory::whereIn('MedicalHistoryID', $extractedMedicalHistoryIds) - ->orderBy('DateRecipient', 'DESC') - ->count(); - - $recipientIds = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) - ->when($doctorStartDate, function($query) use ($doctorStartDate, $doctorEndDate) { - return $query->whereBetween('DateRecipient', [$doctorStartDate, $doctorEndDate]); - }) - ->orderBy('DateRecipient', 'DESC') - ->pluck('MedicalHistoryID') - ->values(); - - $currentCount = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) - ->currentlyHospitalized() - ->orderBy('DateRecipient', 'DESC') - ->count(); - } else { - $medicalHistoryIds = MisMigrationPatient::currentlyInTreatment($branchId) - ->pluck('rf_MedicalHistoryID') - ->unique() - ->toArray(); - - $extractedMedicalHistoryIds = MisMigrationPatient::extractedToday($branchId) - ->pluck('rf_MedicalHistoryID') - ->unique() - ->toArray(); - - $recipientCount = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) - ->when($user->isHeadOfDepartment(), function($query) use ($startDate, $endDate) { - return $query->whereBetween('DateRecipient', [$startDate, $endDate]); - }) - ->when(!$user->isHeadOfDepartment(), function($query) use ($doctorStartDate, $doctorEndDate) { - return $query->whereBetween('DateRecipient', [$doctorStartDate, $doctorEndDate]); - }) - ->orderBy('DateRecipient', 'DESC') - ->count(); - - $extractedCount = MisMedicalHistory::whereIn('MedicalHistoryID', $extractedMedicalHistoryIds) - ->orderBy('DateRecipient', 'DESC') - ->count(); - - $recipientIds = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) - ->when($user->isHeadOfDepartment(), function($query) use ($startDate, $endDate) { - return $query->whereBetween('DateRecipient', [$startDate, $endDate]); - }) - ->when(!$user->isHeadOfDepartment(), function($query) use ($doctorStartDate, $doctorEndDate) { - return $query->whereBetween('DateRecipient', [$doctorStartDate, $doctorEndDate]); - }) - ->orderBy('DateRecipient', 'DESC') - ->pluck('MedicalHistoryID') - ->values(); - - $currentCount = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) - ->currentlyHospitalized() - ->orderBy('DateRecipient', 'DESC') - ->count(); - } + $plan = $this->getPlanOrEmergencyPatients( + 'plan', + $isHeadOrAdmin, + $branchId, + $startDate, + $endDate, + true, + today: true + ); + $emergency = $this->getPlanOrEmergencyPatients( + 'emergency', + $isHeadOrAdmin, + $branchId, + $startDate, + $endDate, + true, + today: true + ); + $outcomeCount = $this->getAllOutcomePatients( + $branchId, + $startDate, + $endDate, + true + ); + $currentCount = $this->getCurrentPatients($branchId, true); + $recipientIds = $this->getPlanOrEmergencyPatients( + null, + $isHeadOrAdmin, + $branchId, + $startDate, + $endDate, + false, + true, + true, + today: true + ); return response()->json([ 'department' => [ 'beds' => $beds, 'percentLoadedBeds' => $percentLoadedBeds, - 'recipientCount' => $recipientCount, - 'extractCount' => $extractedCount, + + 'recipientCount' => $plan + $emergency, //$recipientCount, + 'extractCount' => $outcomeCount, //$extractedCount, 'currentCount' => $currentCount, - 'recipientIds' => $recipientIds + 'deadCount' => $this->getDeceasedOutcomePatients($branchId, $startDate, $endDate, true), + 'surgicalCount' => [ + $this->getSurgicalPatients('plan', $isHeadOrAdmin, $branchId, $startDate, $endDate, true), + $this->getSurgicalPatients('emergency', $isHeadOrAdmin, $branchId, $startDate, $endDate, true) + ], + 'recipientIds' => $recipientIds, ], 'dates' => [ 'startAt' => $startDateCarbon->getTimestampMs(), 'endAt' => $endDateCarbon->getTimestampMs() ], + 'report' => [ + 'unwantedEvents' => $unwantedEvents, + 'isActiveSendButton' => Carbon::createFromFormat('Y-m-d H:i:s', $endDate)->isToday() && (!$user->isHeadOfDepartment() && !$user->isAdmin()), + ], 'metrikaItems' => $metrikaItems ]); } public function store(Request $request) { + $user = Auth::user(); + $misDepartmentId = $user->department->rf_mis_department_id; + + $branchId = MisStationarBranch::where('rf_DepartmentID', $misDepartmentId) + ->value('StationarBranchID'); + + // Определяем, является ли пользователь заведующим/администратором + $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); + $data = $request->validate([ 'metrics' => 'required', 'observationPatients' => 'nullable', 'departmentId' => 'required|integer', - 'unwantedEvent' => 'nullable' + 'unwantedEvents' => 'nullable|array', + 'dates' => 'required|array', ]); - $metrics = $data['metrics']; $observationPatients = $data['observationPatients']; + $unwantedEvents = $data['unwantedEvents']; + + // Определяем даты в зависимости от роли + [$startDate, $endDate] = $this->getDateRangeForRole($user, $data['dates'][0], $data['dates'][1]); $metriks = []; foreach ($metrics as $key => $value) { @@ -188,11 +180,29 @@ class ReportController extends Controller 'sent_at' => now() ]); - if (in_array('unwantedEvent', $data)) { - $unwantedEvent = UnwantedEvent::create([ - 'rf_report_id' => $report->id, - 'comment' => $data['unwantedEvent']['comment'] ?? '', - ]); + if (count($unwantedEvents)) { + foreach ($unwantedEvents as $unwantedEvent) { + // Если есть ID - ищем по нему + if (isset($unwantedEvent['unwanted_event_id']) && $unwantedEvent['unwanted_event_id']) { + UnwantedEvent::updateOrCreate( + ['unwanted_event_id' => $unwantedEvent['unwanted_event_id']], + [ + 'rf_report_id' => $report->report_id, + 'comment' => $unwantedEvent['comment'] ?? '', + 'title' => $unwantedEvent['title'] ?? '', + 'is_visible' => $unwantedEvent['is_visible'] ?? true, + ] + ); + } else { + // Если нет ID - создаем новую запись + UnwantedEvent::create([ + 'rf_report_id' => $report->report_id, + 'comment' => $unwantedEvent['comment'] ?? '', + 'title' => $unwantedEvent['title'] ?? '', + 'is_visible' => $unwantedEvent['is_visible'] ?? true, + ]); + } + } } foreach ($metriks as $metrika) { @@ -205,10 +215,73 @@ class ReportController extends Controller 'rf_department_id' => $data['departmentId'], 'rf_report_id' => $report->report_id, 'rf_medicalhistory_id' => $observationPatient['id'], - 'rf_mkab_id' => null + 'rf_mkab_id' => null, + 'comment' => $observationPatient['comment'] ?? null ]); } + // Сохраняем снимок для каждого типа пациентов + + // 1. Плановые + $planIds = $this->getPlanOrEmergencyPatients('plan', false, $branchId, $startDate, $endDate, false, false, true); + foreach ($planIds as $id) { + MedicalHistorySnapshot::create([ + 'rf_report_id' => $report->report_id, + 'rf_medicalhistory_id' => $id, + 'patient_type' => 'plan' + ]); + } + + // 2. Экстренные + $emergencyIds = $this->getPlanOrEmergencyPatients('emergency', false, $branchId, $startDate, $endDate, false, false, true); + foreach ($emergencyIds as $id) { + MedicalHistorySnapshot::create([ + 'rf_report_id' => $report->report_id, + 'rf_medicalhistory_id' => $id, + 'patient_type' => 'emergency' + ]); + } + + // 3. Выписанные + $dischargedIds = $this->getDischargedPatients($branchId, $startDate, $endDate, true); + foreach ($dischargedIds as $id) { + MedicalHistorySnapshot::create([ + 'rf_report_id' => $report->report_id, + 'rf_medicalhistory_id' => $id, + 'patient_type' => 'discharged' + ]); + } + + // 4. Переведенные + $transferredIds = $this->getTransferredPatients($branchId, $startDate, $endDate, true); + foreach ($transferredIds as $id) { + MedicalHistorySnapshot::create([ + 'rf_report_id' => $report->report_id, + 'rf_medicalhistory_id' => $id, + 'patient_type' => 'transferred' + ]); + } + + // 5. Умершие + $deceasedIds = $this->getDeceasedOutcomePatients($branchId, $startDate, $endDate, false, true); + foreach ($deceasedIds as $id) { + MedicalHistorySnapshot::create([ + 'rf_report_id' => $report->report_id, + 'rf_medicalhistory_id' => $id, + 'patient_type' => 'deceased' + ]); + } + + // 6. Находящиеся на лечении +// $currentIds = $this->getCurrentPatients($branchId, false, true); +// foreach ($currentIds as $id) { +// MedicalHistorySnapshot::create([ +// 'rf_report_id' => $report->report_id, +// 'rf_medicalhistory_id' => $id, +// 'patient_type' => 'current' +// ]); +// } + \DB::commit(); return response()->json([ @@ -220,221 +293,61 @@ class ReportController extends Controller { $user = Auth::user(); $data = $request->validate([ - 'status' => 'required|string', // plan emergency + 'status' => 'required|string', // plan emergency observation deceased 'startAt' => 'nullable', 'endAt' => 'nullable', ]); + // Получаем базовые данные $status = $data['status']; - - $startDateCarbon = Carbon::now()->firstOfMonth(); - $startDate = $data['startAt'] ?? $startDateCarbon->format('Y-m-d'); - $endDateCarbon = Carbon::now(); - $endDate = $data['endAt'] ?? $startDateCarbon->format('Y-m-d'); - - $doctorStartDate = Carbon::now()->addDays(-1)->format('Y-m-d'); - $doctorEndDate = Carbon::now()->format('Y-m-d'); - - if (is_numeric($startDate)) { - $startDateCarbon = Carbon::createFromTimestampMs($startDate); - $startDate = Carbon::createFromTimestampMs($startDate)->setTimezone('Asia/Yakutsk')->format('Y-m-d'); - } - if (is_numeric($endDate)) { - $endDateCarbon = Carbon::createFromTimestampMs($endDate); - $endDate = Carbon::createFromTimestampMs($endDate)->setTimezone('Asia/Yakutsk')->format('Y-m-d'); - } - $model = new MisMedicalHistory(); $misDepartmentId = $request->user()->department->rf_mis_department_id; $userDepartmentId = $request->user()->department->department_id; - $misStationarBranchId = MisStationarBranch::where('rf_DepartmentID', $misDepartmentId)->first()->StationarBranchID; + $branchId = MisStationarBranch::where('rf_DepartmentID', $misDepartmentId)->value('StationarBranchID'); - if ($user->isHeadOfDepartment()) { - if ($status === 'plan') { - // Сначала получаем ID локально - $branchId = MisStationarBranch::where('rf_DepartmentID', $misDepartmentId) - ->value('StationarBranchID'); - - if (!$branchId) { - return collect(); - } - - $medicalHistoryIds = MisMigrationPatient::whereInDepartment($branchId) - ->when($startDate && $endDate, function($query) use ($startDate, $endDate) { - return $query->whereBetween('DateIngoing', [$startDate, $endDate]); - }) - ->pluck('rf_MedicalHistoryID') - ->unique() - ->toArray(); - - if (empty($medicalHistoryIds)) { - return collect(); - } - - // Получаем истории - $patients = MisMedicalHistory::select($model->getFillable()) - ->plan() - ->whereIn('MedicalHistoryID', $medicalHistoryIds) - ->orderBy('DateRecipient', 'DESC') - ->get() - ->map(function ($item, $index) use ($misStationarBranchId) { - $item->num = $index + 1; - $item->misStationarBranchId = $misStationarBranchId; - return $item; - }); - } else if ($status === 'emergency') { - // Сначала получаем ID локально - $branchId = MisStationarBranch::where('rf_DepartmentID', $misDepartmentId) - ->value('StationarBranchID'); - - if (!$branchId) { - return collect(); - } - - $medicalHistoryIds = MisMigrationPatient::whereInDepartment($branchId) - ->when($startDate && $endDate, function($query) use ($startDate, $endDate) { - return $query->whereBetween('DateIngoing', [$startDate, $endDate]); - }) - ->pluck('rf_MedicalHistoryID') - ->unique() - ->toArray(); - - if (empty($medicalHistoryIds)) { - return collect(); - } - - // Получаем истории - $patients = MisMedicalHistory::select($model->getFillable()) - ->emergency() - ->whereIn('MedicalHistoryID', $medicalHistoryIds) - ->with(['surgicalOperations']) - ->orderBy('DateRecipient', 'DESC') - ->get() - ->map(function ($item, $index) use ($misStationarBranchId) { - $item->num = $index + 1; - $item->misStationarBranchId = $misStationarBranchId; - return $item; - }); - } else if ($status === 'observation') { - $medicalHistoryIds = ObservationPatient::where('rf_department_id', $userDepartmentId) - ->pluck('rf_medicalhistory_id') - ->toArray(); - - $patients = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) - ->get()->map(function ($item, $index) use ($misStationarBranchId) { - $item->num = $index + 1; - $item->misStationarBranchId = $misStationarBranchId; - return $item; - }); - - } else if ($status === 'deceased') { - $patients = MisMedicalHistory::select(...$model->getFillable()) - ->deceased() - ->inDepartment($misDepartmentId, $startDate, $endDate) - ->get() - ->map(function ($item, $index) { - $item->num = $index + 1; - return $item; - }); - } - } else { - if ($status === 'plan') { - // Сначала получаем ID локально - $branchId = MisStationarBranch::where('rf_DepartmentID', $misDepartmentId) - ->value('StationarBranchID'); - - if (!$branchId) { - return collect(); - } - - $medicalHistoryIds = MisMigrationPatient::currentlyInTreatment($branchId) -// ->when($startDate && $endDate, function($query) use ($startDate, $endDate) { -// return $query->whereBetween('DateIngoing', [$startDate, $endDate]); -// }) - ->pluck('rf_MedicalHistoryID') - ->unique() - ->toArray(); - - if (empty($medicalHistoryIds)) { - return collect(); - } - - // Получаем истории - $patients = MisMedicalHistory::select($model->getFillable()) - ->plan() - ->whereIn('MedicalHistoryID', $medicalHistoryIds) - ->currentlyHospitalized() - ->orderBy('DateRecipient', 'DESC') - ->get() - ->map(function ($item, $index) use ($misStationarBranchId) { - $item->num = $index + 1; - $item->misStationarBranchId = $misStationarBranchId; - return $item; - }); - } else if ($status === 'emergency') { - // Сначала получаем ID локально - $branchId = MisStationarBranch::where('rf_DepartmentID', $misDepartmentId) - ->value('StationarBranchID'); - - if (!$branchId) { - return collect(); - } - - $medicalHistoryIds = MisMigrationPatient::currentlyInTreatment($branchId) -// ->when($startDate && $endDate, function($query) use ($startDate, $endDate) { -// return $query->whereBetween('DateIngoing', [$startDate, $endDate]); -// }) - ->pluck('rf_MedicalHistoryID') - ->unique() - ->toArray(); - - if (empty($medicalHistoryIds)) { - return collect(); - } - - // Получаем истории - $patients = MisMedicalHistory::select($model->getFillable()) - ->emergency() - ->whereIn('MedicalHistoryID', $medicalHistoryIds) - ->currentlyHospitalized() - ->with(['surgicalOperations']) - ->orderBy('DateRecipient', 'DESC') - ->get() - ->map(function ($item, $index) use ($misStationarBranchId) { - $item->num = $index + 1; - $item->misStationarBranchId = $misStationarBranchId; - return $item; - }); - } else if ($status === 'observation') { - $medicalHistoryIds = ObservationPatient::where('rf_department_id', $userDepartmentId) - ->pluck('rf_medicalhistory_id') - ->toArray(); - - $patients = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) - ->get()->map(function ($item, $index) use ($misStationarBranchId) { - $item->num = $index + 1; - $item->misStationarBranchId = $misStationarBranchId; - return $item; - }); - - } else if ($status === 'deceased') { - $patients = MisMedicalHistory::select(...$model->getFillable()) - ->deceased() - ->inDepartment($misDepartmentId, $startDate, $endDate) - ->get() - ->map(function ($item, $index) { - $item->num = $index + 1; - return $item; - }); - } + if (!$branchId) { + return response()->json([]); } - $patients->load(['migrations' => function ($query) use ($startDate, $endDate, $misStationarBranchId) { - $query->whereHas('diagnosis', function ($query) { - $query->where('rf_DiagnosTypeID', 3); - })->with('diagnosis.mkb') - ->where('rf_StationarBranchID', $misStationarBranchId); - }]); + // Определяем даты в зависимости от роли + [$startDate, $endDate] = $this->getDateRangeForRole($user, $data['startAt'] ?? null, $data['endAt'] ?? null); + +// dd($startDate, $endDate); + + // Определяем, является ли пользователь заведующим/администратором + $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); + + // Обработка каждого статуса + $patients = match($status) { + 'plan', 'emergency' => $this->getPlanOrEmergencyPatients($status, $isHeadOrAdmin, $branchId, $startDate, $endDate), + 'observation' => $this->getObservationPatients($userDepartmentId), + 'outcome-discharged' => $this->getDischargedPatients($branchId, $startDate, $endDate), + 'outcome-transferred' => $this->getTransferredPatients($branchId, $startDate, $endDate), + 'outcome-deceased' => $this->getDeceasedOutcomePatients($branchId, $startDate, $endDate), + default => 0 + }; + +// dd($patients->pluck('MedicalHistoryID')->toArray()); + + // Если есть пациенты, добавляем дополнительные данные + if ($patients->isNotEmpty()) { + $patients = $patients->map(function ($item, $index) use ($branchId, $startDate, $endDate) { + $item->num = $index + 1; + $item->misStationarBranchId = $branchId; + $item->startDate = $startDate; + $item->endDate = $endDate; + return $item; + }); + + // Загружаем связи + $patients->load(['migrations' => function ($query) use ($startDate, $endDate, $branchId) { + $query->whereHas('diagnosis', function ($q) { + $q->where('rf_DiagnosTypeID', 3); + }) + ->with('diagnosis.mkb') + ->where('rf_StationarBranchID', $branchId); + }]); + } return response()->json(FormattedPatientResource::collection($patients)); } @@ -443,97 +356,452 @@ class ReportController extends Controller { $user = Auth::user(); $data = $request->validate([ - 'status' => 'required|string', // plan emergency + 'status' => 'required|string', // plan emergency observation deceased 'startAt' => 'nullable', 'endAt' => 'nullable', ]); + // Получаем базовые данные $status = $data['status']; - - $startDateCarbon = Carbon::now()->firstOfMonth(); - $startDate = $data['startAt'] ?? $startDateCarbon->format('Y-m-d'); - $endDateCarbon = Carbon::now(); - $endDate = $data['endAt'] ?? $endDateCarbon->format('Y-m-d'); - - $doctorStartDate = Carbon::now()->addDays(-1)->format('Y-m-d'); - $doctorEndDate = Carbon::now()->format('Y-m-d'); - - if (is_numeric($startDate)) { - $startDateCarbon = Carbon::createFromTimestampMs($startDate); - $startDate = Carbon::createFromTimestampMs($startDate)->setTimezone('Asia/Yakutsk')->format('Y-m-d'); - } - if (is_numeric($endDate)) { - $endDateCarbon = Carbon::createFromTimestampMs($endDate); - $endDate = Carbon::createFromTimestampMs($endDate)->setTimezone('Asia/Yakutsk')->format('Y-m-d'); - } - + $model = new MisMedicalHistory(); $misDepartmentId = $request->user()->department->rf_mis_department_id; - - // Получаем ID отделения - $branchId = MisStationarBranch::where('rf_DepartmentID', $misDepartmentId) - ->value('StationarBranchID'); + $userDepartmentId = $request->user()->department->department_id; + $branchId = MisStationarBranch::where('rf_DepartmentID', $misDepartmentId)->value('StationarBranchID'); if (!$branchId) { - return response()->json(0); + return response()->json([]); } - if ($user->isHeadOfDepartment()) { - // Получаем ID медицинских историй по миграциям - $medicalHistoryIds = MisMigrationPatient::whereInDepartment($branchId) - ->when($startDate && $endDate, function($query) use ($startDate, $endDate) { - return $query->whereBetween('DateIngoing', [$startDate, $endDate]); - }) - ->pluck('rf_MedicalHistoryID') - ->unique() - ->toArray(); + // Определяем даты в зависимости от роли + [$startDate, $endDate] = $this->getDateRangeForRole($user, $data['startAt'] ?? null, $data['endAt'] ?? null); - if (empty($medicalHistoryIds)) { - return response()->json(0); - } + // Определяем, является ли пользователь заведующим/администратором + $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); - // Подсчет в зависимости от статуса - if ($status === 'plan') { - $count = MisMedicalHistory::plan() - ->whereIn('MedicalHistoryID', $medicalHistoryIds) - ->orderBy('DateRecipient', 'DESC') - ->count(); - } else if ($status === 'emergency') { - $count = MisMedicalHistory::emergency() - ->whereIn('MedicalHistoryID', $medicalHistoryIds) - ->orderBy('DateRecipient', 'DESC') - ->count(); - } else { - $count = 0; + $isOutcomeOrObservation = in_array($status, ['outcome', 'observation']); + if ($isOutcomeOrObservation) + { + switch ($status) { + case 'observation': + $count = ObservationPatient::where('rf_department_id', $userDepartmentId)->count(); + break; + case 'outcome': + $count = $this->getAllOutcomePatients($branchId, $startDate, $endDate, true); + break; } } else { - // Получаем ID медицинских историй по миграциям - $medicalHistoryIds = MisMigrationPatient::currentlyInTreatment($branchId) - ->pluck('rf_MedicalHistoryID') - ->unique() - ->toArray(); - - if (empty($medicalHistoryIds)) { - return response()->json(0); - } - - // Подсчет в зависимости от статуса - if ($status === 'plan') { - $count = MisMedicalHistory::plan() - ->whereIn('MedicalHistoryID', $medicalHistoryIds) - ->currentlyHospitalized() - ->orderBy('DateRecipient', 'DESC') - ->count(); - } else if ($status === 'emergency') { - $count = MisMedicalHistory::emergency() - ->whereIn('MedicalHistoryID', $medicalHistoryIds) - ->currentlyHospitalized() - ->orderBy('DateRecipient', 'DESC') - ->count(); - } else { - $count = 0; - } + $count = $this->getPlanOrEmergencyPatients($status, $isHeadOrAdmin, $branchId, $startDate, $endDate, true); } return response()->json($count); } + + public function getRecipientIds(bool $isHeadOrAdmin, $branchId, $startDate, $endDate) { + + } + + /** + * Определить диапазон дат в зависимости от роли + */ + private function getDateRangeForRole($user, $startAt = null, $endAt = null): array + { + // Функция для парсинга даты + $parseDate = function($dateInput) { + if (is_numeric($dateInput)) { + return Carbon::createFromTimestampMs($dateInput) + ->setTimezone('Asia/Yakutsk'); + } + return Carbon::parse($dateInput, 'Asia/Yakutsk'); + }; + + // Если переданы обе даты (и заведующий, и врач могут выбрать даты) + if ($startAt && $endAt) { + $startDate = $parseDate($startAt); + $endDate = $parseDate($endAt); + + // Если пользователь врач - всегда применяем правило "-1 день" +// if (!$user->isHeadOfDepartment() && !$user->isAdmin()) { +// // Для врача: endDate - выбранная дата, startDate - выбранная дата -1 день +// $startDate = $endDate->copy()->subDay(); +// } + + // Если даты одинаковые (выбран один день) или врач +// dd($startDate->isSameDay($endDate) || (!$user->isHeadOfDepartment() && !$user->isAdmin()))0 +// dd($startDate->isCurrentDay()); + if ($startDate->isSameDay($endDate)) { + // Сдвигаем начало на день назад для всех ролей при выборе одного дня + // И всегда для врача + $startDate = $startDate->copy()->addDays(-1)->setTime(6, 0)->format('Y-m-d H:i:s'); + $endDate = $endDate->setTime(6, 0)->format('Y-m-d H:i:s'); + } else { + // Для диапазона оставляем как есть (только для заведующих) + $startDate = $startDate->setTime(6, 0)->format('Y-m-d H:i:s'); + $endDate = $endDate->setTime(6, 0)->format('Y-m-d H:i:s'); + } + +// dd($startDate); + +// dd($startDate, $endDate); + } + // Если даты не переданы - логика по умолчанию в зависимости от роли + else { + // Для заведующего или администратора - период месяца + if ($user->isHeadOfDepartment() || $user->isAdmin()) { + $startDate = Carbon::now('Asia/Yakutsk') + ->firstOfMonth() + ->setTime(6, 0) + ->format('Y-m-d H:i:s'); + + $endDate = Carbon::now('Asia/Yakutsk') + ->setTime(6, 0) + ->format('Y-m-d H:i:s'); + } + // Для врача - только сутки (вчера 06:00 - сегодня 06:00) + else { + $startDate = Carbon::now('Asia/Yakutsk')->subDay()->setTime(6, 0)->format('Y-m-d H:i:s'); + $endDate = Carbon::now('Asia/Yakutsk')->setTime(6, 0)->format('Y-m-d H:i:s'); + } + } + + return [$startDate, $endDate]; + } + + /** + * Получить пациентов (плановых или экстренных) + */ + private function getPlanOrEmergencyPatients( + ?string $status, + bool $isHeadOrAdmin, + $branchId, + $startDate, $endDate, + bool $returnedCount = false, + bool $all = false, + bool $onlyIds = false, + bool $today = false + ) { + // Определяем, является ли статус outcome + $isOutcomeStatus = in_array($status, ['outcome-transferred', 'outcome-discharged', 'outcome-deceased']); + + if ($isOutcomeStatus) { + switch ($status) { + case 'outcome-transferred': + $query = MisMigrationPatient::transferred($branchId, $startDate, $endDate); + break; + case 'outcome-discharged': + $query = MisMigrationPatient::discharged($branchId, $startDate, $endDate); + break; + case 'outcome-deceased': + $query = MisMigrationPatient::deceasedOutcome($branchId, $startDate, $endDate); + break; + } + } else { + // Разная логика для заведующего и врача + if ($isHeadOrAdmin) { + // Заведующий: используем whereInDepartment + $query = MisMigrationPatient::whereInDepartment($branchId) + ->whereBetween('DateIngoing', [$startDate, $endDate]); + } else { + // Врач: используем currentlyInTreatment + фильтр по дате + $query = MisMigrationPatient::currentlyInTreatment($branchId) + ->when($today, function ($query) use ($startDate, $endDate) { + return $query->whereBetween('DateIngoing', [$startDate, $endDate]); + }); + } + } + + $medicalHistoryIds = $query->pluck('rf_MedicalHistoryID')->toArray(); + + if (empty($medicalHistoryIds)) { + if ($returnedCount) return 0; + return collect(); + } + + // Получаем истории + $query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->with(['surgicalOperations' => function ($query) use ($startDate, $endDate) { + $query->whereBetween('Date', [$startDate, $endDate]); + }]) + ->orderBy('DateRecipient', 'DESC'); + + // Выбираем план или экстренность + if (!$all && !$isOutcomeStatus) { + if ($status === 'plan') { + $query->plan(); + } else if ($status === 'emergency') { + $query->emergency(); + } + } + + // Для врача добавляем условие "в отделении" + if (!$isHeadOrAdmin && !$isOutcomeStatus) { + $query->currentlyHospitalized(); + } + + if ($onlyIds) { + return $query->select('MedicalHistoryID') + ->pluck('MedicalHistoryID')->values(); + } else { + if ($returnedCount) return $query->count(); + else return $query->get(); + } + } + + /** + * Получить пациентов под наблюдением + */ + private function getObservationPatients( + $userDepartmentId + ) { + $observationPatients = ObservationPatient::where('rf_department_id', $userDepartmentId) + ->select(['rf_medicalhistory_id', 'observation_patient_id']) + ->get() + ->groupBy('rf_medicalhistory_id'); + + $medicalHistoryIds = $observationPatients->keys()->toArray(); + + if (empty($medicalHistoryIds)) { + return collect(); + } + + $patients = MisMedicalHistory::whereHas('observationPatient', function ($q) use ($userDepartmentId) { + $q->where('rf_department_id', $userDepartmentId); + }) + ->with(['observationPatient' => function($query) use ($userDepartmentId) { + $query->where('rf_department_id', $userDepartmentId) + ->select(['rf_medicalhistory_id', 'observation_patient_id', 'comment']); + }]) + ->orderBy('DateRecipient', 'DESC') + ->get(); + + // Добавляем комментарии + $patients = $patients->map(function ($patient) { + // Объединяем все комментарии + $patient->comment = $patient->observationPatient + ->pluck('comment') + ->filter() + ->implode('; '); + + return $patient; + }); + + return $patients; + } + + /** + * Получить выбывших пациентов + */ + private function getOutcomePatients( + $misDepartmentId, + $startDate, + $endDate + ) { + // Здесь оставляем оригинальный запрос, т.к. он специфичен для умерших + return MisMedicalHistory::deceased() + ->inDepartment($misDepartmentId, $startDate, $endDate) + ->get(); + } + + /** + * Получить всех выбывших пациентов + */ + private function getAllOutcomePatients($branchId, $startDate, $endDate, bool $returnedCount = false) + { + // Сначала получаем миграции с типами выбытия + $migrations = MisMigrationPatient::outcomePatients($branchId, $startDate, $endDate) + ->select('rf_MedicalHistoryID', 'rf_kl_VisitResultID', 'DateOut') + ->get() + ->groupBy('rf_MedicalHistoryID'); + + if ($migrations->isEmpty()) { + if ($returnedCount) return 0; + return collect(); + } + + $medicalHistoryIds = $migrations->keys()->toArray(); + + // Получаем истории + $patients = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->with(['surgicalOperations']) + ->orderBy('DateRecipient', 'DESC'); + + if ($returnedCount) return $patients->count(); + else $patients = $patients->get(); + + // Добавляем информацию о типе выбытия + return $patients->map(function ($patient) use ($migrations) { + $patientMigrations = $migrations->get($patient->MedicalHistoryID, collect()); + + // Определяем основной тип выбытия (берем последнюю миграцию) + $latestMigration = $patientMigrations->sortByDesc('DateOut')->first(); + + if ($latestMigration) { + $patient->outcome_type = $this->getOutcomeTypeName($latestMigration->rf_kl_VisitResultID); + $patient->outcome_date = $latestMigration->DateOut; + $patient->visit_result_id = $latestMigration->rf_kl_VisitResultID; + } + + return $patient; + }); + } + + /** + * Получить понятное название типа выбытия + */ + private function getOutcomeTypeName($visitResultId): string + { + return match($visitResultId) { + 1, 7, 8, 9, 10, 11, 48, 49, 124 => 'Выписка', + 2, 3, 4, 12, 13, 14 => 'Перевод', + 5, 6, 15, 16 => 'Умер', + // Добавьте другие коды по мере необходимости + default => 'Другое (' . $visitResultId . ')' + }; + } + + /** + * Получить выписанных пациентов + */ + private function getDischargedPatients($branchId, $startDate, $endDate, bool $onlyIds = false) + { + $medicalHistoryIds = MisMigrationPatient::discharged($branchId, $startDate, $endDate) + ->pluck('rf_MedicalHistoryID') + ->unique() + ->toArray(); + + if (empty($medicalHistoryIds)) { + return collect(); + } + + if ($onlyIds) { + return $medicalHistoryIds; + } + + return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->with(['surgicalOperations']) + ->orderBy('DateRecipient', 'DESC') + ->get(); + } + + /** + * Получить переведенных пациентов + */ + private function getTransferredPatients($branchId, $startDate, $endDate, bool $onlyIds = false) + { + $medicalHistoryIds = MisMigrationPatient::transferred($branchId, $startDate, $endDate) + ->pluck('rf_MedicalHistoryID') + ->unique() + ->toArray(); + + if (empty($medicalHistoryIds)) { + return collect(); + } + + if ($onlyIds) { + return $medicalHistoryIds; + } + + return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->with(['surgicalOperations']) + ->orderBy('DateRecipient', 'DESC') + ->get(); + } + + /** + * Получить умерших пациентов (исход) + */ + private function getDeceasedOutcomePatients($branchId, $startDate, $endDate, bool $returnedCount = false, bool $onlyIds = false) + { + $medicalHistoryIds = MisMigrationPatient::deceasedOutcome($branchId, $startDate, $endDate) + ->pluck('rf_MedicalHistoryID') + ->unique() + ->toArray(); + + if (empty($medicalHistoryIds)) { + if ($returnedCount) return 0; + return collect(); + } + + if ($onlyIds) { + return $medicalHistoryIds; + } + + $query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->with(['surgicalOperations']) + ->orderBy('DateRecipient', 'DESC'); + + if ($returnedCount) return $query->count(); + else return $query->get(); + } + + /** + * Получить пациентов с операциями + */ + private function getSurgicalPatients(string $status, bool $isHeadOrAdmin, $branchId, $startDate, $endDate, bool $returnedCount = false) + { + $query = MisSurgicalOperation::where('rf_StationarBranchID', $branchId) + ->whereBetween('Date', [$startDate, $endDate]) + ->orderBy('Date', 'DESC'); + + if ($status === 'plan') { + $query->where('rf_TypeSurgOperationInTimeID', 6); + } else { + $query->whereIn('rf_TypeSurgOperationInTimeID', [4, 5]); + } + + + if ($returnedCount) return $query->count(); + else return $query->get(); + } + + /** + * Находятся на лечении + */ + private function getCurrentPatients($branchId, bool $returnedCount = false, bool $onlyIds = false) + { +// $currentCount = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) +// ->currentlyHospitalized() +// ->orderBy('DateRecipient', 'DESC') +// ->count(); + $medicalHistoryIds = MisMigrationPatient::currentlyInTreatment($branchId) + ->pluck('rf_MedicalHistoryID') + ->unique() + ->toArray(); + + if (empty($medicalHistoryIds)) { + if ($returnedCount) return 0; + return collect(); + } + + if ($onlyIds) return $medicalHistoryIds; + + $patients = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->currentlyHospitalized() + ->with(['surgicalOperations']) + ->orderBy('DateRecipient', 'DESC'); + + if ($returnedCount) return $patients->count(); + else return $patients->get(); + } + + public function removeObservation( + Request $request, + ) { + $data = $request->validate([ + 'id' => 'required' + ]); + + ObservationPatient::where('rf_medicalhistory_id', $data['id'])->delete(); + + return response()->json()->setStatusCode(200); + } + + + + // api/report/unwanted-event + public function removeUnwantedEvent(UnwantedEvent $unwantedEvent, Request $request) + { + $unwantedEvent->delete(); + + return response()->json()->setStatusCode(200); + } } diff --git a/app/Http/Controllers/Web/StatisticController.php b/app/Http/Controllers/Web/StatisticController.php index 5524471..08e7aa3 100644 --- a/app/Http/Controllers/Web/StatisticController.php +++ b/app/Http/Controllers/Web/StatisticController.php @@ -58,7 +58,7 @@ class StatisticController extends Controller ->orderBy('reports.sent_at', 'desc') ->first())->value ?? 0; - $percentLoadedBeds = $beds > 0 ? $occupiedBeds * 100 / $beds : 0; + $percentLoadedBeds = $beds > 0 ? round($occupiedBeds * 100 / $beds) : 0; $data[] = [ 'department' => $department->name_short, diff --git a/app/Http/Resources/Mis/FormattedPatientResource.php b/app/Http/Resources/Mis/FormattedPatientResource.php index fe5bc02..303323e 100644 --- a/app/Http/Resources/Mis/FormattedPatientResource.php +++ b/app/Http/Resources/Mis/FormattedPatientResource.php @@ -27,7 +27,7 @@ class FormattedPatientResource extends JsonResource ]; }), 'operations' => $this->whenLoaded('surgicalOperations', function () { - return $this->surgicalOperations()->where('rf_StationarBranchID', $this->misStationarBranchId) + return $this->operationOnBranch($this->misStationarBranchId, $this->startDate, $this->endDate) ->get() ->map(function (MisSurgicalOperation $operation) { return [ @@ -38,6 +38,8 @@ class FormattedPatientResource extends JsonResource 'fullname' => Str::ucwords(Str::lower("$this->FAMILY $this->Name $this->OT")), 'age' => Carbon::parse($this->BD)->diff(Carbon::now())->format('%y'), 'birth_date' => Carbon::parse($this->BD)->format('d.m.Y'), + 'outcome_type' => $this->when($this->outcome_type, $this->outcome_type), + 'comment' => $this->when($this->comment, $this->comment) ]; } } diff --git a/app/Models/MedicalHistorySnapshot.php b/app/Models/MedicalHistorySnapshot.php new file mode 100644 index 0000000..d7a6696 --- /dev/null +++ b/app/Models/MedicalHistorySnapshot.php @@ -0,0 +1,21 @@ +belongsTo(Report::class, 'rf_report_id'); + } +} diff --git a/app/Models/MisMedicalHistory.php b/app/Models/MisMedicalHistory.php index a9ebd39..7aa2c6e 100644 --- a/app/Models/MisMedicalHistory.php +++ b/app/Models/MisMedicalHistory.php @@ -26,7 +26,7 @@ class MisMedicalHistory extends Model public function observationPatient() { - return $this->belongsTo(ObservationPatient::class, 'MedicalHistoryID', 'rf_medicalhistory_id'); + return $this->hasMany(ObservationPatient::class, 'rf_medicalhistory_id', 'MedicalHistoryID'); } public function surgicalOperations() @@ -34,6 +34,12 @@ class MisMedicalHistory extends Model return $this->hasMany(MisSurgicalOperation::class, 'rf_MedicalHistoryID', 'MedicalHistoryID'); } + public function scopeOperationOnBranch($query, $branchId, $startDate, $endDate) + { + return $this->surgicalOperations()->where('rf_StationarBranchID', $branchId) + ->whereBetween('Date', [$startDate, $endDate]); + } + public function scopeCurrentlyHospitalized($query) { return $query->whereDate('DateExtract', '1900-01-01') @@ -43,25 +49,25 @@ class MisMedicalHistory extends Model /* * Истории со срочностью - Плановая */ - public function scopePlan() + public function scopePlan($query) { - return $this->where('rf_EmerSignID', 1); + return $query->where('rf_EmerSignID', 1); } /* * Истории со срочностью - Экстренная */ - public function scopeEmergency() + public function scopeEmergency($query) { - return $this->where('rf_EmerSignID', 2); + return $query->where('rf_EmerSignID', 2); } /* * Истории с результатом - Умер */ - public function scopeDeceased() + public function scopeDeceased($query) { - return $this->where('rf_kl_VisitResultID', 5); + return $query->where('rf_kl_VisitResultID', 5); } /* diff --git a/app/Models/MisMigrationPatient.php b/app/Models/MisMigrationPatient.php index 77f3406..f118fb8 100644 --- a/app/Models/MisMigrationPatient.php +++ b/app/Models/MisMigrationPatient.php @@ -25,7 +25,7 @@ class MisMigrationPatient extends Model return $this->hasOne(MisMKB::class, 'MKBID', 'rf_MKBID'); } - public function scopeCurrentlyInTreatment($query, $branchId = null) + public function scopeCurrentlyInTreatment($query, $branchId = null, $startDate = null, $endDate = null) { $query->where('rf_kl_VisitResultID', 0) ->where('rf_MedicalHistoryID', '<>', 0); @@ -34,6 +34,10 @@ class MisMigrationPatient extends Model $query->where('rf_StationarBranchID', $branchId); } + if ($startDate && $endDate) { + $query->whereBetween('DateIngoing', [$startDate, $endDate]); + } + return $query; } @@ -48,6 +52,95 @@ class MisMigrationPatient extends Model return $query; } + /** + * Выбывшие пациенты (все исходы) + */ + public function scopeOutcomePatients($query, $branchId = null, $startDate = null, $endDate = null) + { + $query->where('rf_kl_VisitResultID', '<>', 0) // не активное лечение + ->whereDate('DateOut', '<>', '1900-01-01') // есть дата выбытия + ->where('rf_MedicalHistoryID', '<>', 0); + + if ($branchId) { + $query->where('rf_StationarBranchID', $branchId); + } + + if ($startDate && $endDate) { + $query->whereBetween('DateOut', [$startDate, $endDate]); + } + + return $query; + } + + /** + * Выписанные пациенты + */ + public function scopeDischarged($query, $branchId = null, $startDate = null, $endDate = null) + { + // ID выписки + $dischargeCodes = [1, 7, 8, 9, 10, 11, 48, 49, 124]; + + $query->whereIn('rf_kl_VisitResultID', $dischargeCodes) + ->whereDate('DateOut', '<>', '1900-01-01') + ->where('rf_MedicalHistoryID', '<>', 0); + + if ($branchId) { + $query->where('rf_StationarBranchID', $branchId); + } + + if ($startDate && $endDate) { + $query->whereBetween('DateOut', [$startDate, $endDate]); + } + + return $query; + } + + /** + * Перевод в другое отделение + */ + public function scopeTransferred($query, $branchId = null, $startDate = null, $endDate = null) + { + // ID перевода + $transferCodes = [2, 3, 4, 12, 13, 14]; + + $query->whereIn('rf_kl_VisitResultID', $transferCodes) + ->whereDate('DateOut', '<>', '1900-01-01') + ->where('rf_MedicalHistoryID', '<>', 0); + + if ($branchId) { + $query->where('rf_StationarBranchID', $branchId); + } + + if ($startDate && $endDate) { + $query->whereBetween('DateOut', [$startDate, $endDate]); + } + + return $query; + } + + /** + * Умершие пациенты + */ + public function scopeDeceasedOutcome($query, $branchId = null, $startDate = null, $endDate = null) + { + // ID умершего + $deceasedCodes = [5, 6, 15, 16]; + + $query->whereIn('rf_kl_VisitResultID', $deceasedCodes) + ->whereDate('DateOut', '<>', '1900-01-01') + ->where('rf_MedicalHistoryID', '<>', 0); + + if ($branchId) { + $query->where('rf_StationarBranchID', $branchId); + } + + if ($startDate && $endDate) { + $query->whereBetween('DateOut', [$startDate, $endDate]); + } + + return $query; + } + public function scopeExtractedToday($query, $branchId = null, $startDate = null, $endDate = null) { if (is_null($startDate)) $startDate = Carbon::now()->addDays(-1)->format('Y-m-d'); diff --git a/app/Models/Report.php b/app/Models/Report.php index 94453ab..ef48ffc 100644 --- a/app/Models/Report.php +++ b/app/Models/Report.php @@ -25,4 +25,9 @@ class Report extends Model { return $this->hasMany(ObservationPatient::class, 'rf_report_id', 'report_id'); } + + public function unwantedEvents() + { + return $this->hasMany(UnwantedEvent::class, 'rf_report_id', 'report_id'); + } } diff --git a/app/Models/UnwantedEvent.php b/app/Models/UnwantedEvent.php index 4b8708e..026c914 100644 --- a/app/Models/UnwantedEvent.php +++ b/app/Models/UnwantedEvent.php @@ -10,6 +10,13 @@ class UnwantedEvent extends Model protected $fillable = [ 'rf_report_id', - 'comment' + 'comment', + 'title', + 'is_visible', ]; + + public function report() + { + return $this->belongsTo(Report::class, 'rf_report_id'); + } } diff --git a/app/Services/DateRangeService.php b/app/Services/DateRangeService.php new file mode 100644 index 0000000..089b034 --- /dev/null +++ b/app/Services/DateRangeService.php @@ -0,0 +1,70 @@ +getCustomDateRange($startAt, $endAt, $user); + } + + return $this->getDefaultDateRange($user); + } + + private function getCustomDateRange($startAt, $endAt, $user): array + { + $startDate = $this->parseDate($startAt); + $endDate = $this->parseDate($endAt); + + if ($startDate->isSameDay($endDate)) { + $startDate = $startDate->subDay()->setTime(6, 0); + $endDate = $endDate->setTime(6, 0); + } else { + $startDate = $startDate->setTime(6, 0); + $endDate = $endDate->setTime(6, 0); + } + + return [ + $startDate->format('Y-m-d H:i:s'), + $endDate->format('Y-m-d H:i:s') + ]; + } + + private function getDefaultDateRange($user): array + { + if ($user->isHeadOfDepartment() || $user->isAdmin()) { + $startDate = Carbon::now('Asia/Yakutsk') + ->firstOfMonth() + ->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); + } + + return [ + $startDate->format('Y-m-d H:i:s'), + $endDate->format('Y-m-d H:i:s') + ]; + } + + private function parseDate($dateInput): Carbon + { + if (is_numeric($dateInput)) { + return Carbon::createFromTimestampMs($dateInput) + ->setTimezone('Asia/Yakutsk'); + } + + return Carbon::parse($dateInput, 'Asia/Yakutsk'); + } +} diff --git a/app/Services/PatientService.php b/app/Services/PatientService.php new file mode 100644 index 0000000..ca98593 --- /dev/null +++ b/app/Services/PatientService.php @@ -0,0 +1,449 @@ +getOutcomePatients($branchId, $startDate, $endDate, $outcomeType, $countOnly, $onlyIds); + } + + // Обрабатываем обычные типы + $method = 'get' . ucfirst($type) . 'Patients'; + + if (method_exists($this, $method)) { + return $this->$method($isHeadOrAdmin, $branchId, $startDate, $endDate, $countOnly, $onlyIds); + } + + throw new \InvalidArgumentException("Unknown patient type: {$type}"); + } + + public function getPlanPatients( + ?bool $isHeadOrAdmin, + ?int $branchId, + ?string $startDate, + ?string $endDate, + bool $countOnly, + bool $onlyIds + ): Collection|int|array { + return $this->getAdmissionPatients('plan', $isHeadOrAdmin, $branchId, $startDate, $endDate, $countOnly, $onlyIds); + } + + public function getEmergencyPatients( + ?bool $isHeadOrAdmin, + ?int $branchId, + ?string $startDate, + ?string $endDate, + bool $countOnly, + bool $onlyIds + ): Collection|int|array { + return $this->getAdmissionPatients('emergency', $isHeadOrAdmin, $branchId, $startDate, $endDate, $countOnly, $onlyIds); + } + + private function getAdmissionPatients( + string $admissionType, + ?bool $isHeadOrAdmin, + ?int $branchId, + ?string $startDate, + ?string $endDate, + bool $countOnly, + bool $onlyIds + ): Collection|int|array { + $query = $this->getBasePatientsQuery($isHeadOrAdmin, $branchId, $startDate, $endDate); + + if ($admissionType === 'plan') { + $query->plan(); + } else { + $query->emergency(); + } + + return $this->executeQuery($query, $countOnly, $onlyIds); + } + + private function getBasePatientsQuery( + ?bool $isHeadOrAdmin, + ?int $branchId, + ?string $startDate, + ?string $endDate + ) { + $migrationPatient = new MisMigrationPatient(); + + if ($isHeadOrAdmin) { + $medicalHistoryIds = $migrationPatient->newQuery() + ->whereInDepartment($branchId) + ->whereBetween('DateIngoing', [$startDate, $endDate]) + ->pluck('rf_MedicalHistoryID') + ->toArray(); + } else { + $medicalHistoryIds = $migrationPatient->newQuery() + ->currentlyInTreatment($branchId) + ->whereBetween('DateIngoing', [$startDate, $endDate]) + ->pluck('rf_MedicalHistoryID') + ->toArray(); + } + + if (empty($medicalHistoryIds)) { + return MisMedicalHistory::whereRaw('1=0'); // Пустой запрос + } + + return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->with(['surgicalOperations' => function ($query) use ($startDate, $endDate) { + $query->whereBetween('Date', [$startDate, $endDate]); + }]) + ->orderBy('DateRecipient', 'DESC'); + } + + private function executeQuery($query, bool $countOnly, bool $onlyIds): Collection|int|array + { + if ($onlyIds) { + return $query->pluck('MedicalHistoryID')->toArray(); + } + + if ($countOnly) { + return $query->count(); + } + + return $query->get(); + } + + public function getObservationPatients(int $departmentId): Collection + { + return ObservationPatient::where('rf_department_id', $departmentId) + ->with(['medicalHistory']) + ->get() + ->map(function ($observation) { + $patient = $observation->medicalHistory; + $patient->observation_comment = $observation->comment; + return $patient; + }); + } + + public function getOutcomePatients(int $branchId, string $startDate, string $endDate, string $outcomeType, $countOnly = false, $onlyIds = false): Collection|int + { + return match($outcomeType) { + 'discharged' => $this->getDischargedOutcomePatients($branchId, $startDate, $endDate, $countOnly, $onlyIds), + 'transferred' => $this->getTransferredOutcomePatients($branchId, $startDate, $endDate, $countOnly, $onlyIds), + 'deceased' => $this->getDeceasedOutcomePatients($branchId, $startDate, $endDate, $countOnly, $onlyIds), + default => throw new \InvalidArgumentException("Неизвестный тип исхода: {$outcomeType}") + }; + } + + /** + * Получить всех выбывших пациентов + */ + public function getAllOutcomePatients( + int $branchId, + string $startDate, + string $endDate, + bool $countOnly = false + ): Collection|int { + $migrationPatient = new MisMigrationPatient(); + + // Получаем миграции выбывших пациентов за период + $migrations = $migrationPatient->newQuery() + ->outcomePatients($branchId, $startDate, $endDate) + ->select('rf_MedicalHistoryID', 'rf_kl_VisitResultID', 'DateOut') + ->get() + ->groupBy('rf_MedicalHistoryID'); + + if ($migrations->isEmpty()) { + return $countOnly ? 0 : collect(); + } + + $medicalHistoryIds = $migrations->keys()->toArray(); + + $query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->with(['surgicalOperations']) + ->orderBy('DateRecipient', 'DESC'); + + if ($countOnly) { + return $query->count(); + } + + $patients = $query->get(); + + return $this->addOutcomeInfoToPatients($patients, $migrations); + } + + /** + * Получить миграции выбывших пациентов + */ + private function getOutcomeMigrations(int $branchId, string $startDate, string $endDate): Collection + { + $migrationPatient = new MisMigrationPatient(); + + return $migrationPatient->newQuery() + ->outcomePatients($branchId, $startDate, $endDate) + ->select('rf_MedicalHistoryID', 'rf_kl_VisitResultID', 'DateOut') + ->get() + ->groupBy('rf_MedicalHistoryID'); + } + + /** + * Добавить информацию о выбытии к пациентам + */ + private function addOutcomeInfoToPatients(Collection $patients, Collection $migrations): Collection + { + return $patients->map(function ($patient) use ($migrations) { + $patientMigrations = $migrations->get($patient->MedicalHistoryID, collect()); + + if ($patientMigrations->isNotEmpty()) { + $latestMigration = $patientMigrations->sortByDesc('DateOut')->first(); + $patient->outcome_type = $this->getOutcomeTypeName($latestMigration->rf_kl_VisitResultID); + $patient->outcome_date = $latestMigration->DateOut; + $patient->visit_result_id = $latestMigration->rf_kl_VisitResultID; + } + + return $patient; + }); + } + + /** + * Получить понятное название типа выбытия + */ + private function getOutcomeTypeName(int $visitResultId): string + { + return match($visitResultId) { + 1, 7, 8, 9, 10, 11, 48, 49, 124 => 'Выписка', + 2, 3, 4, 12, 13, 14 => 'Перевод', + 5, 6, 15, 16 => 'Умер', + default => 'Другое (' . $visitResultId . ')' + }; + } + + /** + * Получить выписанных пациентов + */ + public function getDischargedOutcomePatients( + int $branchId, + string $startDate, + string $endDate, + bool $countOnly = false, + bool $onlyIds = false + ): Collection|array|int { + return $this->getSpecificOutcomePatients('discharged', $branchId, $startDate, $endDate, $onlyIds, $countOnly); + } + + /** + * Получить переведенных пациентов + */ + public function getTransferredOutcomePatients( + int $branchId, + string $startDate, + string $endDate, + bool $countOnly = false, + bool $onlyIds = false + ): Collection|array|int { + return $this->getSpecificOutcomePatients('transferred', $branchId, $startDate, $endDate, $onlyIds, $countOnly); + } + + /** + * Получить умерших пациентов + */ + public function getDeceasedOutcomePatients( + int $branchId, + string $startDate, + string $endDate, + bool $countOnly = false, + bool $onlyIds = false + ): Collection|int|array { + return $this->getSpecificOutcomePatients('deceased', $branchId, $startDate, $endDate, $onlyIds, $countOnly); + } + + /** + * Общий метод для получения пациентов с определенным типом выбытия + */ + private function getSpecificOutcomePatients( + string $type, + int $branchId, + string $startDate, + string $endDate, + bool $onlyIds = false, + bool $countOnly = false + ): Collection|int|array { + $migrationPatient = new MisMigrationPatient(); + + switch ($type) { + case 'discharged': + $query = $migrationPatient->newQuery()->discharged($branchId, $startDate, $endDate); + break; + case 'transferred': + $query = $migrationPatient->newQuery()->transferred($branchId, $startDate, $endDate); + break; + case 'deceased': + $query = $migrationPatient->newQuery()->deceasedOutcome($branchId, $startDate, $endDate); + break; + default: + throw new \InvalidArgumentException("Неизвестный тип выбытия: {$type}"); + } + + $medicalHistoryIds = $query->pluck('rf_MedicalHistoryID')->unique()->toArray(); + + if (empty($medicalHistoryIds)) { + if ($countOnly) return 0; + if ($onlyIds) return []; + return collect(); + } + + if ($onlyIds) { + return $medicalHistoryIds; + } + + $patients = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->with(['surgicalOperations']) + ->orderBy('DateRecipient', 'DESC'); + + if ($countOnly) { + return $patients->count(); + } + + return $patients->get(); + } + + /** + * Получить пациентов, находящихся на лечении + */ + public function getCurrentPatients( + int $branchId, + bool $countOnly = false, + bool $onlyIds = false + ): Collection|int|array { + $migrationPatient = new MisMigrationPatient(); + + $medicalHistoryIds = $migrationPatient->newQuery() + ->currentlyInTreatment($branchId) + ->pluck('rf_MedicalHistoryID') + ->unique() + ->toArray(); + + if (empty($medicalHistoryIds)) { + if ($countOnly) return 0; + if ($onlyIds) return []; + return collect(); + } + + if ($onlyIds) { + return $medicalHistoryIds; + } + + $query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->currentlyHospitalized() + ->with(['surgicalOperations']) + ->orderBy('DateRecipient', 'DESC'); + + if ($countOnly) { + return $query->count(); + } + + return $query->get(); + } + + /** + * Получить пациентов с операциями + */ + public function getSurgicalPatients( + string $status, + bool $isHeadOrAdmin, + int $branchId, + string $startDate, + string $endDate, + bool $countOnly = false + ): Collection|int { + $query = MisSurgicalOperation::where('rf_StationarBranchID', $branchId) + ->whereBetween('Date', [$startDate, $endDate]) + ->orderBy('Date', 'DESC'); + + if ($status === 'plan') { + $query->where('rf_TypeSurgOperationInTimeID', 6); + } else { + $query->whereIn('rf_TypeSurgOperationInTimeID', [4, 5]); + } + + if ($countOnly) { + return $query->count(); + } + + return $query->get(); + } + + /** + * Получить пациентов (плановых или экстренных), которые были в отделении в течение периода + */ + public function getPatientsInDepartmentDuringPeriod( + ?string $patientType, // 'plan', 'emergency', null (все) + ?bool $isHeadOrAdmin, + ?int $branchId, + ?string $startDate, + ?string $endDate, + bool $countOnly = false, + bool $onlyIds = false, + bool $today = false + ): Collection|int|array { + // Используем скоуп inDepartment из модели MisMedicalHistory + $query = MisMedicalHistory::query() + ->whereHas('migrations', function ($q) use ($branchId, $startDate, $endDate) { + $q->where('rf_StationarBranchID', $branchId) + ->where('rf_MedicalHistoryID', '<>', 0) + ->where(function ($subQ) use ($startDate, $endDate) { + // Пациент находился в отделении в течение периода + // 1. Поступил в течение периода + $subQ->whereBetween('DateIngoing', [$startDate, $endDate]) + // 2. Или выбыл в течение периода + ->orWhereBetween('DateOut', [$startDate, $endDate]) + // 3. Или находился в отделении в течение всего периода + ->orWhere(function ($innerQ) use ($startDate, $endDate) { + $innerQ->where('DateIngoing', '<=', $startDate) + ->where(function ($deepQ) use ($endDate) { + $deepQ->where('DateOut', '>=', $endDate) + ->orWhereNull('DateOut') + ->orWhere('DateOut', '1900-01-01'); + }); + }); + }); + }) + ->with(['surgicalOperations' => function ($query) use ($startDate, $endDate) { + $query->whereBetween('Date', [$startDate, $endDate]); + }]) + ->orderBy('DateRecipient', 'DESC'); + + // Фильтруем по типу (план/экстренность) + if ($patientType === 'plan') { + $query->plan(); + } elseif ($patientType === 'emergency') { + $query->emergency(); + } + + // Для врача добавляем условие "все еще в отделении" + if (!$isHeadOrAdmin && !$today) { + $query->currentlyHospitalized(); + } + + if ($onlyIds) { + return $query->select('MedicalHistoryID') + ->pluck('MedicalHistoryID')->values(); + } + + if ($countOnly) { + return $query->count(); + } + + return $query->get(); + } +} diff --git a/app/Services/ReportService.php b/app/Services/ReportService.php new file mode 100644 index 0000000..915e23a --- /dev/null +++ b/app/Services/ReportService.php @@ -0,0 +1,125 @@ +patientService = $patientService; + } + + public function createReport(array $data): Report + { + DB::beginTransaction(); + + try { + $report = Report::create([ + 'rf_department_id' => $data['departmentId'], + 'rf_user_id' => Auth::id(), + 'sent_at' => now() + ]); + + $this->saveMetrics($report, $data['metrics'] ?? []); + $this->saveUnwantedEvents($report, $data['unwantedEvents'] ?? []); + $this->saveObservationPatients($report, $data['observationPatients'] ?? []); + $this->savePatientSnapshots($report, $data); + + DB::commit(); + + return $report; + } catch (\Exception $e) { + DB::rollBack(); + throw $e; + } + } + + private function saveMetrics(Report $report, array $metrics): void + { + foreach ($metrics as $key => $value) { + MetrikaResult::create([ + 'rf_report_id' => $report->report_id, + 'rf_metrika_item_id' => (int) str_replace('metrika_item_', '', $key), + 'value' => $value + ]); + } + } + + private function saveUnwantedEvents(Report $report, array $unwantedEvents): void + { + foreach ($unwantedEvents as $event) { + if (isset($event['unwanted_event_id'])) { + UnwantedEvent::updateOrCreate( + ['unwanted_event_id' => $event['unwanted_event_id']], + $this->formatUnwantedEventData($report, $event) + ); + } else { + UnwantedEvent::create($this->formatUnwantedEventData($report, $event)); + } + } + } + + private function formatUnwantedEventData(Report $report, array $event): array + { + return [ + 'rf_report_id' => $report->report_id, + 'comment' => $event['comment'] ?? '', + 'title' => $event['title'] ?? '', + 'is_visible' => $event['is_visible'] ?? true, + ]; + } + + private function saveObservationPatients(Report $report, array $observationPatients): void + { + foreach ($observationPatients as $patient) { + ObservationPatient::create([ + 'rf_department_id' => $report->rf_department_id, + 'rf_report_id' => $report->report_id, + 'rf_medicalhistory_id' => $patient['id'], + 'comment' => $patient['comment'] ?? null + ]); + } + } + + private function savePatientSnapshots(Report $report, array $data): void + { + $snapshotTypes = [ + 'plan' => $this->patientService->getPlanPatients( + false, + $data['branchId'], + $data['startDate'], + $data['endDate'], + false, + true + ), + 'emergency' => $this->patientService->getEmergencyPatients( + false, + $data['branchId'], + $data['startDate'], + $data['endDate'], + false, + true + ) + ]; + + foreach ($snapshotTypes as $type => $patientIds) { + foreach ($patientIds as $patientId) { + MedicalHistorySnapshot::create([ + 'rf_report_id' => $report->report_id, + 'rf_medicalhistory_id' => $patientId, + 'patient_type' => $type + ]); + } + } + } +} diff --git a/database/migrations/2026_01_27_102348_add_title_and_visible_column_in_unwanted_events_table.php b/database/migrations/2026_01_27_102348_add_title_and_visible_column_in_unwanted_events_table.php new file mode 100644 index 0000000..e4a3b9d --- /dev/null +++ b/database/migrations/2026_01_27_102348_add_title_and_visible_column_in_unwanted_events_table.php @@ -0,0 +1,30 @@ +string('title')->nullable(); + $table->string('is_visible')->default(true); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('unwanted_events', function (Blueprint $table) { + $table->dropColumn('title'); + $table->dropColumn('is_visible'); + }); + } +}; diff --git a/database/migrations/2026_01_28_151157_create_medical_history_snapshots_table.php b/database/migrations/2026_01_28_151157_create_medical_history_snapshots_table.php new file mode 100644 index 0000000..827630b --- /dev/null +++ b/database/migrations/2026_01_28_151157_create_medical_history_snapshots_table.php @@ -0,0 +1,30 @@ +id('medical_history_snapshot_id'); + $table->foreignIdFor(\App\Models\Report::class, 'rf_report_id'); + $table->foreignIdFor(\App\Models\MisMedicalHistory::class, 'rf_medicalhistory_id'); + $table->string('patient_type'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('medical_history_snapshots'); + } +}; diff --git a/resources/js/Components/ReportSelectDate.vue b/resources/js/Components/ReportSelectDate.vue index 8ec4511..306451e 100644 --- a/resources/js/Components/ReportSelectDate.vue +++ b/resources/js/Components/ReportSelectDate.vue @@ -2,34 +2,158 @@ import {NDatePicker} from 'naive-ui' import {storeToRefs} from "pinia"; import {useReportStore} from "../Stores/report.js"; - -const themeOverride = { - peers: { - Input: { - border: null, - color: null, - colorFocus: null, - borderHover: null, - borderFocus: null, - boxShadowFocus: null, - paddingMedium: '' - } - } -} +import {useAuthStore} from "../Stores/auth.js"; +import {computed, ref, onMounted, onUnmounted} from "vue"; const reportStore = useReportStore() -const { timestampCurrentRange } = storeToRefs(reportStore) +const authStore = useAuthStore() +const {timestampCurrentRange} = storeToRefs(reportStore) + +// Текущее время для обновления +const currentTime = ref(Date.now()) + +// Обновляем время каждую секунду +let intervalId = null +onMounted(() => { + if (authStore.isDoctor) { + intervalId = setInterval(() => { + currentTime.value = Date.now() + }, 1000) + } +}) + +onUnmounted(() => { + if (intervalId) { + clearInterval(intervalId) + } +}) + +// Проверяем, является ли дата сегодняшней +const isToday = (timestamp) => { + const date = new Date(timestamp) + const today = new Date() + + return date.getDate() === today.getDate() && + date.getMonth() === today.getMonth() && + date.getFullYear() === today.getFullYear() +} + +// Функция для форматирования с заглавной буквой +const formatDateWithCapital = (timestamp) => { + const date = new Date(timestamp) + // Форматируем дату на русском + const formatted = date.toLocaleDateString('ru-RU', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' + }) + // Делаем первую букву заглавной + return formatted.charAt(0).toUpperCase() + formatted.slice(1) +} + +const themeOverride = computed(() => { + if (authStore.isDoctor) { + return { + peers: { + Input: { + border: '', + borderHover: '', + borderFocus: '', + boxShadowFocus: '', + color: '', + colorFocus: '', + fontSizeMedium: '22px', + fontWeight: '500' + } + } + } + } +}) + +// Используем кастомную функцию форматирования +const formatComputed = computed(() => { + const baseFormat = "dd.MM.yyyy" + + if (authStore.isDoctor) { + // Возвращаем функцию форматирования вместо строки + return formatDateWithCapital(modelComputed.value) + } + + return baseFormat +}) + +const typeComputed = computed(() => authStore.isDoctor ? 'date' : 'daterange') + +const modelComputed = computed({ + get: () => { + const value = reportStore.timestampCurrentRange + + if (authStore.isDoctor) { + if (Array.isArray(value)) { + const doctorTimestamp = value[1] + + // Если выбрана сегодняшняя дата - показываем текущее время + if (isToday(doctorTimestamp)) { + return currentTime.value + } + // Иначе показываем выбранную дату (без обновления) + return doctorTimestamp + } + return value + } + + if (Array.isArray(value)) { + return value + } else { + return [value, value] + } + }, + set: (value) => { + if (Array.isArray(value)) { + reportStore.timestampCurrentRange = value + } else { + reportStore.timestampCurrentRange = [value, value] + } + + reportStore.getDataOnReportDate(reportStore.timestampCurrentRange) + } +}) + +const classComputed = computed(() => { + const baseClasses = [] + + if (authStore.isDoctor) { + baseClasses.push('w-[400px]') + baseClasses.push('text-end') + } else { + baseClasses.push('max-w-[280px]') + } + + return baseClasses +}) diff --git a/resources/js/Pages/Report/Components/MoveModalComment.vue b/resources/js/Pages/Report/Components/MoveModalComment.vue new file mode 100644 index 0000000..d22952c --- /dev/null +++ b/resources/js/Pages/Report/Components/MoveModalComment.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/resources/js/Pages/Report/Components/ReportForm.vue b/resources/js/Pages/Report/Components/ReportForm.vue index f6c02e2..a406b1f 100644 --- a/resources/js/Pages/Report/Components/ReportForm.vue +++ b/resources/js/Pages/Report/Components/ReportForm.vue @@ -5,6 +5,7 @@ import ReportFormInput from "./ReportFormInput.vue"; import ReportSection from "./ReportSection.vue"; import {useReportStore} from "../../../Stores/report.js"; import {useAuthStore} from "../../../Stores/auth.js"; +import {computed} from "vue"; const props = defineProps({ mode: { @@ -21,17 +22,19 @@ const onSubmit = () => { departmentId: authStore.userDepartment.department_id }) } + +