Профиль хирургии
This commit is contained in:
@@ -6,7 +6,9 @@ use App\Models\Department;
|
||||
use App\Models\MedicalHistorySnapshot;
|
||||
use App\Models\MetrikaResult;
|
||||
use App\Models\MisStationarBranch;
|
||||
use App\Models\ObservationPatient;
|
||||
use App\Models\Report;
|
||||
use App\Models\UnwantedEvent;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonPeriod;
|
||||
@@ -73,6 +75,8 @@ class AutoReportService
|
||||
if ($existingReport && $force) {
|
||||
MetrikaResult::where('rf_report_id', $existingReport->report_id)->delete();
|
||||
MedicalHistorySnapshot::where('rf_report_id', $existingReport->report_id)->delete();
|
||||
UnwantedEvent::where('rf_report_id', $existingReport->report_id)->delete();
|
||||
ObservationPatient::where('rf_report_id', $existingReport->report_id)->delete();
|
||||
$existingReport->delete();
|
||||
}
|
||||
|
||||
@@ -228,13 +232,14 @@ class AutoReportService
|
||||
'metrika_item_12' => $metrics['emergency'] ?? 0, // экстренные
|
||||
'metrika_item_3' => $metrics['recipient'] ?? 0, // поступившие
|
||||
// 'metrika_item_6' => ($metrics['plan_surgery'] ?? 0) + ($metrics['emergency_surgery'] ?? 0), // всего операций
|
||||
'metrika_item_7' => $metrics['discharged'] ?? 0, // выписанные
|
||||
'metrika_item_7' => $metrics['discharged'] + $metrics['deceased'], // выписанные
|
||||
'metrika_item_8' => $metrics['current'] ?? 0, // текущие
|
||||
'metrika_item_9' => $metrics['deceased'] ?? 0, // умершие
|
||||
'metrika_item_11' => $metrics['plan_surgery'] ?? 0, // плановые операции
|
||||
'metrika_item_10' => $metrics['emergency_surgery'] ?? 0, // экстренные операции
|
||||
'metrika_item_13' => $metrics['transferred'] ?? 0, // переведенные
|
||||
'metrika_item_14' => 0, // под наблюдением (будет заполнено отдельно)
|
||||
'metrika_item_15' => $metrics['discharged'] ?? 0, // выбыло
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
45
app/Services/Base/BaseMetricService.php
Normal file
45
app/Services/Base/BaseMetricService.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
// app/Services/Base/BaseMetricService.php
|
||||
|
||||
namespace App\Services\Base;
|
||||
|
||||
use App\Contracts\MetricCalculatorInterface;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
abstract class BaseMetricService
|
||||
{
|
||||
protected array $cache = [];
|
||||
protected array $config = [];
|
||||
|
||||
abstract protected function getMetricId(): int;
|
||||
abstract protected function calculate(array $departmentIds, string $startDate, string $endDate): array;
|
||||
|
||||
public function getCached(array $departmentIds, string $startDate, string $endDate): array
|
||||
{
|
||||
$cacheKey = $this->getCacheKey($departmentIds, $startDate, $endDate);
|
||||
|
||||
if (isset($this->cache[$cacheKey])) {
|
||||
return $this->cache[$cacheKey];
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->calculate($departmentIds, $startDate, $endDate);
|
||||
$this->cache[$cacheKey] = $result;
|
||||
return $result;
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Error in " . static::class . ": " . $e->getMessage());
|
||||
return array_fill_keys($departmentIds, 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getCacheKey(array $departmentIds, string $startDate, string $endDate): string
|
||||
{
|
||||
return static::class . '_' . md5(implode(',', $departmentIds) . $startDate . $endDate);
|
||||
}
|
||||
|
||||
public function clearCache(): void
|
||||
{
|
||||
$this->cache = [];
|
||||
}
|
||||
}
|
||||
@@ -36,12 +36,14 @@ class BedDayService
|
||||
|
||||
// Для одного дня берем последние 30 дней для статистической значимости
|
||||
$actualStartDate = $isRangeOneDay
|
||||
? Carbon::parse($endDate)->subDays(30)->format('Y-m-d')
|
||||
? Carbon::now('Asia/Yakutsk')->startOfYear()->format('Y-m-d')
|
||||
: $startDate;
|
||||
|
||||
// Находим отчеты за период
|
||||
$reports = Report::where('rf_department_id', $departmentId)
|
||||
->whereBetween('created_at', [$actualStartDate, $endDate])
|
||||
// ->whereBetween('created_at', [$actualStartDate, $endDate])
|
||||
->where('sent_at', '>=', $actualStartDate)
|
||||
->where('sent_at', '<=', $endDate)
|
||||
->pluck('report_id');
|
||||
|
||||
if ($reports->isEmpty()) {
|
||||
@@ -107,7 +109,9 @@ class BedDayService
|
||||
|
||||
// Находим все отчеты за период по отделениям
|
||||
$reportsByDepartment = Report::whereIn('rf_department_id', $departmentIds)
|
||||
->whereBetween('created_at', [$actualStartDate, $endDate])
|
||||
// ->whereBetween('created_at', [$actualStartDate, $endDate])
|
||||
->where('sent_at', '>=', $actualStartDate)
|
||||
->where('sent_at', '<=', $endDate)
|
||||
->select('report_id', 'rf_department_id')
|
||||
->get()
|
||||
->groupBy('rf_department_id');
|
||||
@@ -186,7 +190,9 @@ class BedDayService
|
||||
public function getDetailedStatsFromSnapshots(int $departmentId, string $startDate, string $endDate): array
|
||||
{
|
||||
$reports = Report::where('rf_department_id', $departmentId)
|
||||
->whereBetween('created_at', [$startDate, $endDate])
|
||||
// ->whereBetween('created_at', [$startDate, $endDate])
|
||||
->where('sent_at', '>', $startDate)
|
||||
->where('sent_at', '<=', $endDate)
|
||||
->pluck('report_id');
|
||||
|
||||
if ($reports->isEmpty()) {
|
||||
@@ -292,7 +298,6 @@ class BedDayService
|
||||
// Для каждого отчета считаем средний койко-день за последние 30 дней до даты отчета
|
||||
$endDate = $report->created_at;
|
||||
$startDate = Carbon::startOfYear();
|
||||
dd($startDate);
|
||||
|
||||
$avg = $this->getAverageBedDaysFromSnapshots(
|
||||
$report->rf_department_id,
|
||||
|
||||
@@ -63,6 +63,16 @@ readonly class DateRange
|
||||
return $this->endDate->getTimestampMs();
|
||||
}
|
||||
|
||||
public function startFirstOfMonth()
|
||||
{
|
||||
return $this->startDate->firstOfMonth()->setHour(6)->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
public function endFirstOfMonth()
|
||||
{
|
||||
return $this->endDate->firstOfMonth()->setHour(6)->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, является ли дата сегодняшней
|
||||
*/
|
||||
|
||||
47
app/Services/MetricCalculators/AverageBedDaysCalculator.php
Normal file
47
app/Services/MetricCalculators/AverageBedDaysCalculator.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
// app/Services/MetricCalculators/AverageBedDaysCalculator.php
|
||||
|
||||
namespace App\Services\MetricCalculators;
|
||||
|
||||
use App\Services\Base\BaseMetricService;
|
||||
use App\Contracts\MetricCalculatorInterface;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class AverageBedDaysCalculator extends BaseMetricService implements MetricCalculatorInterface
|
||||
{
|
||||
public function getMetricId(): int
|
||||
{
|
||||
return 18;
|
||||
}
|
||||
|
||||
public function calculate(array $departmentIds, string $startDate, string $endDate): array
|
||||
{
|
||||
if (empty($departmentIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$results = DB::table('reports as r')
|
||||
->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id')
|
||||
->whereIn('r.rf_department_id', $departmentIds)
|
||||
->where('mr.rf_metrika_item_id', 18)
|
||||
// ->whereBetween('r.created_at', [$startDate, $endDate])
|
||||
->where('r.sent_at', '>', $startDate)
|
||||
->where('r.sent_at', '<=', $endDate)
|
||||
->select(
|
||||
'r.rf_department_id',
|
||||
DB::raw('AVG(CAST(mr.value AS DECIMAL)) as avg_value')
|
||||
)
|
||||
->groupBy('r.rf_department_id')
|
||||
->get()
|
||||
->keyBy('rf_department_id');
|
||||
|
||||
$averages = [];
|
||||
foreach ($departmentIds as $deptId) {
|
||||
$averages[$deptId] = isset($results[$deptId])
|
||||
? round((float)$results[$deptId]->avg_value, 1)
|
||||
: 0;
|
||||
}
|
||||
|
||||
return $averages;
|
||||
}
|
||||
}
|
||||
58
app/Services/MetricCalculators/LethalityCalculator.php
Normal file
58
app/Services/MetricCalculators/LethalityCalculator.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
// app/Services/MetricCalculators/LethalityCalculator.php
|
||||
|
||||
namespace App\Services\MetricCalculators;
|
||||
|
||||
use App\Services\Base\BaseMetricService;
|
||||
use App\Contracts\MetricCalculatorInterface;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class LethalityCalculator extends BaseMetricService implements MetricCalculatorInterface
|
||||
{
|
||||
public function getMetricId(): int
|
||||
{
|
||||
return 19;
|
||||
}
|
||||
|
||||
public function calculate(array $departmentIds, string $startDate, string $endDate): array
|
||||
{
|
||||
if (empty($departmentIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$results = DB::table('reports as r')
|
||||
->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id')
|
||||
->whereIn('r.rf_department_id', $departmentIds)
|
||||
->whereIn('mr.rf_metrika_item_id', [7, 9])
|
||||
// ->whereBetween('r.created_at', [$startDate, $endDate])
|
||||
->where('r.sent_at', '>', $startDate)
|
||||
->where('r.sent_at', '<=', $endDate)
|
||||
->select(
|
||||
'r.rf_department_id',
|
||||
'mr.rf_metrika_item_id',
|
||||
DB::raw('SUM(CAST(mr.value AS INTEGER)) as total')
|
||||
)
|
||||
->groupBy('r.rf_department_id', 'mr.rf_metrika_item_id')
|
||||
->get()
|
||||
->groupBy('rf_department_id');
|
||||
|
||||
$lethality = [];
|
||||
foreach ($departmentIds as $deptId) {
|
||||
$deceased = 0;
|
||||
$discharged = 0;
|
||||
|
||||
if (isset($results[$deptId])) {
|
||||
foreach ($results[$deptId] as $item) {
|
||||
if ($item->rf_metrika_item_id == 9) $deceased = (int)$item->total;
|
||||
else $discharged = (int)$item->total;
|
||||
}
|
||||
}
|
||||
|
||||
$lethality[$deptId] = ($discharged > 0 && $deceased > 0)
|
||||
? round(($deceased / $discharged) * 100, 1)
|
||||
: 0;
|
||||
}
|
||||
|
||||
return $lethality;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
// app/Services/MetricCalculators/PreoperativeDaysCalculator.php
|
||||
|
||||
namespace App\Services\MetricCalculators;
|
||||
|
||||
use App\Services\Base\BaseMetricService;
|
||||
use App\Contracts\MetricCalculatorInterface;
|
||||
use App\Models\MedicalHistorySnapshot;
|
||||
use App\Models\Report;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class PreoperativeDaysCalculator extends BaseMetricService implements MetricCalculatorInterface
|
||||
{
|
||||
public function getMetricId(): int
|
||||
{
|
||||
return 21;
|
||||
}
|
||||
|
||||
public function calculate(array $departmentIds, string $startDate, string $endDate): array
|
||||
{
|
||||
if (empty($departmentIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Получаем отчеты за период
|
||||
$reports = Report::whereIn('rf_department_id', $departmentIds)
|
||||
->where('sent_at', '>', $startDate)
|
||||
->where('sent_at', '<=', $endDate)
|
||||
// ->whereBetween('created_at', [$startDate, $endDate])
|
||||
->get(['report_id', 'rf_department_id'])
|
||||
->keyBy('report_id');
|
||||
|
||||
if ($reports->isEmpty()) {
|
||||
return array_fill_keys($departmentIds, 0);
|
||||
}
|
||||
|
||||
$reportIds = $reports->keys()->toArray();
|
||||
|
||||
// Получаем пациентов из снапшотов
|
||||
$snapshots = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
|
||||
->whereIn('patient_type', ['discharged', 'deceased'])
|
||||
->get(['rf_report_id', 'rf_medicalhistory_id']);
|
||||
|
||||
if ($snapshots->isEmpty()) {
|
||||
return array_fill_keys($departmentIds, 0);
|
||||
}
|
||||
|
||||
$historyIds = $snapshots->pluck('rf_medicalhistory_id')->unique()->toArray();
|
||||
|
||||
// Получаем первые операции и первые поступления одним запросом
|
||||
$operations = DB::table('stt_surgicaloperation as so')
|
||||
->join('stt_migrationpatient as mp', 'so.rf_MedicalHistoryID', '=', 'mp.rf_MedicalHistoryID')
|
||||
->whereIn('so.rf_MedicalHistoryID', $historyIds)
|
||||
->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()
|
||||
->keyBy('rf_MedicalHistoryID');
|
||||
|
||||
// Группируем по отделениям
|
||||
$results = [];
|
||||
foreach ($snapshots as $snapshot) {
|
||||
$deptId = $reports[$snapshot->rf_report_id]->rf_department_id;
|
||||
$historyId = $snapshot->rf_medicalhistory_id;
|
||||
|
||||
if (!isset($operations[$historyId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$op = $operations[$historyId];
|
||||
$days = Carbon::parse($op->first_admission)
|
||||
->diffInDays(Carbon::parse($op->first_operation));
|
||||
|
||||
if ($days >= 0) {
|
||||
if (!isset($results[$deptId])) {
|
||||
$results[$deptId] = ['total' => 0, 'count' => 0];
|
||||
}
|
||||
$results[$deptId]['total'] += $days;
|
||||
$results[$deptId]['count']++;
|
||||
}
|
||||
}
|
||||
|
||||
// Усредняем по отделениям
|
||||
$preoperative = [];
|
||||
foreach ($departmentIds as $deptId) {
|
||||
$preoperative[$deptId] = isset($results[$deptId]) && $results[$deptId]['count'] > 0
|
||||
? round($results[$deptId]['total'] / $results[$deptId]['count'], 1)
|
||||
: 0;
|
||||
}
|
||||
|
||||
return $preoperative;
|
||||
}
|
||||
}
|
||||
76
app/Services/MetrikaService.php
Normal file
76
app/Services/MetrikaService.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Report;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class MetrikaService
|
||||
{
|
||||
/**
|
||||
* Рассчитать предоперационный койко-день из снапшотов
|
||||
*/
|
||||
public function calculatePreoperativeDaysFromSnapshots(
|
||||
array $departmentIds,
|
||||
string $startDate,
|
||||
string $endDate
|
||||
): array
|
||||
{
|
||||
if (empty($departmentIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
// Получаем снапшоты с операциями
|
||||
$results = DB::table('medical_history_snapshots as mhs')
|
||||
->join('reports as r', 'mhs.rf_report_id', '=', 'r.report_id')
|
||||
->join('stt_migrationpatient as mp', 'mhs.rf_medicalhistory_id', '=', 'mp.rf_MedicalHistoryID')
|
||||
->join('stt_surgicaloperation as so', 'mhs.rf_medicalhistory_id', '=', 'so.rf_MedicalHistoryID')
|
||||
->whereIn('r.rf_department_id', $departmentIds)
|
||||
->whereDate('r.sent_at', '>=', $startDate)
|
||||
->whereDate('r.sent_at', '<=', $endDate)
|
||||
->whereIn('mhs.patient_type', ['discharged', 'deceased'])
|
||||
->select(
|
||||
'r.rf_department_id',
|
||||
'mp.rf_MedicalHistoryID',
|
||||
DB::raw('MIN(mp."DateIngoing") as admission_date'),
|
||||
DB::raw('MIN(so."Date") as first_operation_date')
|
||||
)
|
||||
->groupBy('r.rf_department_id', 'mp.rf_MedicalHistoryID')
|
||||
->havingRaw('MIN(so."Date") IS NOT NULL')
|
||||
->get()
|
||||
->groupBy('rf_department_id');
|
||||
|
||||
$preoperativeDays = [];
|
||||
foreach ($departmentIds as $deptId) {
|
||||
if (!isset($results[$deptId]) || $results[$deptId]->isEmpty()) {
|
||||
$preoperativeDays[$deptId] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
$totalDays = 0;
|
||||
$count = 0;
|
||||
|
||||
foreach ($results[$deptId] as $item) {
|
||||
$admission = Carbon::parse($item->admission_date);
|
||||
$operation = Carbon::parse($item->first_operation_date);
|
||||
$days = $admission->diffInDays($operation);
|
||||
|
||||
if ($days >= 0) {
|
||||
$totalDays += $days;
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
$preoperativeDays[$deptId] = $count > 0 ? round($totalDays / $count, 1) : 0;
|
||||
}
|
||||
|
||||
return $preoperativeDays;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error("Error in calculatePreoperativeDaysFromSnapshots: " . $e->getMessage());
|
||||
return array_fill_keys($departmentIds, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,9 @@ class MisPatientService
|
||||
$query->with('migrations')
|
||||
->whereHas('migrations', function ($q) use ($branchId, $dateRange) {
|
||||
$q->where('rf_StationarBranchID', $branchId)
|
||||
->whereBetween('DateIngoing', $dateRange);
|
||||
->where('DateIngoing', '>', $dateRange[0])
|
||||
->where('DateIngoing', '<=', $dateRange[1]);
|
||||
// ->whereBetween('DateIngoing', $dateRange);
|
||||
});
|
||||
})
|
||||
->whereIn('rf_EmerSignID', [2, 4])
|
||||
@@ -92,7 +94,9 @@ class MisPatientService
|
||||
$query->with('migrations')
|
||||
->whereHas('migrations', function ($q) use ($branchId, $dateRange) {
|
||||
$q->where('rf_StationarBranchID', $branchId)
|
||||
->whereBetween('DateIngoing', $dateRange);
|
||||
->where('DateIngoing', '>=', $dateRange[0])
|
||||
->where('DateIngoing', '<=', $dateRange[1]);
|
||||
// ->whereBetween('DateIngoing', $dateRange);
|
||||
})->where('MedicalHistoryID', '<>', 0);
|
||||
|
||||
return $query;
|
||||
|
||||
@@ -59,15 +59,15 @@ class PatientService
|
||||
$query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
|
||||
->with([
|
||||
'surgicalOperations' => function ($q) use ($dateRange) {
|
||||
$q->whereBetween('Date', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
// $q->whereBetween('Date', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
$q->where('Date', '>=', $dateRange->startSql())
|
||||
->where('Date', '<=', $dateRange->endSql());
|
||||
},
|
||||
'migrations' => function ($q) use ($branchId) {
|
||||
$q->where('rf_StationarBranchID', $branchId)
|
||||
->take(1) // берем только одну последнюю
|
||||
->with(['diagnosis' => function ($q) {
|
||||
$q->where('rf_DiagnosTypeID', 3)
|
||||
->take(1)
|
||||
->with('mkb');
|
||||
->with(['mainDiagnosis' => function ($q) {
|
||||
$q->with('mkb');
|
||||
}]);
|
||||
}
|
||||
])
|
||||
@@ -133,7 +133,9 @@ class PatientService
|
||||
|
||||
return MisMedicalHistory::whereIn('MedicalHistoryID', $allIds)
|
||||
->with(['surgicalOperations' => function ($q) use ($dateRange) {
|
||||
$q->whereBetween('Date', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
// $q->whereBetween('Date', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
$q->where('Date', '>=', $dateRange->startSql())
|
||||
->where('Date', '<=', $dateRange->endSql());
|
||||
}])
|
||||
->orderBy('DateRecipient', 'DESC')
|
||||
->get()
|
||||
@@ -161,7 +163,14 @@ class PatientService
|
||||
}
|
||||
|
||||
if ($onlyIds) $patients = $query->pluck('MedicalHistoryID');
|
||||
else $patients = $query->get();
|
||||
else {
|
||||
// Загрузка отношений, необходимых для FormattedPatientResource
|
||||
$query->with([
|
||||
'outcomeMigration.mainDiagnosis.mkb', // mkb через диагноз
|
||||
'surgicalOperations.serviceMedical', // операции с услугами
|
||||
]);
|
||||
$patients = $query->get();
|
||||
}
|
||||
|
||||
return $patients->map(function ($patient) {
|
||||
$patient->comment = $patient->observationPatient
|
||||
@@ -221,7 +230,9 @@ class PatientService
|
||||
bool $countOnly = false
|
||||
) {
|
||||
$query = MisSurgicalOperation::where('rf_StationarBranchID', $branchId)
|
||||
->whereBetween('Date', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
->where('Date', '>=', $dateRange->startSql())
|
||||
->where('Date', '<=', $dateRange->endSql());
|
||||
// ->whereBetween('Date', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
|
||||
if ($type === 'plan') {
|
||||
$query->where('rf_TypeSurgOperationInTimeID', 6);
|
||||
@@ -273,10 +284,14 @@ class PatientService
|
||||
) {
|
||||
if ($isHeadOrAdmin) {
|
||||
$query = MisMigrationPatient::whereInDepartment($branchId)
|
||||
->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
->where('DateIngoing', '>=', $dateRange->startSql())
|
||||
->where('DateIngoing', '<=', $dateRange->endSql());
|
||||
// ->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
} else {
|
||||
$query = MisMigrationPatient::currentlyInTreatment($branchId)
|
||||
->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
->where('DateIngoing', '>=', $dateRange->startSql())
|
||||
->where('DateIngoing', '<=', $dateRange->endSql());
|
||||
// ->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
}
|
||||
|
||||
$medicalHistoryIds = $query->pluck('rf_MedicalHistoryID')->toArray();
|
||||
@@ -315,19 +330,27 @@ class PatientService
|
||||
// Заведующий: все поступившие за период
|
||||
if ($fillableAuto) {
|
||||
$query = LifeMisMigrationPatient::whereInDepartment($branchId)
|
||||
->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
->where('DateIngoing', '>=', $dateRange->startSql())
|
||||
->where('DateIngoing', '<=', $dateRange->endSql());
|
||||
// ->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
} else {
|
||||
$query = MisMigrationPatient::whereInDepartment($branchId)
|
||||
->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
->where('DateIngoing', '>=', $dateRange->startSql())
|
||||
->where('DateIngoing', '<=', $dateRange->endSql());
|
||||
// ->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
}
|
||||
} else {
|
||||
// Врач: только поступившие за сутки
|
||||
if ($fillableAuto) {
|
||||
$query = LifeMisMigrationPatient::whereInDepartment($branchId)
|
||||
->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
->where('DateIngoing', '>=', $dateRange->startSql())
|
||||
->where('DateIngoing', '<=', $dateRange->endSql());
|
||||
// ->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
} else {
|
||||
$query = MisMigrationPatient::whereInDepartment($branchId)
|
||||
->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
->where('DateIngoing', '>=', $dateRange->startSql())
|
||||
->where('DateIngoing', '<=', $dateRange->endSql());
|
||||
// ->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -50,35 +50,36 @@ class ReportService
|
||||
*/
|
||||
public function storeReport(array $data, User $user, $fillableAuto = false): Report
|
||||
{
|
||||
DB::beginTransaction();
|
||||
|
||||
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);
|
||||
|
||||
return $report;
|
||||
});
|
||||
|
||||
DB::transaction(function () use ($report) {
|
||||
// Сохраняем метрику среднего койко-дня из снапшотов
|
||||
$this->saveAverageBedDaysMetricFromSnapshots($report);
|
||||
|
||||
DB::commit();
|
||||
$this->saveLethalMetricFromSnapshots($report);
|
||||
|
||||
// ОЧИСТКА КЭША ПОСЛЕ УСПЕШНОГО СОЗДАНИЯ ОТЧЕТА
|
||||
$this->clearCacheAfterReportCreation($user, $report);
|
||||
$this->savePreoperativeMetric($report);
|
||||
|
||||
return $report;
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
$this->saveDepartmentLoadedMetric($report);
|
||||
});
|
||||
|
||||
$this->clearCacheAfterReportCreation($user, $report);
|
||||
|
||||
return $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить метрику среднего койко-дня из снапшотов отчета
|
||||
* Сохранить метрику койко-дня из снапшотов отчета
|
||||
*/
|
||||
protected function saveAverageBedDaysMetricFromSnapshots(Report $report): void
|
||||
{
|
||||
@@ -128,7 +129,7 @@ class ReportService
|
||||
}
|
||||
}
|
||||
|
||||
$avgBedDays = $validCount > 0 ? round($totalDays / $validCount, 1) : 0;
|
||||
$bedDays = $validCount > 0 ? $totalDays: 0;
|
||||
|
||||
// Сохраняем метрику
|
||||
MetrikaResult::updateOrCreate(
|
||||
@@ -136,10 +137,10 @@ class ReportService
|
||||
'rf_report_id' => $report->report_id,
|
||||
'rf_metrika_item_id' => 18,
|
||||
],
|
||||
['value' => $avgBedDays]
|
||||
['value' => $bedDays]
|
||||
);
|
||||
|
||||
\Log::info("Saved average bed days metric for report {$report->report_id}: {$avgBedDays} (from {$validCount} patients)");
|
||||
//\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());
|
||||
@@ -147,16 +148,120 @@ class ReportService
|
||||
}
|
||||
}
|
||||
|
||||
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->clearStatisticsCache($user);
|
||||
|
||||
// Также можно очистить кэш для всех пользователей отдела
|
||||
$this->statisticsService->clearDepartmentStatisticsCache($user->rf_department_id);
|
||||
// $this->statisticsService->clearDepartmentStatisticsCache($user->rf_department_id);
|
||||
|
||||
// Очищаем кэш за сегодня и вчера (так как отчеты влияют на эти даты)
|
||||
$this->clearDailyCache($user, $report->created_at);
|
||||
@@ -305,6 +410,22 @@ class ReportService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить метрику отчета
|
||||
*/
|
||||
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,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить нежелательные события
|
||||
*/
|
||||
@@ -460,6 +581,7 @@ class ReportService
|
||||
);
|
||||
|
||||
$reportIds = $reports->pluck('report_id')->toArray();
|
||||
$lastReport = array_first($reportIds);
|
||||
|
||||
// Получаем статистику из снапшотов
|
||||
$snapshotStats = [
|
||||
@@ -471,7 +593,8 @@ class ReportService
|
||||
// 'discharged' => $this->getMetrikaResultCount('discharged', $reportIds),
|
||||
'transferred' => $this->getMetrikaResultCount(13, $reportIds),
|
||||
'recipient' => $this->getMetrikaResultCount(3, $reportIds),
|
||||
'beds' => $this->getMetrikaResultCount(1, $reportIds, false)
|
||||
'beds' => $this->getMetrikaResultCount(1, $reportIds, false),
|
||||
'countStaff' => $this->getMetrikaResultCount(17, [$lastReport], false)
|
||||
];
|
||||
|
||||
// Получаем ID поступивших пациентов
|
||||
@@ -499,6 +622,7 @@ class ReportService
|
||||
'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,
|
||||
@@ -819,7 +943,8 @@ class ReportService
|
||||
{
|
||||
return UnwantedEvent::whereHas('report', function ($query) use ($department, $dateRange) {
|
||||
$query->where('rf_department_id', $department->department_id)
|
||||
->whereBetween('sent_at', [$dateRange->startSql(), $dateRange->endSql()]);
|
||||
->whereDate('sent_at', '>=', $dateRange->startSql())
|
||||
->whereDate('sent_at', '<=', $dateRange->endSql());
|
||||
})
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
@@ -876,14 +1001,16 @@ class ReportService
|
||||
{
|
||||
if ($dateRange->isOneDay) {
|
||||
return Report::where('rf_department_id', $departmentId)
|
||||
->whereDate('created_at', $dateRange->endSql())
|
||||
->orderBy('created_at', 'ASC')
|
||||
->whereDate('sent_at', $dateRange->endSql())
|
||||
->orderBy('sent_at', 'DESC')
|
||||
->get();
|
||||
}
|
||||
|
||||
return Report::where('rf_department_id', $departmentId)
|
||||
->whereBetween('created_at', [$dateRange->startSql(), $dateRange->endSql()])
|
||||
->orderBy('created_at', 'ASC')
|
||||
// ->whereBetween('created_at', [$dateRange->startSql(), $dateRange->endSql()])
|
||||
->where('sent_at', '>', $dateRange->startSql())
|
||||
->where('sent_at', '<=', $dateRange->endSql())
|
||||
->orderBy('sent_at', 'DESC')
|
||||
->get();
|
||||
}
|
||||
|
||||
@@ -972,4 +1099,44 @@ class ReportService
|
||||
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
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Models\Report;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class SnapshotService
|
||||
{
|
||||
@@ -182,28 +183,41 @@ class SnapshotService
|
||||
?int $branchId = null,
|
||||
bool $onlyIds = false
|
||||
): Collection {
|
||||
// Для плановых и экстренных включаем уже лечащихся
|
||||
// $includeCurrent = in_array($type, ['plan', 'emergency']);
|
||||
|
||||
$medicalHistoryIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
|
||||
// Получаем ID историй болезни напрямую через DB::table() — это быстрее
|
||||
$medicalHistoryIds = DB::table('medical_history_snapshots')
|
||||
->select('rf_medicalhistory_id')
|
||||
->whereIn('rf_report_id', $reportIds)
|
||||
->where('patient_type', $type)
|
||||
->pluck('rf_medicalhistory_id')
|
||||
->unique()
|
||||
->toArray();
|
||||
->distinct()
|
||||
->pluck('rf_medicalhistory_id');
|
||||
|
||||
if (empty($medicalHistoryIds)) {
|
||||
if ($medicalHistoryIds->isEmpty()) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
if ($onlyIds) {
|
||||
return collect($medicalHistoryIds);
|
||||
$query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds);
|
||||
|
||||
if ($type === 'plan') {
|
||||
$query->plan();
|
||||
} elseif ($type === 'emergency') {
|
||||
$query->emergency();
|
||||
}
|
||||
|
||||
return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
|
||||
->when($type === 'plan', fn($q) => $q->plan())
|
||||
->when($type === 'emergency', fn($q) => $q->emergency())
|
||||
->orderBy('DateRecipient', 'DESC')
|
||||
->get();
|
||||
// Загрузка отношений, необходимых для FormattedPatientResource
|
||||
$query->with([
|
||||
'outcomeMigration.mainDiagnosis.mkb', // mkb через диагноз
|
||||
'surgicalOperations.serviceMedical', // операции с услугами
|
||||
]);
|
||||
|
||||
$query->orderBy('DateRecipient', 'DESC');
|
||||
|
||||
$results = $query->get();
|
||||
|
||||
if ($onlyIds) {
|
||||
return $results->pluck('MedicalHistoryID');
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user