Работа над журналом для ст. мед сестер

This commit is contained in:
brusnitsyn
2026-05-04 17:11:16 +09:00
parent f107ebd167
commit 7a58812072
61 changed files with 3532 additions and 1163 deletions

View File

@@ -17,10 +17,28 @@ use Illuminate\Support\Facades\Log;
class AutoReportService
{
public function __construct(
protected ReportService $reportService,
protected DateRangeService $dateRangeService,
protected ReportSavePathService $reportSavePathService,
) {}
mixed $dateRangeService = null,
mixed $reportSavePathService = null,
?ReportSavePathService $legacyReportSavePathService = null,
) {
if ($dateRangeService instanceof ReportService) {
$this->dateRangeService = $reportSavePathService instanceof DateRangeService
? $reportSavePathService
: app(DateRangeService::class);
$this->reportSavePathService = $legacyReportSavePathService ?? app(ReportSavePathService::class);
return;
}
$this->dateRangeService = $dateRangeService instanceof DateRangeService
? $dateRangeService
: app(DateRangeService::class);
$this->reportSavePathService = $reportSavePathService ?? app(ReportSavePathService::class);
}
protected DateRangeService $dateRangeService;
protected ReportSavePathService $reportSavePathService;
/**
* Заполнить отчеты для пользователя за период
@@ -34,16 +52,11 @@ class AutoReportService
): int {
$createdCount = 0;
// Для многодневного диапазона расширяем конец на 1 день,
// чтобы покрыть последние сутки (07:00 -> 07:00) целиком.
$start = \Carbon\Carbon::createFromFormat('Y-m-d', $startDate, 'Asia/Yakutsk');
$end = \Carbon\Carbon::createFromFormat('Y-m-d', $endDate, 'Asia/Yakutsk');
$periodEnd = $start->equalTo($end)
? $end->copy()
: $end->copy()->addDay();
// Создаем период по дням
$period = CarbonPeriod::create($start->toDateString(), $periodEnd->toDateString());
$period = CarbonPeriod::create($start->toDateString(), $end->toDateString());
foreach ($period as $date) {
$dateRange = $this->dateRangeService->getNormalizedDateRange($user, $date, $date);

View File

@@ -65,12 +65,12 @@ readonly class DateRange
public function startFirstOfMonth()
{
return $this->startDate->copy()->firstOfMonth()->setHour(6)->format('Y-m-d H:i:s');
return $this->startDate->copy()->firstOfMonth()->setHour(9)->format('Y-m-d H:i:s');
}
public function endFirstOfMonth()
{
return $this->endDate->copy()->firstOfMonth()->setHour(6)->format('Y-m-d H:i:s');
return $this->endDate->copy()->firstOfMonth()->setHour(9)->format('Y-m-d H:i:s');
}
/**

View File

@@ -0,0 +1,117 @@
<?php
namespace App\Services;
use App\Models\MedicalHistory;
use App\Models\MigrationPatient;
use Illuminate\Support\Carbon;
class MedicalHistoryService
{
public function getHistories(DateRange $dateRange, int $departmentId)
{
$query = MedicalHistory::query();
$query->where('recipient_date', '>=', $dateRange->startSql())
->where('recipient_date', '<', $dateRange->endSql())
// 1. Оставляем только тех пациентов, у которых БЫЛО движение в этом отделении
->whereHas('latestMigration', fn($q) => $q->where('department_id', $departmentId))
// 2. Загружаем ТОЛЬКО последнее движение в этом отделении (не все миграции)
->with(['latestMigration' => fn($q) => $q->where('department_id', $departmentId)]);
$result = $query->paginate();
return $result;
}
public function getUrgencyHistory(DateRange $dateRange, int $departmentId, int $urgencyId)
{
$query = MedicalHistory::query();
$query->where('recipient_date', '>=', $dateRange->startSql())
->where('recipient_date', '<', $dateRange->endSql())
->urgency($urgencyId)
->whereHas('migrations', function ($m) use ($departmentId) {
$m->where('department_id', $departmentId);
})
->with([
'migrations' => fn ($m) => $m->where('department_id', $departmentId),
'migrations.operations'
]);
$result = $query->paginate();
return $result;
}
public function getDepartmentHistories(DateRange $dateRange, int $departmentId)
{
return MedicalHistory::query()
->whereHas('migrations', function ($q) use ($departmentId, $dateRange) {
$q->department($departmentId)->current($dateRange);
})
->with(['latestMigration' => function ($q) use ($departmentId, $dateRange) {
$q->department($departmentId)->current($dateRange); // подгружаем только отфильтрованные движения
}])
->get()
// Сортировка по дате поступления в отделение (поле дочерней таблицы)
->sortByDesc(fn ($mh) => $mh->latestMigration->ingoing_date ?? $mh->recipient_date)
->values();
}
/**
* Получить карты поступившие сегодня
* @param DateRange $dateRange
* @param int $departmentId
*/
public function getRecipientHistories(DateRange $dateRange, int $departmentId)
{
$now = Carbon::now();
return MedicalHistory::query()
->whereHas('migrations', function ($q) use ($departmentId, $dateRange) {
$q->department($departmentId)->admitted($dateRange->startSql(), $dateRange->endSql());
})
->with(['latestMigration' => function ($q) use ($departmentId) {
$q->department($departmentId);
}])
->get();
}
public function getDischargedHistories(DateRange $dateRange, int $departmentId)
{
return MedicalHistory::query()
->whereHas('migrations', function ($q) use ($departmentId, $dateRange) {
$q->department($departmentId)->discharged($dateRange->startSql(), $dateRange->endSql());
})
->with(['latestMigration' => function ($q) use ($departmentId) {
$q->department($departmentId);
}])
->get();
}
public function getDeceasedHistories(DateRange $dateRange, int $departmentId)
{
return MedicalHistory::query()
->whereHas('migrations', function ($q) use ($departmentId, $dateRange) {
$q->department($departmentId)->deceased($dateRange->startSql(), $dateRange->endSql());
})
->with(['latestMigration' => function ($q) use ($departmentId) {
$q->department($departmentId);
}])
->get();
}
public function getTransferredHistories(DateRange $dateRange, int $departmentId)
{
return MedicalHistory::query()
->whereHas('migrations', function ($q) use ($departmentId, $dateRange) {
$q->department($departmentId)->transferred($dateRange->startSql(), $dateRange->endSql());
})
->with(['latestMigration' => function ($q) use ($departmentId) {
$q->department($departmentId);
}])
->get();
}
}

View File

@@ -74,16 +74,21 @@ class PreoperativeDaysCalculator extends BaseMetricService implements MetricCalc
}
$op = $operations[$historyId];
$days = Carbon::parse($op->first_admission)
->diffInDays(Carbon::parse($op->first_operation));
$admittedAt = Carbon::parse($op->first_admission);
$operationAt = 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']++;
if ($operationAt->lt($admittedAt)) {
continue;
}
$days = $admittedAt->copy()->startOfDay()
->diffInDays($operationAt->copy()->startOfDay());
if (! isset($results[$deptId])) {
$results[$deptId] = ['total' => 0, 'count' => 0];
}
$results[$deptId]['total'] += $days;
$results[$deptId]['count']++;
}
// Усредняем по отделениям

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,6 @@ use App\Models\MedicalHistorySnapshot;
use App\Models\MisStationarBranch;
use App\Models\Report;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Collection;
class SnapshotService
@@ -280,8 +279,8 @@ class SnapshotService
private function parseDates(array $dates): array
{
return [
Carbon::createFromTimestampMs($dates[0])->setTimezone('Asia/Yakutsk'),
Carbon::createFromTimestampMs($dates[1])->setTimezone('Asia/Yakutsk'),
$this->dateRangeService->parseDate($dates[0]),
$this->dateRangeService->parseDate($dates[1]),
];
}
}

View File

@@ -135,6 +135,11 @@ class StatisticsService
$bedDaysSum = 0;
$avgBedDays = 0;
$preoperativeSum = 0;
$preoperativePatientCount = 0;
$preoperativeTotalRecords = 0;
$preoperativePatientRecords = 0;
$preoperativeAverageSum = 0;
$preoperativeAverageCount = 0;
if (isset($metrics[$deptId])) {
foreach ($metrics[$deptId] as $item) {
@@ -153,10 +158,24 @@ class StatisticsService
16 => $unwanted = (int) $value,
25 => $bedDaysSum += $value,
19 => $lethalitySum = $value,
21 => $preoperativeAverageSum += $value,
26 => $preoperativeSum += $value,
27 => $preoperativePatientCount += (int) $value,
// 24 => $completePlanProgress = (int)$value,
default => null
};
if ((int) $item->rf_metrika_item_id === 21) {
$preoperativeAverageCount += (int) $item->records_count;
}
if ((int) $item->rf_metrika_item_id === 26) {
$preoperativeTotalRecords += (int) $item->records_count;
}
if ((int) $item->rf_metrika_item_id === 27) {
$preoperativePatientRecords += (int) $item->records_count;
}
}
}
@@ -170,10 +189,15 @@ class StatisticsService
$percentLoaded = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0;
// Средний койко-день
$avgBedDays = $outcome > 0 ? round($bedDaysSum / $outcome, 2) : 0;
$avgBedDays = $outcome > 0 ? round($bedDaysSum / $outcome, 1) : 0;
// Предоперационный койко-день
$preoperativeValue =
$canUsePreoperativeTotals = $preoperativePatientCount > 0
&& $preoperativeTotalRecords > 0
&& $preoperativePatientRecords >= $preoperativeTotalRecords;
$preoperativeValue = $canUsePreoperativeTotals
? round($preoperativeSum / $preoperativePatientCount, 1)
: ($preoperativeAverageCount > 0 ? round($preoperativeAverageSum / $preoperativeAverageCount, 1) : 0);
// Летальность
$lethality = $outcome > 0 ? round(($deceased / $outcome) * 100, 2) : 0;
@@ -203,8 +227,14 @@ class StatisticsService
'countStaff' => $staff,
'countObservable' => $observable,
'countUnwanted' => $unwanted,
'averageBedDays' => $avgBedDays,
'bedDaysSum' => $bedDaysSum,
'preoperativeDays' => $preoperativeValue,
'preoperativeSum' => $preoperativeSum,
'preoperativePatientCount' => $preoperativePatientCount,
'progressPlanOfYear' => $periodPlan,
'percentPlanOfYear' => $percentPlanOfYear,
'lethality' => $lethality,
@@ -248,6 +278,11 @@ class StatisticsService
'deceased_sum' => 0,
'percentLoadedBeds_total' => 0,
'percentLoadedBeds_count' => 0,
'bedDaysSum' => 0,
'preoperativeSum' => 0,
'preoperativePatientCount' => 0,
'staff_sum' => 0,
'observable_sum' => 0,
'unwanted_sum' => 0,
@@ -272,6 +307,9 @@ class StatisticsService
$totals['deceased_sum'] += $data['deceased'];
$totals['percentLoadedBeds_total'] += $data['percentLoadedBeds'];
$totals['percentLoadedBeds_count']++;
$totals['bedDaysSum'] += $data['bedDaysSum'];
$totals['preoperativeSum'] += $data['preoperativeSum'];
$totals['preoperativePatientCount'] += $data['preoperativePatientCount'];
$totals['staff_sum'] += $data['countStaff'];
$totals['observable_sum'] += $data['countObservable'];
$totals['unwanted_sum'] += $data['countUnwanted'];
@@ -325,11 +363,13 @@ class StatisticsService
*/
private function createTotalRow(string $type, array $total, bool $isGrandTotal): array
{
if ($total['preoperativePatientCount'] === 0) $total['preoperativePatientCount'] = 1;
if ($total['outcome_sum'] === 0) $total['outcome_sum'] = 1;
return [
'isTotalRow' => ! $isGrandTotal,
'isGrandTotal' => $isGrandTotal,
'department' => $isGrandTotal ? 'ОБЩИЕ ИТОГИ:' : 'ИТОГО:',
'beds' => '—',
'beds' => $total['beds_sum'],
'recipients' => [
'all' => $total['recipients_all_sum'],
'plan' => $total['recipients_plan_sum'],
@@ -344,9 +384,9 @@ class StatisticsService
'emergency' => $total['emergency_surgical_sum'],
],
'deceased' => $total['deceased_sum'],
'averageBedDays' => '—',
'preoperativeDays' => '—',
'lethality' => '—',
'averageBedDays' => round($total['bedDaysSum'] / $total['outcome_sum'], 1),
'preoperativeDays' => round($total['preoperativeSum'] / $total['preoperativePatientCount'] < 0 ?? 1, 1),
'lethality' => round(($total['deceased_sum'] / $total['outcome_sum']) * 100, 2),
'type' => $type,
'departments_count' => $total['departments_count'],
'countStaff' => $total['staff_sum'],