Files
onboard/app/Services/ReportService.php
2026-04-21 10:08:14 +09:00

1747 lines
67 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

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

<?php
namespace App\Services;
use App\Models\Department;
use App\Models\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
{
$report = DB::transaction(function () use ($data, $user, $fillableAuto) {
$report = $this->createOrUpdateReport($data, $user);
// Сохраняем все, что НЕ зависит от других отчетов
$this->saveMetrics($report, $data['metrics'] ?? []);
$this->saveUnwantedEvents($report, $data['unwantedEvents'] ?? []);
$this->saveObservationPatients($report, $data['observationPatients'] ?? [], $user->rf_department_id);
$this->snapshotService->createPatientSnapshots($report, $user, $data['dates'], $fillableAuto);
$this->syncCalculatedMetrics($report, $user, $data);
return $report;
});
DB::transaction(function () use ($report) {
// Сохраняем метрику среднего койко-дня из снапшотов
$this->saveAverageBedDaysMetricFromSnapshots($report);
$this->saveLethalMetricFromSnapshots($report);
$this->savePreoperativeMetric($report);
$this->saveDepartmentLoadedMetric($report);
});
$this->clearCacheAfterReportCreation($user, $report);
return $report;
}
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(),
'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 saveAverageBedDaysMetricFromSnapshots(Report $report): void
{
try {
// Получаем все снапшоты выписанных пациентов из этого отчета
$snapshots = MedicalHistorySnapshot::where('rf_report_id', $report->report_id)
->whereIn('patient_type', ['discharged', 'deceased']) // выписанные и умершие
->with('medicalHistory')
->get();
if ($snapshots->isEmpty()) {
// Если нет выписанных, сохраняем 0
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 18,
],
['value' => 0]
);
\Log::info("No discharged patients in report {$report->report_id}, saved 0");
return;
}
// Рассчитываем средний койко-день по снапшотам
$totalDays = 0;
$validCount = 0;
foreach ($snapshots as $snapshot) {
$history = $snapshot->medicalHistory;
if ($history && $history->DateRecipient && $history->DateExtract) {
// Проверяем что дата выписки не специальная
if ($history->DateExtract->format('Y-m-d') === '2222-01-01') {
continue; // пропускаем текущих пациентов
}
$start = Carbon::parse($history->DateRecipient);
$end = Carbon::parse($history->DateExtract);
// Проверяем что дата выписки позже даты поступления
if ($end->gt($start)) {
$days = $start->diffInDays($end);
$totalDays += $days;
$validCount++;
}
}
}
$bedDays = $validCount > 0 ? $totalDays: 0;
// Сохраняем метрику
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 18,
],
['value' => $bedDays]
);
//\Log::info("Saved average bed days metric for report {$report->report_id}: {$avgBedDays} (from {$validCount} patients)");
} catch (\Exception $e) {
\Log::error("Failed to save average bed days metric: " . $e->getMessage());
// Не прерываем выполнение, если метрика не сохранилась
}
}
protected function saveLethalMetricFromSnapshots(Report $report): void
{
// Получаем все снапшоты выписанных пациентов из этого отчета
$snapshots = MedicalHistorySnapshot::where('rf_report_id', $report->report_id)
->whereIn('patient_type', ['discharged', 'deceased']) // выписанные и умершие
->with('medicalHistory')
->get();
if ($snapshots->isEmpty()) {
// Если нет выписанных, сохраняем 0
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 18,
],
['value' => 0]
);
\Log::info("No discharged patients in report {$report->report_id}, saved 0");
return;
}
}
/**
* Сохранить предоперационный койко-день из снапшотов
*/
protected function savePreoperativeMetric(Report $report): void
{
// 1. Получаем ВСЕ предыдущие отчеты этого отделения
$allPreviousReports = Report::where('rf_department_id', $report->rf_department_id)
->where('sent_at', '<=', $report->sent_at)
->orderBy('sent_at')
->pluck('report_id');
if ($allPreviousReports->isEmpty()) {
$this->saveMetric($report, 21, 0);
return;
}
// 2. Получаем ВСЕХ пациентов из всех отчетов (discharged + deceased)
$allPatients = MedicalHistorySnapshot::whereIn('rf_report_id', $allPreviousReports)
->whereIn('patient_type', ['discharged', 'deceased'])
->pluck('rf_medicalhistory_id')
->unique();
if ($allPatients->isEmpty()) {
$this->saveMetric($report, 21, 0);
return;
}
// 3. Получаем операции для ВСЕХ пациентов
$operations = DB::table('stt_surgicaloperation as so')
->join('stt_migrationpatient as mp', 'so.rf_MedicalHistoryID', '=', 'mp.rf_MedicalHistoryID')
->whereIn('so.rf_MedicalHistoryID', $allPatients)
->whereNotNull('so.Date')
->whereNotNull('mp.DateIngoing')
->select(
'so.rf_MedicalHistoryID',
DB::raw('MIN(so."Date") as first_operation'),
DB::raw('MIN(mp."DateIngoing") as first_admission')
)
->groupBy('so.rf_MedicalHistoryID')
->get();
if ($operations->isEmpty()) {
$this->saveMetric($report, 21, 0);
return;
}
// 4. Считаем общее количество дней и пациентов
$totalDays = 0;
$patientCount = 0;
foreach ($operations as $op) {
$days = Carbon::parse($op->first_admission)
->diffInDays(Carbon::parse($op->first_operation));
if ($days >= 0) {
$totalDays += $days;
$patientCount++;
}
}
// 5. Нарастающий итог = общее количество дней / общее количество пациентов
$avgDays = $patientCount > 0 ? round($totalDays / $patientCount, 1) : 0;
// 6. Сохраняем метрику
$this->saveMetric($report, 21, $avgDays);
}
/**
* Сохранить предоперационный койко-день из снапшотов
*/
protected function saveDepartmentLoadedMetric(Report $report): void
{
// Получаем все снапшоты выписанных пациентов из этого отчета
$currentCount = $report->metrikaResults()->where('rf_metrika_item_id', 8)->value('value');
$bedsCount = $report->metrikaResults()->where('rf_metrika_item_id', 1)->value('value');
$percentLoaded = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0;
$this->saveMetric($report, 22, $percentLoaded);
}
/**
* Очистить кэш после создания отчета
*/
private function clearCacheAfterReportCreation(User $user, Report $report): void
{
// Очищаем кэш статистики для пользователя
// $this->statisticsService->clearStatisticsCache($user);
// Также можно очистить кэш для всех пользователей отдела
// $this->statisticsService->clearDepartmentStatisticsCache($user->rf_department_id);
// Очищаем кэш за сегодня и вчера (так как отчеты влияют на эти даты)
$this->clearDailyCache($user, $report->created_at);
}
/**
* Очистить дневной кэш
*/
private function clearDailyCache(User $user, $reportDate): void
{
$datesToClear = [
Carbon::parse($reportDate)->format('Y-m-d'),
Carbon::parse($reportDate)->subDay()->format('Y-m-d'),
];
foreach ($datesToClear as $date) {
$cacheKey = $this->generateDailyCacheKey($user, $date);
Cache::forget($cacheKey);
}
}
private function generateDailyCacheKey(User $user, string $date): string
{
return 'daily_stats:' . $user->rf_department_id . ':' . $date;
}
/**
* Получить пациентов по статусу
*/
public function getPatientsByStatus(
Department $department,
User $user,
string $status,
DateRange $dateRange,
bool $onlyIds = false,
bool $beforeCreate = false,
?bool $includeCurrentPatients = null
) {
[$baseStatus, $sourceScope] = $this->parseScopedStatus($status);
$branchId = $this->getBranchId($department->rf_mis_department_id);
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] = $this->parseScopedStatus($status);
$branchId = $this->getBranchId($department->rf_mis_department_id);
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) {
$patients = collect($this->getPatientsByStatus($department, $user, $baseStatus, $dateRange));
$misCount = 0;
$specialCount = 0;
foreach ($patients as $patient) {
if ($this->isSpecialScopedPatient($patient)) {
$specialCount++;
} else {
$misCount++;
}
}
$counts["mis-{$baseStatus}"] = $misCount;
$counts["special-{$baseStatus}"] = $specialCount;
}
// Выбывшие = выписанные + умершие (без переведенных)
$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 (($user->isAdmin() || $user->isHeadOfDepartment()) && !$beforeCreate) {
return true;
}
// Проверяем, есть ли отчет на сегодня
$reportToday = Report::whereDate('sent_at', $dateRange->end())
->whereDate('created_at', $dateRange->end())
->where('rf_department_id', $department->department_id)
->first();
return !$dateRange->isEndDateToday() || $reportToday;
}
private function 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,
];
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 = Report::whereDate('sent_at', $dateRange->endSql())
->whereDate('created_at', $dateRange->endSql())
->where('rf_department_id', $department->department_id)
->first();
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
$useSnapshots = $isHeadOrAdmin || !$dateRange->isEndDateToday() || $reportToday;
// Получаем ID пользователя для заполнения отчета
if ($useSnapshots && $isHeadOrAdmin && $reportToday) {
$fillableUserId = $reportToday->rf_lpudoctor_id ?? null;
} else {
$fillableUserId = request()->query('userId', $user->rf_lpudoctor_id);
}
// Получаем нежелательные события
$unwantedEvents = $this->getUnwantedEvents($department, $dateRange);
// Определяем активность кнопки отправки
$isActiveSendButton = $this->isSendButtonActive($user, $dateRange, $reportToday, $fillableUserId);
$message = null;
if ($reportToday) {
$reportDoctor = $reportToday->lpuDoctor;
$message = "Отчет уже создан пользователем: $reportDoctor->FAM_V $reportDoctor->IM_V $reportDoctor->OT_V";
}
// Получаем информацию о враче
$lpuDoctor = $this->getDoctorInfo($fillableUserId, $dateRange);
// Проверяем, является ли диапазон одним днем
// $isRangeOneDay = $this->dateRangeService->isRangeOneDay(
// $endDate->copy()->subDay()->format('Y-m-d H:i:s'),
// $endDate->format('Y-m-d H:i:s')
// );
// Формируем даты для ответа
// $date = $isHeadOrAdmin ? [
// $endDate->copy()->subDay()->getTimestampMs(),
// $endDate->getTimestampMs()
// ] : $endDate->getTimestampMs();
$date = $isHeadOrAdmin ? [
$dateRange->startDate->getTimestampMs(),
$dateRange->endDate->getTimestampMs()
] : $dateRange->endDate->getTimestampMs();
return [
'report_id' => $reportToday?->report_id,
'unwantedEvents' => $unwantedEvents,
'isActiveSendButton' => $isActiveSendButton,
'message' => $message,
'isOneDay' => $dateRange->isOneDay,
'isHeadOrAdmin' => $isHeadOrAdmin,
'dates' => $date,
'userId' => $fillableUserId,
'userName' => $lpuDoctor ? "$lpuDoctor->FAM_V $lpuDoctor->IM_V $lpuDoctor->OT_V" : null
];
}
/**
* Удалить пациента из наблюдения
*/
public function removeObservationPatient(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)
{
return $this->unifiedPatientService->createManualPatient($department, $user, $data);
}
public function setManualPatientOutcome(int $departmentPatientId, array $data)
{
$patient = \App\Models\DepartmentPatient::where('department_patient_id', $departmentPatientId)->firstOrFail();
return $this->unifiedPatientService->recordManualOutcome($patient, $data);
}
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
{
if (!isset($data['startAt'], $data['endAt']) || !$data['startAt'] || !$data['endAt']) {
return;
}
$dateRange = $this->dateRangeService->getNormalizedDateRange(
$user,
(string) $data['startAt'],
(string) $data['endAt']
);
$reportIds = $this->getReportsForDateRange($patient->rf_department_id, $dateRange)
->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,
'updated_at' => now(),
]);
}
/**
* Получить статистику из снапшотов
*/
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
->getLivePatientsByStatus($department, $user, 'recipient', $dateRange, $branchId)
->pluck('id')
->all();
$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 match($status) {
'plan', 'emergency', 'observation', 'reanimation', 'outcome', 'outcome-discharged', 'outcome-transferred', 'outcome-deceased', 'current', 'recipient' =>
$this->unifiedPatientService->getLivePatientsByStatus(
$department,
$user,
$status,
$dateRange,
$branchId,
$onlyIds,
$includeCurrent
),
default => $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)
->whereDate('sent_at', '>=', $dateRange->startSql())
->whereDate('sent_at', '<=', $dateRange->endSql());
})
->get()
->map(function ($item) {
return [
...$item->toArray(),
'created_at' => Carbon::parse($item->created_at)->format('Создано d.m.Y в H:i'),
];
});
}
/**
* Проверить активность кнопки отправки отчета
*/
private function isSendButtonActive(User $user, DateRange $dateRange, ?Report $reportToday, ?int $fillableUserId): bool
{
// Для врача: только сегодня и если отчета еще нет
if (!$user->isHeadOfDepartment() && !$user->isAdmin()) {
return $dateRange->isEndDateToday() && !$reportToday;
}
// Для заведующего/админа: если есть отчет & он заполнен текущим пользователем & диапазон дат = 1 дню
if (
$reportToday &&
$reportToday->rf_lpudoctor_id === intval($fillableUserId) &&
$dateRange->isOneDay
) {
return true;
}
return false;
}
/**
* Получить информацию о враче
*/
private function getDoctorInfo(?int $doctorId, DateRange $dateRange): ?MisLpuDoctor
{
if (!$doctorId) {
return null;
}
// Если дата это период, не показываем врача
if (!$dateRange->isOneDay) {
return null;
}
return MisLpuDoctor::where('LPUDoctorID', $doctorId)->first();
}
/**
* Получить отчеты за диапазон дат
*/
public function getReportsForDateRange(int $departmentId, DateRange $dateRange)
{
if ($dateRange->isOneDay) {
return Report::where('rf_department_id', $departmentId)
->whereDate('sent_at', $dateRange->endSql())
->orderBy('sent_at', 'DESC')
->get();
}
return Report::where('rf_department_id', $departmentId)
// ->whereBetween('created_at', [$dateRange->startSql(), $dateRange->endSql()])
->where('sent_at', '>', $dateRange->startSql())
->where('sent_at', '<=', $dateRange->endSql())
->orderBy('sent_at', 'DESC')
->get();
}
/**
* Получить количество из метрик
*/
private function getMetrikaResultCount(int $metrikaItemId, array $reportIds, bool $sum = true): int
{
$count = 0;
$reports = Report::whereIn('report_id', $reportIds)
->with('metrikaResults')
->orderBy('created_at', 'DESC')
->get();
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('sent_at', '>=', $dateRange->startSql())
->where('sent_at', '<=', $dateRange->endSql());
if ($dateRange->isOneDay) {
$query->where('sent_at', '>=', $dateRange->startFirstOfMonth())
->where('sent_at', '<=', $dateRange->endSql());
} else {
$query->where('sent_at', '>=', $dateRange->startSql())
->where('sent_at', '<=', $dateRange->endSql());
}
$reports = $query->get();
foreach ($reports as $report) {
$outcome = $report->metrikaResults()->where('rf_metrika_item_id', 7)->first();
if ($outcome) $progress += (int)$outcome->value;
}
return [
'plan' => $periodPlan,
'progress' => $progress
];
}
}