* оптимизация обновления при редактировании спец контингента * добавил поддержку заключительных диагнозов * изменил определение законченной операции * добавил поддержку исхода операции * добавил определение отмены для операции через назначение * работа над диапазонами календарей, подсчет статистики * добавил статусы отчетов и подкорректировал привязку спец контингента к отчету * добавил новые сервисы для будущего кеширования * частичное разделение логики подсчета пациентов
1910 lines
71 KiB
PHP
1910 lines
71 KiB
PHP
<?php
|
||
|
||
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\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 UnifiedPatientService $unifiedPatientService,
|
||
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($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
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Создать или обновить отчет
|
||
*/
|
||
public function storeReport(array $data, User $user, $fillableAuto = false): Report
|
||
{
|
||
$this->prepareMemoryForHeavySave();
|
||
|
||
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);
|
||
|
||
$shouldBuildSnapshots = (bool) $fillableAuto;
|
||
if ($shouldBuildSnapshots) {
|
||
$this->snapshotService->createPatientSnapshots($report, $user, $data['dates'], $fillableAuto);
|
||
|
||
$this->syncCalculatedMetrics($report, $user, $data);
|
||
} else {
|
||
MedicalHistorySnapshot::query()
|
||
->where('rf_report_id', $report->report_id)
|
||
->delete();
|
||
}
|
||
|
||
return $report;
|
||
});
|
||
|
||
$shouldBuildDerivedMetrics = (bool) $fillableAuto;
|
||
if ($shouldBuildDerivedMetrics) {
|
||
DB::transaction(function () use ($report) {
|
||
// Сохраняем метрику койко-дня + среднего койко-дня из снапшотов
|
||
$this->saveBedDaysMetric($report);
|
||
|
||
$this->saveLethalMetricFromSnapshots($report);
|
||
|
||
$this->savePreoperativeMetric($report);
|
||
|
||
$this->saveDepartmentLoadedMetric($report);
|
||
});
|
||
}
|
||
} catch (\Throwable $e) {
|
||
throw $e;
|
||
}
|
||
|
||
$this->clearCacheAfterReportCreation($user, $report);
|
||
|
||
return $report;
|
||
}
|
||
|
||
private function prepareMemoryForHeavySave(): void
|
||
{
|
||
$connectionNames = array_unique(array_filter([
|
||
DB::getDefaultConnection(),
|
||
(new MisMedicalHistory)->getConnectionName(),
|
||
(new MisMigrationPatient)->getConnectionName(),
|
||
(new MisStationarBranch)->getConnectionName(),
|
||
]));
|
||
|
||
foreach ($connectionNames as $connectionName) {
|
||
try {
|
||
$connection = DB::connection($connectionName);
|
||
$connection->disableQueryLog();
|
||
$connection->flushQueryLog();
|
||
} catch (\Throwable) {
|
||
// best-effort cleanup only
|
||
}
|
||
}
|
||
|
||
if (function_exists('gc_collect_cycles')) {
|
||
gc_collect_cycles();
|
||
}
|
||
}
|
||
|
||
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');
|
||
}
|
||
|
||
/**
|
||
* Сохранить метрику койко-дня из снапшотов отчета
|
||
*/
|
||
protected function saveBedDaysMetric(Report $report): void
|
||
{
|
||
try {
|
||
$result = $this->calculateBedDaysFromSnapshots($report);
|
||
|
||
MetrikaResult::updateOrCreate(
|
||
[
|
||
'rf_report_id' => $report->report_id,
|
||
'rf_metrika_item_id' => 25, // койко-дни
|
||
],
|
||
['value' => $result['total_days']]
|
||
);
|
||
|
||
MetrikaResult::updateOrCreate(
|
||
[
|
||
'rf_report_id' => $report->report_id,
|
||
'rf_metrika_item_id' => 18, // средний койко-день
|
||
],
|
||
['value' => $result['avg_days']]
|
||
);
|
||
} catch (\Throwable $e) {
|
||
\Log::error('Failed to save bed days metric: ' . $e->getMessage());
|
||
}
|
||
}
|
||
|
||
protected function calculateBedDaysFromSnapshots(Report $report): array
|
||
{
|
||
$snapshots = MedicalHistorySnapshot::where('rf_report_id', $report->report_id)
|
||
->whereIn('patient_type', ['discharged', 'deceased'])
|
||
->with('medicalHistory')
|
||
->get();
|
||
|
||
$totalDays = 0;
|
||
$patientCount = 0;
|
||
|
||
foreach ($snapshots as $snapshot) {
|
||
$history = $snapshot->medicalHistory;
|
||
|
||
if (!$history) {
|
||
continue;
|
||
}
|
||
|
||
$start = $history->DateRecipientHS ?? $history->DateRecipient ?? null;
|
||
|
||
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)) {
|
||
$end = $history->DateDeath;
|
||
} 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)) {
|
||
$end = $history->DateExtract;
|
||
}
|
||
}
|
||
|
||
if (!$end) {
|
||
continue;
|
||
}
|
||
|
||
$start = Carbon::parse($start);
|
||
$end = Carbon::parse($end);
|
||
|
||
if ($end->lt($start)) {
|
||
continue;
|
||
}
|
||
|
||
// Календарные койко-дни
|
||
$days = $start->startOfDay()->diffInDays($end->startOfDay());
|
||
|
||
$totalDays += $days;
|
||
$patientCount++;
|
||
}
|
||
|
||
return [
|
||
'total_days' => $totalDays,
|
||
'patient_count' => $patientCount,
|
||
'avg_days' => $patientCount > 0 ? round($totalDays / $patientCount, 2) : 0,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Рассчитать предоперационные койко-дни по снапшотам отчета
|
||
*
|
||
* Возвращает:
|
||
* - total_days: общее количество предоперационных койко-дней
|
||
* - patient_count: количество пациентов, вошедших в расчет
|
||
* - avg_days: средний предоперационный койко-день
|
||
*/
|
||
protected function calculatePreoperativeDaysFromSnapshots(Report $report): array
|
||
{
|
||
$patientIds = MedicalHistorySnapshot::where('rf_report_id', $report->report_id)
|
||
->whereIn('patient_type', ['discharged', 'deceased'])
|
||
->pluck('rf_medicalhistory_id')
|
||
->unique()
|
||
->values();
|
||
|
||
if ($patientIds->isEmpty()) {
|
||
return [
|
||
'total_days' => 0,
|
||
'patient_count' => 0,
|
||
'avg_days' => 0,
|
||
];
|
||
}
|
||
|
||
$rows = DB::table('stt_medicalhistory as mh')
|
||
->join('stt_surgicaloperation as so', 'so.rf_MedicalHistoryID', '=', 'mh.MedicalHistoryID')
|
||
->whereIn('mh.MedicalHistoryID', $patientIds)
|
||
->whereNotNull('so.Date')
|
||
->select(
|
||
'mh.MedicalHistoryID',
|
||
DB::raw('MIN(so."Date") as first_operation'),
|
||
'mh.DateRecipientHS',
|
||
'mh.DateRecipient'
|
||
)
|
||
->groupBy('mh.MedicalHistoryID', 'mh.DateRecipientHS', 'mh.DateRecipient')
|
||
->get();
|
||
|
||
if ($rows->isEmpty()) {
|
||
return [
|
||
'total_days' => 0,
|
||
'patient_count' => 0,
|
||
'avg_days' => 0,
|
||
];
|
||
}
|
||
|
||
$totalDays = 0;
|
||
$patientCount = 0;
|
||
|
||
foreach ($rows as $row) {
|
||
$startRaw = $row->DateRecipientHS ?? $row->DateRecipient ?? null;
|
||
$operationRaw = $row->first_operation ?? null;
|
||
|
||
if (!$startRaw || !$operationRaw) {
|
||
continue;
|
||
}
|
||
|
||
$start = Carbon::parse($startRaw);
|
||
$operation = Carbon::parse($operationRaw);
|
||
|
||
if ($operation->lt($start)) {
|
||
continue;
|
||
}
|
||
|
||
// Разница календарных дат
|
||
$days = $start->startOfDay()->diffInDays($operation->startOfDay());
|
||
|
||
$totalDays += $days;
|
||
$patientCount++;
|
||
}
|
||
|
||
return [
|
||
'total_days' => $totalDays,
|
||
'patient_count' => $patientCount,
|
||
'avg_days' => $patientCount > 0 ? round($totalDays / $patientCount, 1) : 0,
|
||
];
|
||
}
|
||
|
||
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
|
||
{
|
||
try {
|
||
$result = $this->calculatePreoperativeDaysFromSnapshots($report);
|
||
|
||
$this->saveMetric($report, 26, $result['total_days']);
|
||
$this->saveMetric($report, 21, $result['avg_days']);
|
||
} catch (\Throwable $e) {
|
||
\Log::error('Failed to save preoperative total metric: ' . $e->getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Сохранить % загруженности
|
||
*/
|
||
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
|
||
) {
|
||
[$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
|
||
);
|
||
}
|
||
|
||
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(
|
||
$department,
|
||
$user,
|
||
$status,
|
||
$dateRange,
|
||
$branchId,
|
||
$onlyIds,
|
||
$includeCurrentPatients
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Получить количество пациентов по статусу
|
||
*/
|
||
public function getPatientsCountByStatus(
|
||
Department $department,
|
||
User $user,
|
||
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);
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* Получить 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 ($beforeCreate) {
|
||
return false;
|
||
}
|
||
|
||
$report = $this->getReportForPeriod($department->department_id, $dateRange);
|
||
if (!$report) {
|
||
return false;
|
||
}
|
||
|
||
if ($report->status !== 'submitted') {
|
||
return false;
|
||
}
|
||
|
||
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'], true)
|
||
&& $dateRange->isOneDay
|
||
&& $dateRange->isEndDateToday();
|
||
}
|
||
|
||
/**
|
||
* Создать или обновить отчет
|
||
*/
|
||
private function createOrUpdateReport(array $data, User $user): Report
|
||
{
|
||
$rangeStartAt = isset($data['dates'][0])
|
||
? $this->dateRangeService->toSqlFormat($data['dates'][0])
|
||
: null;
|
||
$rangeEndAt = isset($data['dates'][1])
|
||
? $this->dateRangeService->toSqlFormat($data['dates'][1])
|
||
: null;
|
||
|
||
$dateRange = $this->dateRangeService->createDateRangeForDate($this->dateRangeService->toCarbon($data['dates'][1]), $user);
|
||
|
||
$sentAt = $data['sent_at'] ?? $rangeEndAt ?? $this->dateRangeService->toSqlFormat(\Illuminate\Support\Carbon::now());
|
||
$createdAt = $data['created_at'] ?? $rangeEndAt ?? $this->dateRangeService->toSqlFormat(\Illuminate\Support\Carbon::now());
|
||
|
||
$reportData = [
|
||
'rf_department_id' => $data['departmentId'],
|
||
'rf_user_id' => $user->id,
|
||
'rf_lpudoctor_id' => $data['userId'],
|
||
'sent_at' => $sentAt,
|
||
'period_start' => $dateRange->startSql(),
|
||
'period_end' => $dateRange->endSql(),
|
||
'created_at' => $createdAt,
|
||
'status' => $data['status'] ?? 'draft',
|
||
];
|
||
|
||
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->saveMetric($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->saveMetric($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->saveMetric($report, 14, 0);
|
||
return;
|
||
}
|
||
|
||
foreach ($observationPatients as $patient) {
|
||
ObservationPatient::updateOrCreate(
|
||
[
|
||
'rf_medicalhistory_id' => $patient['medical_history_id'] ?? null,
|
||
'rf_department_patient_id' => $patient['department_patient_id'] ?? null,
|
||
'rf_department_id' => $departmentId,
|
||
],
|
||
[
|
||
'rf_report_id' => $report->report_id,
|
||
'rf_mkab_id' => null,
|
||
'comment' => $patient['comment'] ?? null,
|
||
]
|
||
);
|
||
}
|
||
|
||
// Обновить метрику
|
||
$this->saveMetric($report, 14, 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();
|
||
}
|
||
|
||
/**
|
||
* Получить информацию о текущем отчете
|
||
*/
|
||
public function getCurrentReportInfo(Department $department, User $user, DateRange $dateRange): array
|
||
{
|
||
$reportToday = $this->getReportForPeriod($department->department_id, $dateRange);
|
||
|
||
$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";
|
||
}
|
||
$statusMessage = $reportToday
|
||
? ($reportToday->status === 'submitted'
|
||
? 'Этот отчет в статусе: опубликован'
|
||
: 'Этот отчет в статусе: черновик')
|
||
: null;
|
||
|
||
// Получаем информацию о враче
|
||
$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' => $dateRange->isOneDay ? $message : null,
|
||
'status' => $reportToday?->status ?? 'draft',
|
||
'statusMessage' => $dateRange->isOneDay ? $statusMessage : null,
|
||
'canPublish' => (bool) $reportToday && ($reportToday->status === 'draft') && $isActiveSendButton,
|
||
'isOneDay' => $dateRange->isOneDay,
|
||
'isHeadOrAdmin' => $isHeadOrAdmin,
|
||
'dates' => $date,
|
||
'userId' => $fillableUserId,
|
||
'userName' => $lpuDoctor ? "$lpuDoctor->FAM_V $lpuDoctor->IM_V $lpuDoctor->OT_V" : null
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Удалить пациента из наблюдения
|
||
*/
|
||
public function removeObservationPatient(string $patientId): void
|
||
{
|
||
[$sourceType, $id] = explode(':', $patientId) + [null, null];
|
||
|
||
if ($sourceType === 'manual') {
|
||
ObservationPatient::where('rf_department_patient_id', $id)->delete();
|
||
return;
|
||
}
|
||
|
||
ObservationPatient::where('rf_medicalhistory_id', $id)->delete();
|
||
}
|
||
|
||
public function createManualPatient(Department $department, User $user, array $data)
|
||
{
|
||
$report = $this->resolveReportForManualPatient($department, $user, $data);
|
||
|
||
return $this->unifiedPatientService->createManualPatient($department, $user, $data, $report->report_id);
|
||
}
|
||
|
||
public function setManualPatientOutcome(User $user, int $departmentPatientId, array $data)
|
||
{
|
||
$patient = \App\Models\DepartmentPatient::where('department_patient_id', $departmentPatientId)->firstOrFail();
|
||
$updatedPatient = $this->unifiedPatientService->recordManualOutcome($patient, $data);
|
||
$this->syncManualPatientSnapshots($updatedPatient, $user, []);
|
||
|
||
return $updatedPatient;
|
||
}
|
||
|
||
public function updateManualPatient(User $user, int $departmentPatientId, array $data)
|
||
{
|
||
$patient = $this->resolveManageableManualPatient($user, $departmentPatientId);
|
||
|
||
$updatedPatient = $this->unifiedPatientService->updateManualPatient($patient, $data);
|
||
$this->syncManualPatientSnapshots($updatedPatient, $user, $data);
|
||
|
||
return $updatedPatient;
|
||
}
|
||
|
||
public function linkManualPatientToMis(int $departmentPatientId, int $medicalHistoryId)
|
||
{
|
||
$patient = \App\Models\DepartmentPatient::where('department_patient_id', $departmentPatientId)->firstOrFail();
|
||
|
||
return $this->unifiedPatientService->linkManualPatientToMis($patient, $medicalHistoryId);
|
||
}
|
||
|
||
public function getManualPatientOperations(User $user, int $departmentPatientId)
|
||
{
|
||
$patient = $this->resolveManageableManualPatient($user, $departmentPatientId);
|
||
|
||
return $patient->operations()
|
||
->with('serviceMedical')
|
||
->orderByDesc('started_at')
|
||
->get();
|
||
}
|
||
|
||
public function createManualPatientOperation(User $user, int $departmentPatientId, array $data): DepartmentPatientOperation
|
||
{
|
||
$patient = $this->resolveManageableManualPatient($user, $departmentPatientId);
|
||
$service = MisServiceMedical::query()
|
||
->where('ServiceMedicalID', $data['service_id'])
|
||
->firstOrFail();
|
||
|
||
return $patient->operations()->create([
|
||
'rf_kl_service_medical_id' => $service->ServiceMedicalID,
|
||
'service_code' => $service->ServiceMedicalCode,
|
||
'service_name' => $service->ServiceMedicalName,
|
||
'urgency' => $data['urgency'],
|
||
'started_at' => $data['started_at'],
|
||
'ended_at' => $data['ended_at'],
|
||
'created_by' => $user->id,
|
||
])->load('serviceMedical');
|
||
}
|
||
|
||
public function updateManualPatientOperation(User $user, int $departmentPatientId, int $operationId, array $data): DepartmentPatientOperation
|
||
{
|
||
$patient = $this->resolveManageableManualPatient($user, $departmentPatientId);
|
||
$service = MisServiceMedical::query()
|
||
->where('ServiceMedicalID', $data['service_id'])
|
||
->firstOrFail();
|
||
|
||
$operation = $patient->operations()
|
||
->where('department_patient_operation_id', $operationId)
|
||
->firstOrFail();
|
||
|
||
$operation->update([
|
||
'rf_kl_service_medical_id' => $service->ServiceMedicalID,
|
||
'service_code' => $service->ServiceMedicalCode,
|
||
'service_name' => $service->ServiceMedicalName,
|
||
'urgency' => $data['urgency'],
|
||
'started_at' => $data['started_at'],
|
||
'ended_at' => $data['ended_at'],
|
||
]);
|
||
|
||
return $operation->fresh()->load('serviceMedical');
|
||
}
|
||
|
||
public function deleteManualPatientOperation(User $user, int $departmentPatientId, int $operationId): void
|
||
{
|
||
$patient = $this->resolveManageableManualPatient($user, $departmentPatientId);
|
||
|
||
$patient->operations()
|
||
->where('department_patient_operation_id', $operationId)
|
||
->firstOrFail()
|
||
->delete();
|
||
}
|
||
|
||
public function searchMisPatientsForDepartment(Department $department, string $query)
|
||
{
|
||
return $this->unifiedPatientService->searchMisPatients($department, $query);
|
||
}
|
||
|
||
private function resolveManageableManualPatient(User $user, int $departmentPatientId): DepartmentPatient
|
||
{
|
||
$query = DepartmentPatient::query()
|
||
->where('department_patient_id', $departmentPatientId)
|
||
->whereIn('source_type', ['manual', 'special']);
|
||
|
||
if (!$user->isAdmin() && !$user->isHeadOfDepartment()) {
|
||
$query->where('rf_department_id', $user->department->department_id);
|
||
}
|
||
|
||
return $query->firstOrFail();
|
||
}
|
||
|
||
private function syncManualPatientSnapshots(DepartmentPatient $patient, User $user, array $data): void
|
||
{
|
||
$reportIds = $patient->rf_report_id
|
||
? [$patient->rf_report_id]
|
||
: (isset($data['startAt'], $data['endAt']) && $data['startAt'] && $data['endAt']
|
||
? $this->getReportsForDateRange(
|
||
$patient->rf_department_id,
|
||
$this->dateRangeService->getNormalizedDateRange(
|
||
$user,
|
||
(string) $data['startAt'],
|
||
(string) $data['endAt']
|
||
)
|
||
)->pluck('report_id')->values()->all()
|
||
: []);
|
||
|
||
if (empty($reportIds)) {
|
||
return;
|
||
}
|
||
|
||
MedicalHistorySnapshot::query()
|
||
->whereIn('rf_report_id', $reportIds)
|
||
->where('rf_department_patient_id', $patient->department_patient_id)
|
||
->update([
|
||
'patient_kind' => $patient->patient_kind,
|
||
'full_name' => $patient->full_name,
|
||
'birth_date' => $patient->birth_date,
|
||
'diagnosis_code' => $patient->diagnosis_code,
|
||
'diagnosis_name' => $patient->diagnosis_name,
|
||
'admitted_at' => $patient->admitted_at,
|
||
'outcome_type' => $patient->is_current ? null : $patient->outcome_type,
|
||
'outcome_at' => $patient->is_current ? null : $patient->outcome_at,
|
||
'updated_at' => now(),
|
||
]);
|
||
}
|
||
|
||
private function resolveReportForManualPatient(Department $department, User $user, array $data): Report
|
||
{
|
||
$reportId = $data['report_id'] ?? null;
|
||
if ($reportId) {
|
||
return Report::query()
|
||
->where('report_id', $reportId)
|
||
->where('rf_department_id', $department->department_id)
|
||
->firstOrFail();
|
||
}
|
||
|
||
if (!isset($data['startAt'], $data['endAt']) || !$data['startAt'] || !$data['endAt']) {
|
||
throw new \InvalidArgumentException('Не указан отчет или диапазон для привязки спецконтингента');
|
||
}
|
||
|
||
$dateRange = $this->dateRangeService->getNormalizedDateRange(
|
||
$user,
|
||
(string) $data['startAt'],
|
||
(string) $data['endAt']
|
||
);
|
||
|
||
$existingReport = Report::query()
|
||
->where('rf_department_id', $department->department_id)
|
||
->exactPeriod($dateRange->startSql(), $dateRange->endSql())
|
||
->first();
|
||
|
||
if ($existingReport) {
|
||
return $existingReport;
|
||
}
|
||
|
||
return Report::query()->create([
|
||
'rf_department_id' => $department->department_id,
|
||
'rf_user_id' => $user->id,
|
||
'rf_lpudoctor_id' => $data['user_id'] ?? $user->rf_lpudoctor_id,
|
||
'sent_at' => $dateRange->endSql(),
|
||
'created_at' => $dateRange->endSql(),
|
||
'period_start' => $dateRange->startSql(),
|
||
'period_end' => $dateRange->endSql(),
|
||
'status' => 'draft',
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Получить статистику из снапшотов
|
||
*/
|
||
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);
|
||
$recipientReportIds = $this->getSnapshotRecipientReportIds($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 = $this->snapshotService
|
||
->getPatientsFromSnapshots('recipient', $recipientReportIds)
|
||
->pluck('id')
|
||
->all();
|
||
|
||
// Получаем количество операций из метрик
|
||
$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(Department $department, User $user, DateRange $dateRange, int $branchId): array
|
||
{
|
||
$planCount = $this->unifiedPatientService->getLivePatientCountByStatus($department, $user, 'plan', $dateRange, $branchId, true);
|
||
$emergencyCount = $this->unifiedPatientService->getLivePatientCountByStatus($department, $user, 'emergency', $dateRange, $branchId, true);
|
||
$currentCount = $this->unifiedPatientService->getLivePatientCountByStatus($department, $user, 'current', $dateRange, $branchId);
|
||
$recipientCount = $this->unifiedPatientService->getLivePatientCountByStatus($department, $user, 'recipient', $dateRange, $branchId);
|
||
$outcomeCount = $this->unifiedPatientService->getLivePatientCountByStatus($department, $user, 'outcome', $dateRange, $branchId);
|
||
$deadCount = $this->unifiedPatientService->getLivePatientCountByStatus($department, $user, 'outcome-deceased', $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),
|
||
];
|
||
|
||
// ID поступивших сегодня (для отметки в таблице)
|
||
$recipientIds = $this->unifiedPatientService
|
||
->getRecipientIdsForReport($department, $user, $dateRange, $branchId);
|
||
|
||
$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
|
||
];
|
||
}
|
||
|
||
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];
|
||
}
|
||
|
||
/**
|
||
* Получить пациентов из снапшотов
|
||
*/
|
||
public function getPatientsFromSnapshots(
|
||
Department $department,
|
||
string $status,
|
||
DateRange $dateRange,
|
||
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',
|
||
'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 ($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'], true);
|
||
|
||
return $this->unifiedPatientService->getLivePatientsByStatus(
|
||
$department,
|
||
$user,
|
||
$status,
|
||
$dateRange,
|
||
$branchId,
|
||
$onlyIds,
|
||
$includeCurrent
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Получить количество пациентов из снапшотов
|
||
*/
|
||
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)) {
|
||
return [];
|
||
}
|
||
|
||
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:');
|
||
}
|
||
|
||
/**
|
||
* Получить нежелательные события за дату
|
||
*/
|
||
public function getUnwantedEvents(Department $department, DateRange $dateRange)
|
||
{
|
||
return UnwantedEvent::whereHas('report', function ($query) use ($department, $dateRange) {
|
||
$query->where('rf_department_id', $department->department_id);
|
||
|
||
if ($dateRange->isOneDay) {
|
||
$query->exactPeriod($dateRange->startSql(), $dateRange->endSql());
|
||
} else {
|
||
$query->withinPeriod($dateRange->startSql(), $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()) {
|
||
if ($reportToday && $reportToday->status === 'submitted') {
|
||
return false;
|
||
}
|
||
|
||
return $dateRange->isEndDateToday();
|
||
}
|
||
|
||
// Для заведующего/админа: можно редактировать любой отчет за сутки (включая submitted)
|
||
if (
|
||
$reportToday &&
|
||
$dateRange->isOneDay
|
||
) {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private function getReportForPeriod(int $departmentId, DateRange $dateRange): ?Report
|
||
{
|
||
$query = Report::query()
|
||
->where('rf_department_id', $departmentId)
|
||
->exactPeriod($dateRange->startSql(), $dateRange->endSql())
|
||
->orderByDesc('report_id');
|
||
|
||
if ($dateRange->isOneDay)
|
||
return $query->first();
|
||
else
|
||
return $query->onlySubmitted()->first();
|
||
}
|
||
|
||
/**
|
||
* Получить информацию о враче
|
||
*/
|
||
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)
|
||
->exactPeriod($dateRange->startSql(), $dateRange->endSql())
|
||
->onlySubmitted()
|
||
->orderBy('period_end', 'DESC')
|
||
->get();
|
||
}
|
||
|
||
return Report::where('rf_department_id', $departmentId)
|
||
->withinPeriod($dateRange->startSql(), $dateRange->endSql())
|
||
->onlySubmitted()
|
||
->orderBy('period_end', '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();
|
||
|
||
if (!$sum) {
|
||
foreach ($reports as $report) {
|
||
$metric = $report->metrikaResults
|
||
->firstWhere('rf_metrika_item_id', $metrikaItemId);
|
||
|
||
if ($metric) {
|
||
return intval($metric->value) ?? 0;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
foreach ($reports as $report) {
|
||
foreach ($report->metrikaResults as $metrikaResult) {
|
||
if ($metrikaResult->rf_metrika_item_id === $metrikaItemId) {
|
||
$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('period_start', '>', $dateRange->startSql())
|
||
->where('period_end', '<=', $dateRange->endSql());
|
||
|
||
if ($dateRange->isOneDay) {
|
||
$query->where('period_start', '>=', $dateRange->startFirstOfMonth())
|
||
->where('period_end', '<=', $dateRange->endSql());
|
||
} else {
|
||
$query->where('period_start', '>', $dateRange->startSql())
|
||
->where('period_end', '<=', $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
|
||
];
|
||
}
|
||
|
||
}
|