From 2c6d4a04ccdc2c5d5622e253e348c18900d7eb45 Mon Sep 17 00:00:00 2001 From: brusnitsyn Date: Fri, 31 Jul 2026 17:19:03 +0900 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=B4=20?= =?UTF-8?q?=D0=BD=D0=B0=20DDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GetDepartmentJournalPatientAction.php | 25 ++ .../Contracts/PatientRepositoryInterface.php | 11 + app/Domain/Patient/DTO/PatientData.php | 69 ++++++ .../Actions/CreateReportDutyAction.php | 22 ++ .../GenerateAndSaveReportSnapshotAction.php | 111 +++++++++ .../Actions/SaveObservablePatientsAction.php | 18 ++ .../Actions/SaveReportMetricsAction.php | 79 +++++++ .../Actions/UpdateMetricsReportDutyAction.php | 8 + .../Actions/UpdateReportDutyAction.php | 8 + .../DutyMetricRepositoryInterface.php | 8 + .../PatientSnapshotProviderInterface.php | 17 ++ .../ReportDutyObservablePatientsInterface.php | 11 + .../ReportDutyRepositoryInterface.php | 20 ++ .../ReportDutySnapshotRepositoryInterface.php | 29 +++ .../ReportDuty/DTO/CreateReportDutyDto.php | 22 ++ .../ReportDuty/DTO/MigrationSnapshotData.php | 26 +++ .../ReportDuty/DTO/PatientSnapshotData.php | 33 +++ .../DTO/ReanimationSnapshotData.php | 23 ++ .../ReportDuty/DTO/SnapshotStatistics.php | 31 +++ .../ReportDuty/Events/ReportDutyCreated.php | 8 + .../ReportDuty/Events/ReportDutyUpdated.php | 8 + app/Domain/ReportDuty/Models/ReportDuty.php | 27 +++ .../ReportDuty/Models/ValueObjects/Id.php | 13 ++ .../Models/ValueObjects/PeriodEnd.php | 13 ++ .../Models/ValueObjects/PeriodStart.php | 13 ++ .../Models/ValueObjects/PeriodType.php | 13 ++ .../Models/ValueObjects/ReportDate.php | 13 ++ .../Models/ValueObjects/ReportMonth.php | 13 ++ .../Models/ValueObjects/ReportYear.php | 13 ++ .../Models/ValueObjects/RfDepartmentId.php | 13 ++ .../Models/ValueObjects/RfLpudoctorId.php | 13 ++ .../Models/ValueObjects/RfUserId.php | 13 ++ .../ReportDuty/Models/ValueObjects/SentAt.php | 13 ++ .../Models/ValueObjects/StatusId.php | 13 ++ .../ReportDutyStatisticsCalculator.php | 212 +++++++++++++++++ .../Services/SnapshotDataTransformer.php | 71 ++++++ .../Repositories/DutyMetricRepository.php | 17 ++ .../Patient/PatientRepository.php | 44 ++++ .../ReportDutyObservablePatients.php | 41 ++++ .../Repositories/ReportDutyRepository.php | 76 ++++++ .../ReportDutySnapshotRepository.php | 221 ++++++++++++++++++ .../Controllers/Web/DutyReportController.php | 65 ++++++ .../Controllers/Web/PatientController.php | 21 ++ .../Http/Requests/CreateDutyReportRequest.php | 27 +++ .../Services/MisPatientSnapshotProvider.php | 112 +++++++++ .../NurseReportPatientSnapshotProvider.php | 106 +++++++++ app/Providers/AppServiceProvider.php | 7 + routes/api.php | 6 + 48 files changed, 1796 insertions(+) create mode 100644 app/Domain/Patient/Actions/GetDepartmentJournalPatientAction.php create mode 100644 app/Domain/Patient/Contracts/PatientRepositoryInterface.php create mode 100644 app/Domain/Patient/DTO/PatientData.php create mode 100644 app/Domain/ReportDuty/Actions/CreateReportDutyAction.php create mode 100644 app/Domain/ReportDuty/Actions/GenerateAndSaveReportSnapshotAction.php create mode 100644 app/Domain/ReportDuty/Actions/SaveObservablePatientsAction.php create mode 100644 app/Domain/ReportDuty/Actions/SaveReportMetricsAction.php create mode 100644 app/Domain/ReportDuty/Actions/UpdateMetricsReportDutyAction.php create mode 100644 app/Domain/ReportDuty/Actions/UpdateReportDutyAction.php create mode 100644 app/Domain/ReportDuty/Contracts/DutyMetricRepositoryInterface.php create mode 100644 app/Domain/ReportDuty/Contracts/PatientSnapshotProviderInterface.php create mode 100644 app/Domain/ReportDuty/Contracts/ReportDutyObservablePatientsInterface.php create mode 100644 app/Domain/ReportDuty/Contracts/ReportDutyRepositoryInterface.php create mode 100644 app/Domain/ReportDuty/Contracts/ReportDutySnapshotRepositoryInterface.php create mode 100644 app/Domain/ReportDuty/DTO/CreateReportDutyDto.php create mode 100644 app/Domain/ReportDuty/DTO/MigrationSnapshotData.php create mode 100644 app/Domain/ReportDuty/DTO/PatientSnapshotData.php create mode 100644 app/Domain/ReportDuty/DTO/ReanimationSnapshotData.php create mode 100644 app/Domain/ReportDuty/DTO/SnapshotStatistics.php create mode 100644 app/Domain/ReportDuty/Events/ReportDutyCreated.php create mode 100644 app/Domain/ReportDuty/Events/ReportDutyUpdated.php create mode 100644 app/Domain/ReportDuty/Models/ReportDuty.php create mode 100644 app/Domain/ReportDuty/Models/ValueObjects/Id.php create mode 100644 app/Domain/ReportDuty/Models/ValueObjects/PeriodEnd.php create mode 100644 app/Domain/ReportDuty/Models/ValueObjects/PeriodStart.php create mode 100644 app/Domain/ReportDuty/Models/ValueObjects/PeriodType.php create mode 100644 app/Domain/ReportDuty/Models/ValueObjects/ReportDate.php create mode 100644 app/Domain/ReportDuty/Models/ValueObjects/ReportMonth.php create mode 100644 app/Domain/ReportDuty/Models/ValueObjects/ReportYear.php create mode 100644 app/Domain/ReportDuty/Models/ValueObjects/RfDepartmentId.php create mode 100644 app/Domain/ReportDuty/Models/ValueObjects/RfLpudoctorId.php create mode 100644 app/Domain/ReportDuty/Models/ValueObjects/RfUserId.php create mode 100644 app/Domain/ReportDuty/Models/ValueObjects/SentAt.php create mode 100644 app/Domain/ReportDuty/Models/ValueObjects/StatusId.php create mode 100644 app/Domain/ReportDuty/Services/ReportDutyStatisticsCalculator.php create mode 100644 app/Domain/ReportDuty/Services/SnapshotDataTransformer.php create mode 100644 app/Infrastructure/Database/Repositories/DutyMetricRepository.php create mode 100644 app/Infrastructure/Database/Repositories/Patient/PatientRepository.php create mode 100644 app/Infrastructure/Database/Repositories/ReportDutyObservablePatients.php create mode 100644 app/Infrastructure/Database/Repositories/ReportDutyRepository.php create mode 100644 app/Infrastructure/Database/Repositories/ReportDutySnapshotRepository.php create mode 100644 app/Infrastructure/Http/Controllers/Web/DutyReportController.php create mode 100644 app/Infrastructure/Http/Controllers/Web/PatientController.php create mode 100644 app/Infrastructure/Http/Requests/CreateDutyReportRequest.php create mode 100644 app/Infrastructure/Services/MisPatientSnapshotProvider.php create mode 100644 app/Infrastructure/Services/NurseReportPatientSnapshotProvider.php diff --git a/app/Domain/Patient/Actions/GetDepartmentJournalPatientAction.php b/app/Domain/Patient/Actions/GetDepartmentJournalPatientAction.php new file mode 100644 index 0000000..72be1d8 --- /dev/null +++ b/app/Domain/Patient/Actions/GetDepartmentJournalPatientAction.php @@ -0,0 +1,25 @@ +patientRepository->getCurrentByDepartmentId($departmentId); + + if ($patients && $patients->count() > 0) { + return $patients->map(fn ($patient) => PatientData::fromMis($patient)); + } + + return null; + } +} diff --git a/app/Domain/Patient/Contracts/PatientRepositoryInterface.php b/app/Domain/Patient/Contracts/PatientRepositoryInterface.php new file mode 100644 index 0000000..e8277f7 --- /dev/null +++ b/app/Domain/Patient/Contracts/PatientRepositoryInterface.php @@ -0,0 +1,11 @@ + $this->medicalHistoryId, + 'migrationPatientId' => $this->migrationPatientId, + 'extractMigrationPatientId' => $this->extractMigrationPatientId, + 'diagnosisId' => $this->diagnosisId, + 'attendingDoctorId' => $this->attendingDoctorId, + 'bedActionId' => $this->bedActionId, + 'lastName' => $this->lastName, + 'firstName' => $this->firstName, + 'middleName' => $this->middleName, + 'birthDate' => $this->birthDate, + 'recipientDate' => $this->recipientDate, + 'extractDate' => $this->extractDate, + 'migrationIngoingDate' => $this->migrationIngoingDate, + 'migrationOutDate' => $this->migrationOutDate, + 'migrationBranchId' => $this->migrationBranchId, + 'departmentName' => $this->departmentName, + ]; + } + + public static function fromMis(array $data): self + { + return new self( + $data['rf_MedicalHistoryID'], + $data['rf_MigrationPatientID'], + $data['rf_ExtractMigrationPatientID'], + $data['rf_DiagnosisID'], + $data['rf_AttendingDoctorID'], + $data['rf_BedActionID'], + $data['FAMILY'], + $data['Name'], + $data['OT'], + $data['BD'], + $data['DateRecipient'], + $data['DateExtract'], + $data['DateIngoing'], + $data['DateOut'], + $data['rf_StationarBranchID'], + $data['DepartmentNAME'], + ); + } +} diff --git a/app/Domain/ReportDuty/Actions/CreateReportDutyAction.php b/app/Domain/ReportDuty/Actions/CreateReportDutyAction.php new file mode 100644 index 0000000..fb1ac89 --- /dev/null +++ b/app/Domain/ReportDuty/Actions/CreateReportDutyAction.php @@ -0,0 +1,22 @@ +repository->findOrCreate($dto); + } +} diff --git a/app/Domain/ReportDuty/Actions/GenerateAndSaveReportSnapshotAction.php b/app/Domain/ReportDuty/Actions/GenerateAndSaveReportSnapshotAction.php new file mode 100644 index 0000000..9f5593b --- /dev/null +++ b/app/Domain/ReportDuty/Actions/GenerateAndSaveReportSnapshotAction.php @@ -0,0 +1,111 @@ +department->rf_mis_department_id ?? $report->rf_department_id; + $userId = $userId ?? $report->rf_user_id; + + // 1. Определяем, есть ли отчёт медсестры за этот период + $nurseReportExists = \App\Models\ReportNurse::where('rf_department_id', $report->rf_department_id) + ->where('period_start', $report->period_start) + ->where('period_end', $report->period_end) + ->exists(); + + // 2. Получаем данные пациентов через соответствующий провайдер + if ($nurseReportExists) { + $patients = $this->nurseProvider->getPatients($dateRange, $departmentId); + } else { + $patients = $this->misProvider->getPatients($dateRange, $departmentId); + } + + // 3. Извлекаем миграции и реанимации из пациентов для сохранения + $migrations = collect(); + $reanimations = collect(); + + foreach ($patients as $patient) { + // Преобразуем миграции в DTO + foreach ($patient->migrations as $migData) { + $migrations->push(new \App\Domain\ReportDuty\DTO\MigrationSnapshotData( + id: $migData['id'], + medicalHistoryId: $patient->id, + ingoingDate: $migData['ingoing_date'], + outDate: $migData['out_date'], + diagnosisId: $migData['diagnosis_id'], + diagnosisCode: $migData['diagnosis_code'], + diagnosisName: $migData['diagnosis_name'], + interruptedEventId: $migData['interrupted_event_id'], + stationarBranchId: $migData['stationar_branch_id'], + departmentId: $migData['department_id'], + visitResultId: $migData['visit_result_id'], + statCureResultId: $migData['stat_cure_result_id'], + userId: $migData['user_id'], + misUserId: $migData['mis_user_id'], + comment: $migData['comment'], + reanimations: $migData['reanimations'] ?? [], + )); + } + // Реанимации будем брать из миграций, но можно и отдельно, здесь пока пропускаем + // (в репозитории они будут обработаны из миграций, но у нас нет отдельного хранилища) + } + + // Заметка: реанимации сейчас хранятся внутри миграций, но в репозитории мы ожидаем их отдельно. + // Чтобы упростить, можно либо передать пустую коллекцию, либо извлечь из миграций. + // В оригинале они сохранялись в отдельной таблице. Мы можем передать пустую коллекцию, + // но если хотим сохранять реанимации, нужно их собрать из $patient->migrations['reanimations']. + + // Я пока передам пустую коллекцию, так как в провайдерах реанимации не извлекаются + // (кроме МИС, но там они внутри миграций). Можно позже доработать. + + // 4. Сохраняем снимок в БД + $saveResult = $this->snapshotRepository->upsertSnapshot( + $report->id, + $patients, + $migrations->lazy(), + collect()->lazy(), // пока без реанимаций + $userId, + $departmentId + ); + + // 5. Рассчитываем статистику + $statistics = $this->statisticsCalculator->calculate($patients, $dateRange); + + // 6. Сохраняем метрики + $this->saveMetricsAction->execute($report, $statistics, 0); // staff = 0 пока + + Log::info('Report snapshot generated', [ + 'report_id' => $report->id, + 'saved_patients' => $saveResult['savedPatients'], + 'saved_migrations' => $saveResult['savedMigrations'], + 'statistics' => $statistics, + ]); + + return [ + 'statistics' => $statistics, + 'saved' => $saveResult, + ]; + } +} diff --git a/app/Domain/ReportDuty/Actions/SaveObservablePatientsAction.php b/app/Domain/ReportDuty/Actions/SaveObservablePatientsAction.php new file mode 100644 index 0000000..be8aa52 --- /dev/null +++ b/app/Domain/ReportDuty/Actions/SaveObservablePatientsAction.php @@ -0,0 +1,18 @@ +observablePatients->upsertObservations($report, $observables); + } +} diff --git a/app/Domain/ReportDuty/Actions/SaveReportMetricsAction.php b/app/Domain/ReportDuty/Actions/SaveReportMetricsAction.php new file mode 100644 index 0000000..0cf365e --- /dev/null +++ b/app/Domain/ReportDuty/Actions/SaveReportMetricsAction.php @@ -0,0 +1,79 @@ +byStatus; + $byUrgency = $stats->byUrgency; + $admitted = $stats->admitted; + + // === Базовые счётчики === + $patientsIsRecipient = $statsOverride['recipient'] ?? ($byStatus['recipient'] ?? 0); + $patientsInDepartment = $statsOverride['in_department'] ?? ($byStatus['in_department'] ?? 0); + $patientsIsDischarged = $statsOverride['outcome'] ?? $stats->outcome; + $patientsIsTransferred = $stats->transferred; + $patientsIsDeceased = $byStatus['deceased'] ?? 0; + + // Ср. койко-день + $totalPatients = $stats->totalPatients; + $totalBedDays = $stats->totalBedDays; + $avgBedDay = $totalPatients > 0 ? round($totalBedDays / $totalPatients, 2) : 0; + + // Пред. опер. койко-день + $patientsWithOps = $stats->patientsWithOperations; + $totalPreOpDays = $stats->totalPreopBedDays; + $avgPreOpBedDay = $patientsWithOps > 0 ? round($totalPreOpDays / $patientsWithOps, 2) : 0; + + // % загруженности + $bedsInDepartment = DepartmentMetrikaDefault::where('rf_department_id', $report->rf_department_id) + ->where('rf_metrika_item_id', 1) + ->where('date_end', '>', Carbon::now()) + ->value('value') ?? 0; + + $occupancyPercent = $bedsInDepartment > 0 ? round(($patientsInDepartment * 100) / $bedsInDepartment, 2) : 0; + + // % летальности + $mortalityPercent = $totalPatients > 0 ? round(($patientsIsDeceased * 100) / $totalPatients, 2) : 0; + + // Операции + $totalOperations = $stats->totalOperations; + $plannedOperations = $stats->plannedOperations; + $urgentOperations = $stats->urgentOperations; + + // === Сохранение метрик === + $this->metricRepository->saveMetric($report->id, 1, $bedsInDepartment); // Кол-во коек + $this->metricRepository->saveMetric($report->id, 8, $patientsInDepartment); // Пациентов в отделении + $this->metricRepository->saveMetric($report->id, 3, $patientsIsRecipient); // Поступило + $this->metricRepository->saveMetric($report->id, 15, $patientsIsDischarged); // Выписано + $this->metricRepository->saveMetric($report->id, 7, $patientsIsDischarged); // Выписано (дубль?) + $this->metricRepository->saveMetric($report->id, 13, $patientsIsTransferred); // Переведено + $this->metricRepository->saveMetric($report->id, 9, $patientsIsDeceased); // Умерло + + $this->metricRepository->saveMetric($report->id, 4, $admitted['planned'] ?? 0); // Планово поступило + $this->metricRepository->saveMetric($report->id, 12, $admitted['urgent'] ?? 0); // Экстренно поступило + + $this->metricRepository->saveMetric($report->id, 22, $occupancyPercent); // % загруженности + $this->metricRepository->saveMetric($report->id, 25, round($totalBedDays, 2)); // Всего койко-дней + $this->metricRepository->saveMetric($report->id, 18, $avgBedDay); // Ср. койко-день + $this->metricRepository->saveMetric($report->id, 26, round($totalPreOpDays, 2)); // Пред. опер. койко-день (сумма) + $this->metricRepository->saveMetric($report->id, 27, $patientsWithOps); // Пациентов с операциями (знаменатель) + $this->metricRepository->saveMetric($report->id, 21, $avgPreOpBedDay); // Ср. Пред. опер. койко-день + $this->metricRepository->saveMetric($report->id, 19, $mortalityPercent); // % летальности + $this->metricRepository->saveMetric($report->id, 11, $plannedOperations); // Плановых операций + $this->metricRepository->saveMetric($report->id, 10, $urgentOperations); // Экстренных операций + $this->metricRepository->saveMetric($report->id, 17, $staff); // Мед. персонал + } +} diff --git a/app/Domain/ReportDuty/Actions/UpdateMetricsReportDutyAction.php b/app/Domain/ReportDuty/Actions/UpdateMetricsReportDutyAction.php new file mode 100644 index 0000000..821b38e --- /dev/null +++ b/app/Domain/ReportDuty/Actions/UpdateMetricsReportDutyAction.php @@ -0,0 +1,8 @@ + + */ + public function getPatients(DateRange $dateRange, int $departmentId): LazyCollection; +} diff --git a/app/Domain/ReportDuty/Contracts/ReportDutyObservablePatientsInterface.php b/app/Domain/ReportDuty/Contracts/ReportDutyObservablePatientsInterface.php new file mode 100644 index 0000000..47c042b --- /dev/null +++ b/app/Domain/ReportDuty/Contracts/ReportDutyObservablePatientsInterface.php @@ -0,0 +1,11 @@ + $patients + * @param LazyCollection $migrations + * @param LazyCollection $reanimations + * @param int $userId + * @param int $departmentId + * @return array{savedPatients: int, savedMigrations: int, savedReanimations: int} + */ + public function upsertSnapshot( + int $reportDutyId, + LazyCollection $patients, + LazyCollection $migrations, + LazyCollection $reanimations, + int $userId, + int $departmentId + ): array; +} diff --git a/app/Domain/ReportDuty/DTO/CreateReportDutyDto.php b/app/Domain/ReportDuty/DTO/CreateReportDutyDto.php new file mode 100644 index 0000000..12f8e24 --- /dev/null +++ b/app/Domain/ReportDuty/DTO/CreateReportDutyDto.php @@ -0,0 +1,22 @@ + 0, 'planned' => 0, 'urgent' => 0], + public int $inReanimation = 0, + public int $admittedToday = 0, + public int $inDepartment = 0, + public int $planned = 0, + public int $deceased = 0, + public int $transferred = 0, + public int $discharged = 0, + public int $outcome = 0, + public float $totalBedDays = 0, + public float $totalPreopBedDays = 0, + public int $patientsWithOperations = 0, + public int $totalOperations = 0, + public int $plannedOperations = 0, + public int $urgentOperations = 0, + public int $totalPatients = 0, + ) { + } +} diff --git a/app/Domain/ReportDuty/Events/ReportDutyCreated.php b/app/Domain/ReportDuty/Events/ReportDutyCreated.php new file mode 100644 index 0000000..d142ff1 --- /dev/null +++ b/app/Domain/ReportDuty/Events/ReportDutyCreated.php @@ -0,0 +1,8 @@ + 'date:Y-m-d', + 'sent_at' => 'datetime:Y-m-d H:i:s', + 'period_start' => 'datetime:Y-m-d H:i:s', + 'period_end' => 'datetime:Y-m-d H:i:s', + ]; +} diff --git a/app/Domain/ReportDuty/Models/ValueObjects/Id.php b/app/Domain/ReportDuty/Models/ValueObjects/Id.php new file mode 100644 index 0000000..8c7e583 --- /dev/null +++ b/app/Domain/ReportDuty/Models/ValueObjects/Id.php @@ -0,0 +1,13 @@ +start(); + $periodEnd = $dateRange->end(); + $periodStartCarbon = Carbon::parse($periodStart); + $periodEndCarbon = Carbon::parse($periodEnd); + + $uniqueOperationIds = []; + + // Используем LazyCollection, чтобы не загружать всё в память + foreach ($patients as $patient) { + /** @var PatientSnapshotData $patient */ + + // ===== СТАТУСЫ ===== + // Используем уже вычисленные поля (patientStatus, periodFlags и т.д.) + // Но для расчёта койко-дней и операций нам нужны миграции и операции внутри периода. + // Также мы можем переиспользовать логику, которая была в saveReportSnapshot. + // Но мы перенесём сюда всю обработку, потому что здесь мы вычисляем статистики на основе сырых данных. + + $hasRecipientInPeriod = false; + $hasTransferInPeriod = false; + $hasDischargeInPeriod = false; + $hasDeathInPeriod = false; + $hasActiveMigrationInPeriod = false; + $hasExtractInPeriod = false; + + // Проверяем extract_date из карты + if ($patient->extractDate) { + $extractDate = Carbon::parse($patient->extractDate); + if ($extractDate >= $periodStartCarbon && $extractDate <= $periodEndCarbon) { + $hasExtractInPeriod = true; + } + } + + // Идём по миграциям пациента (в массиве migrations) + $migrations = $patient->migrations; // массив, но можно преобразовать в коллекцию + $migrationOutDate = null; + $migrationVisitResultId = null; + + foreach ($migrations as $migration) { + $ingoingDate = isset($migration['ingoing_date']) ? Carbon::parse($migration['ingoing_date']) : null; + $outDate = isset($migration['out_date']) ? Carbon::parse($migration['out_date']) : null; + $visitResultId = $migration['visit_result_id'] ?? null; + $statCureResultId = $migration['stat_cure_result_id'] ?? null; + + // Поступление в периоде + if ($ingoingDate && $ingoingDate >= $periodStartCarbon && $ingoingDate <= $periodEndCarbon) { + $hasRecipientInPeriod = true; + } + + // Активная миграция на конец периода + if ($ingoingDate && $ingoingDate <= $periodEndCarbon) { + if (!$outDate || $outDate > $periodEndCarbon) { + $hasActiveMigrationInPeriod = true; + } + } + + // Выбытие в периоде (есть out_date в периоде) + if ($outDate && $outDate >= $periodStartCarbon && $outDate <= $periodEndCarbon) { + $migrationOutDate = $outDate; + $migrationVisitResultId = $visitResultId; + if (in_array($visitResultId, [5, 15])) { + $hasDeathInPeriod = true; + } elseif (in_array($visitResultId, [4, 14])) { + $hasTransferInPeriod = true; + } else { + $hasDischargeInPeriod = true; + } + } + } + + // Если нет исхода по миграциям, проверяем extract_date и death_date + if (!$hasDeathInPeriod && !$hasTransferInPeriod && !$hasDischargeInPeriod) { + if ($hasExtractInPeriod) { + $visitResultId = $patient->visitResultId; + $deathDate = $patient->deathDate ? Carbon::parse($patient->deathDate) : null; + if ($deathDate && $deathDate <= $periodEndCarbon) { + $hasDeathInPeriod = true; + } elseif (in_array($visitResultId, [4, 14])) { + $hasTransferInPeriod = true; + } else { + $hasDischargeInPeriod = true; + } + } + } + + // Заполнение статистики по статусам + if ($hasDeathInPeriod) { + $stats->deceased++; + $stats->byStatus['deceased'] = ($stats->byStatus['deceased'] ?? 0) + 1; + } elseif ($hasTransferInPeriod) { + $stats->transferred++; + $stats->byStatus['transferred'] = ($stats->byStatus['transferred'] ?? 0) + 1; + } elseif ($hasDischargeInPeriod) { + $stats->discharged++; + $stats->outcome++; + $stats->byStatus['discharged'] = ($stats->byStatus['discharged'] ?? 0) + 1; + } elseif ($hasActiveMigrationInPeriod) { + $stats->inDepartment++; + $stats->byStatus['in_department'] = ($stats->byStatus['in_department'] ?? 0) + 1; + } + + // Поступление (recipient) + if ($hasRecipientInPeriod) { + $stats->admitted['today']++; + $stats->byStatus['recipient'] = ($stats->byStatus['recipient'] ?? 0) + 1; + if ($patient->urgencyId == 1) { + $stats->admitted['planned']++; + $stats->planned++; + } + if ($patient->urgencyId == 2) { + $stats->admitted['urgent']++; + } + } + + // Срочность (из patientUrgency, но можно из urgencyId) + if ($patient->patientUrgency) { + $stats->byUrgency[$patient->patientUrgency] = ($stats->byUrgency[$patient->patientUrgency] ?? 0) + 1; + } + + // Реанимация + if ($patient->inReanimation) { + $stats->inReanimation++; + } + + // Поступил сегодня (admittedToday) + if ($patient->admittedToday) { + $stats->admittedToday++; + } + + // ===== КОЙКО-ДНИ И ОПЕРАЦИИ ===== + // Нужно взять первую миграцию (актуальную) для расчёта койко-дней + // берём первую миграцию из массива (индекс 0) + if (!empty($migrations)) { + $firstMigration = $migrations[0]; + $migrationStart = isset($firstMigration['ingoing_date']) ? Carbon::parse($firstMigration['ingoing_date']) : null; + $migrationEnd = isset($firstMigration['out_date']) ? Carbon::parse($firstMigration['out_date']) : null; + + if ($migrationStart) { + // Проверяем пересечение с отчетным периодом + $hasIntersection = $migrationStart <= $periodEndCarbon && + ($migrationEnd === null || $migrationEnd >= $periodStartCarbon); + if ($hasIntersection) { + $calcStart = $migrationStart > $periodStartCarbon ? $migrationStart : $periodStartCarbon; + $calcEnd = $migrationEnd && $migrationEnd < $periodEndCarbon ? $migrationEnd : $periodEndCarbon; + + $bedDays = $calcStart->diffInDays($calcEnd); + $stats->totalBedDays += max(0, $bedDays); + + // Предоперационные дни: операции в периоде + $opsInPeriod = collect($patient->operations ?? []) + ->filter(function ($op) use ($periodStartCarbon, $periodEndCarbon) { + $opStart = isset($op['start_date']) ? Carbon::parse($op['start_date']) : null; + return $opStart && $opStart >= $periodStartCarbon && $opStart < $periodEndCarbon; + }); + + if ($opsInPeriod->isNotEmpty()) { + $stats->patientsWithOperations++; + $firstOpInPeriod = $opsInPeriod->sortBy('start_date')->first(); + if ($firstOpInPeriod && isset($firstOpInPeriod['start_date'])) { + $opDate = Carbon::parse($firstOpInPeriod['start_date']); + if ($opDate > $migrationStart) { + $preOpDays = $migrationStart->copy()->startOfDay() + ->diffInDays($opDate->copy()->startOfDay()); + $stats->totalPreopBedDays += max(0, $preOpDays); + } + } + } + + // Собираем операции для уникализации + foreach ($patient->operations ?? [] as $operation) { + $opStart = isset($operation['start_date']) ? Carbon::parse($operation['start_date']) : null; + if ($opStart && $opStart >= $periodStartCarbon && $opStart < $periodEndCarbon) { + $opId = $operation['id'] ?? null; + if ($opId) { + $uniqueOperationIds[$opId] = [ + 'id' => $opId, + 'urgent_status' => $operation['urgent_status'] ?? null, + ]; + } + } + } + } + } + } + + // Увеличиваем общее количество пациентов (для среднего) + $stats->totalPatients++; + } + + // После цикла заполняем итоговые показатели по операциям + $stats->totalOperations = count($uniqueOperationIds); + $stats->plannedOperations = collect($uniqueOperationIds)->where('urgent_status', 6)->count(); + $stats->urgentOperations = collect($uniqueOperationIds)->whereIn('urgent_status', [4,5])->count(); + + return $stats; + } +} diff --git a/app/Domain/ReportDuty/Services/SnapshotDataTransformer.php b/app/Domain/ReportDuty/Services/SnapshotDataTransformer.php new file mode 100644 index 0000000..6ef8b10 --- /dev/null +++ b/app/Domain/ReportDuty/Services/SnapshotDataTransformer.php @@ -0,0 +1,71 @@ + $patients + * @return array{patients: LazyCollection, migrations: LazyCollection, reanimations: LazyCollection} + */ + public function transform(LazyCollection $patients): array + { + $migrationsCollect = collect(); + $reanimationsCollect = collect(); + + foreach ($patients as $patient) { + // Для каждой миграции пациента создаём MigrationSnapshotData + foreach ($patient->migrations as $migration) { + $migrationsCollect->push(new MigrationSnapshotData( + id: $migration['id'] ?? 0, + medicalHistoryId: $patient->id, + ingoingDate: $migration['ingoing_date'] ?? null, + outDate: $migration['out_date'] ?? null, + diagnosisId: $migration['diagnosis_id'] ?? null, + diagnosisCode: $migration['diagnosis_code'] ?? null, + diagnosisName: $migration['diagnosis_name'] ?? null, + interruptedEventId: $migration['interrupted_event_id'] ?? null, + stationarBranchId: $migration['stationar_branch_id'] ?? null, + departmentId: $migration['department_id'] ?? null, + visitResultId: $migration['visit_result_id'] ?? null, + statCureResultId: $migration['stat_cure_result_id'] ?? null, + userId: $migration['user_id'] ?? null, + misUserId: $migration['mis_user_id'] ?? null, + comment: $migration['comment'] ?? null, + reanimations: $migration['reanimations'] ?? [], + )); + + // Для каждой реанимации в миграции + foreach ($migration['reanimations'] ?? [] as $reanimation) { + $reanimationsCollect->push(new ReanimationSnapshotData( + id: $reanimation['id'] ?? 0, + migrationPatientId: $migration['id'] ?? 0, + medicalHistoryId: $patient->id, + inDate: $reanimation['in_date'] ?? null, + outDate: $reanimation['out_date'] ?? null, + description: $reanimation['description'] ?? null, + stationarBranchId: $reanimation['stationar_branch_id'] ?? null, + migrationStationarBranchId: $reanimation['migration_stationar_branch_id'] ?? null, + migrationDepartmentId: $reanimation['migration_department_id'] ?? null, + doctorId: $reanimation['doctor_id'] ?? null, + userId: $reanimation['user_id'] ?? null, + misUserId: $reanimation['mis_user_id'] ?? null, + comment: $reanimation['comment'] ?? null, + )); + } + } + } + + // Преобразуем в LazyCollection для единообразия + return [ + 'patients' => $patients, // уже LazyCollection + 'migrations' => LazyCollection::make($migrationsCollect), + 'reanimations' => LazyCollection::make($reanimationsCollect), + ]; + } +} diff --git a/app/Infrastructure/Database/Repositories/DutyMetricRepository.php b/app/Infrastructure/Database/Repositories/DutyMetricRepository.php new file mode 100644 index 0000000..6ce9bf3 --- /dev/null +++ b/app/Infrastructure/Database/Repositories/DutyMetricRepository.php @@ -0,0 +1,17 @@ + $reportId, 'rf_metrika_item_id' => $metricId], + ['value' => $value] + ); + } +} diff --git a/app/Infrastructure/Database/Repositories/Patient/PatientRepository.php b/app/Infrastructure/Database/Repositories/Patient/PatientRepository.php new file mode 100644 index 0000000..9a484d4 --- /dev/null +++ b/app/Infrastructure/Database/Repositories/Patient/PatientRepository.php @@ -0,0 +1,44 @@ +table('stt_LastJournalHistoryEntry as lj') + ->join('stt_MedicalHistory as mh', 'mh.MedicalHistoryID', '=', 'lj.rf_MedicalHistoryID') + ->join('stt_MigrationPatient as mp', 'mp.MigrationPatientID', '=', 'lj.rf_MigrationPatientID') + ->leftJoin('stt_StationarBranch as sb', 'sb.StationarBranchID', '=', 'mp.rf_StationarBranchID') + ->leftJoin('oms_Department as d', 'd.DepartmentID', '=', 'sb.rf_DepartmentID') + ->select($this::JOURNAL_SELECT) + ->where('lj.rf_ExtractMigrationPatientId', '=', 0) + ->where('d.DepartmentID', '=', $departmentId) + ->orderBy('mh.DateRecipient'); + + $results = Cache::remember($this::TTL_KEY . $departmentId, $this::TTL, function () use ($query) { + return $query->get(); + }); + + return $results; + } + + public function getById(int $patientId) + { + // TODO: Implement getById() method. + } +} diff --git a/app/Infrastructure/Database/Repositories/ReportDutyObservablePatients.php b/app/Infrastructure/Database/Repositories/ReportDutyObservablePatients.php new file mode 100644 index 0000000..58981cd --- /dev/null +++ b/app/Infrastructure/Database/Repositories/ReportDutyObservablePatients.php @@ -0,0 +1,41 @@ + $observable['original_id'] ?? $observable['id'], + 'source_type' => $observable['source_type'] ?? 'mis', + 'observable_in' => $observable['observable_in'] ?? $report->period_start, + ], [ + 'source_type' => $observable['source_type'] ?? 'mis', + 'original_id' => $observable['original_id'] ?? $observable['id'], + 'observable_in' => $observable['observable_in'] ?? $report->period_start, + 'observable_out' => $observable['observable_out'] ?? null, + 'observable_reason' => $observable['observable_reason'], + 'out_reason' => $observable['out_reason'] ?? null, + 'medical_card_number' => $observable['medical_card_number'], + 'full_name' => $observable['full_name'], + 'birth_date' => $observable['birth_date'], + 'recipient_date' => $observable['recipient_date'], + 'extract_date' => $observable['extract_date'], + 'death_date' => $observable['death_date'], + 'male' => $observable['male'], + 'urgency_id' => $observable['urgency_id'], + 'hospital_result_id' => $observable['hospital_result_id'], + 'visit_result_id' => $observable['visit_result_id'], + 'comment' => $observable['observable_reason'], + 'user_id' => $report->rf_user_id, + ]); + } + } +} diff --git a/app/Infrastructure/Database/Repositories/ReportDutyRepository.php b/app/Infrastructure/Database/Repositories/ReportDutyRepository.php new file mode 100644 index 0000000..76632bb --- /dev/null +++ b/app/Infrastructure/Database/Repositories/ReportDutyRepository.php @@ -0,0 +1,76 @@ + $dto->reportDate, + 'period_start' => $dto->periodStart, + 'period_end' => $dto->periodEnd, + 'rf_department_id' => $dto->rfDepartmentId, + ]; + + $data = [ + 'report_date' => $dto->reportDate, + 'sent_at' => Carbon::now()->format('Y-m-d H:i:s'), + 'period_type' => $dto->periodType, + 'period_start' => $dto->periodStart, + 'period_end' => $dto->periodEnd, + 'status_id' => $dto->statusId, + 'rf_lpudoctor_id' => $dto->rfLpuDoctorId, + 'rf_department_id' => $dto->rfDepartmentId, + 'rf_user_id' => $dto->rfUserId, + ]; + + // Проверяем существование + $exists = ReportDuty::where($attributes)->exists(); + + if ($exists) { + // Если существует, обновляем только некоторые поля + $updateData = array_diff_key($data, array_flip(['rf_lpudoctor_id', 'rf_user_id'])); + $report = ReportDuty::updateOrCreate($attributes, $updateData); + } else { + $report = ReportDuty::create($data); + } + + return $report; + } + + public function save(\App\Domain\ReportDuty\Models\ReportDuty $reportDuty): ?\App\Domain\ReportDuty\Models\ReportDuty + { + // TODO: Implement save() method. + } + + public function update(\App\Domain\ReportDuty\Models\ReportDuty $reportDuty): ?\App\Domain\ReportDuty\Models\ReportDuty + { + // TODO: Implement update() method. + } + + public function findByPeriod(string $startAt, string $endAt): ?\App\Domain\ReportDuty\Models\ReportDuty + { + // TODO: Implement findByPeriod() method. + } + + public function findByDepartment(int $departmentId): ?\App\Domain\ReportDuty\Models\ReportDuty + { + // TODO: Implement findByDepartment() method. + } + + public function findByUser(int $userId): ?\App\Domain\ReportDuty\Models\ReportDuty + { + // TODO: Implement findByUser() method. + } + + public function findById(int $id): ?\App\Domain\ReportDuty\Models\ReportDuty + { + // TODO: Implement findById() method. + } +} diff --git a/app/Infrastructure/Database/Repositories/ReportDutySnapshotRepository.php b/app/Infrastructure/Database/Repositories/ReportDutySnapshotRepository.php new file mode 100644 index 0000000..2d29d8c --- /dev/null +++ b/app/Infrastructure/Database/Repositories/ReportDutySnapshotRepository.php @@ -0,0 +1,221 @@ +isEmpty()) { + return ['savedPatients' => 0, 'savedMigrations' => 0, 'savedReanimations' => 0]; + } + + $savedPatients = 0; + $savedMigrations = 0; + $savedReanimations = 0; + + DB::transaction(function () use ( + $reportDutyId, + $patients, + $migrations, + $reanimations, + $userId, + $departmentId, + &$savedPatients, + &$savedMigrations, + &$savedReanimations + ) { + // Получаем дату окончания периода отчета (для фильтрации дат смерти) + $reportEndDate = DB::table('report_duties') + ->where('id', $reportDutyId) + ->value('period_end'); + + // === 1. Подготовка данных пациентов === + $patientBatch = []; + foreach ($patients as $patient) { + /** @var PatientSnapshotData $patient */ + // Преобразуем в массив для upsert + $patientData = [ + 'report_duty_id' => $reportDutyId, + 'source_type' => $patient->sourceType, + 'original_id' => $patient->id, + 'medical_card_number' => $patient->medicalCardNumber, + 'full_name' => $patient->fullName, + 'birth_date' => $patient->birthDate, + 'recipient_date' => $patient->recipientDate, + 'male' => $patient->male, + 'urgency_id' => $patient->urgencyId, + 'comment' => $patient->comment, + 'user_id' => $userId, + 'profit_type_id' => $patient->profitTypeId, + ]; + + // Обработка даты смерти и выбытия + if (!empty($patient->deathDate)) { + $deathDate = Carbon::parse($patient->deathDate); + $extractDate = $patient->extractDate ? Carbon::parse($patient->extractDate) : null; + $visitResultId = $patient->visitResultId; + + // Если исход смерти (visit_result_id 5 или 15) и дата смерти позже даты отчёта - очищаем + if (in_array($visitResultId, [5, 15]) && $deathDate->gt($reportEndDate)) { + $patientData['death_date'] = null; + } else { + $patientData['death_date'] = $deathDate->format('Y-m-d'); + } + + // Если пациент умер, а даты выбытия нет, используем дату смерти + if (!empty($patientData['death_date']) && empty($patientData['extract_date'])) { + $patientData['extract_date'] = $patientData['death_date']; + } + } else { + // Если нет даты смерти, используем extract_date как есть + $patientData['extract_date'] = $patient->extractDate; + $patientData['death_date'] = null; + } + + // Добавляем visit_result_id (если есть) + $patientData['visit_result_id'] = $patient->visitResultId; + $patientData['extract_date'] = $patientData['extract_date'] ?? null; + + $patientBatch[] = $patientData; + } + + // === 2. Upsert пациентов === + $patientUniqueBy = ['report_duty_id', 'source_type', 'original_id']; + $patientUpdateColumns = array_diff(array_keys($patientBatch[0]), $patientUniqueBy); + + DB::table('report_duty_patients')->upsert( + $patientBatch, + $patientUniqueBy, + $patientUpdateColumns + ); + $savedPatients = count($patientBatch); + + // === 3. Получаем ID сохранённых пациентов (для связей с миграциями) === + $patientIds = []; + if ($migrations->isNotEmpty() || $reanimations->isNotEmpty()) { + $patientIds = DB::table('report_duty_patients') + ->where('report_duty_id', $reportDutyId) + ->pluck('id', 'original_id') + ->toArray(); + } + + // === 4. Подготовка и Upsert миграций === + if ($migrations->isNotEmpty() && !empty($patientIds)) { + $migrationBatch = []; + /** @var MigrationSnapshotData $migration */ + foreach ($migrations as $migration) { + $patientDbId = $patientIds[$migration->medicalHistoryId] ?? null; + if (!$patientDbId) { + continue; + } + + $migrationBatch[] = [ + 'medical_history_id' => $patientDbId, + 'original_id' => $migration->id, + 'ingoing_date' => $migration->ingoingDate, + 'out_date' => $migration->outDate, + 'diagnosis_id' => $migration->diagnosisId, + 'diagnosis_code' => $migration->diagnosisCode, + 'diagnosis_name' => $migration->diagnosisName, + 'interrupted_event_id' => $migration->interruptedEventId, + 'stationar_branch_id' => $migration->stationarBranchId, + 'department_id' => $migration->departmentId, + 'visit_result_id' => $migration->visitResultId, + 'stat_cure_result_id' => $migration->statCureResultId, + 'user_id' => $migration->userId, + 'mis_user_id' => $migration->misUserId, + 'comment' => $migration->comment, + ]; + } + + if (!empty($migrationBatch)) { + $migrationUniqueBy = ['medical_history_id', 'ingoing_date']; + $migrationUpdateColumns = array_diff(array_keys($migrationBatch[0]), $migrationUniqueBy); + + DB::table('report_duty_migration_patients')->upsert( + $migrationBatch, + $migrationUniqueBy, + $migrationUpdateColumns + ); + $savedMigrations = count($migrationBatch); + + // === 5. Получаем ID сохранённых миграций для реанимаций === + // Строим маппинг [medical_history_id][original_id] => id + $migrationIds = []; + $migrationRecords = DB::table('report_duty_migration_patients') + ->whereIn('medical_history_id', array_values($patientIds)) + ->get(['id', 'medical_history_id', 'original_id']); + + foreach ($migrationRecords as $record) { + $migrationIds[$record->medical_history_id][$record->original_id] = $record->id; + } + + // === 6. Подготовка и Upsert реанимаций === + if ($reanimations->isNotEmpty()) { + $reanimationBatch = []; + /** @var ReanimationSnapshotData $reanimation */ + foreach ($reanimations as $reanimation) { + $patientDbId = $patientIds[$reanimation->medicalHistoryId] ?? null; + if (!$patientDbId) { + continue; + } + $migrationDbId = $migrationIds[$patientDbId][$reanimation->migrationPatientId] ?? null; + if (!$migrationDbId) { + continue; + } + + $reanimationBatch[] = [ + 'migration_patient_id' => $migrationDbId, + 'medical_history_id' => $patientDbId, + 'original_id' => $reanimation->id, + 'in_date' => $reanimation->inDate, + 'out_date' => $reanimation->outDate, + 'description' => $reanimation->description, + 'stationar_branch_id' => $reanimation->stationarBranchId, + 'migration_stationar_branch_id' => $reanimation->migrationStationarBranchId, + 'migration_department_id' => $reanimation->migrationDepartmentId, + 'doctor_id' => $reanimation->doctorId, + 'user_id' => $reanimation->userId, + 'mis_user_id' => $reanimation->misUserId, + 'comment' => $reanimation->comment, + ]; + } + + if (!empty($reanimationBatch)) { + $reanimationUniqueBy = ['migration_patient_id', 'in_date']; + $reanimationUpdateColumns = array_diff(array_keys($reanimationBatch[0]), $reanimationUniqueBy); + + DB::table('report_duty_reanimations')->upsert( + $reanimationBatch, + $reanimationUniqueBy, + $reanimationUpdateColumns + ); + $savedReanimations = count($reanimationBatch); + } + } + } + } + }); + + return [ + 'savedPatients' => $savedPatients, + 'savedMigrations' => $savedMigrations, + 'savedReanimations' => $savedReanimations, + ]; + } +} diff --git a/app/Infrastructure/Http/Controllers/Web/DutyReportController.php b/app/Infrastructure/Http/Controllers/Web/DutyReportController.php new file mode 100644 index 0000000..c32ec14 --- /dev/null +++ b/app/Infrastructure/Http/Controllers/Web/DutyReportController.php @@ -0,0 +1,65 @@ +validated(); + + // Создаём DTO и DateRange + $dateRange = new DateRange( + $validated['period_start'], + $validated['period_end'] + ); + + $dto = new CreateReportDutyDto( + reportDate: $dateRange->endSql(), + periodStart: $dateRange->startSql(), + periodEnd: $dateRange->endSql(), + periodType: $validated['period_type'] ?? 'day', + statusId: $validated['status_id'] ?? 2, + rfLpuDoctorId: $request->user()->rf_lpudoctor_id, + rfDepartmentId: $request->user()->rf_department_id, + rfUserId: $request->user()->id, + ); + + // Вызов Use Case для создания отчёта + $report = $createAction->execute($dto); + + // Вызов Use Case для сохранения снимка и расчёта статистики + $stats = $snapshotAction->execute( + $report, + $dateRange, + $request->user()->rf_department_id, + $request->user()->id + ); + + // Сохраняем наблюдаемых пациентов и нежелательные события + if ($request->has('observables')) { + $observableAction = app(SaveObservablePatientsAction::class); + $observableAction->execute($report, $validated['observables']); + } + + if ($request->has('unwanted_events')) { + $unwantedAction = app(SaveUnwantedEventsAction::class); + $unwantedAction->execute($report, $validated['unwanted_events']); + } + + // Возвращаем Inertia-ответ (редирект или рендер) + return Inertia::location(route('reports.duty.show', $report->id)); + } +} diff --git a/app/Infrastructure/Http/Controllers/Web/PatientController.php b/app/Infrastructure/Http/Controllers/Web/PatientController.php new file mode 100644 index 0000000..b7f0f41 --- /dev/null +++ b/app/Infrastructure/Http/Controllers/Web/PatientController.php @@ -0,0 +1,21 @@ +query('departmentId'); + + $patients = $patientAction->execute((int) $departmentId); + + return response()->json($patients); + } +} diff --git a/app/Infrastructure/Http/Requests/CreateDutyReportRequest.php b/app/Infrastructure/Http/Requests/CreateDutyReportRequest.php new file mode 100644 index 0000000..67bff4b --- /dev/null +++ b/app/Infrastructure/Http/Requests/CreateDutyReportRequest.php @@ -0,0 +1,27 @@ +check(); + } + + public function rules(): array + { + return [ + 'period_start' => ['required', 'date'], + 'period_end' => ['required', 'date', 'after_or_equal:period_start'], + 'period_type' => ['nullable', 'string', 'in:day,week,month'], + 'status_id' => ['nullable', 'integer', 'in:1,2,3'], + 'observables' => ['nullable', 'array'], + 'observables.*.id' => ['required', 'integer'], + 'unwanted_events' => ['nullable', 'array'], + 'unwanted_events.*.title' => ['required', 'string'], + ]; + } +} diff --git a/app/Infrastructure/Services/MisPatientSnapshotProvider.php b/app/Infrastructure/Services/MisPatientSnapshotProvider.php new file mode 100644 index 0000000..c6b1a70 --- /dev/null +++ b/app/Infrastructure/Services/MisPatientSnapshotProvider.php @@ -0,0 +1,112 @@ +startOfYear()->format('Y-m-d'); + + return MedicalHistory::query() + ->whereHas('migrations', function ($q) use ($departmentId, $dateRange, $startYear) { + $q->where('department_id', $departmentId) + ->where('ingoing_date', '<=', $dateRange->endSql()) + ->where(function ($sub) use ($dateRange, $startYear) { + $sub->whereNull('out_date') + ->where('ingoing_date', '>', $startYear); + $sub->orWhere(function ($sub2) use ($dateRange, $startYear) { + $sub2->whereNotNull('out_date') + ->where('out_date', '>', $dateRange->startSql()) + ->where('out_date', '>', $startYear); + }); + }); + }) + ->with([ + 'latestMigration' => function ($q) use ($departmentId) { + $q->where('department_id', $departmentId); + }, + 'latestMigration.operations', + 'latestMigration.reanimations', + 'migrations' => function ($q) use ($departmentId, $dateRange, $startYear) { + $q->where('department_id', $departmentId) + ->where('ingoing_date', '<=', $dateRange->endSql()) + ->where(function ($sub) use ($dateRange, $startYear) { + $sub->whereNull('out_date') + ->where('ingoing_date', '>', $startYear); + $sub->orWhere(function ($sub2) use ($dateRange, $startYear) { + $sub2->whereNotNull('out_date') + ->where('out_date', '>', $dateRange->startSql()) + ->where('out_date', '>', $startYear); + }); + }); + }, + 'migrations.reanimations' => function ($q) use ($dateRange) { + $q->where(function ($sub) use ($dateRange) { + $sub->whereNull('out_date') + ->orWhere('out_date', '>=', $dateRange->startSql()); + }); + }, + 'operations' => function ($q) use ($departmentId, $dateRange) { + $q->where('department_id', $departmentId) + ->where('start_date', '>=', $dateRange->startSql()) + ->where('start_date', '<', $dateRange->endSql()); + } + ]) + ->lazy() + ->map(function (MedicalHistory $h) use ($dateRange) { + $patientStatus = $this->classifier::classify($h, $dateRange); + $periodFlags = $this->classifier::classifyPeriodFlags($h, $dateRange); + $patientUrgency = null; + $patientReanimation = null; + if (!in_array($patientStatus, [ + PatientStatusClassifier::STATUS_DECEASED, + PatientStatusClassifier::STATUS_DISCHARGED, + PatientStatusClassifier::STATUS_TRANSFERRED + ])) { + $patientUrgency = $this->classifier::classifyUrgency($h->urgency_id); + $patientReanimation = $this->classifier::classifyReanimation($h->latestMigration?->reanimations, $dateRange); + } + + return new PatientSnapshotData( + id: $h->id, + sourceType: 'mis', + medicalCardNumber: $h->medical_card_number, + fullName: $h->full_name, + birthDate: $h->birth_date, + recipientDate: $h->recipient_date, + extractDate: $h->extract_date, + deathDate: $h->death_date, + male: $h->male, + urgencyId: $h->urgency_id, + visitResultId: $h->visit_result_id, + hospitalResultId: $h->hospital_result_id, + comment: $h->comment, + profitTypeId: $h->profit_type_id, + migrations: $h->migrations->map(fn($m) => $m->toArray())->toArray(), + operations: $h->operations->toArray(), + patientStatus: $patientStatus, + patientUrgency: $patientUrgency, + periodFlags: $periodFlags, + inReanimation: $patientReanimation, + admittedToday: $this->classifier::classifyAdmitted($h->latestMigration?->ingoing_date, $dateRange), + inObservable: $this->classifier::classifyObservable($h->observable, $dateRange), + ); + }); + } +} diff --git a/app/Infrastructure/Services/NurseReportPatientSnapshotProvider.php b/app/Infrastructure/Services/NurseReportPatientSnapshotProvider.php new file mode 100644 index 0000000..f5501be --- /dev/null +++ b/app/Infrastructure/Services/NurseReportPatientSnapshotProvider.php @@ -0,0 +1,106 @@ +where('period_start', $dateRange->startSql()) + ->where('period_end', $dateRange->endSql()) + ->latest('id') + ->first(); + + if (!$nurseReport) { + return LazyCollection::make([]); + } + + return ReportNursePatient::query() + ->where('report_nurse_id', $nurseReport->id) + ->with([ + 'migrations', + 'operations' => function ($q) use ($dateRange) { + $q->where('start_date', '>=', $dateRange->startSql()) + ->where('start_date', '<', $dateRange->endSql()); + }, + 'latestMigration', + ]) + ->lazy() + ->map(function (ReportNursePatient $h) use ($dateRange) { + $patientStatus = $this->classifier::classify($h, $dateRange); + $periodFlags = $this->classifier::classifyPeriodFlags($h, $dateRange); + $patientUrgency = null; + $patientReanimation = null; + if (!in_array($patientStatus, [ + PatientStatusClassifier::STATUS_DECEASED, + PatientStatusClassifier::STATUS_DISCHARGED, + PatientStatusClassifier::STATUS_TRANSFERRED + ])) { + $patientUrgency = $this->classifier::classifyUrgency($h->urgency_id); + $patientReanimation = false; // в отчёте медсестры нет реанимации + } + + // Преобразуем миграции в массив (как в оригинале) + $migrations = $h->migrations->map(fn($m) => [ + 'id' => $m->original_id, + 'ingoing_date' => $m->ingoing_date, + 'out_date' => $m->out_date, + 'diagnosis_id' => $m->diagnosis_id, + 'diagnosis_code' => $m->diagnosis_code, + 'diagnosis_name' => $m->diagnosis_name, + 'interrupted_event_id' => $m->interrupted_event_id, + 'stationar_branch_id' => $m->stationar_branch_id, + 'department_id' => $m->department_id, + 'visit_result_id' => $m->visit_result_id, + 'stat_cure_result_id' => $m->stat_cure_result_id, + 'user_id' => $m->user_id, + 'mis_user_id' => $m->mis_user_id, + 'comment' => $m->comment, + 'reanimations' => [], + ])->toArray(); + + return new PatientSnapshotData( + id: $h->original_id, + sourceType: 'nurse_report', + medicalCardNumber: $h->medical_card_number, + fullName: $h->full_name, + birthDate: $h->birth_date, + recipientDate: $h->recipient_date, + extractDate: $h->extract_date, + deathDate: $h->death_date, + male: $h->male, + urgencyId: $h->urgency_id, + visitResultId: $h->visit_result_id, + hospitalResultId: $h->hospital_result_id, + comment: $h->comment, + profitTypeId: $h->profit_type_id, + migrations: $migrations, + operations: $h->operations->toArray(), + patientStatus: $patientStatus, + patientUrgency: $patientUrgency, + periodFlags: $periodFlags, + inReanimation: $patientReanimation, + admittedToday: $this->classifier::classifyAdmitted($h->latestMigration?->ingoing_date, $dateRange), + inObservable: false, + ); + }); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 5c2f57e..37c65f4 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,8 @@ namespace App\Providers; +use App\Domain\Patient\Contracts\PatientRepositoryInterface; +use App\Infrastructure\Database\Repositories\Patient\PatientRepository; use App\Models\DepartmentPatient; use App\Models\ReportDuty; use App\Models\ReportNurse; @@ -24,6 +26,11 @@ class AppServiceProvider extends ServiceProvider }); $this->app->singleton(CacheInvalidator::class); + + $this->app->bind( + PatientRepositoryInterface::class, + PatientRepository::class + ); } /** diff --git a/routes/api.php b/routes/api.php index 6d7d371..a45ce05 100644 --- a/routes/api.php +++ b/routes/api.php @@ -129,3 +129,9 @@ Route::prefix('ai')->group(function () { Route::post('/ask', [\App\Http\Controllers\Api\Ai\AiController::class, 'ask']); }); +Route::prefix('/test')->group(function () { + Route::prefix('mis')->group(function () { + Route::get('patients', [\App\Infrastructure\Http\Controllers\Web\PatientController::class, 'getDepartmentPatient']); + }); +}); +