diff --git a/Dockerfile b/Dockerfile index 1ba79e8..a76c696 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # Этап 1: PHP зависимости FROM dh-mirror.gitverse.ru/php:8.3-fpm AS phpbuild -# Установка системных зависимостей +# Установка системных зависимостей В ОДНОМ RUN RUN apt-get update && apt-get install -y \ git \ curl \ @@ -21,31 +21,37 @@ RUN apt-get update && apt-get install -y \ libffi-dev \ pkg-config \ libssl-dev \ - && rm -rf /var/lib/apt/lists/* - -# Установка PHP расширений -RUN docker-php-ext-configure gd \ - --with-freetype \ - --with-jpeg \ - --with-webp \ + # Устанавливаем PHP расширения + && docker-php-ext-configure gd \ + --with-freetype \ + --with-jpeg \ + --with-webp \ && docker-php-ext-install -j$(nproc) \ - bcmath \ - intl \ - mbstring \ - zip \ - opcache \ - pdo \ - pdo_mysql \ - pdo_pgsql \ - gd \ - exif \ - sockets \ - xsl \ - ffi \ - pcntl - -# Установка Redis расширения -RUN pecl install redis && docker-php-ext-enable redis + bcmath \ + intl \ + mbstring \ + zip \ + opcache \ + pdo \ + pdo_mysql \ + pdo_pgsql \ + gd \ + exif \ + sockets \ + xsl \ + ffi \ + pcntl \ + # Устанавливаем Redis + && pecl install redis \ + && docker-php-ext-enable redis \ + # Сразу чистим + && rm -rf /usr/src/* \ + && rm -rf /tmp/* \ + && rm -rf /var/tmp/* \ + && find / -type d -name "tests" -exec rm -rf {} + 2>/dev/null || true \ + # Чистим apt + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && rm -rf /var/lib/apt/lists/* # Настройка opcache для production RUN echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/opcache.ini && \ @@ -108,7 +114,6 @@ RUN npm run build # Этап 3: Финальный образ FROM dh-mirror.gitverse.ru/php:8.3-fpm -# Установка runtime зависимостей RUN apt-get update && apt-get install -y \ libxml2 \ libonig5 \ diff --git a/app/Http/Controllers/Api/ReportController.php b/app/Http/Controllers/Api/ReportController.php index e9a6acf..96ffadf 100644 --- a/app/Http/Controllers/Api/ReportController.php +++ b/app/Http/Controllers/Api/ReportController.php @@ -7,6 +7,7 @@ use App\Http\Resources\Mis\FormattedPatientResource; use App\Models\MedicalHistorySnapshot; use App\Models\MetrikaGroup; use App\Models\MetrikaResult; +use App\Models\MisLpuDoctor; use App\Models\MisMedicalHistory; use App\Models\MisMigrationPatient; use App\Models\MisStationarBranch; @@ -14,6 +15,7 @@ use App\Models\MisSurgicalOperation; use App\Models\ObservationPatient; use App\Models\Report; use App\Models\UnwantedEvent; +use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Auth; @@ -41,6 +43,27 @@ class ReportController extends Controller $endDateCarbon = Carbon::parse($endDate)->setTimeZone('Asia/Yakutsk'); } + $reportIds = []; + $reports = $this->getReportsForDateRange($user->rf_department_id, $startDate, $endDate); + $reportIds = $reports->pluck('report_id')->toArray(); + + // Определяем, используем ли мы снапшоты + $useSnapshots = ($user->isAdmin() || $user->isHeadOfDepartment()) || Carbon::parse($endDate)->isToday() === false; + if ($useSnapshots) { + $report = Report::whereDate('sent_at', $endDate) + ->where('rf_department_id', $department->department_id) + ->first(); + $fillableUserId = $report->rf_lpudoctor_id ?? null; + } else { + $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') @@ -71,43 +94,68 @@ class ReportController extends Controller // Определяем, является ли пользователь заведующим/администратором $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); - $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); + // Получаем статистику в зависимости от источника данных + if ($useSnapshots) { + // Используем снапшоты для статистики + $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); - $recipientIds = $this->getPlanOrEmergencyPatients( - null, - $isHeadOrAdmin, - $branchId, - $startDate, - $endDate, - false, - true, - true, - today: true - ); + // Для операций все равно используем реплику с фильтрацией по датам + $surgicalCount = [ + $this->getSurgicalPatients('plan', $isHeadOrAdmin, $branchId, $startDate, $endDate, true), + $this->getSurgicalPatients('emergency', $isHeadOrAdmin, $branchId, $startDate, $endDate, true) + ]; + + $recipientIds = $this->getRecipientIdsFromSnapshots($reportIds); + } else { + // Используем реплику для статистики + $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); + $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 = $this->getPlanOrEmergencyPatients( + null, + $isHeadOrAdmin, + $branchId, + $startDate, + $endDate, + false, + true, + true, + today: true + ); + } return response()->json([ 'department' => [ @@ -117,11 +165,8 @@ class ReportController extends Controller 'recipientCount' => $plan + $emergency, //$recipientCount, 'extractCount' => $outcomeCount, //$extractedCount, 'currentCount' => $currentCount, - 'deadCount' => $this->getDeceasedOutcomePatients($branchId, $startDate, $endDate, true), - 'surgicalCount' => [ - $this->getSurgicalPatients('plan', $isHeadOrAdmin, $branchId, $startDate, $endDate, true), - $this->getSurgicalPatients('emergency', $isHeadOrAdmin, $branchId, $startDate, $endDate, true) - ], + 'deadCount' => $deadCount, + 'surgicalCount' => $surgicalCount, 'recipientIds' => $recipientIds, ], 'dates' => [ @@ -132,10 +177,121 @@ class ReportController extends Controller 'unwantedEvents' => $unwantedEvents, 'isActiveSendButton' => Carbon::createFromFormat('Y-m-d H:i:s', $endDate)->isToday() && (!$user->isHeadOfDepartment() && !$user->isAdmin()), ], - 'metrikaItems' => $metrikaItems + 'metrikaItems' => $metrikaItems, + 'userId' => $fillableUserId, + 'userName' => $lpuDoctor ? "$lpuDoctor->FAM_V $lpuDoctor->IM_V $lpuDoctor->OT_V" : null ]); } + /** + * Получить количество пациентов из снапшотов + */ + private function getCountFromSnapshots(string $type, array $reportIds) + { + return match($type) { + 'plan' => MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->where('patient_type', 'plan') + ->distinct('rf_medicalhistory_id') + ->count('rf_medicalhistory_id'), + 'emergency' => MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->where('patient_type', 'emergency') + ->distinct('rf_medicalhistory_id') + ->count('rf_medicalhistory_id'), + 'outcome' => MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->whereIn('patient_type', ['discharged', 'transferred', 'deceased']) + ->distinct('rf_medicalhistory_id') + ->count('rf_medicalhistory_id'), + 'deceased' => MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->where('patient_type', 'deceased') + ->distinct('rf_medicalhistory_id') + ->count('rf_medicalhistory_id'), + 'discharged' => MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->where('patient_type', 'discharged') + ->distinct('rf_medicalhistory_id') + ->count('rf_medicalhistory_id'), + 'transferred' => MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->where('patient_type', 'transferred') + ->distinct('rf_medicalhistory_id') + ->count('rf_medicalhistory_id'), + default => 0 + }; + } + + /** + * Получить ID пациентов на лечении из снапшотов + */ + private function getCurrentPatientsFromSnapshots(array $reportIds, $branchId) + { + // Получаем ID всех пациентов из снапшотов за период + $allSnapshotPatientIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->pluck('rf_medicalhistory_id') + ->unique() + ->toArray(); + + if (empty($allSnapshotPatientIds)) { + return 0; + } + + // Получаем ID выбывших пациентов + $outcomePatientIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->whereIn('patient_type', ['discharged', 'transferred', 'deceased']) + ->pluck('rf_medicalhistory_id') + ->unique() + ->toArray(); + + // Текущие пациенты = все пациенты - выбывшие + $currentPatientIds = array_diff($allSnapshotPatientIds, $outcomePatientIds); + + return count($currentPatientIds); + } + + /** + * Получить ID поступивших пациентов из снапшотов + */ + private function getRecipientIdsFromSnapshots(array $reportIds) + { + $planIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->where('patient_type', 'plan') + ->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); + } + +// /** +// * Получить отчеты по промежутку дат (оптимизированная версия) +// */ +// private function getReportsForDateRange($departmentId, $startDate, $endDate) +// { +// $start = Carbon::parse($startDate); +// $end = Carbon::parse($endDate); +// +// // Если промежуток большой, ограничиваем количество отчетов +// $daysDiff = $start->diffInDays($end); +// +// if ($daysDiff > 30) { +// // Для больших промежутков берем только последние отчеты +// return Report::where('rf_department_id', $departmentId) +// ->whereBetween('created_at', [$start, $end]) +// ->orderBy('created_at', 'DESC') +// ->take(30) // Ограничиваем количеством +// ->get() +// ->reverse(); // Возвращаем в правильном порядке +// } +// +// return Report::where('rf_department_id', $departmentId) +// ->whereBetween('created_at', [$start, $end]) +// ->orderBy('created_at', 'ASC') +// ->get(); +// } + public function store(Request $request) { $user = Auth::user(); @@ -153,6 +309,7 @@ class ReportController extends Controller 'departmentId' => 'required|integer', 'unwantedEvents' => 'nullable|array', 'dates' => 'required|array', + 'userId' => 'required|integer', ]); $metrics = $data['metrics']; $observationPatients = $data['observationPatients']; @@ -176,6 +333,7 @@ class ReportController extends Controller $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() ]); @@ -210,14 +368,22 @@ class ReportController extends Controller $metrika->save(); } - foreach ($observationPatients as $observationPatient) { - ObservationPatient::create([ - 'rf_department_id' => $data['departmentId'], - 'rf_report_id' => $report->report_id, - 'rf_medicalhistory_id' => $observationPatient['id'], - 'rf_mkab_id' => null, - 'comment' => $observationPatient['comment'] ?? null - ]); + if (count($observationPatients)) { + foreach ($observationPatients as $observationPatient) { + ObservationPatient::updateOrCreate( + [ + 'rf_medicalhistory_id' => $observationPatient['id'], + 'rf_department_id' => $data['departmentId'] + ], + [ + 'rf_department_id' => $data['departmentId'], + 'rf_report_id' => $report->report_id, + 'rf_medicalhistory_id' => $observationPatient['id'], + 'rf_mkab_id' => null, + 'comment' => $observationPatient['comment'] ?? null + ] + ); + } } // Сохраняем снимок для каждого типа пациентов @@ -312,22 +478,38 @@ class ReportController extends Controller // Определяем даты в зависимости от роли [$startDate, $endDate] = $this->getDateRangeForRole($user, $data['startAt'] ?? null, $data['endAt'] ?? null); -// dd($startDate, $endDate); + // Для заведующего/админа ищем отчет по endDate (дате просмотра) + $reportIds = []; + $reports = $this->getReportsForDateRange($user->rf_department_id, $startDate, $endDate); + $reportIds = $reports->pluck('report_id')->toArray(); - // Определяем, является ли пользователь заведующим/администратором - $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); + // Определяем, используем ли мы снапшоты + $useSnapshots = ($user->isAdmin() || $user->isHeadOfDepartment()) || Carbon::parse($endDate)->isToday() === false; // Обработка каждого статуса - $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 - }; + if ($useSnapshots) { + // Используем снапшоты: получаем ID пациентов, затем данные из реплики + $patients = match($status) { + 'plan', 'emergency' => $this->getPatientsFromSnapshotsUsingReplica($status, $reportIds, $branchId, $startDate, $endDate), + 'observation' => $this->getObservationPatientsFromSnapshotsUsingReplica($userDepartmentId, $reportIds), + 'outcome-discharged' => $this->getOutcomePatientsFromSnapshotsUsingReplica('discharged', $reportIds, $branchId, $startDate, $endDate), + 'outcome-transferred' => $this->getOutcomePatientsFromSnapshotsUsingReplica('transferred', $reportIds, $branchId, $startDate, $endDate), + 'outcome-deceased' => $this->getOutcomePatientsFromSnapshotsUsingReplica('deceased', $reportIds, $branchId, $startDate, $endDate), + default => collect() + }; + } else { + // // Используем реплику для врачей или когда нет отчетов + $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); -// dd($patients->pluck('MedicalHistoryID')->toArray()); + $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 + }; + } // Если есть пациенты, добавляем дополнительные данные if ($patients->isNotEmpty()) { @@ -375,31 +557,65 @@ class ReportController extends Controller // Определяем даты в зависимости от роли [$startDate, $endDate] = $this->getDateRangeForRole($user, $data['startAt'] ?? null, $data['endAt'] ?? null); - // Определяем, является ли пользователь заведующим/администратором - $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); + // Для заведующего/админа ищем отчеты по промежутку дат + $reportIds = []; + $reports = $this->getReportsForDateRange($user->rf_department_id, $startDate, $endDate); + $reportIds = $reports->pluck('report_id')->toArray(); - $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; + // Определяем, используем ли мы снапшоты + $useSnapshots = ($user->isAdmin() || $user->isHeadOfDepartment()) || Carbon::parse($endDate)->isToday() === false; + + if ($useSnapshots) { + // Считаем из снапшотов + $patientTypeMap = [ + 'plan' => 'plan', + 'emergency' => 'emergency', + 'observation' => 'observation', + 'outcome' => null, + 'outcome-discharged' => 'discharged', + 'outcome-transferred' => 'transferred', + 'outcome-deceased' => 'deceased' + ]; + + $patientType = $patientTypeMap[$status] ?? null; + + if ($status === 'outcome') { + // Считаем уникальных пациентов по всем типам исходов + $count = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->whereIn('patient_type', ['discharged', 'transferred', 'deceased']) + ->distinct('rf_medicalhistory_id') + ->count('rf_medicalhistory_id'); + } elseif ($patientType) { + $count = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->where('patient_type', $patientType) + ->distinct('rf_medicalhistory_id') + ->count('rf_medicalhistory_id'); + } else { + $count = 0; } } else { - $count = $this->getPlanOrEmergencyPatients($status, $isHeadOrAdmin, $branchId, $startDate, $endDate, true); + // Определяем, является ли пользователь заведующим/администратором + $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); + + $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 { + $count = $this->getPlanOrEmergencyPatients($status, $isHeadOrAdmin, $branchId, $startDate, $endDate, true); + } } return response()->json($count); } - public function getRecipientIds(bool $isHeadOrAdmin, $branchId, $startDate, $endDate) { - - } - /** * Определить диапазон дат в зависимости от роли */ @@ -419,15 +635,6 @@ class ReportController extends Controller $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)) { // Сдвигаем начало на день назад для всех ролей при выборе одного дня // И всегда для врача @@ -438,17 +645,13 @@ class ReportController extends Controller $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() + ->subDay() ->setTime(6, 0) ->format('Y-m-d H:i:s'); @@ -804,4 +1007,211 @@ class ReportController extends Controller return response()->json()->setStatusCode(200); } + + public function getDepartmentUsers(Request $request) + { + $user = Auth::user(); + + $departmentId = $user->department->rf_mis_department_id; + + $users = MisLpuDoctor::select(['LPUDoctorID', 'FAM_V', 'IM_V', 'OT_V']) + ->active() + ->inMyDepartment() + ->get(); + + return response()->json([ + ...$users + ])->setStatusCode(200); + } + + /** + * Получить все отчеты в промежутке дат (для агрегации данных) + */ + private function getReportsForDateRange($departmentId, $startDate, $endDate) + { + return Report::where('rf_department_id', $departmentId) + ->whereBetween('created_at', [$startDate, $endDate]) + ->orderBy('created_at', 'ASC') + ->get(); + } + + /** + * Получить плановых/экстренных пациентов из снапшотов через реплику + */ + private function getPatientsFromSnapshotsUsingReplica(string $status, array $reportIds, $branchId, $startDate, $endDate) + { + // Получаем ID пациентов из снапшотов + $patientType = $status === 'plan' ? 'plan' : 'emergency'; + + $medicalHistoryIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->where('patient_type', $patientType) + ->pluck('rf_medicalhistory_id') + ->unique() + ->toArray(); + + if (empty($medicalHistoryIds)) { + return collect(); + } + + // Используем существующие скоупы для получения данных из реплики + $query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->with(['surgicalOperations' => function ($query) use ($startDate, $endDate) { + $query->whereBetween('Date', [$startDate, $endDate]); + }]) + ->orderBy('DateRecipient', 'DESC'); + + // Применяем те же фильтры + if ($status === 'plan') { + $query->plan(); + } else { + $query->emergency(); + } + + return $query->get(); + } + + /** + * Получить пациентов под наблюдением из снапшотов через реплику + */ + private function getObservationPatientsFromSnapshotsUsingReplica($userDepartmentId, array $reportIds) + { + // Получаем ID пациентов из снапшотов наблюдения + $medicalHistoryIds = ObservationPatient::whereIn('rf_report_id', $reportIds) + ->pluck('rf_medicalhistory_id') + ->unique() + ->toArray(); + + if (empty($medicalHistoryIds)) { + return collect(); + } + + // Используем существующий метод с фильтрацией по ID из снапшотов + $patients = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->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 getOutcomePatientsFromSnapshotsUsingReplica(string $outcomeType, array $reportIds, $branchId, $startDate, $endDate) + { + // Получаем ID пациентов из снапшотов исходов + $medicalHistoryIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) + ->where('patient_type', $outcomeType) + ->pluck('rf_medicalhistory_id') + ->unique() + ->toArray(); + + if (empty($medicalHistoryIds)) { + return collect(); + } + + // Используем соответствующий метод для получения данных из реплики + return match($outcomeType) { + 'discharged' => $this->getDischargedPatientsByIds($medicalHistoryIds, $branchId), + 'transferred' => $this->getTransferredPatientsByIds($medicalHistoryIds, $branchId), + 'deceased' => $this->getDeceasedPatientsByIds($medicalHistoryIds, $branchId), + default => collect() + }; + } + + /** + * Получить выписанных пациентов по ID из снапшотов + */ + private function getDischargedPatientsByIds(array $medicalHistoryIds, $branchId) + { + return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->with(['surgicalOperations']) + ->orderBy('DateRecipient', 'DESC') + ->get() + ->map(function ($patient) use ($branchId) { + // Получаем информацию о выписке + $migration = $patient->migrations + ->where('rf_kl_VisitResultID', 'in', [1, 7, 8, 9, 10, 11, 48, 49, 124]) + ->whereNotNull('DateOut') + ->sortByDesc('DateOut') + ->first(); + + if ($migration) { + $patient->outcome_type = $this->getOutcomeTypeName($migration->rf_kl_VisitResultID); + $patient->outcome_date = $migration->DateOut; + $patient->visit_result_id = $migration->rf_kl_VisitResultID; + } + + return $patient; + }); + } + + /** + * Получить переведенных пациентов по ID из снапшотов + */ + private function getTransferredPatientsByIds(array $medicalHistoryIds, $branchId) + { + return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->with(['surgicalOperations']) + ->orderBy('DateRecipient', 'DESC') + ->get() + ->map(function ($patient) use ($branchId) { + // Получаем информацию о переводе + $migration = $patient->migrations + ->where('rf_kl_VisitResultID', 'in', [2, 3, 4, 12, 13, 14]) + ->whereNotNull('DateOut') + ->sortByDesc('DateOut') + ->first(); + + if ($migration) { + $patient->outcome_type = $this->getOutcomeTypeName($migration->rf_kl_VisitResultID); + $patient->outcome_date = $migration->DateOut; + $patient->visit_result_id = $migration->rf_kl_VisitResultID; + } + + return $patient; + }); + } + + /** + * Получить умерших пациентов по ID из снапшотов + */ + private function getDeceasedPatientsByIds(array $medicalHistoryIds, $branchId) + { + return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + ->with(['surgicalOperations']) + ->orderBy('DateRecipient', 'DESC') + ->get() + ->map(function ($patient) use ($branchId) { + // Получаем информацию о смерти + $migration = $patient->migrations + ->where('rf_kl_VisitResultID', 'in', [5, 6, 15, 16]) + ->whereNotNull('DateOut') + ->sortByDesc('DateOut') + ->first(); + + if ($migration) { + $patient->outcome_type = $this->getOutcomeTypeName($migration->rf_kl_VisitResultID); + $patient->outcome_date = $migration->DateOut; + $patient->visit_result_id = $migration->rf_kl_VisitResultID; + } + + return $patient; + }); + } } diff --git a/app/Models/MedicalHistorySnapshot.php b/app/Models/MedicalHistorySnapshot.php index d7a6696..75c18d4 100644 --- a/app/Models/MedicalHistorySnapshot.php +++ b/app/Models/MedicalHistorySnapshot.php @@ -18,4 +18,27 @@ class MedicalHistorySnapshot extends Model { return $this->belongsTo(Report::class, 'rf_report_id'); } + + public function medicalHistory() + { + return $this->belongsTo(MisMedicalHistory::class, 'rf_medicalhistory_id', 'MedicalHistoryID'); + } + + // Скоупы для фильтрации + public function scopeForReport($query, $reportId) + { + return $query->where('rf_report_id', $reportId); + } + + public function scopeByPatientType($query, $type) + { + return $query->where('patient_type', $type); + } + + public function scopeByDepartment($query, $departmentId) + { + return $query->whereHas('medicalHistory.migrations.branch', function($q) use ($departmentId) { + $q->where('rf_DepartmentID', $departmentId); + }); + } } diff --git a/app/Models/MisLpuDoctor.php b/app/Models/MisLpuDoctor.php new file mode 100644 index 0000000..4527c53 --- /dev/null +++ b/app/Models/MisLpuDoctor.php @@ -0,0 +1,25 @@ +whereDate('DateEnd', '2222-01-01'); + } + + public function scopeInMyDepartment($query) + { + $user = Auth::user(); + $department = $user->department->first(); + + return $query->where('rf_DepartmentID', $department->rf_mis_department_id); + } +} diff --git a/app/Models/MisMigrationPatient.php b/app/Models/MisMigrationPatient.php index f118fb8..6d72b8b 100644 --- a/app/Models/MisMigrationPatient.php +++ b/app/Models/MisMigrationPatient.php @@ -25,6 +25,9 @@ class MisMigrationPatient extends Model return $this->hasOne(MisMKB::class, 'MKBID', 'rf_MKBID'); } + /** + * Находятся на лечении + */ public function scopeCurrentlyInTreatment($query, $branchId = null, $startDate = null, $endDate = null) { $query->where('rf_kl_VisitResultID', 0) diff --git a/app/Models/Report.php b/app/Models/Report.php index ef48ffc..db7ce9c 100644 --- a/app/Models/Report.php +++ b/app/Models/Report.php @@ -14,6 +14,7 @@ class Report extends Model 'sent_at', 'rf_department_id', 'rf_user_id', + 'rf_lpudoctor_id' ]; public function metrikaResults(): \Illuminate\Database\Eloquent\Relations\HasMany diff --git a/database/migrations/2026_01_30_151807_add_rf_lpudoctor_id_in_reports_table.php b/database/migrations/2026_01_30_151807_add_rf_lpudoctor_id_in_reports_table.php new file mode 100644 index 0000000..e973dd2 --- /dev/null +++ b/database/migrations/2026_01_30_151807_add_rf_lpudoctor_id_in_reports_table.php @@ -0,0 +1,28 @@ +foreignIdFor(\App\Models\MisLpuDoctor::class, 'rf_lpudoctor_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('reports', function (Blueprint $table) { + $table->dropColumn('rf_lpudoctor_id'); + }); + } +}; diff --git a/docker-compose.yml b/docker-compose.yml index 18f48d4..77a2ae0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ services: #PHP Service app: - image: registry.brusoff.su/aokb-onboard:1.0 + image: registry.brusoff.su/aokb-onboard:1.3 build: . container_name: aokb_onboard_app restart: unless-stopped diff --git a/resources/js/Components/ReportSelectDate.vue b/resources/js/Components/ReportSelectDate.vue index 306451e..51caa40 100644 --- a/resources/js/Components/ReportSelectDate.vue +++ b/resources/js/Components/ReportSelectDate.vue @@ -116,6 +116,12 @@ const modelComputed = computed({ reportStore.timestampCurrentRange = [value, value] } + const queryString = window.location.search + const params = new URLSearchParams(queryString) + const userId = params.get('userId') + + reportStore.reportInfo.userId = userId + reportStore.getDataOnReportDate(reportStore.timestampCurrentRange) } }) diff --git a/resources/js/Components/StartButton.vue b/resources/js/Components/StartButton.vue index fb4c6be..f6d0270 100644 --- a/resources/js/Components/StartButton.vue +++ b/resources/js/Components/StartButton.vue @@ -19,8 +19,16 @@ const props = defineProps({ icon: { type: [Object, Function], default: null + }, + tag: { + type: [Object, Function, String], + default: Link + }, + click: { + type: [Function, Object], } }) +const emits = defineEmits(['click']) const buttonThemeOverride = { heightLarge: '64px', @@ -33,10 +41,25 @@ const pThemeOverride = { } const hasIcon = computed(() => props.icon !== null) + +const isLink = computed(() => props.tag === 'link') + +const onClick = () => { + if (isLink.value) return + + emits('click') +}