Форматирование

This commit is contained in:
brusnitsyn
2026-04-24 16:46:10 +09:00
parent fd0e6ee817
commit 63daa62888
87 changed files with 1380 additions and 791 deletions

View File

@@ -5,19 +5,19 @@ namespace App\Services;
use App\Models\Department;
use App\Models\DepartmentPatient;
use App\Models\DepartmentPatientOperation;
use App\Models\MisServiceMedical;
use App\Models\MedicalHistorySnapshot;
use App\Models\MetrikaResult;
use App\Models\MisLpuDoctor;
use App\Models\MisMigrationPatient;
use App\Models\MisMedicalHistory;
use App\Models\MisMigrationPatient;
use App\Models\MisServiceMedical;
use App\Models\MisStationarBranch;
use App\Models\ObservationPatient;
use App\Models\Report;
use App\Models\ReanimationPatientIndicator;
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;
@@ -88,7 +88,7 @@ class ReportService
$branchId,
$dateRange,
true
)
),
];
$manualSurgicalCount = $this->getManualSurgicalCounts($department, $dateRange);
$surgicalCount = [
@@ -117,7 +117,7 @@ class ReportService
'planCount' => $planCount,
'emergencyCount' => $emergencyCount,
'percentDead' => $percentDead,
'beds' => $beds->value
'beds' => $beds->value,
];
}
@@ -139,33 +139,23 @@ class ReportService
$this->saveObservationPatients($report, $data['observationPatients'] ?? [], $user->rf_department_id);
$shouldBuildSnapshots = (bool) $fillableAuto;
if ($shouldBuildSnapshots) {
$this->snapshotService->createPatientSnapshots($report, $user, $data['dates'], $fillableAuto);
$this->snapshotService->createPatientSnapshots($report, $user, $data['dates'], $fillableAuto);
$this->syncCalculatedMetrics($report, $user, $data);
} else {
MedicalHistorySnapshot::query()
->where('rf_report_id', $report->report_id)
->delete();
}
$this->syncCalculatedMetrics($report, $user, $data);
return $report;
});
$shouldBuildDerivedMetrics = (bool) $fillableAuto;
if ($shouldBuildDerivedMetrics) {
DB::transaction(function () use ($report) {
// Сохраняем метрику койко-дня + среднего койко-дня из снапшотов
$this->saveBedDaysMetric($report);
DB::transaction(function () use ($report) {
// Сохраняем метрику койко-дня + среднего койко-дня из снапшотов
$this->saveBedDaysMetric($report);
$this->saveLethalMetricFromSnapshots($report);
$this->saveLethalMetricFromSnapshots($report);
$this->savePreoperativeMetric($report);
$this->savePreoperativeMetric($report);
$this->saveDepartmentLoadedMetric($report);
});
}
$this->saveDepartmentLoadedMetric($report);
});
} catch (\Throwable $e) {
throw $e;
}
@@ -367,7 +357,7 @@ class ReportService
['value' => $result['avg_days']]
);
} catch (\Throwable $e) {
\Log::error('Failed to save bed days metric: ' . $e->getMessage());
\Log::error('Failed to save bed days metric: '.$e->getMessage());
}
}
@@ -384,31 +374,31 @@ class ReportService
foreach ($snapshots as $snapshot) {
$history = $snapshot->medicalHistory;
if (!$history) {
if (! $history) {
continue;
}
$start = $history->DateRecipientHS ?? $history->DateRecipient ?? null;
if (!$start) {
if (! $start) {
continue;
}
$end = null;
if ($snapshot->patient_type === 'deceased') {
if ($history->DateDeath && !in_array($history->DateDeath->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) {
if ($history->DateDeath && ! in_array($history->DateDeath->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) {
$end = $history->DateDeath;
} elseif ($history->DateExtract && !in_array($history->DateExtract->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) {
} elseif ($history->DateExtract && ! in_array($history->DateExtract->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) {
$end = $history->DateExtract;
}
} else {
if ($history->DateExtract && !in_array($history->DateExtract->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) {
if ($history->DateExtract && ! in_array($history->DateExtract->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) {
$end = $history->DateExtract;
}
}
if (!$end) {
if (! $end) {
continue;
}
@@ -485,7 +475,7 @@ class ReportService
$startRaw = $row->DateRecipientHS ?? $row->DateRecipient ?? null;
$operationRaw = $row->first_operation ?? null;
if (!$startRaw || !$operationRaw) {
if (! $startRaw || ! $operationRaw) {
continue;
}
@@ -529,6 +519,7 @@ class ReportService
);
\Log::info("No discharged patients in report {$report->report_id}, saved 0");
return;
}
}
@@ -542,9 +533,10 @@ class ReportService
$result = $this->calculatePreoperativeDaysFromSnapshots($report);
$this->saveMetric($report, 26, $result['total_days']);
$this->saveMetric($report, 27, $result['patient_count']);
$this->saveMetric($report, 21, $result['avg_days']);
} catch (\Throwable $e) {
\Log::error('Failed to save preoperative total metric: ' . $e->getMessage());
\Log::error('Failed to save preoperative total metric: '.$e->getMessage());
}
}
@@ -568,10 +560,10 @@ class ReportService
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);
@@ -595,7 +587,7 @@ class ReportService
private function generateDailyCacheKey(User $user, string $date): string
{
return 'daily_stats:' . $user->rf_department_id . ':' . $date;
return 'daily_stats:'.$user->rf_department_id.':'.$date;
}
/**
@@ -625,6 +617,7 @@ class ReportService
);
}
// Для реанимации всегда берем live-данные из реплики.
if ($baseStatus === 'reanimation') {
return $this->getPatientsFromReplica(
$department,
@@ -637,7 +630,7 @@ class ReportService
);
}
$useSnapshots = !$this->shouldUseReplicaForLiveStatus($user, $baseStatus, $dateRange)
$useSnapshots = ! $this->shouldUseReplicaForLiveStatus($user, $baseStatus, $dateRange)
&& $this->shouldUseSnapshots($department, $user, $dateRange, $beforeCreate);
if ($useSnapshots) {
@@ -675,7 +668,7 @@ class ReportService
return $this->getPatientsCountFromReplica($department, $user, $status, $dateRange, $branchId);
}
$useSnapshots = !$this->shouldUseReplicaForLiveStatus($user, $baseStatus, $dateRange)
$useSnapshots = ! $this->shouldUseReplicaForLiveStatus($user, $baseStatus, $dateRange)
&& $this->shouldUseSnapshots($department, $user, $dateRange);
if ($useSnapshots) {
@@ -757,7 +750,7 @@ class ReportService
}
$report = $this->getReportForPeriod($department->department_id, $dateRange);
if (!$report) {
if (! $report) {
return false;
}
@@ -774,7 +767,7 @@ class ReportService
return false;
}
return in_array($status, ['plan', 'emergency', 'recipient', 'current'], true)
return in_array($status, ['plan', 'emergency', 'recipient', 'current', 'reanimation'], true)
&& $dateRange->isOneDay
&& $dateRange->isEndDateToday();
}
@@ -819,7 +812,7 @@ class ReportService
MetrikaResult::create([
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 1,
'value' => $beds->value
'value' => $beds->value,
]);
}
@@ -832,7 +825,7 @@ class ReportService
private function saveMetrics(Report $report, array $metrics): void
{
foreach ($metrics as $key => $value) {
$metrikaId = (int)str_replace('metrika_item_', '', $key);
$metrikaId = (int) str_replace('metrika_item_', '', $key);
MetrikaResult::updateOrCreate(
[
@@ -870,6 +863,7 @@ class ReportService
if (empty($unwantedEvents)) {
$report->unwantedEvents()->delete();
$this->saveMetric($report, 16, 0);
return;
}
@@ -912,6 +906,7 @@ class ReportService
->delete();
// Обновить метрику
$this->saveMetric($report, 14, 0);
return;
}
@@ -936,12 +931,12 @@ class ReportService
private function syncCalculatedMetrics(Report $report, User $user, array $data): void
{
if (!isset($data['dates'][0], $data['dates'][1])) {
if (! isset($data['dates'][0], $data['dates'][1])) {
return;
}
$department = Department::query()->where('department_id', $report->rf_department_id)->first();
if (!$department) {
if (! $department) {
return;
}
@@ -1017,7 +1012,7 @@ class ReportService
$reportToday = $this->getReportForPeriod($department->department_id, $dateRange);
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
$useSnapshots = $isHeadOrAdmin || !$dateRange->isEndDateToday() || $reportToday;
$useSnapshots = $isHeadOrAdmin || ! $dateRange->isEndDateToday() || $reportToday;
// Получаем ID пользователя для заполнения отчета
if ($useSnapshots && $isHeadOrAdmin && $reportToday) {
@@ -1047,19 +1042,19 @@ class ReportService
$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')
// );
// $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 ? [
// $endDate->copy()->subDay()->getTimestampMs(),
// $endDate->getTimestampMs()
// ] : $endDate->getTimestampMs();
$date = $isHeadOrAdmin ? [
$dateRange->startDate->getTimestampMs(),
$dateRange->endDate->getTimestampMs()
$dateRange->endDate->getTimestampMs(),
] : $dateRange->endDate->getTimestampMs();
return [
@@ -1074,7 +1069,7 @@ class ReportService
'isHeadOrAdmin' => $isHeadOrAdmin,
'dates' => $date,
'userId' => $fillableUserId,
'userName' => $lpuDoctor ? "$lpuDoctor->FAM_V $lpuDoctor->IM_V $lpuDoctor->OT_V" : null
'userName' => $lpuDoctor ? "$lpuDoctor->FAM_V $lpuDoctor->IM_V $lpuDoctor->OT_V" : null,
];
}
@@ -1087,6 +1082,7 @@ class ReportService
if ($sourceType === 'manual') {
ObservationPatient::where('rf_department_patient_id', $id)->delete();
return;
}
@@ -1187,6 +1183,69 @@ class ReportService
->delete();
}
public function saveReanimationIndicator(
User $user,
int $departmentId,
int $medicalHistoryId,
string $indicator,
?string $comment = null,
?int $reportId = null
): ReanimationPatientIndicator {
return ReanimationPatientIndicator::create([
'rf_department_id' => $departmentId,
'rf_report_id' => $reportId,
'rf_medicalhistory_id' => $medicalHistoryId,
'indicator' => $indicator,
'comment' => $comment,
'created_by' => $user->id,
]);
}
public function getLatestReanimationIndicators(int $departmentId, array $medicalHistoryIds)
{
if (empty($medicalHistoryIds)) {
return collect();
}
$subQuery = ReanimationPatientIndicator::query()
->selectRaw('MAX(reanimation_patient_indicator_id) as max_id, rf_medicalhistory_id')
->where('rf_department_id', $departmentId)
->whereIn('rf_medicalhistory_id', $medicalHistoryIds)
->groupBy('rf_medicalhistory_id');
return ReanimationPatientIndicator::query()
->joinSub($subQuery, 'latest', function ($join) {
$join->on('reanimation_patient_indicators.reanimation_patient_indicator_id', '=', 'latest.max_id');
})
->get([
'reanimation_patient_indicators.rf_medicalhistory_id',
'reanimation_patient_indicators.indicator',
'reanimation_patient_indicators.comment',
])
->keyBy('rf_medicalhistory_id');
}
public function getReanimationIndicatorsHistory(
int $departmentId,
int $medicalHistoryId,
int $limit = 50
) {
return ReanimationPatientIndicator::query()
->where('rf_department_id', $departmentId)
->where('rf_medicalhistory_id', $medicalHistoryId)
->orderByDesc('reanimation_patient_indicator_id')
->limit($limit)
->get([
'reanimation_patient_indicator_id',
'rf_report_id',
'rf_medicalhistory_id',
'indicator',
'comment',
'created_by',
'created_at',
]);
}
public function searchMisPatientsForDepartment(Department $department, string $query)
{
return $this->unifiedPatientService->searchMisPatients($department, $query);
@@ -1198,7 +1257,7 @@ class ReportService
->where('department_patient_id', $departmentPatientId)
->whereIn('source_type', ['manual', 'special']);
if (!$user->isAdmin() && !$user->isHeadOfDepartment()) {
if (! $user->isAdmin() && ! $user->isHeadOfDepartment()) {
$query->where('rf_department_id', $user->department->department_id);
}
@@ -1250,7 +1309,7 @@ class ReportService
->firstOrFail();
}
if (!isset($data['startAt'], $data['endAt']) || !$data['startAt'] || !$data['endAt']) {
if (! isset($data['startAt'], $data['endAt']) || ! $data['startAt'] || ! $data['endAt']) {
throw new \InvalidArgumentException('Не указан отчет или диапазон для привязки спецконтингента');
}
@@ -1303,11 +1362,11 @@ class ReportService
'outcome' => $this->getMetrikaResultCount(7, $reportIds),
'deceased' => $this->getMetrikaResultCount(9, $reportIds),
'current' => $this->getMetrikaResultCount(8, $reportIds, false),
// 'discharged' => $this->getMetrikaResultCount('discharged', $reportIds),
// '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)
'countStaff' => $this->getMetrikaResultCount(17, [$lastReport], false),
];
// Получаем ID поступивших пациентов
@@ -1319,7 +1378,7 @@ class ReportService
// Получаем количество операций из метрик
$surgicalCount = [
$this->getMetrikaResultCount(10, $reportIds), // экстренные операции
$this->getMetrikaResultCount(11, $reportIds) // плановые операции
$this->getMetrikaResultCount(11, $reportIds), // плановые операции
];
if ($snapshotStats['outcome'] == 0) {
@@ -1332,7 +1391,7 @@ class ReportService
return [
'recipientCount' => $snapshotStats['recipient'] ?? 0,
'extractCount' => $snapshotStats['outcome'] ?? 0,
'currentCount' => $snapshotStats['current'] ?? 0,//$this->calculateCurrentPatientsFromSnapshots($reportIds, $branchId),
'currentCount' => $snapshotStats['current'] ?? 0, // $this->calculateCurrentPatientsFromSnapshots($reportIds, $branchId),
'deadCount' => $snapshotStats['deceased'] ?? 0,
'countStaff' => $snapshotStats['countStaff'] ?? 0,
'surgicalCount' => $surgicalCount,
@@ -1367,7 +1426,7 @@ class ReportService
$branchId,
$dateRange,
true
)
),
];
$manualSurgicalCount = $this->getManualSurgicalCounts($department, $dateRange);
$surgicalCount = [
@@ -1400,7 +1459,7 @@ class ReportService
'planCount' => $planCount, // плановые (поступившие + уже лечащиеся)
'emergencyCount' => $emergencyCount, // экстренные (поступившие + уже лечащиеся)
'percentDead' => $percentDead,
'beds' => $beds->value
'beds' => $beds->value,
];
}
@@ -1458,11 +1517,12 @@ class ReportService
$patientTypeMap = [
'plan' => 'plan',
'emergency' => 'emergency',
'current' => 'current',
'recipient' => 'recipient',
'outcome-discharged' => 'discharged',
'outcome-transferred' => 'transferred',
'outcome-deceased' => 'deceased',
'observation' => 'observation'
'observation' => 'observation',
];
$patientType = $patientTypeMap[$baseStatus] ?? null;
@@ -1471,6 +1531,35 @@ class ReportService
return $this->unifiedPatientService->getObservationPatients($department, $onlyIds, $sourceScope);
}
if ($baseStatus === 'outcome') {
$discharged = $this->snapshotService->getPatientsFromSnapshots(
'discharged',
$reportIds,
$branchId,
false,
false,
$recipientReportIds
);
$deceased = $this->snapshotService->getPatientsFromSnapshots(
'deceased',
$reportIds,
$branchId,
false,
false,
$recipientReportIds
);
$merged = \App\Data\UnifiedPatientData::unique($discharged->concat($deceased))
->sortByDesc(fn (\App\Data\UnifiedPatientData $patient) => $patient->admittedAt ?? '')
->values();
return $this->filterSnapshotPatientsByScope($merged, $sourceScope, $onlyIds);
}
if (! $patientType) {
return collect();
}
if ($dateRange->isOneDay && in_array($baseStatus, ['plan', 'emergency'], true)) {
$patients = $this->snapshotService->getPatientsFromOneDayCurrentSnapshots(
$patientType,
@@ -1509,7 +1598,7 @@ class ReportService
[$baseStatus] = $this->parseScopedStatus($status);
// Для плановых и экстренных включаем уже лечащихся
$includeCurrent = $isIncludeCurrent ?? in_array($baseStatus, ['plan', 'emergency'], true);
$includeCurrent = $isIncludeCurrent ?? in_array($baseStatus, ['plan', 'emergency', 'reanimation'], true);
return $this->unifiedPatientService->getLivePatientsByStatus(
$department,
@@ -1558,12 +1647,12 @@ class ReportService
'observation' => 'observation',
'outcome-discharged' => 'discharged',
'outcome-transferred' => 'transferred',
'outcome-deceased' => 'deceased'
'outcome-deceased' => 'deceased',
];
$patientType = $patientTypeMap[$baseStatus] ?? null;
if (!$patientType) {
if (! $patientType) {
return 0;
}
@@ -1605,20 +1694,18 @@ class ReportService
string $status,
DateRange $dateRange,
int $branchId
): int
{
): int {
[$baseStatus] = $this->parseScopedStatus($status);
return match($status) {
'plan', 'emergency', 'observation', 'reanimation', 'outcome', 'outcome-discharged', 'outcome-transferred', 'outcome-deceased', 'current', 'recipient' =>
$this->unifiedPatientService->getLivePatientCountByStatus(
$department,
$user,
$status,
$dateRange,
$branchId,
in_array($status, ['plan', 'emergency'], true)
),
return match ($status) {
'plan', 'emergency', 'observation', 'reanimation', 'outcome', 'outcome-discharged', 'outcome-transferred', 'outcome-deceased', 'current', 'recipient' => $this->unifiedPatientService->getLivePatientCountByStatus(
$department,
$user,
$status,
$dateRange,
$branchId,
in_array($status, ['plan', 'emergency'], true)
),
default => $this->unifiedPatientService->getLivePatientCountByStatus(
$department,
$user,
@@ -1700,7 +1787,7 @@ class ReportService
private function isSendButtonActive(User $user, DateRange $dateRange, ?Report $reportToday, ?int $fillableUserId): bool
{
// Для врача: только сегодня и если отчета еще нет
if (!$user->isHeadOfDepartment() && !$user->isAdmin()) {
if (! $user->isHeadOfDepartment() && ! $user->isAdmin()) {
if ($reportToday && $reportToday->status === 'submitted') {
return false;
}
@@ -1726,10 +1813,11 @@ class ReportService
->exactPeriod($dateRange->startSql(), $dateRange->endSql())
->orderByDesc('report_id');
if ($dateRange->isOneDay)
if ($dateRange->isOneDay) {
return $query->first();
else
} else {
return $query->onlySubmitted()->first();
}
}
/**
@@ -1737,12 +1825,12 @@ class ReportService
*/
private function getDoctorInfo(?int $doctorId, DateRange $dateRange): ?MisLpuDoctor
{
if (!$doctorId) {
if (! $doctorId) {
return null;
}
// Если дата это период, не показываем врача
if (!$dateRange->isOneDay) {
if (! $dateRange->isOneDay) {
return null;
}
@@ -1780,7 +1868,7 @@ class ReportService
->orderBy('created_at', 'DESC')
->get();
if (!$sum) {
if (! $sum) {
foreach ($reports as $report) {
$metric = $report->metrikaResults
->firstWhere('rf_metrika_item_id', $metrikaItemId);
@@ -1852,7 +1940,7 @@ class ReportService
}
return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
->with(['observationPatient' => function($query) use ($departmentId) {
->with(['observationPatient' => function ($query) use ($departmentId) {
$query->where('rf_department_id', $departmentId)
->select(['rf_medicalhistory_id', 'observation_patient_id', 'comment']);
}])
@@ -1863,6 +1951,7 @@ class ReportService
->pluck('comment')
->filter()
->implode('; ');
return $patient;
});
}
@@ -1875,7 +1964,7 @@ class ReportService
$periodPlanModel = $department->recipientPlanOfYear();
// Рассчитываем коэффициент периода (округляем в большую сторону)
$monthsInPeriod = ceil($dateRange->startDate->diffInMonths($dateRange->endDate));
$annualPlan = $periodPlanModel ? (int)$periodPlanModel->value : 0;
$annualPlan = $periodPlanModel ? (int) $periodPlanModel->value : 0;
$oneMonthPlan = ceil($annualPlan / 12);
$periodPlan = round($oneMonthPlan * $monthsInPeriod);
@@ -1897,13 +1986,14 @@ class ReportService
foreach ($reports as $report) {
$outcome = $report->metrikaResults()->where('rf_metrika_item_id', 7)->first();
if ($outcome) $progress += (int)$outcome->value;
if ($outcome) {
$progress += (int) $outcome->value;
}
}
return [
'plan' => $periodPlan,
'progress' => $progress
'progress' => $progress,
];
}
}