* переписал функции прототипов в сервисы

* оптимизация доставки контента до клиента
* переписал запросы выборок
* убрал из подсчета переведенных
* добавил сохранение метрикам для вывода в дашборд
This commit is contained in:
brusnitsyn
2026-02-04 17:05:13 +09:00
parent 9ee33bc517
commit eab78a0291
16 changed files with 1644 additions and 737 deletions

View File

@@ -18,6 +18,7 @@ use App\Models\UnwantedEvent;
use App\Models\User;
use App\Services\DateRangeService;
use App\Services\MisPatientService;
use App\Services\ReportService;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
@@ -28,14 +29,11 @@ use Inertia\Inertia;
class ReportController extends Controller
{
protected MisPatientService $misPatientService;
protected DateRangeService $dateService;
public function __construct(MisPatientService $misPatientService, DateRangeService $dateRangeService)
{
$this->misPatientService = $misPatientService;
$this->dateService = $dateRangeService;
}
public function __construct(
protected MisPatientService $misPatientService,
protected ReportService $reportService,
protected DateRangeService $dateRangeService)
{ }
public function index(Request $request)
{
@@ -614,78 +612,20 @@ class ReportController extends Controller
public function getPatients(Request $request)
{
$user = Auth::user();
$data = $request->validate([
'status' => 'required|string', // plan emergency observation deceased
$validated = $request->validate([
'status' => 'required|string',
'startAt' => 'nullable',
'endAt' => 'nullable',
]);
// Получаем базовые данные
$status = $data['status'];
$model = new MisMedicalHistory();
$misDepartmentId = $request->user()->department->rf_mis_department_id;
$userDepartmentId = $request->user()->department->department_id;
$branchId = MisStationarBranch::where('rf_DepartmentID', $misDepartmentId)->value('StationarBranchID');
$dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user);
if (!$branchId) {
return response()->json([]);
}
// Определяем даты в зависимости от роли
[$startDate, $endDate] = $this->getDateRangeForRole($user, $data['startAt'] ?? null, $data['endAt'] ?? null);
// Для заведующего/админа ищем отчет по endDate (дате просмотра)
$reportIds = [];
$reports = $this->getReportsForDateRange($user->rf_department_id, $startDate, $endDate);
$reportIds = $reports->pluck('report_id')->toArray();
// Определяем, используем ли мы снапшоты
$useSnapshots = ($user->isAdmin() || $user->isHeadOfDepartment()) || Carbon::parse($endDate)->isToday() === false;
// Обработка каждого статуса
if ($useSnapshots) {
// Используем снапшоты: получаем ID пациентов, затем данные из реплики
$patients = match($status) {
'plan', 'emergency' => $this->getPatientsFromSnapshotsUsingReplica($status, $reportIds, $branchId, $startDate, $endDate),
'observation' => $this->getObservationPatientsFromSnapshotsUsingReplica($userDepartmentId, $reportIds),
'outcome-discharged' => $this->getOutcomePatientsFromSnapshotsUsingReplica('discharged', $reportIds, $branchId, $startDate, $endDate),
'outcome-transferred' => $this->getOutcomePatientsFromSnapshotsUsingReplica('transferred', $reportIds, $branchId, $startDate, $endDate),
'outcome-deceased' => $this->getOutcomePatientsFromSnapshotsUsingReplica('deceased', $reportIds, $branchId, $startDate, $endDate),
default => collect()
};
} else {
// // Используем реплику для врачей или когда нет отчетов
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
$patients = match($status) {
'plan', 'emergency' => $this->getPlanOrEmergencyPatients($status, $isHeadOrAdmin, $branchId, $startDate, $endDate),
'observation' => $this->getObservationPatients($userDepartmentId),
'outcome-discharged' => $this->getDischargedPatients($branchId, $startDate, $endDate),
'outcome-transferred' => $this->getTransferredPatients($branchId, $startDate, $endDate),
'outcome-deceased' => $this->getDeceasedOutcomePatients($branchId, $startDate, $endDate),
default => 0
};
}
// Если есть пациенты, добавляем дополнительные данные
if ($patients->isNotEmpty()) {
$patients = $patients->map(function ($item, $index) use ($branchId, $startDate, $endDate) {
$item->num = $index + 1;
$item->misStationarBranchId = $branchId;
$item->startDate = $startDate;
$item->endDate = $endDate;
return $item;
});
// Загружаем связи
$patients->load(['migrations' => function ($query) use ($startDate, $endDate, $branchId) {
$query->whereHas('diagnosis', function ($q) {
$q->where('rf_DiagnosTypeID', 3);
})
->with('diagnosis.mkb')
->where('rf_StationarBranchID', $branchId);
}]);
}
$patients = $this->reportService->getPatientsByStatus(
Auth::user(),
$validated['status'],
$dateRange
);
return response()->json(FormattedPatientResource::collection($patients));
}
@@ -693,81 +633,20 @@ class ReportController extends Controller
public function getPatientsCount(Request $request)
{
$user = Auth::user();
$data = $request->validate([
'status' => 'required|string', // plan emergency observation deceased
$validated = $request->validate([
'status' => 'required|string',
'startAt' => 'nullable',
'endAt' => 'nullable',
]);
// Получаем базовые данные
$status = $data['status'];
$model = new MisMedicalHistory();
$misDepartmentId = $request->user()->department->rf_mis_department_id;
$userDepartmentId = $request->user()->department->department_id;
$branchId = MisStationarBranch::where('rf_DepartmentID', $misDepartmentId)->value('StationarBranchID');
$dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user);
if (!$branchId) {
return response()->json([]);
}
// Определяем даты в зависимости от роли
[$startDate, $endDate] = $this->getDateRangeForRole($user, $data['startAt'] ?? null, $data['endAt'] ?? null);
// Для заведующего/админа ищем отчеты по промежутку дат
$reportIds = [];
$reports = $this->getReportsForDateRange($user->rf_department_id, $startDate, $endDate);
$reportIds = $reports->pluck('report_id')->toArray();
// Определяем, используем ли мы снапшоты
$useSnapshots = ($user->isAdmin() || $user->isHeadOfDepartment()) || Carbon::parse($endDate)->isToday() === false;
if ($useSnapshots) {
// Считаем из снапшотов
$patientTypeMap = [
'plan' => 'plan',
'emergency' => 'emergency',
'observation' => 'observation',
'outcome' => null,
'outcome-discharged' => 'discharged',
'outcome-transferred' => 'transferred',
'outcome-deceased' => 'deceased'
];
$patientType = $patientTypeMap[$status] ?? null;
if ($status === 'outcome') {
// Считаем уникальных пациентов по всем типам исходов
$count = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
->whereIn('patient_type', ['discharged', 'transferred', 'deceased'])
->distinct('rf_medicalhistory_id')
->count('rf_medicalhistory_id');
} elseif ($patientType) {
$count = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
->where('patient_type', $patientType)
->distinct('rf_medicalhistory_id')
->count('rf_medicalhistory_id');
} else {
$count = 0;
}
} else {
// Определяем, является ли пользователь заведующим/администратором
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
$isOutcomeOrObservation = in_array($status, ['outcome', 'observation']);
if ($isOutcomeOrObservation)
{
switch ($status) {
case 'observation':
$count = ObservationPatient::where('rf_department_id', $userDepartmentId)->count();
break;
case 'outcome':
$count = $this->getAllOutcomePatients($branchId, $startDate, $endDate, true);
break;
}
} else {
$count = $this->getPlanOrEmergencyPatients($status, $isHeadOrAdmin, $branchId, $startDate, $endDate, true);
}
}
$count = $this->reportService->getPatientsCountByStatus(
Auth::user(),
$validated['status'],
$dateRange,
);
return response()->json($count);
}

View File

@@ -3,118 +3,158 @@
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
use App\Models\MisStationarBranch;
use App\Http\Resources\Mis\FormattedPatientResource;
use App\Models\MetrikaGroup;
use App\Models\MisLpuDoctor;
use App\Models\Report;
use App\Models\UnwantedEvent;
use App\Services\DateRangeService;
use App\Services\MisPatientService;
use App\Services\ReportService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
class ReportController extends Controller
{
protected DateRangeService $dateService;
protected MisPatientService $misPatientService;
public function __construct(MisPatientService $misPatientService, DateRangeService $dateRangeService)
{
$this->misPatientService = $misPatientService;
$this->dateService = $dateRangeService;
}
public function __construct(
protected ReportService $reportService,
protected DateRangeService $dateRangeService
) {}
public function index(Request $request)
{
$user = \Auth::user();
$user = Auth::user();
$department = $user->department;
$queryStartDate = $request->query('startAt');
$queryEndDate = $request->query('endAt');
[$startDate, $endDate] = $this->dateService->getDateRangeForUser($user, $queryStartDate, $queryEndDate);
$isRangeOneDay = $this->dateService->isRangeOneDay($startDate, $endDate);
$dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user);
// Если диапазон содержит сутки
if ($isRangeOneDay) {
// Устанавливаем дату отчета, как последний день из выборки
$dateReport = $endDate;
} else {
// Устанавливаем дату отчета, как выборку
$dateReport = [$startDate, $endDate];
}
// Получаем статистику
$statistics = $this->reportService->getReportStatistics($user, $dateRange);
if ($isRangeOneDay) {
// Статистика выводится с нарастающим числом
$reports = $department->reports()
->whereDate('created_at', $dateReport)
->get();
} else {
$reports = $department->reports()
->whereBetween('created_at', $dateReport)
->get();
}
// Получаем метрики
$metrikaGroup = MetrikaGroup::whereMetrikaGroupId(2)->first();
$metrikaItems = $metrikaGroup->metrikaItems;
$isReports = $reports->count() > 0;
$allCount = 0; $outcomeCount = 0; $currentCount = 0; $occupiedBeds = 0; $planCount = 0;
$emergencyCount = 0; $planSurgical = 0; $emergencySurgical = 0; $transferredCount = 0;
$deceasedCount = 0;
if ($isReports) {
foreach ($reports as $report) {
$allCount += $this->getMetrikaResultFromReport($report, 3, $isRangeOneDay);
$currentCount += $this->getMetrikaResultFromReport($report, 8, false);
$occupiedBeds += $this->getMetrikaResultFromReport($report, 8, $isRangeOneDay);
$planCount += $this->getMetrikaResultFromReport($report, 4, $isRangeOneDay);
$emergencyCount += $this->getMetrikaResultFromReport($report, 12, $isRangeOneDay);
$planSurgical += $this->getMetrikaResultFromReport($report, 11, $isRangeOneDay);
$emergencySurgical += $this->getMetrikaResultFromReport($report, 10, $isRangeOneDay);
$transferredCount += $this->getMetrikaResultFromReport($report, 13, $isRangeOneDay);
$outcomeCount += $this->getMetrikaResultFromReport($report, 7, $isRangeOneDay);
$deceasedCount += $this->getMetrikaResultFromReport($report, 9, $isRangeOneDay);
}
} else {
$misDepartmentId = $request->user()->department->rf_mis_department_id;
$branchId = MisStationarBranch::where('rf_DepartmentID', $misDepartmentId)
->value('StationarBranchID');
$planCount = $this->misPatientService->getInStationarPatients('plan', $branchId, $dateReport)->count();
}
$bedsCount = $department->metrikaDefault()
->where('rf_metrika_item_id', 1)->value('value');
$percentLoadedBeds = $bedsCount > 0 ? round($occupiedBeds * 100 / $bedsCount) : 0;
// Получаем информацию о текущем отчете
$reportInfo = $this->reportService->getCurrentReportInfo($user, $dateRange);
return Inertia::render('Report/Index', [
'department' => [
'beds' => $bedsCount,
'recipients' => [
'all' => $allCount,
'plan' => $planCount,
'emergency' => $emergencyCount,
'transferred' => $transferredCount,
],
'outcome' => $outcomeCount,
'consist' => $currentCount,
'percentLoadedBeds' => $percentLoadedBeds,
'surgical' => [
'plan' => $planSurgical,
'emergency' => $emergencySurgical
],
'deceased' => $deceasedCount,
'beds' => $department->beds,
'percentLoadedBeds' => $this->calculateBedOccupancy($department, $user),
...$statistics,
],
'dates' => [
'startAt' => $dateRange->startTimestamp(),
'endAt' => $dateRange->endTimestamp()
],
'report' => $reportInfo,
'metrikaItems' => $metrikaItems,
'userId' => $reportInfo['userId'],
'userName' => $reportInfo['userName']
]);
}
private function getMetrikaResultFromReport(Report $report, int $metrikaItem, bool $sum = true)
public function store(Request $request)
{
if ($sum) {
return (int) ($report->metrikaResults()
->where('rf_metrika_item_id', $metrikaItem)
->sum(DB::raw('CAST(value AS INTEGER)')) ?: 0);
}
$validated = $request->validate([
'metrics' => 'required|array',
'observationPatients' => 'nullable|array',
'departmentId' => 'required|integer',
'unwantedEvents' => 'nullable|array',
'dates' => 'required|array',
'userId' => 'required|integer',
'reportId' => 'nullable|integer'
]);
return (int) ($report->metrikaResults()
->where('rf_metrika_item_id', $metrikaItem)
->value('value') ?: 0);
$report = $this->reportService->storeReport($validated, Auth::user());
return response()->json([
'message' => 'success',
'report_id' => $report->report_id
]);
}
public function getPatients(Request $request)
{
$user = Auth::user();
$validated = $request->validate([
'status' => 'required|string',
'startAt' => 'nullable',
'endAt' => 'nullable',
]);
$dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user);
$patients = $this->reportService->getPatientsByStatus(
Auth::user(),
$validated['status'],
$dateRange
);
return response()->json(FormattedPatientResource::collection($patients));
}
public function getPatientsCount(Request $request)
{
$user = Auth::user();
$validated = $request->validate([
'status' => 'required|string',
'startAt' => 'nullable',
'endAt' => 'nullable',
]);
$dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user);
$count = $this->reportService->getPatientsCountByStatus(
Auth::user(),
$validated['status'],
$dateRange,
);
return response()->json($count);
}
public function removeObservation(Request $request)
{
$validated = $request->validate(['id' => 'required|integer']);
$this->reportService->removeObservationPatient($validated['id']);
return response()->json(['message' => 'Удалено'], 200);
}
public function removeUnwantedEvent(UnwantedEvent $unwantedEvent)
{
$unwantedEvent->delete();
return response()->json(['message' => 'Удалено'], 200);
}
public function getDepartmentUsers()
{
$users = MisLpuDoctor::select(['LPUDoctorID', 'FAM_V', 'IM_V', 'OT_V'])
->active()
->inMyDepartment()
->get();
return response()->json($users, 200);
}
/**
* Рассчитать загруженность коек
*/
private function calculateBedOccupancy($department, $user): int
{
$beds = (int)$department->metrikaDefault()->where('rf_metrika_item_id', 1)->first()->value;
$occupiedBeds = optional(Report::where('rf_department_id', $user->rf_department_id)
->join('metrika_results', 'reports.report_id', '=', 'metrika_results.rf_report_id')
->where('metrika_results.rf_metrika_item_id', 8)
->orderBy('sent_at', 'desc')
->first())->value ?? 0;
return $beds > 0 ? round(intval($occupiedBeds) * 100 / $beds) : 0;
}
}

View File

@@ -20,21 +20,12 @@ class FormattedPatientResource extends JsonResource
return [
'id' => $this->MedicalHistoryID,
'num' => $this->num,
'mkb' => $this->whenLoaded('migrations', function () {
'mkb.ds' => $this->migrations->first()->diagnosis->first()?->mkb?->DS,
'operations' => $this->surgicalOperations->map(function ($operation) {
return [
'ds' => $this->migrations()->first()->diagnosis()->first()?->mkb()->first()->DS ?? null,
'name' => $this->migrations()->first()->diagnosis()->first()?->mkb()->first()->NAME ?? null,
'code' => $operation->serviceMedical->ServiceMedicalCode
];
}),
'operations' => $this->whenLoaded('surgicalOperations', function () {
return $this->operationOnBranch($this->misStationarBranchId, $this->startDate, $this->endDate)
->get()
->map(function (MisSurgicalOperation $operation) {
return [
'code' => $operation->serviceMedical->ServiceMedicalCode ?? null,
];
});
}),
'fullname' => Str::ucwords(Str::lower("$this->FAMILY $this->Name $this->OT")),
'age' => Carbon::parse($this->BD)->diff(Carbon::now())->format('%y'),
'birth_date' => Carbon::parse($this->BD)->format('d.m.Y'),

View File

@@ -34,6 +34,15 @@ class MisMedicalHistory extends Model
return $this->hasMany(MisSurgicalOperation::class, 'rf_MedicalHistoryID', 'MedicalHistoryID');
}
public function surgicalOperationsInBranch($branchId)
{
$operations = MisSurgicalOperation::where('rf_MedicalHistoryID', $this->MedicalHistoryID)
->where('rf_StationarBranchID', $branchId)
->get();
return $operations;
}
public function scopeOperationOnBranch($query, $branchId, $startDate, $endDate)
{
return $this->surgicalOperations()->where('rf_StationarBranchID', $branchId)

View File

@@ -2,6 +2,7 @@
namespace App\Models;
use App\Services\DateRange;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
@@ -25,20 +26,29 @@ class MisMigrationPatient extends Model
return $this->hasOne(MisMKB::class, 'MKBID', 'rf_MKBID');
}
public function medicalHistory()
{
return $this->belongsTo(MisMedicalHistory::class, 'rf_MedicalHistoryID', 'MedicalHistoryID');
}
/**
* Находятся на лечении
*/
public function scopeCurrentlyInTreatment($query, $branchId = null, $startDate = null, $endDate = null)
public function scopeCurrentlyInTreatment($query, $branchId = null, DateRange $dateRange = null)
{
$query->where('rf_kl_VisitResultID', 0)
->where('rf_kl_StatCureResultID', 0)
->whereHas('medicalHistory', function ($query) use ($branchId, $dateRange) {
$query->whereDate('DateExtract', '1900-01-01');
})
->where('rf_MedicalHistoryID', '<>', 0);
if ($branchId) {
$query->where('rf_StationarBranchID', $branchId);
}
if ($startDate && $endDate) {
$query->whereBetween('DateIngoing', [$startDate, $endDate]);
if ($dateRange) {
$query->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
}
return $query;
@@ -58,7 +68,7 @@ class MisMigrationPatient extends Model
/**
* Выбывшие пациенты (все исходы)
*/
public function scopeOutcomePatients($query, $branchId = null, $startDate = null, $endDate = null)
public function scopeOutcomePatients($query, $branchId = null, DateRange $dateRange = null)
{
$query->where('rf_kl_VisitResultID', '<>', 0) // не активное лечение
->whereDate('DateOut', '<>', '1900-01-01') // есть дата выбытия
@@ -68,8 +78,8 @@ class MisMigrationPatient extends Model
$query->where('rf_StationarBranchID', $branchId);
}
if ($startDate && $endDate) {
$query->whereBetween('DateOut', [$startDate, $endDate]);
if ($dateRange) {
$query->whereBetween('DateOut', [$dateRange->startSql(), $dateRange->endSql()]);
}
return $query;
@@ -78,7 +88,7 @@ class MisMigrationPatient extends Model
/**
* Выписанные пациенты
*/
public function scopeDischarged($query, $branchId = null, $startDate = null, $endDate = null)
public function scopeDischarged($query, $branchId = null, DateRange $dateRange = null)
{
// ID выписки
$dischargeCodes = [1, 7, 8, 9, 10, 11, 48, 49, 124];
@@ -91,8 +101,8 @@ class MisMigrationPatient extends Model
$query->where('rf_StationarBranchID', $branchId);
}
if ($startDate && $endDate) {
$query->whereBetween('DateOut', [$startDate, $endDate]);
if ($dateRange) {
$query->whereBetween('DateOut', [$dateRange->startSql(), $dateRange->endSql()]);
}
return $query;
@@ -101,7 +111,7 @@ class MisMigrationPatient extends Model
/**
* Перевод в другое отделение
*/
public function scopeTransferred($query, $branchId = null, $startDate = null, $endDate = null)
public function scopeTransferred($query, $branchId = null, DateRange $dateRange = null)
{
// ID перевода
$transferCodes = [2, 3, 4, 12, 13, 14];
@@ -114,8 +124,8 @@ class MisMigrationPatient extends Model
$query->where('rf_StationarBranchID', $branchId);
}
if ($startDate && $endDate) {
$query->whereBetween('DateOut', [$startDate, $endDate]);
if ($dateRange) {
$query->whereBetween('DateOut', [$dateRange->startSql(), $dateRange->endSql()]);
}
return $query;
@@ -124,7 +134,7 @@ class MisMigrationPatient extends Model
/**
* Умершие пациенты
*/
public function scopeDeceasedOutcome($query, $branchId = null, $startDate = null, $endDate = null)
public function scopeDeceasedOutcome($query, $branchId = null, DateRange $dateRange = null)
{
// ID умершего
$deceasedCodes = [5, 6, 15, 16];
@@ -137,22 +147,22 @@ class MisMigrationPatient extends Model
$query->where('rf_StationarBranchID', $branchId);
}
if ($startDate && $endDate) {
$query->whereBetween('DateOut', [$startDate, $endDate]);
if ($dateRange) {
$query->whereBetween('DateOut', [$dateRange->startSql(), $dateRange->endSql()]);
}
return $query;
}
public function scopeExtractedToday($query, $branchId = null, $startDate = null, $endDate = null)
public function scopeExtractedToday($query, $branchId = null, DateRange $dateRange = null)
{
if (is_null($startDate)) $startDate = Carbon::now()->addDays(-1)->format('Y-m-d');
if (is_null($endDate)) $endDate = Carbon::now()->format('Y-m-d');
// if (is_null($startDate)) $startDate = Carbon::now()->addDays(-1)->format('Y-m-d');
// if (is_null($endDate)) $endDate = Carbon::now()->format('Y-m-d');
$query->where('rf_kl_VisitResultID', '<>', 0)
->where('rf_MedicalHistoryID', '<>', 0)
->when($startDate && $endDate, function($query) use ($startDate, $endDate) {
return $query->whereBetween('DateOut', [$startDate, $endDate]);
->when($dateRange, function($query) use ($dateRange) {
return $query->whereBetween('DateOut', [$dateRange->startSql(), $dateRange->endSql()]);
});
if ($branchId) {

View File

@@ -0,0 +1,89 @@
<?php
namespace App\Services;
use Carbon\Carbon;
use Illuminate\Support\Collection;
readonly class DateRange
{
public function __construct(
public Carbon $startDate,
public Carbon $endDate,
public string $startDateRaw,
public string $endDateRaw,
public bool $isOneDay
) {}
/**
* Получить начало диапазона в Carbon
*/
public function start(): Carbon
{
return $this->startDate;
}
/**
* Получить конец диапазона в Carbon
*/
public function end(): Carbon
{
return $this->endDate;
}
/**
* Получить начало диапазона в SQL формате
*/
public function startSql(): string
{
return $this->startDate->format('Y-m-d H:i:s');
}
/**
* Получить конец диапазона в SQL формате
*/
public function endSql(): string
{
return $this->endDate->format('Y-m-d H:i:s');
}
/**
* Получить начало диапазона как timestamp
*/
public function startTimestamp(): int
{
return $this->startDate->getTimestampMs();
}
/**
* Получить конец диапазона как timestamp
*/
public function endTimestamp(): int
{
return $this->endDate->getTimestampMs();
}
/**
* Проверить, является ли дата сегодняшней
*/
public function isEndDateToday(): bool
{
return $this->endDate->isToday();
}
/**
* Получить диапазон дней
*/
public function getDaysRange(): Collection
{
$days = collect();
$current = $this->startDate->copy();
while ($current->lte($this->endDate)) {
$days->push($current->copy());
$current->addDay();
}
return $days;
}
}

View File

@@ -2,10 +2,43 @@
namespace App\Services;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
class DateRangeService
{
/**
* Получить унифицированный объект диапазона дат
*/
public function getNormalizedDateRange(User $user, ?string $startAt = null, ?string $endAt = null): DateRange
{
[$startDate, $endDate] = $this->getDateRangeForUser($user, $startAt, $endAt);
$startCarbon = Carbon::parse($startDate)->setTimeZone('Asia/Yakutsk');
$endCarbon = Carbon::parse($endDate)->setTimeZone('Asia/Yakutsk');
return new DateRange(
startDate: $startCarbon,
endDate: $endCarbon,
startDateRaw: $startDate,
endDateRaw: $endDate,
isOneDay: $this->isRangeOneDay($startDate, $endDate)
);
}
/**
* Получить диапазон дат из запроса
*/
public function getDateRangeFromRequest(Request $request, User $user): DateRange
{
return $this->getNormalizedDateRange(
$user,
$request->query('startAt', $request->get('startAt')),
$request->query('endAt', $request->get('endAt'))
);
}
public function getDateRangeForUser($user, $startAt = null, $endAt = null): array
{
if ($startAt && $endAt) {
@@ -22,9 +55,7 @@ class DateRangeService
$startDate = $this->parseDate($startAt);
$endDate = $this->parseDate($endAt);
if ($startDate->diffInDays($endDate) === 1.0) return true;
return false;
return $startDate->diffInDays($endDate) === 1.0;
}
private function getCustomDateRange($startAt, $endAt, $user): array
@@ -70,4 +101,28 @@ class DateRangeService
return Carbon::parse($dateInput, 'Asia/Yakutsk');
}
/**
* Конвертировать дату в Carbon объект
*/
public function toCarbon($date): Carbon
{
if ($date instanceof Carbon) {
return $date;
}
if (is_string($date)) {
return Carbon::parse($date, 'Asia/Yakutsk');
}
return Carbon::parse($date);
}
/**
* Получить дату в формате для SQL запроса
*/
public function toSqlFormat($date): string
{
return $this->toCarbon($date)->format('Y-m-d H:i:s');
}
}

View File

@@ -2,119 +2,207 @@
namespace App\Services;
use App\Models\MisMigrationPatient;
use App\Models\MisMedicalHistory;
use App\Models\MisSurgicalOperation;
use App\Models\MisMigrationPatient;
use App\Models\ObservationPatient;
use Illuminate\Support\Collection;
use Carbon\Carbon;
class PatientService
{
public function getPatientsByType(
string $type,
?bool $isHeadOrAdmin,
?int $branchId,
?string $startDate = null,
?string $endDate = null,
/**
* Получить плановых или экстренных пациентов
*/
public function getPlanOrEmergencyPatients(
?string $type,
bool $isHeadOrAdmin,
int $branchId,
DateRange $dateRange,
bool $countOnly = false,
bool $onlyIds = false
): Collection|int|array {
// Обрабатываем outcome- типы
if (str_starts_with($type, 'outcome-')) {
$outcomeType = str_replace('outcome-', '', $type);
return $this->getOutcomePatients($branchId, $startDate, $endDate, $outcomeType, $countOnly, $onlyIds);
}
// Обрабатываем обычные типы
$method = 'get' . ucfirst($type) . 'Patients';
if (method_exists($this, $method)) {
return $this->$method($isHeadOrAdmin, $branchId, $startDate, $endDate, $countOnly, $onlyIds);
}
throw new \InvalidArgumentException("Unknown patient type: {$type}");
}
public function getPlanPatients(
?bool $isHeadOrAdmin,
?int $branchId,
?string $startDate,
?string $endDate,
bool $countOnly,
bool $onlyIds
): Collection|int|array {
return $this->getAdmissionPatients('plan', $isHeadOrAdmin, $branchId, $startDate, $endDate, $countOnly, $onlyIds);
}
public function getEmergencyPatients(
?bool $isHeadOrAdmin,
?int $branchId,
?string $startDate,
?string $endDate,
bool $countOnly,
bool $onlyIds
): Collection|int|array {
return $this->getAdmissionPatients('emergency', $isHeadOrAdmin, $branchId, $startDate, $endDate, $countOnly, $onlyIds);
}
private function getAdmissionPatients(
string $admissionType,
?bool $isHeadOrAdmin,
?int $branchId,
?string $startDate,
?string $endDate,
bool $countOnly,
bool $onlyIds
): Collection|int|array {
$query = $this->getBasePatientsQuery($isHeadOrAdmin, $branchId, $startDate, $endDate);
if ($admissionType === 'plan') {
$query->plan();
} else {
$query->emergency();
}
return $this->executeQuery($query, $countOnly, $onlyIds);
}
private function getBasePatientsQuery(
?bool $isHeadOrAdmin,
?int $branchId,
?string $startDate,
?string $endDate
bool $onlyIds = false,
bool $includeCurrent = false
) {
$migrationPatient = new MisMigrationPatient();
// Получаем поступивших сегодня
$recipientQuery = $this->buildRecipientQuery($type, $isHeadOrAdmin, $branchId, $dateRange);
$recipientIds = $recipientQuery->pluck('rf_MedicalHistoryID')->toArray();
if ($isHeadOrAdmin) {
$medicalHistoryIds = $migrationPatient->newQuery()
->whereInDepartment($branchId)
->whereBetween('DateIngoing', [$startDate, $endDate])
// Если нужно добавить уже находящихся в отделении
if ($includeCurrent) {
$currentIds = MisMigrationPatient::currentlyInTreatment($branchId)
->pluck('rf_MedicalHistoryID')
->toArray();
$medicalHistoryIds = array_unique(array_merge($recipientIds, $currentIds));
} else {
$medicalHistoryIds = $migrationPatient->newQuery()
->currentlyInTreatment($branchId)
->whereBetween('DateIngoing', [$startDate, $endDate])
->pluck('rf_MedicalHistoryID')
->toArray();
$medicalHistoryIds = $recipientIds;
}
if (empty($medicalHistoryIds)) {
return MisMedicalHistory::whereRaw('1=0'); // Пустой запрос
if ($countOnly) return 0;
if ($onlyIds) return [];
return collect();
}
if ($onlyIds) {
return $medicalHistoryIds;
}
// Получаем истории
$query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
->with([
'surgicalOperations' => function ($q) use ($dateRange) {
$q->whereBetween('Date', [$dateRange->startSql(), $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');
}]);
}
])
->orderBy('DateRecipient', 'DESC');
// Фильтруем по типу (план/экстренные)
if ($type === 'plan') {
$query->plan();
} elseif ($type === 'emergency') {
$query->emergency();
}
if ($countOnly) {
return $query->count();
}
return $query->get()->map(function ($patient) use ($recipientIds, $branchId) {
// Добавляем флаг "поступил сегодня"
$patient->is_recipient_today = in_array($patient->MedicalHistoryID, $recipientIds);
return $patient;
});
}
/**
* Получить всех пациентов в отделении (поступившие сегодня + уже лечащиеся)
*/
public function getAllPatientsInDepartment(
bool $isHeadOrAdmin,
int $branchId,
DateRange $dateRange,
bool $countOnly = false
) {
// Поступившие сегодня
$recipientIds = $this->buildRecipientQuery(null, $isHeadOrAdmin, $branchId, $dateRange)
->pluck('rf_MedicalHistoryID')
->toArray();
// Уже находящиеся на лечении
$currentIds = MisMigrationPatient::currentlyInTreatment($branchId)
->pluck('rf_MedicalHistoryID')
->toArray();
// Объединяем и убираем дубли
$allIds = array_unique(array_merge($recipientIds, $currentIds));
if (empty($allIds)) {
if ($countOnly) return 0;
return collect();
}
if ($countOnly) {
return count($allIds);
}
return MisMedicalHistory::whereIn('MedicalHistoryID', $allIds)
->with(['surgicalOperations' => function ($q) use ($startDate, $endDate) {
$q->whereBetween('Date', [$startDate, $endDate]);
}])
->orderBy('DateRecipient', 'DESC')
->get()
->map(function ($patient) use ($recipientIds) {
$patient->is_recipient_today = in_array($patient->MedicalHistoryID, $recipientIds);
return $patient;
});
}
/**
* Получить пациентов под наблюдением
*/
public function getObservationPatients(int $departmentId)
{
$patients = MisMedicalHistory::whereHas('observationPatient', function ($q) use ($departmentId) {
$q->where('rf_department_id', $departmentId);
})
->with(['observationPatient' => function($query) use ($departmentId) {
$query->where('rf_department_id', $departmentId)
->select(['rf_medicalhistory_id', 'observation_patient_id', 'comment']);
}])
->orderBy('DateRecipient', 'DESC')
->get();
return $patients->map(function ($patient) {
$patient->comment = $patient->observationPatient
->pluck('comment')
->filter()
->implode('; ');
return $patient;
});
}
/**
* Получить выбывших пациентов
*/
public function getOutcomePatients(
int $branchId,
DateRange $dateRange,
string $outcomeType = 'all'
) {
$methodMap = [
'discharged' => 'discharged',
'transferred' => 'transferred',
'deceased' => 'deceasedOutcome',
'all' => 'outcomePatients',
];
$method = $methodMap[$outcomeType] ?? 'outcomePatients';
$medicalHistoryIds = MisMigrationPatient::{$method}($branchId, $dateRange)
->pluck('rf_MedicalHistoryID')
->unique()
->toArray();
if (empty($medicalHistoryIds)) {
return collect();
}
return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
->with(['surgicalOperations' => function ($query) use ($startDate, $endDate) {
$query->whereBetween('Date', [$startDate, $endDate]);
}])
->orderBy('DateRecipient', 'DESC');
->with(['surgicalOperations'])
->orderBy('DateRecipient', 'DESC')
->get()
->map(function ($patient) {
return $this->addOutcomeInfo($patient);
});
}
private function executeQuery($query, bool $countOnly, bool $onlyIds): Collection|int|array
{
if ($onlyIds) {
return $query->pluck('MedicalHistoryID')->toArray();
}
/**
* Получить пациентов с операциями
*/
public function getSurgicalPatients(
string $type,
int $branchId,
DateRange $dateRange,
bool $countOnly = false
) {
$query = MisMedicalHistory::whereHas('surgicalOperations', function ($q) use ($type, $branchId, $dateRange) {
$q->where('rf_StationarBranchID', $branchId)
->whereBetween('Date', [$dateRange->startSql(), $dateRange->endSql()]);
if ($type === 'plan') {
$q->where('rf_TypeSurgOperationInTimeID', 6);
} else {
$q->whereIn('rf_TypeSurgOperationInTimeID', [4, 5]);
}
});
if ($countOnly) {
return $query->count();
@@ -123,224 +211,18 @@ class PatientService
return $query->get();
}
public function getObservationPatients(int $departmentId): Collection
/**
* Получить текущих пациентов
*/
public function getCurrentPatients(int $branchId, bool $countOnly = false)
{
return ObservationPatient::where('rf_department_id', $departmentId)
->with(['medicalHistory'])
->get()
->map(function ($observation) {
$patient = $observation->medicalHistory;
$patient->observation_comment = $observation->comment;
return $patient;
});
}
public function getOutcomePatients(int $branchId, string $startDate, string $endDate, string $outcomeType, $countOnly = false, $onlyIds = false): Collection|int
{
return match($outcomeType) {
'discharged' => $this->getDischargedOutcomePatients($branchId, $startDate, $endDate, $countOnly, $onlyIds),
'transferred' => $this->getTransferredOutcomePatients($branchId, $startDate, $endDate, $countOnly, $onlyIds),
'deceased' => $this->getDeceasedOutcomePatients($branchId, $startDate, $endDate, $countOnly, $onlyIds),
default => throw new \InvalidArgumentException("Неизвестный тип исхода: {$outcomeType}")
};
}
/**
* Получить всех выбывших пациентов
*/
public function getAllOutcomePatients(
int $branchId,
string $startDate,
string $endDate,
bool $countOnly = false
): Collection|int {
$migrationPatient = new MisMigrationPatient();
// Получаем миграции выбывших пациентов за период
$migrations = $migrationPatient->newQuery()
->outcomePatients($branchId, $startDate, $endDate)
->select('rf_MedicalHistoryID', 'rf_kl_VisitResultID', 'DateOut')
->get()
->groupBy('rf_MedicalHistoryID');
if ($migrations->isEmpty()) {
return $countOnly ? 0 : collect();
}
$medicalHistoryIds = $migrations->keys()->toArray();
$query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
->with(['surgicalOperations'])
->orderBy('DateRecipient', 'DESC');
if ($countOnly) {
return $query->count();
}
$patients = $query->get();
return $this->addOutcomeInfoToPatients($patients, $migrations);
}
/**
* Получить миграции выбывших пациентов
*/
private function getOutcomeMigrations(int $branchId, string $startDate, string $endDate): Collection
{
$migrationPatient = new MisMigrationPatient();
return $migrationPatient->newQuery()
->outcomePatients($branchId, $startDate, $endDate)
->select('rf_MedicalHistoryID', 'rf_kl_VisitResultID', 'DateOut')
->get()
->groupBy('rf_MedicalHistoryID');
}
/**
* Добавить информацию о выбытии к пациентам
*/
private function addOutcomeInfoToPatients(Collection $patients, Collection $migrations): Collection
{
return $patients->map(function ($patient) use ($migrations) {
$patientMigrations = $migrations->get($patient->MedicalHistoryID, collect());
if ($patientMigrations->isNotEmpty()) {
$latestMigration = $patientMigrations->sortByDesc('DateOut')->first();
$patient->outcome_type = $this->getOutcomeTypeName($latestMigration->rf_kl_VisitResultID);
$patient->outcome_date = $latestMigration->DateOut;
$patient->visit_result_id = $latestMigration->rf_kl_VisitResultID;
}
return $patient;
});
}
/**
* Получить понятное название типа выбытия
*/
private function getOutcomeTypeName(int $visitResultId): string
{
return match($visitResultId) {
1, 7, 8, 9, 10, 11, 48, 49, 124 => 'Выписка',
2, 3, 4, 12, 13, 14 => 'Перевод',
5, 6, 15, 16 => 'Умер',
default => 'Другое (' . $visitResultId . ')'
};
}
/**
* Получить выписанных пациентов
*/
public function getDischargedOutcomePatients(
int $branchId,
string $startDate,
string $endDate,
bool $countOnly = false,
bool $onlyIds = false
): Collection|array|int {
return $this->getSpecificOutcomePatients('discharged', $branchId, $startDate, $endDate, $onlyIds, $countOnly);
}
/**
* Получить переведенных пациентов
*/
public function getTransferredOutcomePatients(
int $branchId,
string $startDate,
string $endDate,
bool $countOnly = false,
bool $onlyIds = false
): Collection|array|int {
return $this->getSpecificOutcomePatients('transferred', $branchId, $startDate, $endDate, $onlyIds, $countOnly);
}
/**
* Получить умерших пациентов
*/
public function getDeceasedOutcomePatients(
int $branchId,
string $startDate,
string $endDate,
bool $countOnly = false,
bool $onlyIds = false
): Collection|int|array {
return $this->getSpecificOutcomePatients('deceased', $branchId, $startDate, $endDate, $onlyIds, $countOnly);
}
/**
* Общий метод для получения пациентов с определенным типом выбытия
*/
private function getSpecificOutcomePatients(
string $type,
int $branchId,
string $startDate,
string $endDate,
bool $onlyIds = false,
bool $countOnly = false
): Collection|int|array {
$migrationPatient = new MisMigrationPatient();
switch ($type) {
case 'discharged':
$query = $migrationPatient->newQuery()->discharged($branchId, $startDate, $endDate);
break;
case 'transferred':
$query = $migrationPatient->newQuery()->transferred($branchId, $startDate, $endDate);
break;
case 'deceased':
$query = $migrationPatient->newQuery()->deceasedOutcome($branchId, $startDate, $endDate);
break;
default:
throw new \InvalidArgumentException("Неизвестный тип выбытия: {$type}");
}
$medicalHistoryIds = $query->pluck('rf_MedicalHistoryID')->unique()->toArray();
if (empty($medicalHistoryIds)) {
if ($countOnly) return 0;
if ($onlyIds) return [];
return collect();
}
if ($onlyIds) {
return $medicalHistoryIds;
}
$patients = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
->with(['surgicalOperations'])
->orderBy('DateRecipient', 'DESC');
if ($countOnly) {
return $patients->count();
}
return $patients->get();
}
/**
* Получить пациентов, находящихся на лечении
*/
public function getCurrentPatients(
int $branchId,
bool $countOnly = false,
bool $onlyIds = false
): Collection|int|array {
$migrationPatient = new MisMigrationPatient();
$medicalHistoryIds = $migrationPatient->newQuery()
->currentlyInTreatment($branchId)
$medicalHistoryIds = MisMigrationPatient::currentlyInTreatment($branchId)
->pluck('rf_MedicalHistoryID')
->unique()
->toArray();
if (empty($medicalHistoryIds)) {
if ($countOnly) return 0;
if ($onlyIds) return [];
return collect();
}
if ($onlyIds) {
return $medicalHistoryIds;
return $countOnly ? 0 : collect();
}
$query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
@@ -356,94 +238,127 @@ class PatientService
}
/**
* Получить пациентов с операциями
* Собрать базовый запрос для пациентов
*/
public function getSurgicalPatients(
string $status,
private function buildPatientQuery(
?string $type,
bool $isHeadOrAdmin,
int $branchId,
string $startDate,
string $endDate,
bool $countOnly = false
): Collection|int {
$query = MisSurgicalOperation::where('rf_StationarBranchID', $branchId)
->whereBetween('Date', [$startDate, $endDate])
->orderBy('Date', 'DESC');
if ($status === 'plan') {
$query->where('rf_TypeSurgOperationInTimeID', 6);
DateRange $dateRange
) {
if ($isHeadOrAdmin) {
$query = MisMigrationPatient::whereInDepartment($branchId)
->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
} else {
$query->whereIn('rf_TypeSurgOperationInTimeID', [4, 5]);
$query = MisMigrationPatient::currentlyInTreatment($branchId)
->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
}
if ($countOnly) {
return $query->count();
$medicalHistoryIds = $query->pluck('rf_MedicalHistoryID')->toArray();
if (empty($medicalHistoryIds)) {
return MisMedicalHistory::whereRaw('1 = 0');
}
return $query->get();
}
$query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds);
/**
* Получить пациентов (плановых или экстренных), которые были в отделении в течение периода
*/
public function getPatientsInDepartmentDuringPeriod(
?string $patientType, // 'plan', 'emergency', null (все)
?bool $isHeadOrAdmin,
?int $branchId,
?string $startDate,
?string $endDate,
bool $countOnly = false,
bool $onlyIds = false,
bool $today = false
): Collection|int|array {
// Используем скоуп inDepartment из модели MisMedicalHistory
$query = MisMedicalHistory::query()
->whereHas('migrations', function ($q) use ($branchId, $startDate, $endDate) {
$q->where('rf_StationarBranchID', $branchId)
->where('rf_MedicalHistoryID', '<>', 0)
->where(function ($subQ) use ($startDate, $endDate) {
// Пациент находился в отделении в течение периода
// 1. Поступил в течение периода
$subQ->whereBetween('DateIngoing', [$startDate, $endDate])
// 2. Или выбыл в течение периода
->orWhereBetween('DateOut', [$startDate, $endDate])
// 3. Или находился в отделении в течение всего периода
->orWhere(function ($innerQ) use ($startDate, $endDate) {
$innerQ->where('DateIngoing', '<=', $startDate)
->where(function ($deepQ) use ($endDate) {
$deepQ->where('DateOut', '>=', $endDate)
->orWhereNull('DateOut')
->orWhere('DateOut', '1900-01-01');
});
});
});
})
->with(['surgicalOperations' => function ($query) use ($startDate, $endDate) {
$query->whereBetween('Date', [$startDate, $endDate]);
}])
->orderBy('DateRecipient', 'DESC');
// Фильтруем по типу (план/экстренность)
if ($patientType === 'plan') {
if ($type === 'plan') {
$query->plan();
} elseif ($patientType === 'emergency') {
} elseif ($type === 'emergency') {
$query->emergency();
}
// Для врача добавляем условие "все еще в отделении"
if (!$isHeadOrAdmin && !$today) {
if (!$isHeadOrAdmin && !in_array($type, ['discharged', 'transferred', 'deceased'])) {
$query->currentlyHospitalized();
}
if ($onlyIds) {
return $query->select('MedicalHistoryID')
->pluck('MedicalHistoryID')->values();
return $query;
}
/**
* Построить запрос для поступивших пациентов
*/
private function buildRecipientQuery(
?string $type,
bool $isHeadOrAdmin,
int $branchId,
DateRange $dateRange
) {
// Разная логика для заведующего и врача
if ($isHeadOrAdmin) {
// Заведующий: все поступившие за период
$query = MisMigrationPatient::whereInDepartment($branchId)
->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
} else {
// Врач: только поступившие за сутки
$query = MisMigrationPatient::whereInDepartment($branchId)
->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
}
if ($countOnly) {
return $query->count();
return $query;
}
/**
* Добавить информацию об исходе пациенту
*/
private function addOutcomeInfo(MisMedicalHistory $patient)
{
$latestMigration = $patient->migrations
->whereNotNull('DateOut')
->where('DateOut', '<>', '1900-01-01')
->sortByDesc('DateOut')
->first();
if ($latestMigration) {
$patient->outcome_type = $this->getOutcomeTypeName($latestMigration->rf_kl_VisitResultID);
$patient->outcome_date = $latestMigration->DateOut;
$patient->visit_result_id = $latestMigration->rf_kl_VisitResultID;
}
return $query->get();
return $patient;
}
/**
* Получить количество пациентов по типу с учетом уже находящихся в отделении
*/
public function getPatientsCountWithCurrent(
?string $type,
bool $isHeadOrAdmin,
int $branchId,
DateRange $dateRange
): int {
// Поступившие сегодня указанного типа
$recipientCount = $this->buildRecipientQuery($type, $isHeadOrAdmin, $branchId, $dateRange)
->count();
// Если нужны плановые/экстренные среди уже лечащихся
$currentCount = 0;
if ($type === 'plan' || $type === 'emergency') {
$currentIds = MisMigrationPatient::currentlyInTreatment($branchId)
->pluck('rf_MedicalHistoryID')
->toArray();
if (!empty($currentIds)) {
$currentCount = MisMedicalHistory::whereIn('MedicalHistoryID', $currentIds)
->when($type === 'plan', fn($q) => $q->plan())
->when($type === 'emergency', fn($q) => $q->emergency())
->count();
}
}
return $currentCount;
}
/**
* Получить название типа исхода
*/
private function getOutcomeTypeName(int $visitResultId): string
{
return match($visitResultId) {
1, 7, 8, 9, 10, 11, 48, 49, 124 => 'Выписка',
2, 3, 4, 12, 13, 14 => 'Перевод',
5, 6, 15, 16 => 'Умер',
default => 'Другое (' . $visitResultId . ')'
};
}
}

View File

@@ -2,41 +2,65 @@
namespace App\Services;
use App\Models\Report;
use App\Models\MetrikaResult;
use App\Models\UnwantedEvent;
use App\Models\ObservationPatient;
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
{
private PatientService $patientService;
public function __construct(
protected DateRangeService $dateRangeService,
protected PatientService $patientQueryService,
protected SnapshotService $snapshotService
) {}
public function __construct(PatientService $patientService)
/**
* Получить статистику для отчета
*/
public function getReportStatistics(User $user, DateRange $dateRange): array
{
$this->patientService = $patientService;
$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 createReport(array $data): Report
/**
* Создать или обновить отчет
*/
public function storeReport(array $data, User $user): Report
{
DB::beginTransaction();
try {
$report = Report::create([
'rf_department_id' => $data['departmentId'],
'rf_user_id' => Auth::id(),
'sent_at' => now()
]);
$report = $this->createOrUpdateReport($data, $user);
$this->saveMetrics($report, $data['metrics'] ?? []);
$this->saveUnwantedEvents($report, $data['unwantedEvents'] ?? []);
$this->saveObservationPatients($report, $data['observationPatients'] ?? []);
$this->savePatientSnapshots($report, $data);
$this->saveObservationPatients($report, $data['observationPatients'] ?? [], $user->rf_department_id);
// Сохраняем снапшоты пациентов
$this->snapshotService->createPatientSnapshots($report, $user, $data['dates']);
DB::commit();
return $report;
} catch (\Exception $e) {
DB::rollBack();
@@ -44,82 +68,707 @@ class ReportService
}
}
/**
* Получить пациентов по статусу
*/
public function getPatientsByStatus(
User $user,
string $status,
DateRange $dateRange
) {
$branchId = $this->getBranchId($user->department->rf_mis_department_id);
$useSnapshots = $this->shouldUseSnapshots($user, $dateRange);
if ($useSnapshots) {
return $this->getPatientsFromSnapshots($user, $status, $dateRange, $branchId);
}
return $this->getPatientsFromReplica($user, $status, $dateRange, $branchId);
}
/**
* Получить количество пациентов по статусу
*/
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
{
if ($user->isAdmin() || $user->isHeadOfDepartment()) {
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' => now(),
];
if (isset($data['reportId']) && $data['reportId']) {
$report = Report::updateOrCreate(
['report_id' => $data['reportId']],
$reportData
);
} else {
$report = Report::create($reportData);
}
return $report;
}
/**
* Сохранить метрики отчета
*/
private function saveMetrics(Report $report, array $metrics): void
{
foreach ($metrics as $key => $value) {
MetrikaResult::create([
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => (int) str_replace('metrika_item_', '', $key),
'value' => $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'])) {
if (isset($event['unwanted_event_id']) && $event['unwanted_event_id']) {
UnwantedEvent::updateOrCreate(
['unwanted_event_id' => $event['unwanted_event_id']],
$this->formatUnwantedEventData($report, $event)
[
'rf_report_id' => $report->report_id,
'comment' => $event['comment'] ?? '',
'title' => $event['title'] ?? '',
'is_visible' => $event['is_visible'] ?? true,
]
);
} else {
UnwantedEvent::create($this->formatUnwantedEventData($report, $event));
}
}
}
private function formatUnwantedEventData(Report $report, array $event): array
{
return [
'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): void
{
foreach ($observationPatients as $patient) {
ObservationPatient::create([
'rf_department_id' => $report->rf_department_id,
'rf_report_id' => $report->report_id,
'rf_medicalhistory_id' => $patient['id'],
'comment' => $patient['comment'] ?? null
]);
}
}
private function savePatientSnapshots(Report $report, array $data): void
{
$snapshotTypes = [
'plan' => $this->patientService->getPlanPatients(
false,
$data['branchId'],
$data['startDate'],
$data['endDate'],
false,
true
),
'emergency' => $this->patientService->getEmergencyPatients(
false,
$data['branchId'],
$data['startDate'],
$data['endDate'],
false,
true
)
];
foreach ($snapshotTypes as $type => $patientIds) {
foreach ($patientIds as $patientId) {
MedicalHistorySnapshot::create([
UnwantedEvent::create([
'rf_report_id' => $report->report_id,
'rf_medicalhistory_id' => $patientId,
'patient_type' => $type
'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 && $reportToday->rf_lpudoctor_id !== intval($fillableUserId)) {
$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),
// 'discharged' => $this->getMetrikaResultCount('discharged', $reportIds),
'transferred' => $this->getMetrikaResultCount(13, $reportIds),
'recipient' => $this->getMetrikaResultCount(3, $reportIds),
];
// Получаем 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' => $this->calculateCurrentPatientsFromSnapshots($reportIds, $branchId),
'deadCount' => $snapshotStats['deceased'] ?? 0,
'surgicalCount' => $surgicalCount,
'recipientIds' => $recipientIds,
];
}
/**
* Получить статистику из реплики БД
*/
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 // только поступившие сегодня
);
return [
'recipientCount' => $recipientCount, // только поступившие сегодня
'extractCount' => $outcomeCount,
'currentCount' => $currentCount, // все в отделении
'deadCount' => $deadCount,
'surgicalCount' => $surgicalCount,
'recipientIds' => $recipientIds, // ID поступивших сегодня
'planCount' => $planCount, // плановые (поступившие + уже лечащиеся)
'emergencyCount' => $emergencyCount, // экстренные (поступившие + уже лечащиеся)
];
}
/**
* Получить пациентов из снапшотов
*/
private function getPatientsFromSnapshots(User $user, string $status, DateRange $dateRange, int $branchId)
{
$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->getObservationPatientsFromSnapshots($user->rf_department_id, $reportIds);
}
return $this->snapshotService->getPatientsFromSnapshots($patientType, $reportIds, $branchId);
}
/**
* Получить пациентов из реплики БД
*/
private function getPatientsFromReplica(User $user, string $status, DateRange $dateRange, int $branchId)
{
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
// Для плановых и экстренных включаем уже лечащихся
$includeCurrent = in_array($status, ['plan', 'emergency']);
return match($status) {
'plan', 'emergency' => $this->patientQueryService->getPlanOrEmergencyPatients(
$status,
$isHeadOrAdmin,
$branchId,
$dateRange,
false,
false,
$includeCurrent
),
'observation' => $this->patientQueryService->getObservationPatients($user->rf_department_id),
'outcome-discharged' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'discharged'
),
'outcome-transferred' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'transferred'
),
'outcome-deceased' => $this->patientQueryService->getOutcomePatients(
$branchId,
$dateRange,
'deceased'
),
'current' => $this->patientQueryService->getAllPatientsInDepartment(
$isHeadOrAdmin,
$branchId,
$dateRange
),
'recipient' => $this->patientQueryService->getPlanOrEmergencyPatients(
null,
$isHeadOrAdmin,
$branchId,
$dateRange,
false,
false,
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', 'transferred', '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->isEndDateToday()) {
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): int
{
$count = 0;
$reports = Report::whereIn('report_id', $reportIds)
->with('metrikaResults')
->get();
foreach ($reports as $report) {
foreach ($report->metrikaResults as $metrikaResult) {
if ($metrikaResult->rf_metrika_item_id === $metrikaItemId) {
$count += intval($metrikaResult->value) ?? 0;
}
}
}
return $count;
}
/**
* Рассчитать текущих пациентов из снапшотов
*/
private function calculateCurrentPatientsFromSnapshots(array $reportIds, int $branchId): int
{
// Получаем ID всех пациентов из снапшотов
$allPatientIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
->pluck('rf_medicalhistory_id')
->unique()
->toArray();
if (empty($allPatientIds)) {
return 0;
}
// Получаем ID выбывших пациентов
$outcomePatientIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
->whereIn('patient_type', ['discharged', 'transferred', 'deceased'])
->pluck('rf_medicalhistory_id')
->unique()
->toArray();
// Текущие = все - выбывшие
$currentPatientIds = array_diff($allPatientIds, $outcomePatientIds);
return count($currentPatientIds);
}
/**
* Получить пациентов под наблюдением из снапшотов
*/
private function getObservationPatientsFromSnapshots(int $departmentId, array $reportIds)
{
$medicalHistoryIds = ObservationPatient::whereIn('rf_report_id', $reportIds)
->where('rf_department_id', $departmentId)
->pluck('rf_medicalhistory_id')
->unique()
->toArray();
if (empty($medicalHistoryIds)) {
return collect();
}
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;
});
}
}

View File

@@ -0,0 +1,198 @@
<?php
namespace App\Services;
use App\Models\MedicalHistorySnapshot;
use App\Models\MisMedicalHistory;
use App\Models\MisStationarBranch;
use App\Models\Report;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Collection;
class SnapshotService
{
public function __construct(
protected PatientService $patientService
) {}
/**
* Создать снапшоты пациентов для отчета
*/
public function createPatientSnapshots(Report $report, User $user, array $dates): void
{
$branchId = $this->getBranchId($user->department->rf_mis_department_id);
[$startDate, $endDate] = $this->parseDates($dates);
// Плановые пациенты
$this->createSnapshotsForType(
$report,
'plan',
$this->patientService->getPlanOrEmergencyPatients(
'plan',
$user->isHeadOfDepartment() || $user->isAdmin(),
$branchId,
$startDate,
$endDate,
false,
true
)
);
// Экстренные пациенты
$this->createSnapshotsForType(
$report,
'emergency',
$this->patientService->getPlanOrEmergencyPatients(
'emergency',
$user->isHeadOfDepartment() || $user->isAdmin(),
$branchId,
$startDate,
$endDate,
false,
true
)
);
// Выписанные
$this->createSnapshotsForType(
$report,
'discharged',
$this->patientService->getOutcomePatients(
$branchId,
$startDate,
$endDate,
'discharged'
)->pluck('MedicalHistoryID')->toArray()
);
// Переведенные
$this->createSnapshotsForType(
$report,
'transferred',
$this->patientService->getOutcomePatients(
$branchId,
$startDate,
$endDate,
'transferred'
)->pluck('MedicalHistoryID')->toArray()
);
// Умершие
$this->createSnapshotsForType(
$report,
'deceased',
$this->patientService->getOutcomePatients(
$branchId,
$startDate,
$endDate,
'deceased'
)->pluck('MedicalHistoryID')->toArray()
);
// Поступившие
$recipientIds = $this->patientService->getPlanOrEmergencyPatients(
null,
$user->isHeadOfDepartment() || $user->isAdmin(),
$branchId,
$startDate,
$endDate,
false,
true
);
$this->createSnapshotsForType($report, 'recipient', $recipientIds);
}
/**
* Получить статистику из снапшотов
*/
public function getStatisticsFromSnapshots(array $reportIds): array
{
return [
'plan' => $this->getCountFromSnapshots('plan', $reportIds),
'emergency' => $this->getCountFromSnapshots('emergency', $reportIds),
'outcome' => $this->getCountFromSnapshots('outcome', $reportIds),
'deceased' => $this->getCountFromSnapshots('deceased', $reportIds),
'discharged' => $this->getCountFromSnapshots('discharged', $reportIds),
'transferred' => $this->getCountFromSnapshots('transferred', $reportIds),
'recipient' => $this->getCountFromSnapshots('recipient', $reportIds),
];
}
/**
* Получить пациентов из снапшотов по типу
*/
public function getPatientsFromSnapshots(
string $type,
array $reportIds,
?int $branchId = null
): Collection {
$medicalHistoryIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
->where('patient_type', $type)
->pluck('rf_medicalhistory_id')
->unique()
->toArray();
if (empty($medicalHistoryIds)) {
return collect();
}
return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
->when($type === 'plan', fn($q) => $q->plan())
->when($type === 'emergency', fn($q) => $q->emergency())
->orderBy('DateRecipient', 'DESC')
->get();
}
/**
* Получить количество пациентов из снапшотов
*/
private function getCountFromSnapshots(string $type, array $reportIds): int
{
$query = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds);
if ($type === 'outcome') {
$query->whereIn('patient_type', ['discharged', 'deceased']);
} else {
$query->where('patient_type', $type);
}
return $query->distinct('rf_medicalhistory_id')
->count('rf_medicalhistory_id');
}
/**
* Создать снапшоты для определенного типа пациентов
*/
private function createSnapshotsForType(Report $report, string $type, array $medicalHistoryIds): void
{
foreach ($medicalHistoryIds as $id) {
MedicalHistorySnapshot::create([
'rf_report_id' => $report->report_id,
'rf_medicalhistory_id' => $id,
'patient_type' => $type,
]);
}
}
/**
* Получить ID отделения
*/
private function getBranchId(int $misDepartmentId): ?int
{
return MisStationarBranch::where('rf_DepartmentID', $misDepartmentId)
->value('StationarBranchID');
}
/**
* Разобрать даты
*/
private function parseDates(array $dates): array
{
return [
Carbon::createFromTimestampMs($dates[0])->setTimezone('Asia/Yakutsk'),
Carbon::createFromTimestampMs($dates[1])->setTimezone('Asia/Yakutsk'),
];
}
}

View File

@@ -27,7 +27,7 @@ const dateType = computed(() => {
})
const queryDate = ref([null, null])
const modelValue = ref(props.date)
const modelValue = defineModel('date')
const setQueryDate = () => {
router.reload({
@@ -75,14 +75,21 @@ const formattedValue = computed(() => {
}
})
watch(() => modelValue.value, (newVal) => {
if (isUseDateRange.value) {
queryDate.value = newVal
} else {
queryDate.value = [newVal, newVal]
}
watch(() => modelValue.value, (newVal, oldVal) => {
if (!newVal) return
setQueryDate()
if (Array.isArray(newVal)) {
if (newVal.length === 2 &&
(!oldVal || newVal[0] !== oldVal[0] || newVal[1] !== oldVal[1])) {
queryDate.value = newVal
setQueryDate()
}
} else {
if (newVal !== oldVal) {
queryDate.value = newVal
setQueryDate()
}
}
})
</script>

View File

@@ -2,6 +2,7 @@
import { ref, reactive } from 'vue'
import {Head, router, useForm} from '@inertiajs/vue3'
import { useAuthStore } from '../../Stores/auth.js'
import { TbUser, TbLock } from 'vue-icons-plus/tb'
import {
NForm, NFormItem, NInput, NButton, NCheckbox,
NSpace, NCard, NIcon, NAlert, NModal, darkTheme,
@@ -114,7 +115,7 @@ const handleForgotPassword = async () => {
@keydown.enter="handleLogin"
>
<template #prefix>
<n-icon><Mail /></n-icon>
<n-icon><TbUser /></n-icon>
</template>
</n-input>
</n-form-item>
@@ -130,7 +131,7 @@ const handleForgotPassword = async () => {
@keydown.enter="handleLogin"
>
<template #prefix>
<n-icon><LockClosed /></n-icon>
<n-icon><TbLock /></n-icon>
</template>
</n-input>
</n-form-item>

View File

@@ -12,6 +12,7 @@ import {useReportStore} from "../../../Stores/report.js";
import ReportSelectDate from "../../../Components/ReportSelectDate.vue";
import DepartmentSelect from "../../../Components/DepartmentSelect.vue";
import UnwantedEventModal from "./UnwantedEventModal.vue";
import DatePickerQuery from "../../../Components/DatePickerQuery.vue";
const props = defineProps({
mode: {
@@ -66,7 +67,7 @@ const currentDate = computed(() => {
</NSpace>
<div class="col-3 w-full">
<ReportSelectDate :is-one-day="reportStore.reportInfo.report?.isOneDay"/>
<DatePickerQuery :is-head-or-admin="reportStore.reportInfo.report?.isHeadOrAdmin" v-model:date="reportStore.timestampCurrentRange" :is-one-day="reportStore.reportInfo.report?.isOneDay" />
</div>
</div>

View File

@@ -44,9 +44,14 @@ onMounted(async () => {
await fetchPatientCount()
})
watch(() => reportStore.timestampCurrentRange, (newRange) => {
if (newRange) fetchPatientCount()
})
watch(() => reportStore.timestampCurrentRange, (newRange, oldRange) => {
// Проверяем, что диапазон изменился и валиден
if (newRange &&
newRange.length === 2 &&
(!oldRange || newRange[0] !== oldRange[0] || newRange[1] !== oldRange[1])) {
fetchPatientCount()
}
}, { deep: true })
</script>
<template>

View File

@@ -145,7 +145,7 @@ const columns = computed(() => {
const operationColumn = {
title: 'Операции',
key: 'operations',
render: (row) => row.operations.length ?
render: (row) => row.operations?.length ?
h(
NText,
{},

View File

@@ -2,9 +2,28 @@
import AppLayout from "../../Layouts/AppLayout.vue";
import ReportForm from "./Components/ReportForm.vue";
import {useReportStore} from "../../Stores/report.js";
import {computed, onMounted} from "vue";
import {computed, onMounted, watch} from "vue";
import {useAuthStore} from "../../Stores/auth.js";
const props = defineProps({
department: {
type: Object,
default: {}
},
report: {
type: Object,
default: {}
},
metrikaItems: {
type: Array,
default: []
},
dates: {
type: Object,
default: {}
}
})
const reportStore = useReportStore()
const authStore = useAuthStore()
@@ -15,7 +34,24 @@ onMounted(() => {
reportStore.reportInfo.userId = userId
reportStore.getReportInfo()
reportStore.reportInfo = props
reportStore.reportForm.metrika_item_3 = props.department.recipientCount
reportStore.reportForm.metrika_item_7 = props.department.extractCount
reportStore.reportForm.metrika_item_8 = props.department.currentCount
reportStore.reportForm.metrika_item_9 = props.department.deadCount
reportStore.reportForm.metrika_item_10 = props.department.surgicalCount[1]
reportStore.reportForm.metrika_item_11 = props.department.surgicalCount[0]
reportStore.unwantedEvents = props.report.unwantedEvents
reportStore.timestampCurrentRange = [
props.dates.startAt,
props.dates.endAt,
]
// reportStore.getReportInfo()
})
// reportStore.getReportInfo()
@@ -26,6 +62,28 @@ const mode = computed(() => {
return 'fillable'
})
watch(() => props, (newProps) => {
reportStore.reportInfo = newProps
reportStore.reportForm.metrika_item_3 = newProps.department.recipientCount
reportStore.reportForm.metrika_item_7 = newProps.department.extractCount
reportStore.reportForm.metrika_item_8 = newProps.department.currentCount
reportStore.reportForm.metrika_item_9 = newProps.department.deadCount
reportStore.reportForm.metrika_item_10 = newProps.department.surgicalCount[1]
reportStore.reportForm.metrika_item_11 = newProps.department.surgicalCount[0]
reportStore.unwantedEvents = newProps.report.unwantedEvents
reportStore.timestampCurrentRange = [
newProps.dates.startAt,
newProps.dates.endAt,
]
}, {
deep: true, // важно для глубокого отслеживания
immediate: true // выполнить сразу при создании
})
</script>
<template>