Профиль хирургии

This commit is contained in:
brusnitsyn
2026-03-25 17:37:32 +09:00
parent 52a80ccd3b
commit f566ab96df
75 changed files with 3841 additions and 1009 deletions

View File

@@ -50,35 +50,36 @@ class ReportService
*/
public function storeReport(array $data, User $user, $fillableAuto = false): Report
{
DB::beginTransaction();
try {
$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);
DB::commit();
$this->saveLethalMetricFromSnapshots($report);
// ОЧИСТКА КЭША ПОСЛЕ УСПЕШНОГО СОЗДАНИЯ ОТЧЕТА
$this->clearCacheAfterReportCreation($user, $report);
$this->savePreoperativeMetric($report);
return $report;
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
$this->saveDepartmentLoadedMetric($report);
});
$this->clearCacheAfterReportCreation($user, $report);
return $report;
}
/**
* Сохранить метрику среднего койко-дня из снапшотов отчета
* Сохранить метрику койко-дня из снапшотов отчета
*/
protected function saveAverageBedDaysMetricFromSnapshots(Report $report): void
{
@@ -128,7 +129,7 @@ class ReportService
}
}
$avgBedDays = $validCount > 0 ? round($totalDays / $validCount, 1) : 0;
$bedDays = $validCount > 0 ? $totalDays: 0;
// Сохраняем метрику
MetrikaResult::updateOrCreate(
@@ -136,10 +137,10 @@ class ReportService
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 18,
],
['value' => $avgBedDays]
['value' => $bedDays]
);
\Log::info("Saved average bed days metric for report {$report->report_id}: {$avgBedDays} (from {$validCount} patients)");
//\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());
@@ -147,16 +148,120 @@ class ReportService
}
}
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->clearStatisticsCache($user);
// Также можно очистить кэш для всех пользователей отдела
$this->statisticsService->clearDepartmentStatisticsCache($user->rf_department_id);
// $this->statisticsService->clearDepartmentStatisticsCache($user->rf_department_id);
// Очищаем кэш за сегодня и вчера (так как отчеты влияют на эти даты)
$this->clearDailyCache($user, $report->created_at);
@@ -305,6 +410,22 @@ class ReportService
}
}
/**
* Сохранить метрику отчета
*/
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,
]
);
}
/**
* Сохранить нежелательные события
*/
@@ -460,6 +581,7 @@ class ReportService
);
$reportIds = $reports->pluck('report_id')->toArray();
$lastReport = array_first($reportIds);
// Получаем статистику из снапшотов
$snapshotStats = [
@@ -471,7 +593,8 @@ class ReportService
// 'discharged' => $this->getMetrikaResultCount('discharged', $reportIds),
'transferred' => $this->getMetrikaResultCount(13, $reportIds),
'recipient' => $this->getMetrikaResultCount(3, $reportIds),
'beds' => $this->getMetrikaResultCount(1, $reportIds, false)
'beds' => $this->getMetrikaResultCount(1, $reportIds, false),
'countStaff' => $this->getMetrikaResultCount(17, [$lastReport], false)
];
// Получаем ID поступивших пациентов
@@ -499,6 +622,7 @@ class ReportService
'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,
@@ -819,7 +943,8 @@ class ReportService
{
return UnwantedEvent::whereHas('report', function ($query) use ($department, $dateRange) {
$query->where('rf_department_id', $department->department_id)
->whereBetween('sent_at', [$dateRange->startSql(), $dateRange->endSql()]);
->whereDate('sent_at', '>=', $dateRange->startSql())
->whereDate('sent_at', '<=', $dateRange->endSql());
})
->get()
->map(function ($item) {
@@ -876,14 +1001,16 @@ class ReportService
{
if ($dateRange->isOneDay) {
return Report::where('rf_department_id', $departmentId)
->whereDate('created_at', $dateRange->endSql())
->orderBy('created_at', 'ASC')
->whereDate('sent_at', $dateRange->endSql())
->orderBy('sent_at', 'DESC')
->get();
}
return Report::where('rf_department_id', $departmentId)
->whereBetween('created_at', [$dateRange->startSql(), $dateRange->endSql()])
->orderBy('created_at', 'ASC')
// ->whereBetween('created_at', [$dateRange->startSql(), $dateRange->endSql()])
->where('sent_at', '>', $dateRange->startSql())
->where('sent_at', '<=', $dateRange->endSql())
->orderBy('sent_at', 'DESC')
->get();
}
@@ -972,4 +1099,44 @@ class ReportService
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
];
}
}