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"
>
-