Files
onboard/app/Services/ReportService.php

823 lines
29 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\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\DB;
class ReportService
{
public function __construct(
protected DateRangeService $dateRangeService,
protected PatientService $patientQueryService,
protected SnapshotService $snapshotService
) {}
/**
* Получить статистику для отчета
*/
public function getReportStatistics(User $user, DateRange $dateRange): array
{
$department = $user->department;
$misDepartmentId = $department->rf_mis_department_id;
$branchId = $this->getBranchId($misDepartmentId);
// Определяем, используем ли мы снапшоты
$useSnapshots = $this->shouldUseSnapshots($user, $dateRange);
if ($useSnapshots) {
return $this->getStatisticsFromSnapshots($user, $dateRange, $branchId);
}
return $this->getStatisticsFromReplica($user, $dateRange, $branchId);
}
/**
* Создать или обновить отчет
*/
public function storeReport(array $data, User $user, $fillableAuto = false): Report
{
DB::beginTransaction();
try {
$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);
DB::commit();
return $report;
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
/**
* Получить пациентов по статусу
*/
public function getPatientsByStatus(
User $user,
string $status,
DateRange $dateRange,
bool $onlyIds = false,
bool $beforeCreate = false,
?bool $includeCurrentPatients = null
) {
$branchId = $this->getBranchId($user->department->rf_mis_department_id);
$useSnapshots = $this->shouldUseSnapshots($user, $dateRange, $beforeCreate);
if ($useSnapshots) {
return $this->getPatientsFromSnapshots($user, $status, $dateRange, $branchId);
}
return $this->getPatientsFromReplica($user, $status, $dateRange, $branchId, $onlyIds, $includeCurrentPatients);
}
/**
* Получить количество пациентов по статусу
*/
public function getPatientsCountByStatus(
User $user,
string $status,
DateRange $dateRange
): int {
$branchId = $this->getBranchId($user->department->rf_mis_department_id);
$useSnapshots = $this->shouldUseSnapshots($user, $dateRange);
if ($useSnapshots) {
return $this->getPatientsCountFromSnapshots($user, $status, $dateRange);
}
return $this->getPatientsCountFromReplica($user, $status, $dateRange, $branchId);
}
/**
* Получить ID отделения из стационарного отделения
*/
private function getBranchId(int $misDepartmentId): ?int
{
return MisStationarBranch::where('rf_DepartmentID', $misDepartmentId)
->value('StationarBranchID');
}
/**
* Определить, нужно ли использовать снапшоты
*/
private function shouldUseSnapshots(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())
->first();
return !$dateRange->isEndDateToday() || $reportToday;
}
/**
* Создать или обновить отчет
*/
private function createOrUpdateReport(array $data, User $user): Report
{
$reportData = [
'rf_department_id' => $data['departmentId'],
'rf_user_id' => $user->id,
'rf_lpudoctor_id' => $data['userId'],
'sent_at' => $data['sent_at'] ?? $this->dateRangeService->toSqlFormat(\Illuminate\Support\Carbon::now()),
'created_at' => $data['created_at'] ?? $this->dateRangeService->toSqlFormat(\Illuminate\Support\Carbon::now()),
];
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 saveUnwantedEvents(Report $report, array $unwantedEvents): void
{
if (empty($unwantedEvents)) {
$report->unwantedEvents()->delete();
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,
]);
}
}
}
/**
* Сохранить пациентов под наблюдением
*/
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();
return;
}
foreach ($observationPatients as $patient) {
ObservationPatient::updateOrCreate(
[
'rf_medicalhistory_id' => $patient['id'],
'rf_department_id' => $departmentId,
],
[
'rf_report_id' => $report->report_id,
'rf_mkab_id' => null,
'comment' => $patient['comment'] ?? null,
]
);
}
}
/**
* Получить информацию о текущем отчете
*/
public function getCurrentReportInfo(User $user, DateRange $dateRange): array
{
$department = $user->department;
$reportToday = Report::whereDate('sent_at', $dateRange->endSql())
->whereDate('created_at', $dateRange->endSql())
->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($user, $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(int $medicalHistoryId): void
{
ObservationPatient::where('rf_medicalhistory_id', $medicalHistoryId)->delete();
}
/**
* Получить статистику из снапшотов
*/
private function getStatisticsFromSnapshots(User $user, DateRange $dateRange, int $branchId): array
{
// Получаем отчеты за период
$reports = $this->getReportsForDateRange(
$user->rf_department_id,
$dateRange
);
$reportIds = $reports->pluck('report_id')->toArray();
// Получаем статистику из снапшотов
$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)
];
// Получаем ID поступивших пациентов
$recipientIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
->where('patient_type', 'recipient')
->pluck('rf_medicalhistory_id')
->unique()
->toArray();
// Получаем количество операций из метрик
$surgicalCount = [
$this->getMetrikaResultCount(10, $reportIds), // экстренные операции
$this->getMetrikaResultCount(11, $reportIds) // плановые операции
];
return [
'recipientCount' => $snapshotStats['recipient'] ?? 0,
'extractCount' => $snapshotStats['outcome'] ?? 0,
'currentCount' => $snapshotStats['current'] ?? 0,//$this->calculateCurrentPatientsFromSnapshots($reportIds, $branchId),
'deadCount' => $snapshotStats['deceased'] ?? 0,
'surgicalCount' => $surgicalCount,
'recipientIds' => $recipientIds,
'beds' => $snapshotStats['beds'] ?? 0
];
}
/**
* Получить статистику из реплики БД
*/
private function getStatisticsFromReplica(User $user, DateRange $dateRange, int $branchId): array
{
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
// Плановые: поступившие сегодня + уже лечащиеся
$planCount = $this->patientQueryService->getPatientsCountWithCurrent(
'plan',
$isHeadOrAdmin,
$branchId,
$dateRange
);
// Экстренные: поступившие сегодня + уже лечащиеся
$emergencyCount = $this->patientQueryService->getPatientsCountWithCurrent(
'emergency',
$isHeadOrAdmin,
$branchId,
$dateRange
);
// Все пациенты в отделении: поступившие + лечащиеся
$currentCount = $this->patientQueryService->getAllPatientsInDepartment(
$isHeadOrAdmin,
$branchId,
$dateRange,
true
);
// Поступившие сегодня (только новые поступления)
$recipientCount = $this->patientQueryService->getPlanOrEmergencyPatients(
null, // все типы
$isHeadOrAdmin,
$branchId,
$dateRange,
true,
false,
false // не включаем уже лечащихся
);
// Выбывшие за период
$outcomeCount = $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'all'
)->count();
// Умершие за период
$deadCount = $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'deceased'
)->count();
// Операции
$surgicalCount = [
$this->patientQueryService->getSurgicalPatients(
'emergency',
$branchId,
$dateRange,
true
),
$this->patientQueryService->getSurgicalPatients(
'plan',
$branchId,
$dateRange,
true
)
];
// ID поступивших сегодня (для отметки в таблице)
$recipientIds = $this->patientQueryService->getPlanOrEmergencyPatients(
null,
$isHeadOrAdmin,
$branchId,
$dateRange,
false,
true,
false // только поступившие сегодня
);
$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();
return [
'recipientCount' => $recipientCount, // только поступившие сегодня
'extractCount' => $outcomeCount,
'currentCount' => $currentCount, // все в отделении
'deadCount' => $deadCount,
'surgicalCount' => $surgicalCount,
'recipientIds' => $recipientIds, // ID поступивших сегодня
'planCount' => $planCount, // плановые (поступившие + уже лечащиеся)
'emergencyCount' => $emergencyCount, // экстренные (поступившие + уже лечащиеся)
'beds' => $beds->value
];
}
/**
* Получить пациентов из снапшотов
*/
private function getPatientsFromSnapshots(
User $user,
string $status,
DateRange $dateRange,
int $branchId,
bool $onlyIds = false
) {
$reports = $this->getReportsForDateRange(
$user->rf_department_id,
$dateRange
);
$reportIds = $reports->pluck('report_id')->toArray();
$patientTypeMap = [
'plan' => 'plan',
'emergency' => 'emergency',
'outcome-discharged' => 'discharged',
'outcome-transferred' => 'transferred',
'outcome-deceased' => 'deceased',
'observation' => 'observation'
];
$patientType = $patientTypeMap[$status] ?? null;
if ($patientType === 'observation') {
return $this->patientQueryService->getObservationPatients($user->rf_department_id, $onlyIds); //$this->getObservationPatientsFromSnapshots($user->rf_department_id, $reportIds, $onlyIds);
}
return $this->snapshotService->getPatientsFromSnapshots($patientType, $reportIds, $branchId, $onlyIds);
}
/**
* Получить пациентов из реплики БД
*/
private function getPatientsFromReplica(
User $user,
string $status,
DateRange $dateRange,
int $branchId,
bool $onlyIds = false,
?bool $isIncludeCurrent = null
) {
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
// Для плановых и экстренных включаем уже лечащихся
$includeCurrent = $isIncludeCurrent ?? in_array($status, ['plan', 'emergency']);
return match($status) {
'plan', 'emergency' => $this->patientQueryService->getPlanOrEmergencyPatients(
$status,
$isHeadOrAdmin,
$branchId,
$dateRange,
false,
$onlyIds,
$includeCurrent
),
'observation' => $this->patientQueryService->getObservationPatients($user->rf_department_id, $onlyIds),
'outcome' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'without-transferred',
$onlyIds
), // Выписанные без перевода
'outcome-discharged' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'discharged',
$onlyIds
),
'outcome-transferred' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'transferred',
$onlyIds
),
'outcome-deceased' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'deceased',
$onlyIds
),
'current' => $this->patientQueryService->getAllPatientsInDepartment(
$isHeadOrAdmin,
$branchId,
$dateRange,
false,
$onlyIds
),
'recipient' => $this->patientQueryService->getPlanOrEmergencyPatients(
null,
$isHeadOrAdmin,
$branchId,
$dateRange,
false,
$onlyIds,
false // только поступившие
),
default => collect()
};
}
/**
* Получить количество пациентов из снапшотов
*/
private function getPatientsCountFromSnapshots(User $user, string $status, DateRange $dateRange): int
{
$reports = $this->getReportsForDateRange(
$user->rf_department_id,
$dateRange
);
$reportIds = $reports->pluck('report_id')->toArray();
if ($status === 'outcome') {
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[$status] ?? null;
if (!$patientType) {
return 0;
}
if ($patientType === 'observation') {
return ObservationPatient::whereIn('rf_report_id', $reportIds)
->distinct('rf_medicalhistory_id')
->count('rf_medicalhistory_id');
}
return MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
->where('patient_type', $patientType)
->distinct('rf_medicalhistory_id')
->count('rf_medicalhistory_id');
}
/**
* Получить количество пациентов из реплики БД
*/
private function getPatientsCountFromReplica(User $user, string $status, DateRange $dateRange, int $branchId): int
{
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
return match($status) {
'plan', 'emergency' => $this->patientQueryService->getPatientsCountWithCurrent(
$status,
$isHeadOrAdmin,
$branchId,
$dateRange,
),
'observation' => ObservationPatient::where('rf_department_id', $user->rf_department_id)->count(),
'outcome' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'all'
)->count(),
'outcome-discharged' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'discharged'
)->count(),
'outcome-transferred' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'transferred'
)->count(),
'outcome-deceased' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'deceased'
)->count(),
default => 0
};
}
/**
* Получить нежелательные события за дату
*/
private function getUnwantedEvents(User $user, DateRange $dateRange)
{
return UnwantedEvent::whereHas('report', function ($query) use ($user, $dateRange) {
$query->where('rf_department_id', $user->rf_department_id)
->whereDate('created_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;
}
// Для заведующего/админа: если есть отчет и он заполнен текущим пользователем
if ($reportToday && $reportToday->rf_lpudoctor_id === intval($fillableUserId)) {
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();
}
/**
* Получить отчеты за диапазон дат
*/
private function getReportsForDateRange(int $departmentId, DateRange $dateRange)
{
if ($dateRange->isOneDay) {
return Report::where('rf_department_id', $departmentId)
->whereDate('created_at', $dateRange->endSql())
->orderBy('created_at', 'ASC')
->get();
}
return Report::where('rf_department_id', $departmentId)
->whereBetween('created_at', [$dateRange->startSql(), $dateRange->endSql()])
->orderBy('created_at', 'ASC')
->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();
foreach ($reports as $report) {
foreach ($report->metrikaResults as $metrikaResult) {
if ($metrikaResult->rf_metrika_item_id === $metrikaItemId) {
if ($sum) $count += intval($metrikaResult->value) ?? 0;
else $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;
});
}
}