1317 lines
49 KiB
PHP
1317 lines
49 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Domain\Reports\ValueObjects\MetrikaConfig;
|
||
use App\Infrastructure\Reports\Services\AutoFillReportPayloadBuilder;
|
||
use App\Infrastructure\Reports\Services\CalculatedMetricsSynchronizer;
|
||
use App\Infrastructure\Reports\Services\ReportPatientsReadService;
|
||
use App\Infrastructure\Reports\Services\ReportMetricsFinalizer;
|
||
use App\Models\Department;
|
||
use App\Models\DepartmentPatient;
|
||
use App\Models\DepartmentPatientOperation;
|
||
use App\Models\MedicalHistorySnapshot;
|
||
use App\Models\MetrikaResult;
|
||
use App\Models\MisLpuDoctor;
|
||
use App\Models\MisMedicalHistory;
|
||
use App\Models\MisMigrationPatient;
|
||
use App\Models\MisServiceMedical;
|
||
use App\Models\MisStationarBranch;
|
||
use App\Models\ObservationPatient;
|
||
use App\Models\Report;
|
||
use App\Models\ReanimationPatientIndicator;
|
||
use App\Models\UnwantedEvent;
|
||
use App\Models\User;
|
||
use Carbon\Carbon;
|
||
use Illuminate\Support\Facades\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,
|
||
?ReportMetricsFinalizer $reportMetricsFinalizer = null,
|
||
?CalculatedMetricsSynchronizer $calculatedMetricsSynchronizer = null,
|
||
?AutoFillReportPayloadBuilder $autoFillReportPayloadBuilder = null,
|
||
?ReportPatientsReadService $reportPatientsReadService = null,
|
||
) {
|
||
$this->reportMetricsFinalizer = $reportMetricsFinalizer ?? app(ReportMetricsFinalizer::class);
|
||
$this->calculatedMetricsSynchronizer = $calculatedMetricsSynchronizer ?? app(CalculatedMetricsSynchronizer::class);
|
||
$this->autoFillReportPayloadBuilder = $autoFillReportPayloadBuilder ?? app(AutoFillReportPayloadBuilder::class);
|
||
$this->reportPatientsReadService = $reportPatientsReadService ?? app(ReportPatientsReadService::class);
|
||
}
|
||
|
||
protected ReportMetricsFinalizer $reportMetricsFinalizer;
|
||
|
||
protected CalculatedMetricsSynchronizer $calculatedMetricsSynchronizer;
|
||
|
||
protected AutoFillReportPayloadBuilder $autoFillReportPayloadBuilder;
|
||
|
||
protected ReportPatientsReadService $reportPatientsReadService;
|
||
|
||
/**
|
||
* Получить статистику для отчета
|
||
*/
|
||
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 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);
|
||
|
||
$this->snapshotService->createPatientSnapshots($report, $user, $data['dates'], $fillableAuto);
|
||
|
||
$this->syncCalculatedMetrics($report, $user, $data);
|
||
|
||
return $report;
|
||
});
|
||
|
||
DB::transaction(function () use ($report) {
|
||
$this->reportMetricsFinalizer->finalize($report);
|
||
$this->saveLethalMetricFromSnapshots($report);
|
||
});
|
||
} catch (\Throwable $e) {
|
||
throw $e;
|
||
}
|
||
|
||
$this->clearCacheAfterReportCreation($user, $report);
|
||
|
||
return $report;
|
||
}
|
||
|
||
public function prepareForHeavySave(): void
|
||
{
|
||
$this->prepareMemoryForHeavySave();
|
||
}
|
||
|
||
public function syncCalculatedMetricsForStoredReport(Report $report, User $user, array $data): void
|
||
{
|
||
$this->calculatedMetricsSynchronizer->sync($report, $user, $data);
|
||
}
|
||
|
||
public function finalizeStoredReport(Report $report): void
|
||
{
|
||
$this->reportMetricsFinalizer->finalize($report);
|
||
}
|
||
|
||
public function saveLethalMetricForStoredReport(Report $report): void
|
||
{
|
||
$this->saveLethalMetricFromSnapshots($report);
|
||
}
|
||
|
||
public function clearCacheAfterStoredReport(User $user, Report $report): void
|
||
{
|
||
$this->clearCacheAfterReportCreation($user, $report);
|
||
}
|
||
|
||
private function prepareMemoryForHeavySave(): void
|
||
{
|
||
$connectionNames = array_unique(array_filter([
|
||
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
|
||
{
|
||
return $this->autoFillReportPayloadBuilder->build($user, $department, $dateRange);
|
||
}
|
||
|
||
/**
|
||
* Сохранить метрику койко-дня из снапшотов отчета
|
||
*/
|
||
protected function saveBedDaysMetric(Report $report): void
|
||
{
|
||
try {
|
||
$result = $this->calculateBedDaysFromSnapshots($report);
|
||
|
||
MetrikaResult::updateOrCreate(
|
||
[
|
||
'rf_report_id' => $report->report_id,
|
||
'rf_metrika_item_id' => MetrikaConfig::TOTAL_BED_DAYS,
|
||
],
|
||
['value' => $result['total_days']]
|
||
);
|
||
|
||
MetrikaResult::updateOrCreate(
|
||
[
|
||
'rf_report_id' => $report->report_id,
|
||
'rf_metrika_item_id' => MetrikaConfig::AVERAGE_BED_DAYS,
|
||
],
|
||
['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' => MetrikaConfig::AVERAGE_BED_DAYS,
|
||
],
|
||
['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, MetrikaConfig::TOTAL_PREOPERATIVE_DAYS, $result['total_days']);
|
||
$this->saveMetric($report, MetrikaConfig::PREOPERATIVE_PATIENT_COUNT, $result['patient_count']);
|
||
$this->saveMetric($report, MetrikaConfig::PREOPERATIVE_AVERAGE_DAYS, $result['avg_days']);
|
||
} catch (\Throwable $e) {
|
||
\Log::error('Failed to save preoperative total metric: '.$e->getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Сохранить % загруженности
|
||
*/
|
||
protected function saveDepartmentLoadedMetric(Report $report): void
|
||
{
|
||
// Получаем все снапшоты выписанных пациентов из этого отчета
|
||
$currentCount = $report->metrikaResults()->where('rf_metrika_item_id', MetrikaConfig::CURRENT)->value('value');
|
||
$bedsCount = $report->metrikaResults()->where('rf_metrika_item_id', MetrikaConfig::BEDS)->value('value');
|
||
|
||
$percentLoaded = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0;
|
||
|
||
$this->saveMetric($report, MetrikaConfig::DEPARTMENT_LOADED, $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
|
||
) {
|
||
return $this->reportPatientsReadService->getPatientsByStatus(
|
||
$department,
|
||
$user,
|
||
$status,
|
||
$dateRange,
|
||
$onlyIds,
|
||
$beforeCreate,
|
||
$includeCurrentPatients
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Получить количество пациентов по статусу
|
||
*/
|
||
public function getPatientsCountByStatus(
|
||
Department $department,
|
||
User $user,
|
||
string $status,
|
||
DateRange $dateRange
|
||
): int {
|
||
return $this->reportPatientsReadService->getPatientsCountByStatus($department, $user, $status, $dateRange);
|
||
}
|
||
|
||
public function getPatientsCountsMap(Department $department, User $user, DateRange $dateRange): array
|
||
{
|
||
return $this->reportPatientsReadService->getPatientsCountsMap($department, $user, $dateRange);
|
||
}
|
||
|
||
/**
|
||
* Получить 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 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' => MetrikaConfig::BEDS,
|
||
'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, MetrikaConfig::UNWANTED_EVENTS, 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, MetrikaConfig::UNWANTED_EVENTS, 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, MetrikaConfig::OBSERVATION, 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, MetrikaConfig::OBSERVATION, count($observationPatients));
|
||
}
|
||
|
||
private function syncCalculatedMetrics(Report $report, User $user, array $data): void
|
||
{
|
||
$this->calculatedMetricsSynchronizer->sync($report, $user, $data);
|
||
}
|
||
|
||
/**
|
||
* Получить информацию о текущем отчете
|
||
*/
|
||
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 saveReanimationIndicator(
|
||
User $user,
|
||
int $departmentId,
|
||
int $medicalHistoryId,
|
||
string $indicator,
|
||
?string $comment = null,
|
||
?int $reportId = null
|
||
): ReanimationPatientIndicator {
|
||
return ReanimationPatientIndicator::create([
|
||
'rf_department_id' => $departmentId,
|
||
'rf_report_id' => $reportId,
|
||
'rf_medicalhistory_id' => $medicalHistoryId,
|
||
'indicator' => $indicator,
|
||
'comment' => $comment,
|
||
'created_by' => $user->id,
|
||
]);
|
||
}
|
||
|
||
public function getLatestReanimationIndicators(int $departmentId, array $medicalHistoryIds)
|
||
{
|
||
if (empty($medicalHistoryIds)) {
|
||
return collect();
|
||
}
|
||
|
||
$subQuery = ReanimationPatientIndicator::query()
|
||
->selectRaw('MAX(reanimation_patient_indicator_id) as max_id, rf_medicalhistory_id')
|
||
->where('rf_department_id', $departmentId)
|
||
->whereIn('rf_medicalhistory_id', $medicalHistoryIds)
|
||
->groupBy('rf_medicalhistory_id');
|
||
|
||
return ReanimationPatientIndicator::query()
|
||
->joinSub($subQuery, 'latest', function ($join) {
|
||
$join->on('reanimation_patient_indicators.reanimation_patient_indicator_id', '=', 'latest.max_id');
|
||
})
|
||
->get([
|
||
'reanimation_patient_indicators.rf_medicalhistory_id',
|
||
'reanimation_patient_indicators.indicator',
|
||
'reanimation_patient_indicators.comment',
|
||
])
|
||
->keyBy('rf_medicalhistory_id');
|
||
}
|
||
|
||
public function getReanimationIndicatorsHistory(
|
||
int $departmentId,
|
||
int $medicalHistoryId,
|
||
int $limit = 50
|
||
) {
|
||
return ReanimationPatientIndicator::query()
|
||
->where('rf_department_id', $departmentId)
|
||
->where('rf_medicalhistory_id', $medicalHistoryId)
|
||
->orderByDesc('reanimation_patient_indicator_id')
|
||
->limit($limit)
|
||
->get([
|
||
'reanimation_patient_indicator_id',
|
||
'rf_report_id',
|
||
'rf_medicalhistory_id',
|
||
'indicator',
|
||
'comment',
|
||
'created_by',
|
||
'created_at',
|
||
]);
|
||
}
|
||
|
||
public function searchMisPatientsForDepartment(Department $department, string $query)
|
||
{
|
||
return $this->unifiedPatientService->searchMisPatients($department, $query);
|
||
}
|
||
|
||
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,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Получить пациентов из снапшотов
|
||
*/
|
||
public function getPatientsFromSnapshots(
|
||
Department $department,
|
||
string $status,
|
||
DateRange $dateRange,
|
||
int $branchId,
|
||
bool $onlyIds = false
|
||
) {
|
||
return $this->reportPatientsReadService->getPatientsFromSnapshots(
|
||
$department,
|
||
$status,
|
||
$dateRange,
|
||
$branchId,
|
||
$onlyIds
|
||
);
|
||
}
|
||
|
||
private function getSnapshotRecipientReportIds(array $reportIds): array
|
||
{
|
||
if (empty($reportIds)) {
|
||
return [];
|
||
}
|
||
|
||
return [reset($reportIds)];
|
||
}
|
||
|
||
/**
|
||
* Получить нежелательные события за дату
|
||
*/
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* Получить статистику выполнения плана по госпитализации
|
||
*/
|
||
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,
|
||
];
|
||
}
|
||
}
|