diff --git a/app/Http/Controllers/Api/ReportController.php b/app/Http/Controllers/Api/ReportController.php index c720cc3..880ab69 100644 --- a/app/Http/Controllers/Api/ReportController.php +++ b/app/Http/Controllers/Api/ReportController.php @@ -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); } diff --git a/app/Http/Controllers/Web/ReportController.php b/app/Http/Controllers/Web/ReportController.php index 437b71d..5fda37a 100644 --- a/app/Http/Controllers/Web/ReportController.php +++ b/app/Http/Controllers/Web/ReportController.php @@ -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; } } diff --git a/app/Http/Resources/Mis/FormattedPatientResource.php b/app/Http/Resources/Mis/FormattedPatientResource.php index 303323e..731675a 100644 --- a/app/Http/Resources/Mis/FormattedPatientResource.php +++ b/app/Http/Resources/Mis/FormattedPatientResource.php @@ -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'), diff --git a/app/Models/MisMedicalHistory.php b/app/Models/MisMedicalHistory.php index 7aa2c6e..cc12b33 100644 --- a/app/Models/MisMedicalHistory.php +++ b/app/Models/MisMedicalHistory.php @@ -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) diff --git a/app/Models/MisMigrationPatient.php b/app/Models/MisMigrationPatient.php index 6d72b8b..481717f 100644 --- a/app/Models/MisMigrationPatient.php +++ b/app/Models/MisMigrationPatient.php @@ -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) { diff --git a/app/Services/DateRange.php b/app/Services/DateRange.php new file mode 100644 index 0000000..d7c0ba7 --- /dev/null +++ b/app/Services/DateRange.php @@ -0,0 +1,89 @@ +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; + } +} diff --git a/app/Services/DateRangeService.php b/app/Services/DateRangeService.php index 6e0b644..a85dcdf 100644 --- a/app/Services/DateRangeService.php +++ b/app/Services/DateRangeService.php @@ -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'); + } } diff --git a/app/Services/PatientService.php b/app/Services/PatientService.php index ca98593..f84a91b 100644 --- a/app/Services/PatientService.php +++ b/app/Services/PatientService.php @@ -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 . ')' + }; } } diff --git a/app/Services/ReportService.php b/app/Services/ReportService.php index 915e23a..d08d7ef 100644 --- a/app/Services/ReportService.php +++ b/app/Services/ReportService.php @@ -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; + }); + } } diff --git a/app/Services/SnapshotService.php b/app/Services/SnapshotService.php new file mode 100644 index 0000000..36db8c5 --- /dev/null +++ b/app/Services/SnapshotService.php @@ -0,0 +1,198 @@ +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'), + ]; + } +} diff --git a/resources/js/Components/DatePickerQuery.vue b/resources/js/Components/DatePickerQuery.vue index c43760f..7d83815 100644 --- a/resources/js/Components/DatePickerQuery.vue +++ b/resources/js/Components/DatePickerQuery.vue @@ -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() + } + } }) diff --git a/resources/js/Pages/Auth/Login.vue b/resources/js/Pages/Auth/Login.vue index a653c19..7c8944e 100644 --- a/resources/js/Pages/Auth/Login.vue +++ b/resources/js/Pages/Auth/Login.vue @@ -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" > @@ -130,7 +131,7 @@ const handleForgotPassword = async () => { @keydown.enter="handleLogin" > diff --git a/resources/js/Pages/Report/Components/ReportHeader.vue b/resources/js/Pages/Report/Components/ReportHeader.vue index d9c159d..0f56dae 100644 --- a/resources/js/Pages/Report/Components/ReportHeader.vue +++ b/resources/js/Pages/Report/Components/ReportHeader.vue @@ -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(() => {
- +
diff --git a/resources/js/Pages/Report/Components/ReportSectionHeader.vue b/resources/js/Pages/Report/Components/ReportSectionHeader.vue index ba6d582..21714ac 100644 --- a/resources/js/Pages/Report/Components/ReportSectionHeader.vue +++ b/resources/js/Pages/Report/Components/ReportSectionHeader.vue @@ -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 })