Перевод на доменную архитектуру
This commit is contained in:
@@ -2,6 +2,11 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Domain\Reports\ValueObjects\MetrikaConfig;
|
||||
use App\Infrastructure\Reports\Services\AutoFillReportPayloadBuilder;
|
||||
use App\Infrastructure\Reports\Services\CalculatedMetricsSynchronizer;
|
||||
use App\Infrastructure\Reports\Services\ReportPatientsReadService;
|
||||
use App\Infrastructure\Reports\Services\ReportMetricsFinalizer;
|
||||
use App\Models\Department;
|
||||
use App\Models\DepartmentPatient;
|
||||
use App\Models\DepartmentPatientOperation;
|
||||
@@ -28,8 +33,25 @@ class ReportService
|
||||
protected UnifiedPatientService $unifiedPatientService,
|
||||
protected PatientService $patientQueryService,
|
||||
protected SnapshotService $snapshotService,
|
||||
protected StatisticsService $statisticsService
|
||||
) {}
|
||||
protected StatisticsService $statisticsService,
|
||||
?ReportMetricsFinalizer $reportMetricsFinalizer = null,
|
||||
?CalculatedMetricsSynchronizer $calculatedMetricsSynchronizer = null,
|
||||
?AutoFillReportPayloadBuilder $autoFillReportPayloadBuilder = null,
|
||||
?ReportPatientsReadService $reportPatientsReadService = null,
|
||||
) {
|
||||
$this->reportMetricsFinalizer = $reportMetricsFinalizer ?? app(ReportMetricsFinalizer::class);
|
||||
$this->calculatedMetricsSynchronizer = $calculatedMetricsSynchronizer ?? app(CalculatedMetricsSynchronizer::class);
|
||||
$this->autoFillReportPayloadBuilder = $autoFillReportPayloadBuilder ?? app(AutoFillReportPayloadBuilder::class);
|
||||
$this->reportPatientsReadService = $reportPatientsReadService ?? app(ReportPatientsReadService::class);
|
||||
}
|
||||
|
||||
protected ReportMetricsFinalizer $reportMetricsFinalizer;
|
||||
|
||||
protected CalculatedMetricsSynchronizer $calculatedMetricsSynchronizer;
|
||||
|
||||
protected AutoFillReportPayloadBuilder $autoFillReportPayloadBuilder;
|
||||
|
||||
protected ReportPatientsReadService $reportPatientsReadService;
|
||||
|
||||
/**
|
||||
* Получить статистику для отчета
|
||||
@@ -49,78 +71,6 @@ class ReportService
|
||||
return $this->getStatisticsFromReplica($department, $user, $dateRange, $branchId);
|
||||
}
|
||||
|
||||
public function shouldUseSnapshotsForPage(Department $department, User $user, DateRange $dateRange): bool
|
||||
{
|
||||
return $this->shouldUseSnapshots($department, $user, $dateRange);
|
||||
}
|
||||
|
||||
public function getFastReplicaStatisticsFromPatientsPayload(
|
||||
Department $department,
|
||||
User $user,
|
||||
DateRange $dateRange,
|
||||
array $patientsPayload
|
||||
): array {
|
||||
$branchId = $this->getBranchId($department->rf_mis_department_id);
|
||||
|
||||
$planCount = count($patientsPayload['mis-plan'] ?? []) + count($patientsPayload['special-plan'] ?? []);
|
||||
$emergencyCount = count($patientsPayload['mis-emergency'] ?? []) + count($patientsPayload['special-emergency'] ?? []);
|
||||
$dischargedCount = count($patientsPayload['mis-outcome-discharged'] ?? []) + count($patientsPayload['special-outcome-discharged'] ?? []);
|
||||
$deadCount = count($patientsPayload['mis-outcome-deceased'] ?? []) + count($patientsPayload['special-outcome-deceased'] ?? []);
|
||||
$outcomeCount = $dischargedCount + $deadCount;
|
||||
|
||||
$recipientPatients = $this->unifiedPatientService
|
||||
->getLivePatientsByStatus($department, $user, 'recipient', $dateRange, $branchId);
|
||||
$recipientCount = $recipientPatients->count();
|
||||
$recipientIds = $recipientPatients->pluck('id')->all();
|
||||
|
||||
$currentCount = $this->unifiedPatientService
|
||||
->getLivePatientCountByStatus($department, $user, 'current', $dateRange, $branchId);
|
||||
|
||||
$misSurgicalCount = [
|
||||
$this->patientQueryService->getSurgicalPatients(
|
||||
'emergency',
|
||||
$branchId,
|
||||
$dateRange,
|
||||
true
|
||||
),
|
||||
$this->patientQueryService->getSurgicalPatients(
|
||||
'plan',
|
||||
$branchId,
|
||||
$dateRange,
|
||||
true
|
||||
),
|
||||
];
|
||||
$manualSurgicalCount = $this->getManualSurgicalCounts($department, $dateRange);
|
||||
$surgicalCount = [
|
||||
($misSurgicalCount[0] ?? 0) + ($manualSurgicalCount[0] ?? 0),
|
||||
($misSurgicalCount[1] ?? 0) + ($manualSurgicalCount[1] ?? 0),
|
||||
];
|
||||
|
||||
$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,
|
||||
'planCount' => $planCount,
|
||||
'emergencyCount' => $emergencyCount,
|
||||
'percentDead' => $percentDead,
|
||||
'beds' => $beds->value,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать или обновить отчет
|
||||
*/
|
||||
@@ -147,14 +97,8 @@ class ReportService
|
||||
});
|
||||
|
||||
DB::transaction(function () use ($report) {
|
||||
// Сохраняем метрику койко-дня + среднего койко-дня из снапшотов
|
||||
$this->saveBedDaysMetric($report);
|
||||
|
||||
$this->reportMetricsFinalizer->finalize($report);
|
||||
$this->saveLethalMetricFromSnapshots($report);
|
||||
|
||||
$this->savePreoperativeMetric($report);
|
||||
|
||||
$this->saveDepartmentLoadedMetric($report);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
throw $e;
|
||||
@@ -165,6 +109,31 @@ class ReportService
|
||||
return $report;
|
||||
}
|
||||
|
||||
public function prepareForHeavySave(): void
|
||||
{
|
||||
$this->prepareMemoryForHeavySave();
|
||||
}
|
||||
|
||||
public function syncCalculatedMetricsForStoredReport(Report $report, User $user, array $data): void
|
||||
{
|
||||
$this->calculatedMetricsSynchronizer->sync($report, $user, $data);
|
||||
}
|
||||
|
||||
public function finalizeStoredReport(Report $report): void
|
||||
{
|
||||
$this->reportMetricsFinalizer->finalize($report);
|
||||
}
|
||||
|
||||
public function saveLethalMetricForStoredReport(Report $report): void
|
||||
{
|
||||
$this->saveLethalMetricFromSnapshots($report);
|
||||
}
|
||||
|
||||
public function clearCacheAfterStoredReport(User $user, Report $report): void
|
||||
{
|
||||
$this->clearCacheAfterReportCreation($user, $report);
|
||||
}
|
||||
|
||||
private function prepareMemoryForHeavySave(): void
|
||||
{
|
||||
$connectionNames = array_unique(array_filter([
|
||||
@@ -191,146 +160,7 @@ class ReportService
|
||||
|
||||
public function buildAutoFillReportPayload(User $user, Department $department, DateRange $dateRange): array
|
||||
{
|
||||
$branchId = $this->getBranchId($department->rf_mis_department_id);
|
||||
|
||||
$metrics = $this->buildAutoFillMetrics($department, $user, $branchId, $dateRange);
|
||||
|
||||
return [
|
||||
'departmentId' => $department->department_id,
|
||||
'userId' => $user->rf_lpudoctor_id ?? $user->id,
|
||||
'dates' => [
|
||||
$dateRange->startTimestamp(),
|
||||
$dateRange->endTimestamp(),
|
||||
],
|
||||
'sent_at' => $dateRange->endSql(),
|
||||
'created_at' => $dateRange->endSql(),
|
||||
'status' => 'submitted',
|
||||
'metrics' => [
|
||||
'metrika_item_4' => $metrics['plan'],
|
||||
'metrika_item_12' => $metrics['emergency'],
|
||||
'metrika_item_3' => $metrics['recipient'],
|
||||
'metrika_item_7' => $metrics['discharged'] + $metrics['deceased'],
|
||||
'metrika_item_8' => $metrics['current'],
|
||||
'metrika_item_9' => $metrics['deceased'],
|
||||
'metrika_item_10' => $metrics['emergency_surgery'],
|
||||
'metrika_item_11' => $metrics['plan_surgery'],
|
||||
'metrika_item_13' => $metrics['transferred'],
|
||||
'metrika_item_14' => 0,
|
||||
'metrika_item_15' => $metrics['discharged'],
|
||||
],
|
||||
'observationPatients' => [],
|
||||
'unwantedEvents' => [],
|
||||
];
|
||||
}
|
||||
|
||||
private function buildAutoFillMetrics(Department $department, User $user, int $branchId, DateRange $dateRange): array
|
||||
{
|
||||
$manualSurgicalCount = $this->getManualSurgicalCounts($department, $dateRange);
|
||||
$recipientQuery = $this->buildRecipientMedicalHistoryQuery($branchId, $dateRange);
|
||||
$dischargeCodes = [1, 11, 2, 12, 7, 18, 48];
|
||||
$deceasedCodes = [5, 6, 15, 16];
|
||||
$transferCodes = [4, 14];
|
||||
|
||||
$planRecipient = (clone $recipientQuery)
|
||||
->where('rf_EmerSignID', 1)
|
||||
->distinct()
|
||||
->count('MedicalHistoryID');
|
||||
|
||||
$emergencyRecipient = (clone $recipientQuery)
|
||||
->whereIn('rf_EmerSignID', [2, 4])
|
||||
->distinct()
|
||||
->count('MedicalHistoryID');
|
||||
|
||||
$recipientTotal = (clone $recipientQuery)
|
||||
->distinct()
|
||||
->count('MedicalHistoryID');
|
||||
|
||||
$discharged = $this->countOutcomeByVisitResultIds($branchId, $dateRange, $dischargeCodes);
|
||||
$deceased = $this->countOutcomeByVisitResultIds($branchId, $dateRange, $deceasedCodes);
|
||||
$transferred = $this->countOutcomeByVisitResultIds($branchId, $dateRange, $transferCodes);
|
||||
|
||||
return [
|
||||
'plan' => $planRecipient,
|
||||
'emergency' => $emergencyRecipient,
|
||||
'recipient' => $recipientTotal,
|
||||
'discharged' => $discharged,
|
||||
'transferred' => $transferred,
|
||||
'deceased' => $deceased,
|
||||
'current' => $this->unifiedPatientService->getLivePatientCountByStatus($department, $user, 'current', $dateRange, $branchId, null, true),
|
||||
'plan_surgery' => $this->patientQueryService->getSurgicalPatients(
|
||||
'plan',
|
||||
$branchId,
|
||||
$dateRange,
|
||||
true
|
||||
) + ($manualSurgicalCount[1] ?? 0),
|
||||
'emergency_surgery' => $this->patientQueryService->getSurgicalPatients(
|
||||
'emergency',
|
||||
$branchId,
|
||||
$dateRange,
|
||||
true
|
||||
) + ($manualSurgicalCount[0] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
private function buildRecipientMedicalHistoryQuery(int $branchId, DateRange $dateRange)
|
||||
{
|
||||
$startAt = $dateRange->start()->copy()->subDay()->format('Y-m-d H:i:s');
|
||||
$endAt = $dateRange->end()->copy()->addDay()->format('Y-m-d H:i:s');
|
||||
|
||||
if ($dateRange->isOneDay) {
|
||||
$startAt = $dateRange->startSql();
|
||||
$endAt = $dateRange->endSql();
|
||||
}
|
||||
|
||||
return MisMedicalHistory::query()
|
||||
->where('MedicalHistoryID', '<>', 0)
|
||||
->whereExists(function ($query) use ($branchId, $startAt, $endAt) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('stt_migrationpatient as mp')
|
||||
->whereColumn('mp.rf_MedicalHistoryID', 'stt_medicalhistory.MedicalHistoryID')
|
||||
->where('mp.rf_StationarBranchID', $branchId)
|
||||
->where('mp.DateIngoing', '>', $startAt)
|
||||
->where('mp.DateIngoing', '<=', $endAt);
|
||||
});
|
||||
}
|
||||
|
||||
private function buildTreatedMedicalHistoryQuery(int $branchId, DateRange $dateRange)
|
||||
{
|
||||
$query = MisMedicalHistory::query()
|
||||
->where('MedicalHistoryID', '<>', 0)
|
||||
->whereExists(function ($query) use ($branchId) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('stt_migrationpatient as mp')
|
||||
->whereColumn('mp.rf_MedicalHistoryID', 'stt_medicalhistory.MedicalHistoryID')
|
||||
->where('mp.rf_StationarBranchID', $branchId);
|
||||
});
|
||||
|
||||
if ($dateRange->isOneDay) {
|
||||
return $query
|
||||
->where('DateExtract', '>', $dateRange->startSql())
|
||||
->where('DateExtract', '<=', $dateRange->endSql());
|
||||
}
|
||||
|
||||
$startAt = $dateRange->startSql();
|
||||
$endDate = $dateRange->end()->toDateString();
|
||||
|
||||
return $query
|
||||
->where('DateExtract', '>', $startAt)
|
||||
->whereDate('DateExtract', '<=', $endDate);
|
||||
}
|
||||
|
||||
private function countOutcomeByVisitResultIds(int $branchId, DateRange $dateRange, array $visitResultIds): int
|
||||
{
|
||||
return $this->buildTreatedMedicalHistoryQuery($branchId, $dateRange)
|
||||
->whereExists(function ($query) use ($branchId, $visitResultIds) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('stt_migrationpatient as mp')
|
||||
->whereColumn('mp.rf_MedicalHistoryID', 'stt_medicalhistory.MedicalHistoryID')
|
||||
->where('mp.rf_StationarBranchID', $branchId)
|
||||
->whereIn('mp.rf_kl_VisitResultID', $visitResultIds);
|
||||
})
|
||||
->distinct()
|
||||
->count('MedicalHistoryID');
|
||||
return $this->autoFillReportPayloadBuilder->build($user, $department, $dateRange);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -344,7 +174,7 @@ class ReportService
|
||||
MetrikaResult::updateOrCreate(
|
||||
[
|
||||
'rf_report_id' => $report->report_id,
|
||||
'rf_metrika_item_id' => 25, // койко-дни
|
||||
'rf_metrika_item_id' => MetrikaConfig::TOTAL_BED_DAYS,
|
||||
],
|
||||
['value' => $result['total_days']]
|
||||
);
|
||||
@@ -352,7 +182,7 @@ class ReportService
|
||||
MetrikaResult::updateOrCreate(
|
||||
[
|
||||
'rf_report_id' => $report->report_id,
|
||||
'rf_metrika_item_id' => 18, // средний койко-день
|
||||
'rf_metrika_item_id' => MetrikaConfig::AVERAGE_BED_DAYS,
|
||||
],
|
||||
['value' => $result['avg_days']]
|
||||
);
|
||||
@@ -513,7 +343,7 @@ class ReportService
|
||||
MetrikaResult::updateOrCreate(
|
||||
[
|
||||
'rf_report_id' => $report->report_id,
|
||||
'rf_metrika_item_id' => 18,
|
||||
'rf_metrika_item_id' => MetrikaConfig::AVERAGE_BED_DAYS,
|
||||
],
|
||||
['value' => 0]
|
||||
);
|
||||
@@ -532,9 +362,9 @@ class ReportService
|
||||
try {
|
||||
$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']);
|
||||
$this->saveMetric($report, MetrikaConfig::TOTAL_PREOPERATIVE_DAYS, $result['total_days']);
|
||||
$this->saveMetric($report, MetrikaConfig::PREOPERATIVE_PATIENT_COUNT, $result['patient_count']);
|
||||
$this->saveMetric($report, MetrikaConfig::PREOPERATIVE_AVERAGE_DAYS, $result['avg_days']);
|
||||
} catch (\Throwable $e) {
|
||||
\Log::error('Failed to save preoperative total metric: '.$e->getMessage());
|
||||
}
|
||||
@@ -546,12 +376,12 @@ class ReportService
|
||||
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');
|
||||
$currentCount = $report->metrikaResults()->where('rf_metrika_item_id', MetrikaConfig::CURRENT)->value('value');
|
||||
$bedsCount = $report->metrikaResults()->where('rf_metrika_item_id', MetrikaConfig::BEDS)->value('value');
|
||||
|
||||
$percentLoaded = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0;
|
||||
|
||||
$this->saveMetric($report, 22, $percentLoaded);
|
||||
$this->saveMetric($report, MetrikaConfig::DEPARTMENT_LOADED, $percentLoaded);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -602,48 +432,13 @@ class ReportService
|
||||
bool $beforeCreate = false,
|
||||
?bool $includeCurrentPatients = null
|
||||
) {
|
||||
[$baseStatus, $sourceScope] = $this->parseScopedStatus($status);
|
||||
$branchId = $this->getBranchId($department->rf_mis_department_id);
|
||||
|
||||
if ($sourceScope === 'special') {
|
||||
return $this->getPatientsFromReplica(
|
||||
$department,
|
||||
$user,
|
||||
$status,
|
||||
$dateRange,
|
||||
$branchId,
|
||||
$onlyIds,
|
||||
$includeCurrentPatients
|
||||
);
|
||||
}
|
||||
|
||||
// Для реанимации всегда берем live-данные из реплики.
|
||||
if ($baseStatus === 'reanimation') {
|
||||
return $this->getPatientsFromReplica(
|
||||
$department,
|
||||
$user,
|
||||
$status,
|
||||
$dateRange,
|
||||
$branchId,
|
||||
$onlyIds,
|
||||
$includeCurrentPatients
|
||||
);
|
||||
}
|
||||
|
||||
$useSnapshots = ! $this->shouldUseReplicaForLiveStatus($user, $baseStatus, $dateRange)
|
||||
&& $this->shouldUseSnapshots($department, $user, $dateRange, $beforeCreate);
|
||||
|
||||
if ($useSnapshots) {
|
||||
return $this->getPatientsFromSnapshots($department, $status, $dateRange, $branchId, $onlyIds);
|
||||
}
|
||||
|
||||
return $this->getPatientsFromReplica(
|
||||
return $this->reportPatientsReadService->getPatientsByStatus(
|
||||
$department,
|
||||
$user,
|
||||
$status,
|
||||
$dateRange,
|
||||
$branchId,
|
||||
$onlyIds,
|
||||
$beforeCreate,
|
||||
$includeCurrentPatients
|
||||
);
|
||||
}
|
||||
@@ -657,78 +452,12 @@ class ReportService
|
||||
string $status,
|
||||
DateRange $dateRange
|
||||
): int {
|
||||
[$baseStatus, $sourceScope] = $this->parseScopedStatus($status);
|
||||
$branchId = $this->getBranchId($department->rf_mis_department_id);
|
||||
|
||||
if ($sourceScope === 'special') {
|
||||
return $this->getPatientsCountFromReplica($department, $user, $status, $dateRange, $branchId);
|
||||
}
|
||||
|
||||
if ($baseStatus === 'reanimation') {
|
||||
return $this->getPatientsCountFromReplica($department, $user, $status, $dateRange, $branchId);
|
||||
}
|
||||
|
||||
$useSnapshots = ! $this->shouldUseReplicaForLiveStatus($user, $baseStatus, $dateRange)
|
||||
&& $this->shouldUseSnapshots($department, $user, $dateRange);
|
||||
|
||||
if ($useSnapshots) {
|
||||
return $this->getPatientsCountFromSnapshots($department, $status, $dateRange);
|
||||
}
|
||||
|
||||
return $this->getPatientsCountFromReplica($department, $user, $status, $dateRange, $branchId);
|
||||
return $this->reportPatientsReadService->getPatientsCountByStatus($department, $user, $status, $dateRange);
|
||||
}
|
||||
|
||||
public function getPatientsCountsMap(Department $department, User $user, DateRange $dateRange): array
|
||||
{
|
||||
$baseStatuses = [
|
||||
'plan',
|
||||
'emergency',
|
||||
'observation',
|
||||
'reanimation',
|
||||
'outcome-discharged',
|
||||
'outcome-deceased',
|
||||
'outcome-transferred',
|
||||
];
|
||||
|
||||
$counts = [
|
||||
'mis-plan' => 0,
|
||||
'mis-emergency' => 0,
|
||||
'mis-observation' => 0,
|
||||
'mis-reanimation' => 0,
|
||||
'mis-outcome' => 0,
|
||||
'mis-outcome-discharged' => 0,
|
||||
'mis-outcome-deceased' => 0,
|
||||
'mis-outcome-transferred' => 0,
|
||||
'special-plan' => 0,
|
||||
'special-emergency' => 0,
|
||||
'special-observation' => 0,
|
||||
'special-reanimation' => 0,
|
||||
'special-outcome' => 0,
|
||||
'special-outcome-discharged' => 0,
|
||||
'special-outcome-deceased' => 0,
|
||||
'special-outcome-transferred' => 0,
|
||||
];
|
||||
|
||||
foreach ($baseStatuses as $baseStatus) {
|
||||
$counts["mis-{$baseStatus}"] = $this->getPatientsCountByStatus(
|
||||
$department,
|
||||
$user,
|
||||
"mis-{$baseStatus}",
|
||||
$dateRange
|
||||
);
|
||||
$counts["special-{$baseStatus}"] = $this->getPatientsCountByStatus(
|
||||
$department,
|
||||
$user,
|
||||
"special-{$baseStatus}",
|
||||
$dateRange
|
||||
);
|
||||
}
|
||||
|
||||
// Выбывшие = выписанные + умершие (без переведенных)
|
||||
$counts['mis-outcome'] = ($counts['mis-outcome-discharged'] ?? 0) + ($counts['mis-outcome-deceased'] ?? 0);
|
||||
$counts['special-outcome'] = ($counts['special-outcome-discharged'] ?? 0) + ($counts['special-outcome-deceased'] ?? 0);
|
||||
|
||||
return $counts;
|
||||
return $this->reportPatientsReadService->getPatientsCountsMap($department, $user, $dateRange);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -761,17 +490,6 @@ class ReportService
|
||||
return true;
|
||||
}
|
||||
|
||||
private function shouldUseReplicaForLiveStatus(User $user, string $status, DateRange $dateRange): bool
|
||||
{
|
||||
if ($user->isHeadOfDepartment() || $user->isAdmin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($status, ['plan', 'emergency', 'recipient', 'current', 'reanimation'], true)
|
||||
&& $dateRange->isOneDay
|
||||
&& $dateRange->isEndDateToday();
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать или обновить отчет
|
||||
*/
|
||||
@@ -811,7 +529,7 @@ class ReportService
|
||||
$beds = $department->metrikaDefault->where('rf_metrika_item_id', 1)->first();
|
||||
MetrikaResult::create([
|
||||
'rf_report_id' => $report->report_id,
|
||||
'rf_metrika_item_id' => 1,
|
||||
'rf_metrika_item_id' => MetrikaConfig::BEDS,
|
||||
'value' => $beds->value,
|
||||
]);
|
||||
}
|
||||
@@ -862,7 +580,7 @@ class ReportService
|
||||
{
|
||||
if (empty($unwantedEvents)) {
|
||||
$report->unwantedEvents()->delete();
|
||||
$this->saveMetric($report, 16, 0);
|
||||
$this->saveMetric($report, MetrikaConfig::UNWANTED_EVENTS, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -889,7 +607,7 @@ class ReportService
|
||||
}
|
||||
|
||||
// Обновить метрику
|
||||
$this->saveMetric($report, 16, count($unwantedEvents));
|
||||
$this->saveMetric($report, MetrikaConfig::UNWANTED_EVENTS, count($unwantedEvents));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -905,7 +623,7 @@ class ReportService
|
||||
->where('rf_report_id', $report->report_id)
|
||||
->delete();
|
||||
// Обновить метрику
|
||||
$this->saveMetric($report, 14, 0);
|
||||
$this->saveMetric($report, MetrikaConfig::OBSERVATION, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -926,82 +644,12 @@ class ReportService
|
||||
}
|
||||
|
||||
// Обновить метрику
|
||||
$this->saveMetric($report, 14, count($observationPatients));
|
||||
$this->saveMetric($report, MetrikaConfig::OBSERVATION, count($observationPatients));
|
||||
}
|
||||
|
||||
private function syncCalculatedMetrics(Report $report, User $user, array $data): void
|
||||
{
|
||||
if (! isset($data['dates'][0], $data['dates'][1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$department = Department::query()->where('department_id', $report->rf_department_id)->first();
|
||||
if (! $department) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dateRange = $this->dateRangeService->getNormalizedDateRange(
|
||||
$user,
|
||||
(string) $data['dates'][0],
|
||||
(string) $data['dates'][1]
|
||||
);
|
||||
|
||||
$branchId = $this->getBranchId($department->rf_mis_department_id);
|
||||
|
||||
$planCount = $this->countUniqueSnapshotsForTypes($report->report_id, ['plan']);
|
||||
$emergencyCount = $this->countUniqueSnapshotsForTypes($report->report_id, ['emergency']);
|
||||
$recipientCount = $this->countUniqueSnapshotsForTypes($report->report_id, ['recipient']);
|
||||
$dischargedCount = $this->countUniqueSnapshotsForTypes($report->report_id, ['discharged']);
|
||||
$transferredCount = $this->countUniqueSnapshotsForTypes($report->report_id, ['transferred']);
|
||||
$deceasedCount = $this->countUniqueSnapshotsForTypes($report->report_id, ['deceased']);
|
||||
$currentCount = $this->countUniqueSnapshotsForTypes($report->report_id, ['current']);
|
||||
$outcomeCount = $dischargedCount + $deceasedCount;
|
||||
|
||||
$manualSurgicalCount = $this->getManualSurgicalCounts($department, $dateRange);
|
||||
$misEmergencySurgery = $branchId
|
||||
? $this->patientQueryService->getSurgicalPatients('emergency', $branchId, $dateRange, true)
|
||||
: 0;
|
||||
$misPlanSurgery = $branchId
|
||||
? $this->patientQueryService->getSurgicalPatients('plan', $branchId, $dateRange, true)
|
||||
: 0;
|
||||
|
||||
$observationCount = ObservationPatient::query()
|
||||
->where('rf_department_id', $department->department_id)
|
||||
->where('rf_report_id', $report->report_id)
|
||||
->count();
|
||||
|
||||
$unwantedEventsCount = UnwantedEvent::query()
|
||||
->where('rf_report_id', $report->report_id)
|
||||
->count();
|
||||
|
||||
$this->saveMetric($report, 3, $recipientCount);
|
||||
$this->saveMetric($report, 4, $planCount);
|
||||
$this->saveMetric($report, 7, $outcomeCount);
|
||||
$this->saveMetric($report, 8, $currentCount);
|
||||
$this->saveMetric($report, 9, $deceasedCount);
|
||||
$this->saveMetric($report, 10, $misEmergencySurgery + ($manualSurgicalCount[0] ?? 0));
|
||||
$this->saveMetric($report, 11, $misPlanSurgery + ($manualSurgicalCount[1] ?? 0));
|
||||
$this->saveMetric($report, 12, $emergencyCount);
|
||||
$this->saveMetric($report, 13, $transferredCount);
|
||||
$this->saveMetric($report, 14, $observationCount);
|
||||
$this->saveMetric($report, 15, $dischargedCount);
|
||||
$this->saveMetric($report, 16, $unwantedEventsCount);
|
||||
}
|
||||
|
||||
private function countUniqueSnapshotsForTypes(int $reportId, array $patientTypes): int
|
||||
{
|
||||
return MedicalHistorySnapshot::query()
|
||||
->where('rf_report_id', $reportId)
|
||||
->whereIn('patient_type', $patientTypes)
|
||||
->get(['medical_history_snapshot_id', 'patient_uid', 'rf_medicalhistory_id'])
|
||||
->map(function (MedicalHistorySnapshot $snapshot) {
|
||||
return $snapshot->patient_uid
|
||||
?: ($snapshot->rf_medicalhistory_id
|
||||
? "mis:{$snapshot->rf_medicalhistory_id}"
|
||||
: "snapshot:{$snapshot->medical_history_snapshot_id}");
|
||||
})
|
||||
->unique()
|
||||
->count();
|
||||
$this->calculatedMetricsSynchronizer->sync($report, $user, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1463,38 +1111,6 @@ class ReportService
|
||||
];
|
||||
}
|
||||
|
||||
private function getManualSurgicalCounts(Department $department, DateRange $dateRange): array
|
||||
{
|
||||
$baseQuery = DepartmentPatientOperation::query()
|
||||
->whereBetween('started_at', [$dateRange->startSql(), $dateRange->endSql()])
|
||||
->whereHas('patient', function ($query) use ($department) {
|
||||
$query->where('rf_department_id', $department->department_id)
|
||||
->whereIn('source_type', ['manual', 'special']);
|
||||
});
|
||||
|
||||
$emergencyCount = (clone $baseQuery)
|
||||
->where(function ($query) {
|
||||
$query->where('urgency', 'emergency')
|
||||
->orWhere(function ($fallback) {
|
||||
$fallback->whereNull('urgency')
|
||||
->whereHas('patient', fn ($patientQuery) => $patientQuery->where('patient_kind', 'emergency'));
|
||||
});
|
||||
})
|
||||
->count();
|
||||
|
||||
$planCount = (clone $baseQuery)
|
||||
->where(function ($query) {
|
||||
$query->where('urgency', 'plan')
|
||||
->orWhere(function ($fallback) {
|
||||
$fallback->whereNull('urgency')
|
||||
->whereHas('patient', fn ($patientQuery) => $patientQuery->where('patient_kind', 'plan'));
|
||||
});
|
||||
})
|
||||
->count();
|
||||
|
||||
return [$emergencyCount, $planCount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить пациентов из снапшотов
|
||||
*/
|
||||
@@ -1505,177 +1121,15 @@ class ReportService
|
||||
int $branchId,
|
||||
bool $onlyIds = false
|
||||
) {
|
||||
[$baseStatus, $sourceScope] = $this->parseScopedStatus($status);
|
||||
$reports = $this->getReportsForDateRange(
|
||||
$department->department_id,
|
||||
$dateRange
|
||||
);
|
||||
|
||||
$reportIds = $reports->pluck('report_id')->toArray();
|
||||
$recipientReportIds = $this->getSnapshotRecipientReportIds($reportIds);
|
||||
|
||||
$patientTypeMap = [
|
||||
'plan' => 'plan',
|
||||
'emergency' => 'emergency',
|
||||
'current' => 'current',
|
||||
'recipient' => 'recipient',
|
||||
'outcome-discharged' => 'discharged',
|
||||
'outcome-transferred' => 'transferred',
|
||||
'outcome-deceased' => 'deceased',
|
||||
'observation' => 'observation',
|
||||
];
|
||||
|
||||
$patientType = $patientTypeMap[$baseStatus] ?? null;
|
||||
|
||||
if ($patientType === 'observation') {
|
||||
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,
|
||||
$reportIds,
|
||||
false,
|
||||
$recipientReportIds
|
||||
);
|
||||
|
||||
return $this->filterSnapshotPatientsByScope($patients, $sourceScope, $onlyIds);
|
||||
}
|
||||
|
||||
$patients = $this->snapshotService->getPatientsFromSnapshots(
|
||||
$patientType,
|
||||
$reportIds,
|
||||
$branchId,
|
||||
false,
|
||||
in_array($baseStatus, ['plan', 'emergency'], true),
|
||||
$recipientReportIds
|
||||
);
|
||||
|
||||
return $this->filterSnapshotPatientsByScope($patients, $sourceScope, $onlyIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить пациентов из реплики БД
|
||||
*/
|
||||
private function getPatientsFromReplica(
|
||||
Department $department,
|
||||
User $user,
|
||||
string $status,
|
||||
DateRange $dateRange,
|
||||
int $branchId,
|
||||
bool $onlyIds = false,
|
||||
?bool $isIncludeCurrent = null
|
||||
) {
|
||||
[$baseStatus] = $this->parseScopedStatus($status);
|
||||
|
||||
// Для плановых и экстренных включаем уже лечащихся
|
||||
$includeCurrent = $isIncludeCurrent ?? in_array($baseStatus, ['plan', 'emergency', 'reanimation'], true);
|
||||
|
||||
return $this->unifiedPatientService->getLivePatientsByStatus(
|
||||
return $this->reportPatientsReadService->getPatientsFromSnapshots(
|
||||
$department,
|
||||
$user,
|
||||
$status,
|
||||
$dateRange,
|
||||
$branchId,
|
||||
$onlyIds,
|
||||
$includeCurrent
|
||||
$onlyIds
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить количество пациентов из снапшотов
|
||||
*/
|
||||
private function getPatientsCountFromSnapshots(Department $department, string $status, DateRange $dateRange): int
|
||||
{
|
||||
[$baseStatus, $sourceScope] = $this->parseScopedStatus($status);
|
||||
$reports = $this->getReportsForDateRange(
|
||||
$department->department_id,
|
||||
$dateRange
|
||||
);
|
||||
|
||||
$reportIds = $reports->pluck('report_id')->toArray();
|
||||
|
||||
if ($baseStatus === 'outcome') {
|
||||
if ($sourceScope !== 'all') {
|
||||
return $this->getPatientsFromSnapshots(
|
||||
$department,
|
||||
$status,
|
||||
$dateRange,
|
||||
$this->getBranchId($department->rf_mis_department_id),
|
||||
false
|
||||
)->count();
|
||||
}
|
||||
|
||||
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[$baseStatus] ?? null;
|
||||
|
||||
if (! $patientType) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($patientType === 'observation') {
|
||||
return $this->unifiedPatientService->getObservationPatients($department, false, $sourceScope)->count();
|
||||
}
|
||||
|
||||
if ($sourceScope !== 'all') {
|
||||
return $this->getPatientsFromSnapshots(
|
||||
$department,
|
||||
$status,
|
||||
$dateRange,
|
||||
$this->getBranchId($department->rf_mis_department_id),
|
||||
false
|
||||
)->count();
|
||||
}
|
||||
|
||||
return MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
|
||||
->where('patient_type', $patientType)
|
||||
->distinct('rf_medicalhistory_id')
|
||||
->count('rf_medicalhistory_id');
|
||||
}
|
||||
|
||||
private function getSnapshotRecipientReportIds(array $reportIds): array
|
||||
{
|
||||
if (empty($reportIds)) {
|
||||
@@ -1685,79 +1139,6 @@ class ReportService
|
||||
return [reset($reportIds)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить количество пациентов из реплики БД
|
||||
*/
|
||||
private function getPatientsCountFromReplica(
|
||||
Department $department,
|
||||
User $user,
|
||||
string $status,
|
||||
DateRange $dateRange,
|
||||
int $branchId
|
||||
): 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)
|
||||
),
|
||||
default => $this->unifiedPatientService->getLivePatientCountByStatus(
|
||||
$department,
|
||||
$user,
|
||||
$status,
|
||||
$dateRange,
|
||||
$branchId,
|
||||
in_array($baseStatus, ['plan', 'emergency'], true)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
private function filterSnapshotPatientsByScope($patients, string $sourceScope, bool $onlyIds = false)
|
||||
{
|
||||
if ($sourceScope === 'all') {
|
||||
return $onlyIds ? $patients->pluck('id') : $patients;
|
||||
}
|
||||
|
||||
$filtered = $patients->filter(function ($patient) use ($sourceScope) {
|
||||
return match ($sourceScope) {
|
||||
'mis' => $patient->sourceType === 'mis',
|
||||
'special' => in_array($patient->sourceType, ['manual', 'special'], true),
|
||||
default => true,
|
||||
};
|
||||
})->values();
|
||||
|
||||
return $onlyIds ? $filtered->pluck('id') : $filtered;
|
||||
}
|
||||
|
||||
private function parseScopedStatus(string $status): array
|
||||
{
|
||||
foreach (['mis', 'special'] as $scope) {
|
||||
$prefix = "{$scope}-";
|
||||
|
||||
if (str_starts_with($status, $prefix)) {
|
||||
return [substr($status, strlen($prefix)), $scope];
|
||||
}
|
||||
}
|
||||
|
||||
return [$status, 'all'];
|
||||
}
|
||||
|
||||
private function isSpecialScopedPatient($patient): bool
|
||||
{
|
||||
$sourceType = $patient->sourceType ?? $patient->source_type ?? null;
|
||||
|
||||
if ($sourceType !== null) {
|
||||
return in_array($sourceType, ['manual', 'special'], true);
|
||||
}
|
||||
|
||||
return str_starts_with((string) ($patient->id ?? ''), 'manual:');
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить нежелательные события за дату
|
||||
*/
|
||||
@@ -1892,70 +1273,6 @@ class ReportService
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить статистику выполнения плана по госпитализации
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user