Files
onboard/app/Services/ReportService.php
2026-03-25 17:37:32 +09:00

1143 lines
42 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Services;
use App\Models\Department;
use App\Models\MedicalHistorySnapshot;
use App\Models\MetrikaResult;
use App\Models\MisLpuDoctor;
use App\Models\MisMigrationPatient;
use App\Models\MisMedicalHistory;
use App\Models\MisStationarBranch;
use App\Models\ObservationPatient;
use App\Models\Report;
use App\Models\UnwantedEvent;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class ReportService
{
public function __construct(
protected DateRangeService $dateRangeService,
protected PatientService $patientQueryService,
protected SnapshotService $snapshotService,
protected StatisticsService $statisticsService
) {}
/**
* Получить статистику для отчета
*/
public function getReportStatistics(Department $department, User $user, DateRange $dateRange): array
{
$misDepartmentId = $department->rf_mis_department_id;
$branchId = $this->getBranchId($misDepartmentId);
// Определяем, используем ли мы снапшоты
$useSnapshots = $this->shouldUseSnapshots($department, $user, $dateRange);
if ($useSnapshots) {
return $this->getStatisticsFromSnapshots($department, $dateRange, $branchId);
}
return $this->getStatisticsFromReplica($user, $dateRange, $branchId);
}
/**
* Создать или обновить отчет
*/
public function storeReport(array $data, User $user, $fillableAuto = false): Report
{
$report = DB::transaction(function () use ($data, $user, $fillableAuto) {
$report = $this->createOrUpdateReport($data, $user);
// Сохраняем все, что НЕ зависит от других отчетов
$this->saveMetrics($report, $data['metrics'] ?? []);
$this->saveUnwantedEvents($report, $data['unwantedEvents'] ?? []);
$this->saveObservationPatients($report, $data['observationPatients'] ?? [], $user->rf_department_id);
$this->snapshotService->createPatientSnapshots($report, $user, $data['dates'], $fillableAuto);
return $report;
});
DB::transaction(function () use ($report) {
// Сохраняем метрику среднего койко-дня из снапшотов
$this->saveAverageBedDaysMetricFromSnapshots($report);
$this->saveLethalMetricFromSnapshots($report);
$this->savePreoperativeMetric($report);
$this->saveDepartmentLoadedMetric($report);
});
$this->clearCacheAfterReportCreation($user, $report);
return $report;
}
/**
* Сохранить метрику койко-дня из снапшотов отчета
*/
protected function saveAverageBedDaysMetricFromSnapshots(Report $report): void
{
try {
// Получаем все снапшоты выписанных пациентов из этого отчета
$snapshots = MedicalHistorySnapshot::where('rf_report_id', $report->report_id)
->whereIn('patient_type', ['discharged', 'deceased']) // выписанные и умершие
->with('medicalHistory')
->get();
if ($snapshots->isEmpty()) {
// Если нет выписанных, сохраняем 0
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 18,
],
['value' => 0]
);
\Log::info("No discharged patients in report {$report->report_id}, saved 0");
return;
}
// Рассчитываем средний койко-день по снапшотам
$totalDays = 0;
$validCount = 0;
foreach ($snapshots as $snapshot) {
$history = $snapshot->medicalHistory;
if ($history && $history->DateRecipient && $history->DateExtract) {
// Проверяем что дата выписки не специальная
if ($history->DateExtract->format('Y-m-d') === '2222-01-01') {
continue; // пропускаем текущих пациентов
}
$start = Carbon::parse($history->DateRecipient);
$end = Carbon::parse($history->DateExtract);
// Проверяем что дата выписки позже даты поступления
if ($end->gt($start)) {
$days = $start->diffInDays($end);
$totalDays += $days;
$validCount++;
}
}
}
$bedDays = $validCount > 0 ? $totalDays: 0;
// Сохраняем метрику
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 18,
],
['value' => $bedDays]
);
//\Log::info("Saved average bed days metric for report {$report->report_id}: {$avgBedDays} (from {$validCount} patients)");
} catch (\Exception $e) {
\Log::error("Failed to save average bed days metric: " . $e->getMessage());
// Не прерываем выполнение, если метрика не сохранилась
}
}
protected function saveLethalMetricFromSnapshots(Report $report): void
{
// Получаем все снапшоты выписанных пациентов из этого отчета
$snapshots = MedicalHistorySnapshot::where('rf_report_id', $report->report_id)
->whereIn('patient_type', ['discharged', 'deceased']) // выписанные и умершие
->with('medicalHistory')
->get();
if ($snapshots->isEmpty()) {
// Если нет выписанных, сохраняем 0
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 18,
],
['value' => 0]
);
\Log::info("No discharged patients in report {$report->report_id}, saved 0");
return;
}
}
/**
* Сохранить предоперационный койко-день из снапшотов
*/
protected function savePreoperativeMetric(Report $report): void
{
// 1. Получаем ВСЕ предыдущие отчеты этого отделения
$allPreviousReports = Report::where('rf_department_id', $report->rf_department_id)
->where('sent_at', '<=', $report->sent_at)
->orderBy('sent_at')
->pluck('report_id');
if ($allPreviousReports->isEmpty()) {
$this->saveMetric($report, 21, 0);
return;
}
// 2. Получаем ВСЕХ пациентов из всех отчетов (discharged + deceased)
$allPatients = MedicalHistorySnapshot::whereIn('rf_report_id', $allPreviousReports)
->whereIn('patient_type', ['discharged', 'deceased'])
->pluck('rf_medicalhistory_id')
->unique();
if ($allPatients->isEmpty()) {
$this->saveMetric($report, 21, 0);
return;
}
// 3. Получаем операции для ВСЕХ пациентов
$operations = DB::table('stt_surgicaloperation as so')
->join('stt_migrationpatient as mp', 'so.rf_MedicalHistoryID', '=', 'mp.rf_MedicalHistoryID')
->whereIn('so.rf_MedicalHistoryID', $allPatients)
->whereNotNull('so.Date')
->whereNotNull('mp.DateIngoing')
->select(
'so.rf_MedicalHistoryID',
DB::raw('MIN(so."Date") as first_operation'),
DB::raw('MIN(mp."DateIngoing") as first_admission')
)
->groupBy('so.rf_MedicalHistoryID')
->get();
if ($operations->isEmpty()) {
$this->saveMetric($report, 21, 0);
return;
}
// 4. Считаем общее количество дней и пациентов
$totalDays = 0;
$patientCount = 0;
foreach ($operations as $op) {
$days = Carbon::parse($op->first_admission)
->diffInDays(Carbon::parse($op->first_operation));
if ($days >= 0) {
$totalDays += $days;
$patientCount++;
}
}
// 5. Нарастающий итог = общее количество дней / общее количество пациентов
$avgDays = $patientCount > 0 ? round($totalDays / $patientCount, 1) : 0;
// 6. Сохраняем метрику
$this->saveMetric($report, 21, $avgDays);
}
/**
* Сохранить предоперационный койко-день из снапшотов
*/
protected function saveDepartmentLoadedMetric(Report $report): void
{
// Получаем все снапшоты выписанных пациентов из этого отчета
$currentCount = $report->metrikaResults()->where('rf_metrika_item_id', 8)->value('value');
$bedsCount = $report->metrikaResults()->where('rf_metrika_item_id', 1)->value('value');
$percentLoaded = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0;
$this->saveMetric($report, 22, $percentLoaded);
}
/**
* Очистить кэш после создания отчета
*/
private function clearCacheAfterReportCreation(User $user, Report $report): void
{
// Очищаем кэш статистики для пользователя
// $this->statisticsService->clearStatisticsCache($user);
// Также можно очистить кэш для всех пользователей отдела
// $this->statisticsService->clearDepartmentStatisticsCache($user->rf_department_id);
// Очищаем кэш за сегодня и вчера (так как отчеты влияют на эти даты)
$this->clearDailyCache($user, $report->created_at);
}
/**
* Очистить дневной кэш
*/
private function clearDailyCache(User $user, $reportDate): void
{
$datesToClear = [
Carbon::parse($reportDate)->format('Y-m-d'),
Carbon::parse($reportDate)->subDay()->format('Y-m-d'),
];
foreach ($datesToClear as $date) {
$cacheKey = $this->generateDailyCacheKey($user, $date);
Cache::forget($cacheKey);
}
}
private function generateDailyCacheKey(User $user, string $date): string
{
return 'daily_stats:' . $user->rf_department_id . ':' . $date;
}
/**
* Получить пациентов по статусу
*/
public function getPatientsByStatus(
Department $department,
User $user,
string $status,
DateRange $dateRange,
bool $onlyIds = false,
bool $beforeCreate = false,
?bool $includeCurrentPatients = null
) {
$branchId = $this->getBranchId($department->rf_mis_department_id);
$useSnapshots = $this->shouldUseSnapshots($department, $user, $dateRange, $beforeCreate);
if ($useSnapshots) {
return $this->getPatientsFromSnapshots($department, $status, $dateRange, $branchId);
}
return $this->getPatientsFromReplica($department, $user, $status, $dateRange, $branchId, $onlyIds, $includeCurrentPatients);
}
/**
* Получить количество пациентов по статусу
*/
public function getPatientsCountByStatus(
Department $department,
User $user,
string $status,
DateRange $dateRange
): int {
$branchId = $this->getBranchId($department->rf_mis_department_id);
$useSnapshots = $this->shouldUseSnapshots($department, $user, $dateRange);
if ($useSnapshots) {
return $this->getPatientsCountFromSnapshots($department, $status, $dateRange);
}
return $this->getPatientsCountFromReplica($department, $user, $status, $dateRange, $branchId);
}
/**
* Получить ID отделения из стационарного отделения
*/
private function getBranchId(int $misDepartmentId): ?int
{
return MisStationarBranch::where('rf_DepartmentID', $misDepartmentId)
->value('StationarBranchID');
}
/**
* Определить, нужно ли использовать снапшоты
*/
private function shouldUseSnapshots(Department $department, User $user, DateRange $dateRange, bool $beforeCreate = false): bool
{
if (($user->isAdmin() || $user->isHeadOfDepartment()) && !$beforeCreate) {
return true;
}
// Проверяем, есть ли отчет на сегодня
$reportToday = Report::whereDate('sent_at', $dateRange->end())
->whereDate('created_at', $dateRange->end())
->where('rf_department_id', $department->department_id)
->first();
return !$dateRange->isEndDateToday() || $reportToday;
}
/**
* Создать или обновить отчет
*/
private function createOrUpdateReport(array $data, User $user): Report
{
$reportData = [
'rf_department_id' => $data['departmentId'],
'rf_user_id' => $user->id,
'rf_lpudoctor_id' => $data['userId'],
'sent_at' => $data['sent_at'] ?? $this->dateRangeService->toSqlFormat(\Illuminate\Support\Carbon::now()),
'created_at' => $data['created_at'] ?? $this->dateRangeService->toSqlFormat(\Illuminate\Support\Carbon::now()),
];
if (isset($data['reportId']) && $data['reportId']) {
$report = Report::updateOrCreate(
['report_id' => $data['reportId']],
$reportData
);
} else {
$report = Report::create($reportData);
$department = Department::where('department_id', $reportData['rf_department_id'])->first();
$beds = $department->metrikaDefault->where('rf_metrika_item_id', 1)->first();
MetrikaResult::create([
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 1,
'value' => $beds->value
]);
}
return $report;
}
/**
* Сохранить метрики отчета
*/
private function saveMetrics(Report $report, array $metrics): void
{
foreach ($metrics as $key => $value) {
$metrikaId = (int)str_replace('metrika_item_', '', $key);
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => $metrikaId,
],
[
'value' => $value,
]
);
}
}
/**
* Сохранить метрику отчета
*/
private function saveMetric(Report $report, int $metrikaId, float $value): void
{
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => $metrikaId,
],
[
'value' => $value,
]
);
}
/**
* Сохранить нежелательные события
*/
private function saveUnwantedEvents(Report $report, array $unwantedEvents): void
{
if (empty($unwantedEvents)) {
$report->unwantedEvents()->delete();
$this->saveMetrics($report, [16 => 0]);
return;
}
foreach ($unwantedEvents as $event) {
if (isset($event['unwanted_event_id']) && $event['unwanted_event_id']) {
UnwantedEvent::updateOrCreate(
['unwanted_event_id' => $event['unwanted_event_id']],
[
'rf_report_id' => $report->report_id,
'comment' => $event['comment'] ?? '',
'title' => $event['title'] ?? '',
'is_visible' => $event['is_visible'] ?? true,
]
);
} else {
UnwantedEvent::create([
'rf_report_id' => $report->report_id,
'comment' => $event['comment'] ?? '',
'title' => $event['title'] ?? '',
'is_visible' => $event['is_visible'] ?? true,
]);
}
}
// Обновить метрику
$this->saveMetrics($report, [16 => count($unwantedEvents)]);
}
/**
* Сохранить пациентов под наблюдением
*/
private function saveObservationPatients(
Report $report,
array $observationPatients,
int $departmentId
): void {
if (empty($observationPatients)) {
ObservationPatient::where('rf_department_id', $departmentId)
->where('rf_report_id', $report->report_id)
->delete();
// Обновить метрику
$this->saveMetrics($report, [14 => 0]);
return;
}
foreach ($observationPatients as $patient) {
ObservationPatient::updateOrCreate(
[
'rf_medicalhistory_id' => $patient['id'],
'rf_department_id' => $departmentId,
],
[
'rf_report_id' => $report->report_id,
'rf_mkab_id' => null,
'comment' => $patient['comment'] ?? null,
]
);
}
// Обновить метрику
$this->saveMetrics($report, [14 => count($observationPatients)]);
}
/**
* Получить информацию о текущем отчете
*/
public function getCurrentReportInfo(Department $department, User $user, DateRange $dateRange): array
{
$reportToday = Report::whereDate('sent_at', $dateRange->endSql())
->whereDate('created_at', $dateRange->endSql())
->where('rf_department_id', $department->department_id)
->first();
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
$useSnapshots = $isHeadOrAdmin || !$dateRange->isEndDateToday() || $reportToday;
// Получаем ID пользователя для заполнения отчета
if ($useSnapshots && $isHeadOrAdmin && $reportToday) {
$fillableUserId = $reportToday->rf_lpudoctor_id ?? null;
} else {
$fillableUserId = request()->query('userId', $user->rf_lpudoctor_id);
}
// Получаем нежелательные события
$unwantedEvents = $this->getUnwantedEvents($department, $dateRange);
// Определяем активность кнопки отправки
$isActiveSendButton = $this->isSendButtonActive($user, $dateRange, $reportToday, $fillableUserId);
$message = null;
if ($reportToday) {
$reportDoctor = $reportToday->lpuDoctor;
$message = "Отчет уже создан пользователем: $reportDoctor->FAM_V $reportDoctor->IM_V $reportDoctor->OT_V";
}
// Получаем информацию о враче
$lpuDoctor = $this->getDoctorInfo($fillableUserId, $dateRange);
// Проверяем, является ли диапазон одним днем
// $isRangeOneDay = $this->dateRangeService->isRangeOneDay(
// $endDate->copy()->subDay()->format('Y-m-d H:i:s'),
// $endDate->format('Y-m-d H:i:s')
// );
// Формируем даты для ответа
// $date = $isHeadOrAdmin ? [
// $endDate->copy()->subDay()->getTimestampMs(),
// $endDate->getTimestampMs()
// ] : $endDate->getTimestampMs();
$date = $isHeadOrAdmin ? [
$dateRange->startDate->getTimestampMs(),
$dateRange->endDate->getTimestampMs()
] : $dateRange->endDate->getTimestampMs();
return [
'report_id' => $reportToday?->report_id,
'unwantedEvents' => $unwantedEvents,
'isActiveSendButton' => $isActiveSendButton,
'message' => $message,
'isOneDay' => $dateRange->isOneDay,
'isHeadOrAdmin' => $isHeadOrAdmin,
'dates' => $date,
'userId' => $fillableUserId,
'userName' => $lpuDoctor ? "$lpuDoctor->FAM_V $lpuDoctor->IM_V $lpuDoctor->OT_V" : null
];
}
/**
* Удалить пациента из наблюдения
*/
public function removeObservationPatient(int $medicalHistoryId): void
{
ObservationPatient::where('rf_medicalhistory_id', $medicalHistoryId)->delete();
}
/**
* Получить статистику из снапшотов
*/
private function getStatisticsFromSnapshots(Department $department, DateRange $dateRange, int $branchId): array
{
// Получаем отчеты за период
$reports = $this->getReportsForDateRange(
$department->department_id,
$dateRange
);
$reportIds = $reports->pluck('report_id')->toArray();
$lastReport = array_first($reportIds);
// Получаем статистику из снапшотов
$snapshotStats = [
'plan' => $this->getMetrikaResultCount(4, $reportIds),
'emergency' => $this->getMetrikaResultCount(12, $reportIds),
'outcome' => $this->getMetrikaResultCount(7, $reportIds),
'deceased' => $this->getMetrikaResultCount(9, $reportIds),
'current' => $this->getMetrikaResultCount(8, $reportIds, false),
// 'discharged' => $this->getMetrikaResultCount('discharged', $reportIds),
'transferred' => $this->getMetrikaResultCount(13, $reportIds),
'recipient' => $this->getMetrikaResultCount(3, $reportIds),
'beds' => $this->getMetrikaResultCount(1, $reportIds, false),
'countStaff' => $this->getMetrikaResultCount(17, [$lastReport], false)
];
// Получаем ID поступивших пациентов
$recipientIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
->where('patient_type', 'recipient')
->pluck('rf_medicalhistory_id')
->unique()
->toArray();
// Получаем количество операций из метрик
$surgicalCount = [
$this->getMetrikaResultCount(10, $reportIds), // экстренные операции
$this->getMetrikaResultCount(11, $reportIds) // плановые операции
];
if ($snapshotStats['outcome'] == 0) {
$percentDead = 0;
} else {
$percentDead = ($snapshotStats['deceased'] / $snapshotStats['outcome']) * 100;
$percentDead = round($percentDead, 2);
}
return [
'recipientCount' => $snapshotStats['recipient'] ?? 0,
'extractCount' => $snapshotStats['outcome'] ?? 0,
'currentCount' => $snapshotStats['current'] ?? 0,//$this->calculateCurrentPatientsFromSnapshots($reportIds, $branchId),
'deadCount' => $snapshotStats['deceased'] ?? 0,
'countStaff' => $snapshotStats['countStaff'] ?? 0,
'surgicalCount' => $surgicalCount,
'recipientIds' => $recipientIds,
'beds' => $snapshotStats['beds'] ?? 0,
'percentDead' => $percentDead,
];
}
/**
* Получить статистику из реплики БД
*/
private function getStatisticsFromReplica(User $user, DateRange $dateRange, int $branchId): array
{
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
// Плановые: поступившие сегодня + уже лечащиеся
$planCount = $this->patientQueryService->getPatientsCountWithCurrent(
'plan',
$isHeadOrAdmin,
$branchId,
$dateRange
);
// Экстренные: поступившие сегодня + уже лечащиеся
$emergencyCount = $this->patientQueryService->getPatientsCountWithCurrent(
'emergency',
$isHeadOrAdmin,
$branchId,
$dateRange
);
// Все пациенты в отделении: поступившие + лечащиеся
$currentCount = $this->patientQueryService->getAllPatientsInDepartment(
$isHeadOrAdmin,
$branchId,
$dateRange,
true
);
// Поступившие сегодня (только новые поступления)
$recipientCount = $this->patientQueryService->getPlanOrEmergencyPatients(
null, // все типы
$isHeadOrAdmin,
$branchId,
$dateRange,
true,
false,
false // не включаем уже лечащихся
);
// Выбывшие за период
$outcomeCount = $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'without-transferred'
)->count();
// Умершие за период
$deadCount = $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'deceased'
)->count();
// Операции
$surgicalCount = [
$this->patientQueryService->getSurgicalPatients(
'emergency',
$branchId,
$dateRange,
true
),
$this->patientQueryService->getSurgicalPatients(
'plan',
$branchId,
$dateRange,
true
)
];
// ID поступивших сегодня (для отметки в таблице)
$recipientIds = $this->patientQueryService->getPlanOrEmergencyPatients(
null,
$isHeadOrAdmin,
$branchId,
$dateRange,
false,
true,
false // только поступившие сегодня
);
$misBranch = MisStationarBranch::where('StationarBranchID', $branchId)->first();
$beds = Department::where('rf_mis_department_id', $misBranch->rf_DepartmentID)
->first()->metrikaDefault->where('rf_metrika_item_id', 1)->first();
if ($outcomeCount == 0) {
$percentDead = 0;
} else {
$percentDead = ($deadCount / $outcomeCount) * 100;
$percentDead = round($percentDead, 2);
}
return [
'recipientCount' => $recipientCount, // только поступившие сегодня
'extractCount' => $outcomeCount,
'currentCount' => $currentCount, // все в отделении
'deadCount' => $deadCount,
'surgicalCount' => $surgicalCount,
'recipientIds' => $recipientIds, // ID поступивших сегодня
'planCount' => $planCount, // плановые (поступившие + уже лечащиеся)
'emergencyCount' => $emergencyCount, // экстренные (поступившие + уже лечащиеся)
'percentDead' => $percentDead,
'beds' => $beds->value
];
}
/**
* Получить пациентов из снапшотов
*/
public function getPatientsFromSnapshots(
Department $department,
string $status,
DateRange $dateRange,
int $branchId,
bool $onlyIds = false
) {
$reports = $this->getReportsForDateRange(
$department->department_id,
$dateRange
);
$reportIds = $reports->pluck('report_id')->toArray();
$patientTypeMap = [
'plan' => 'plan',
'emergency' => 'emergency',
'outcome-discharged' => 'discharged',
'outcome-transferred' => 'transferred',
'outcome-deceased' => 'deceased',
'observation' => 'observation'
];
$patientType = $patientTypeMap[$status] ?? null;
if ($patientType === 'observation') {
return $this->patientQueryService->getObservationPatients($department->department_id, $onlyIds); //$this->getObservationPatientsFromSnapshots($user->rf_department_id, $reportIds, $onlyIds);
}
return $this->snapshotService->getPatientsFromSnapshots($patientType, $reportIds, $branchId, $onlyIds);
}
/**
* Получить пациентов из реплики БД
*/
private function getPatientsFromReplica(
Department $department,
User $user,
string $status,
DateRange $dateRange,
int $branchId,
bool $onlyIds = false,
?bool $isIncludeCurrent = null
) {
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
// Для плановых и экстренных включаем уже лечащихся
$includeCurrent = $isIncludeCurrent ?? in_array($status, ['plan', 'emergency']);
return match($status) {
'plan', 'emergency' => $this->patientQueryService->getPlanOrEmergencyPatients(
$status,
$isHeadOrAdmin,
$branchId,
$dateRange,
false,
$onlyIds,
$includeCurrent
),
'observation' => $this->patientQueryService->getObservationPatients($department->department_id, $onlyIds),
'outcome' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'without-transferred',
$onlyIds
), // Выписанные без перевода
'outcome-discharged' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'discharged',
$onlyIds
),
'outcome-transferred' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'transferred',
$onlyIds
),
'outcome-deceased' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'deceased',
$onlyIds
),
'current' => $this->patientQueryService->getAllPatientsInDepartment(
$isHeadOrAdmin,
$branchId,
$dateRange,
false,
$onlyIds
),
'recipient' => $this->patientQueryService->getPlanOrEmergencyPatients(
null,
$isHeadOrAdmin,
$branchId,
$dateRange,
false,
$onlyIds,
false // только поступившие
),
default => collect()
};
}
/**
* Получить количество пациентов из снапшотов
*/
private function getPatientsCountFromSnapshots(Department $department, string $status, DateRange $dateRange): int
{
$reports = $this->getReportsForDateRange(
$department->department_id,
$dateRange
);
$reportIds = $reports->pluck('report_id')->toArray();
if ($status === 'outcome') {
return MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
->whereIn('patient_type', ['discharged', 'deceased'])
->distinct('rf_medicalhistory_id')
->count('rf_medicalhistory_id');
}
$patientTypeMap = [
'plan' => 'plan',
'emergency' => 'emergency',
'observation' => 'observation',
'outcome-discharged' => 'discharged',
'outcome-transferred' => 'transferred',
'outcome-deceased' => 'deceased'
];
$patientType = $patientTypeMap[$status] ?? null;
if (!$patientType) {
return 0;
}
if ($patientType === 'observation') {
return ObservationPatient::whereIn('rf_report_id', $reportIds)
->distinct('rf_medicalhistory_id')
->count('rf_medicalhistory_id');
}
return MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
->where('patient_type', $patientType)
->distinct('rf_medicalhistory_id')
->count('rf_medicalhistory_id');
}
/**
* Получить количество пациентов из реплики БД
*/
private function getPatientsCountFromReplica(
Department $department,
User $user,
string $status,
DateRange $dateRange,
int $branchId
): int
{
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
return match($status) {
'plan', 'emergency' => $this->patientQueryService->getPatientsCountWithCurrent(
$status,
$isHeadOrAdmin,
$branchId,
$dateRange,
),
'observation' => ObservationPatient::where('rf_department_id', $department->department_id)->count(),
'outcome' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'without-transferred'
)->count(),
'outcome-discharged' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'discharged'
)->count(),
'outcome-transferred' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'transferred'
)->count(),
'outcome-deceased' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'deceased'
)->count(),
default => 0
};
}
/**
* Получить нежелательные события за дату
*/
public function getUnwantedEvents(Department $department, DateRange $dateRange)
{
return UnwantedEvent::whereHas('report', function ($query) use ($department, $dateRange) {
$query->where('rf_department_id', $department->department_id)
->whereDate('sent_at', '>=', $dateRange->startSql())
->whereDate('sent_at', '<=', $dateRange->endSql());
})
->get()
->map(function ($item) {
return [
...$item->toArray(),
'created_at' => Carbon::parse($item->created_at)->format('Создано d.m.Y в H:i'),
];
});
}
/**
* Проверить активность кнопки отправки отчета
*/
private function isSendButtonActive(User $user, DateRange $dateRange, ?Report $reportToday, ?int $fillableUserId): bool
{
// Для врача: только сегодня и если отчета еще нет
if (!$user->isHeadOfDepartment() && !$user->isAdmin()) {
return $dateRange->isEndDateToday() && !$reportToday;
}
// Для заведующего/админа: если есть отчет & он заполнен текущим пользователем & диапазон дат = 1 дню
if (
$reportToday &&
$reportToday->rf_lpudoctor_id === intval($fillableUserId) &&
$dateRange->isOneDay
) {
return true;
}
return false;
}
/**
* Получить информацию о враче
*/
private function getDoctorInfo(?int $doctorId, DateRange $dateRange): ?MisLpuDoctor
{
if (!$doctorId) {
return null;
}
// Если дата это период, не показываем врача
if (!$dateRange->isOneDay) {
return null;
}
return MisLpuDoctor::where('LPUDoctorID', $doctorId)->first();
}
/**
* Получить отчеты за диапазон дат
*/
public function getReportsForDateRange(int $departmentId, DateRange $dateRange)
{
if ($dateRange->isOneDay) {
return Report::where('rf_department_id', $departmentId)
->whereDate('sent_at', $dateRange->endSql())
->orderBy('sent_at', 'DESC')
->get();
}
return Report::where('rf_department_id', $departmentId)
// ->whereBetween('created_at', [$dateRange->startSql(), $dateRange->endSql()])
->where('sent_at', '>', $dateRange->startSql())
->where('sent_at', '<=', $dateRange->endSql())
->orderBy('sent_at', 'DESC')
->get();
}
/**
* Получить количество из метрик
*/
private function getMetrikaResultCount(int $metrikaItemId, array $reportIds, bool $sum = true): int
{
$count = 0;
$reports = Report::whereIn('report_id', $reportIds)
->with('metrikaResults')
->orderBy('created_at', 'DESC')
->get();
foreach ($reports as $report) {
foreach ($report->metrikaResults as $metrikaResult) {
if ($metrikaResult->rf_metrika_item_id === $metrikaItemId) {
if ($sum) $count += intval($metrikaResult->value) ?? 0;
else $count = intval($metrikaResult->value) ?? 0;
}
}
}
return $count;
}
/**
* Рассчитать текущих пациентов из снапшотов
*/
private function calculateCurrentPatientsFromSnapshots(array $reportIds, int $branchId): int
{
// Получаем ID всех пациентов из снапшотов
$allPatientIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
->pluck('rf_medicalhistory_id')
->unique()
->toArray();
if (empty($allPatientIds)) {
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($allPatientIds, $outcomePatientIds);
return count($currentPatientIds);
}
/**
* Получить пациентов под наблюдением из снапшотов
*/
private function getObservationPatientsFromSnapshots(int $departmentId, array $reportIds, bool $onlyIds = false)
{
$medicalHistoryIds = ObservationPatient::whereIn('rf_report_id', $reportIds)
->where('rf_department_id', $departmentId)
->pluck('rf_medicalhistory_id')
->unique()
->toArray();
if (empty($medicalHistoryIds)) {
return collect();
}
if ($onlyIds) {
return collect($medicalHistoryIds);
}
return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
->with(['observationPatient' => function($query) use ($departmentId) {
$query->where('rf_department_id', $departmentId)
->select(['rf_medicalhistory_id', 'observation_patient_id', 'comment']);
}])
->orderBy('DateRecipient', 'DESC')
->get()
->map(function ($patient) {
$patient->comment = $patient->observationPatient
->pluck('comment')
->filter()
->implode('; ');
return $patient;
});
}
/**
* Получить статистику выполнения плана по госпитализации
*/
public function getRecipientPlanOfYear(Department $department, DateRange $dateRange): array
{
$periodPlanModel = $department->recipientPlanOfYear();
// Рассчитываем коэффициент периода (округляем в большую сторону)
$monthsInPeriod = ceil($dateRange->startDate->diffInMonths($dateRange->endDate));
$annualPlan = $periodPlanModel ? (int)$periodPlanModel->value : 0;
$oneMonthPlan = ceil($annualPlan / 12);
$periodPlan = round($oneMonthPlan * $monthsInPeriod);
$progress = 0;
$query = $department->reports()
->with('metrikaResults')
->where('sent_at', '>=', $dateRange->startSql())
->where('sent_at', '<=', $dateRange->endSql());
if ($dateRange->isOneDay) {
$query->where('sent_at', '>=', $dateRange->startFirstOfMonth())
->where('sent_at', '<=', $dateRange->endSql());
} else {
$query->where('sent_at', '>=', $dateRange->startSql())
->where('sent_at', '<=', $dateRange->endSql());
}
$reports = $query->get();
foreach ($reports as $report) {
$outcome = $report->metrikaResults()->where('rf_metrika_item_id', 7)->first();
if ($outcome) $progress += (int)$outcome->value;
}
return [
'plan' => $periodPlan,
'progress' => $progress
];
}
}