* переписал функции прототипов в сервисы
* оптимизация доставки контента до клиента * переписал запросы выборок * убрал из подсчета переведенных * добавил сохранение метрикам для вывода в дашборд
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
89
app/Services/DateRange.php
Normal file
89
app/Services/DateRange.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
readonly class DateRange
|
||||
{
|
||||
public function __construct(
|
||||
public Carbon $startDate,
|
||||
public Carbon $endDate,
|
||||
public string $startDateRaw,
|
||||
public string $endDateRaw,
|
||||
public bool $isOneDay
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Получить начало диапазона в Carbon
|
||||
*/
|
||||
public function start(): Carbon
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить конец диапазона в Carbon
|
||||
*/
|
||||
public function end(): Carbon
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить начало диапазона в SQL формате
|
||||
*/
|
||||
public function startSql(): string
|
||||
{
|
||||
return $this->startDate->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить конец диапазона в SQL формате
|
||||
*/
|
||||
public function endSql(): string
|
||||
{
|
||||
return $this->endDate->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить начало диапазона как timestamp
|
||||
*/
|
||||
public function startTimestamp(): int
|
||||
{
|
||||
return $this->startDate->getTimestampMs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить конец диапазона как timestamp
|
||||
*/
|
||||
public function endTimestamp(): int
|
||||
{
|
||||
return $this->endDate->getTimestampMs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, является ли дата сегодняшней
|
||||
*/
|
||||
public function isEndDateToday(): bool
|
||||
{
|
||||
return $this->endDate->isToday();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить диапазон дней
|
||||
*/
|
||||
public function getDaysRange(): Collection
|
||||
{
|
||||
$days = collect();
|
||||
$current = $this->startDate->copy();
|
||||
|
||||
while ($current->lte($this->endDate)) {
|
||||
$days->push($current->copy());
|
||||
$current->addDay();
|
||||
}
|
||||
|
||||
return $days;
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 . ')'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
198
app/Services/SnapshotService.php
Normal file
198
app/Services/SnapshotService.php
Normal file
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\MedicalHistorySnapshot;
|
||||
use App\Models\MisMedicalHistory;
|
||||
use App\Models\MisStationarBranch;
|
||||
use App\Models\Report;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class SnapshotService
|
||||
{
|
||||
public function __construct(
|
||||
protected PatientService $patientService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Создать снапшоты пациентов для отчета
|
||||
*/
|
||||
public function createPatientSnapshots(Report $report, User $user, array $dates): void
|
||||
{
|
||||
$branchId = $this->getBranchId($user->department->rf_mis_department_id);
|
||||
[$startDate, $endDate] = $this->parseDates($dates);
|
||||
|
||||
// Плановые пациенты
|
||||
$this->createSnapshotsForType(
|
||||
$report,
|
||||
'plan',
|
||||
$this->patientService->getPlanOrEmergencyPatients(
|
||||
'plan',
|
||||
$user->isHeadOfDepartment() || $user->isAdmin(),
|
||||
$branchId,
|
||||
$startDate,
|
||||
$endDate,
|
||||
false,
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
// Экстренные пациенты
|
||||
$this->createSnapshotsForType(
|
||||
$report,
|
||||
'emergency',
|
||||
$this->patientService->getPlanOrEmergencyPatients(
|
||||
'emergency',
|
||||
$user->isHeadOfDepartment() || $user->isAdmin(),
|
||||
$branchId,
|
||||
$startDate,
|
||||
$endDate,
|
||||
false,
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
// Выписанные
|
||||
$this->createSnapshotsForType(
|
||||
$report,
|
||||
'discharged',
|
||||
$this->patientService->getOutcomePatients(
|
||||
$branchId,
|
||||
$startDate,
|
||||
$endDate,
|
||||
'discharged'
|
||||
)->pluck('MedicalHistoryID')->toArray()
|
||||
);
|
||||
|
||||
// Переведенные
|
||||
$this->createSnapshotsForType(
|
||||
$report,
|
||||
'transferred',
|
||||
$this->patientService->getOutcomePatients(
|
||||
$branchId,
|
||||
$startDate,
|
||||
$endDate,
|
||||
'transferred'
|
||||
)->pluck('MedicalHistoryID')->toArray()
|
||||
);
|
||||
|
||||
// Умершие
|
||||
$this->createSnapshotsForType(
|
||||
$report,
|
||||
'deceased',
|
||||
$this->patientService->getOutcomePatients(
|
||||
$branchId,
|
||||
$startDate,
|
||||
$endDate,
|
||||
'deceased'
|
||||
)->pluck('MedicalHistoryID')->toArray()
|
||||
);
|
||||
|
||||
// Поступившие
|
||||
$recipientIds = $this->patientService->getPlanOrEmergencyPatients(
|
||||
null,
|
||||
$user->isHeadOfDepartment() || $user->isAdmin(),
|
||||
$branchId,
|
||||
$startDate,
|
||||
$endDate,
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
$this->createSnapshotsForType($report, 'recipient', $recipientIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить статистику из снапшотов
|
||||
*/
|
||||
public function getStatisticsFromSnapshots(array $reportIds): array
|
||||
{
|
||||
return [
|
||||
'plan' => $this->getCountFromSnapshots('plan', $reportIds),
|
||||
'emergency' => $this->getCountFromSnapshots('emergency', $reportIds),
|
||||
'outcome' => $this->getCountFromSnapshots('outcome', $reportIds),
|
||||
'deceased' => $this->getCountFromSnapshots('deceased', $reportIds),
|
||||
'discharged' => $this->getCountFromSnapshots('discharged', $reportIds),
|
||||
'transferred' => $this->getCountFromSnapshots('transferred', $reportIds),
|
||||
'recipient' => $this->getCountFromSnapshots('recipient', $reportIds),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить пациентов из снапшотов по типу
|
||||
*/
|
||||
public function getPatientsFromSnapshots(
|
||||
string $type,
|
||||
array $reportIds,
|
||||
?int $branchId = null
|
||||
): Collection {
|
||||
$medicalHistoryIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
|
||||
->where('patient_type', $type)
|
||||
->pluck('rf_medicalhistory_id')
|
||||
->unique()
|
||||
->toArray();
|
||||
|
||||
if (empty($medicalHistoryIds)) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
|
||||
->when($type === 'plan', fn($q) => $q->plan())
|
||||
->when($type === 'emergency', fn($q) => $q->emergency())
|
||||
->orderBy('DateRecipient', 'DESC')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить количество пациентов из снапшотов
|
||||
*/
|
||||
private function getCountFromSnapshots(string $type, array $reportIds): int
|
||||
{
|
||||
$query = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds);
|
||||
|
||||
if ($type === 'outcome') {
|
||||
$query->whereIn('patient_type', ['discharged', 'deceased']);
|
||||
} else {
|
||||
$query->where('patient_type', $type);
|
||||
}
|
||||
|
||||
return $query->distinct('rf_medicalhistory_id')
|
||||
->count('rf_medicalhistory_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать снапшоты для определенного типа пациентов
|
||||
*/
|
||||
private function createSnapshotsForType(Report $report, string $type, array $medicalHistoryIds): void
|
||||
{
|
||||
foreach ($medicalHistoryIds as $id) {
|
||||
MedicalHistorySnapshot::create([
|
||||
'rf_report_id' => $report->report_id,
|
||||
'rf_medicalhistory_id' => $id,
|
||||
'patient_type' => $type,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить ID отделения
|
||||
*/
|
||||
private function getBranchId(int $misDepartmentId): ?int
|
||||
{
|
||||
return MisStationarBranch::where('rf_DepartmentID', $misDepartmentId)
|
||||
->value('StationarBranchID');
|
||||
}
|
||||
|
||||
/**
|
||||
* Разобрать даты
|
||||
*/
|
||||
private function parseDates(array $dates): array
|
||||
{
|
||||
return [
|
||||
Carbon::createFromTimestampMs($dates[0])->setTimezone('Asia/Yakutsk'),
|
||||
Carbon::createFromTimestampMs($dates[1])->setTimezone('Asia/Yakutsk'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ const dateType = computed(() => {
|
||||
})
|
||||
|
||||
const queryDate = ref([null, null])
|
||||
const modelValue = ref(props.date)
|
||||
const modelValue = defineModel('date')
|
||||
|
||||
const setQueryDate = () => {
|
||||
router.reload({
|
||||
@@ -75,14 +75,21 @@ const formattedValue = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => modelValue.value, (newVal) => {
|
||||
if (isUseDateRange.value) {
|
||||
queryDate.value = newVal
|
||||
} else {
|
||||
queryDate.value = [newVal, newVal]
|
||||
}
|
||||
watch(() => modelValue.value, (newVal, oldVal) => {
|
||||
if (!newVal) return
|
||||
|
||||
setQueryDate()
|
||||
if (Array.isArray(newVal)) {
|
||||
if (newVal.length === 2 &&
|
||||
(!oldVal || newVal[0] !== oldVal[0] || newVal[1] !== oldVal[1])) {
|
||||
queryDate.value = newVal
|
||||
setQueryDate()
|
||||
}
|
||||
} else {
|
||||
if (newVal !== oldVal) {
|
||||
queryDate.value = newVal
|
||||
setQueryDate()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { ref, reactive } from 'vue'
|
||||
import {Head, router, useForm} from '@inertiajs/vue3'
|
||||
import { useAuthStore } from '../../Stores/auth.js'
|
||||
import { TbUser, TbLock } from 'vue-icons-plus/tb'
|
||||
import {
|
||||
NForm, NFormItem, NInput, NButton, NCheckbox,
|
||||
NSpace, NCard, NIcon, NAlert, NModal, darkTheme,
|
||||
@@ -114,7 +115,7 @@ const handleForgotPassword = async () => {
|
||||
@keydown.enter="handleLogin"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon><Mail /></n-icon>
|
||||
<n-icon><TbUser /></n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
@@ -130,7 +131,7 @@ const handleForgotPassword = async () => {
|
||||
@keydown.enter="handleLogin"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon><LockClosed /></n-icon>
|
||||
<n-icon><TbLock /></n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {useReportStore} from "../../../Stores/report.js";
|
||||
import ReportSelectDate from "../../../Components/ReportSelectDate.vue";
|
||||
import DepartmentSelect from "../../../Components/DepartmentSelect.vue";
|
||||
import UnwantedEventModal from "./UnwantedEventModal.vue";
|
||||
import DatePickerQuery from "../../../Components/DatePickerQuery.vue";
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
@@ -66,7 +67,7 @@ const currentDate = computed(() => {
|
||||
</NSpace>
|
||||
|
||||
<div class="col-3 w-full">
|
||||
<ReportSelectDate :is-one-day="reportStore.reportInfo.report?.isOneDay"/>
|
||||
<DatePickerQuery :is-head-or-admin="reportStore.reportInfo.report?.isHeadOrAdmin" v-model:date="reportStore.timestampCurrentRange" :is-one-day="reportStore.reportInfo.report?.isOneDay" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -44,9 +44,14 @@ onMounted(async () => {
|
||||
await fetchPatientCount()
|
||||
})
|
||||
|
||||
watch(() => reportStore.timestampCurrentRange, (newRange) => {
|
||||
if (newRange) fetchPatientCount()
|
||||
})
|
||||
watch(() => reportStore.timestampCurrentRange, (newRange, oldRange) => {
|
||||
// Проверяем, что диапазон изменился и валиден
|
||||
if (newRange &&
|
||||
newRange.length === 2 &&
|
||||
(!oldRange || newRange[0] !== oldRange[0] || newRange[1] !== oldRange[1])) {
|
||||
fetchPatientCount()
|
||||
}
|
||||
}, { deep: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -145,7 +145,7 @@ const columns = computed(() => {
|
||||
const operationColumn = {
|
||||
title: 'Операции',
|
||||
key: 'operations',
|
||||
render: (row) => row.operations.length ?
|
||||
render: (row) => row.operations?.length ?
|
||||
h(
|
||||
NText,
|
||||
{},
|
||||
|
||||
@@ -2,9 +2,28 @@
|
||||
import AppLayout from "../../Layouts/AppLayout.vue";
|
||||
import ReportForm from "./Components/ReportForm.vue";
|
||||
import {useReportStore} from "../../Stores/report.js";
|
||||
import {computed, onMounted} from "vue";
|
||||
import {computed, onMounted, watch} from "vue";
|
||||
import {useAuthStore} from "../../Stores/auth.js";
|
||||
|
||||
const props = defineProps({
|
||||
department: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
report: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
metrikaItems: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
dates: {
|
||||
type: Object,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
|
||||
const reportStore = useReportStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
@@ -15,7 +34,24 @@ onMounted(() => {
|
||||
|
||||
reportStore.reportInfo.userId = userId
|
||||
|
||||
reportStore.getReportInfo()
|
||||
reportStore.reportInfo = props
|
||||
|
||||
reportStore.reportForm.metrika_item_3 = props.department.recipientCount
|
||||
reportStore.reportForm.metrika_item_7 = props.department.extractCount
|
||||
reportStore.reportForm.metrika_item_8 = props.department.currentCount
|
||||
|
||||
reportStore.reportForm.metrika_item_9 = props.department.deadCount
|
||||
reportStore.reportForm.metrika_item_10 = props.department.surgicalCount[1]
|
||||
reportStore.reportForm.metrika_item_11 = props.department.surgicalCount[0]
|
||||
|
||||
reportStore.unwantedEvents = props.report.unwantedEvents
|
||||
|
||||
reportStore.timestampCurrentRange = [
|
||||
props.dates.startAt,
|
||||
props.dates.endAt,
|
||||
]
|
||||
|
||||
// reportStore.getReportInfo()
|
||||
})
|
||||
|
||||
// reportStore.getReportInfo()
|
||||
@@ -26,6 +62,28 @@ const mode = computed(() => {
|
||||
return 'fillable'
|
||||
})
|
||||
|
||||
watch(() => props, (newProps) => {
|
||||
|
||||
reportStore.reportInfo = newProps
|
||||
|
||||
reportStore.reportForm.metrika_item_3 = newProps.department.recipientCount
|
||||
reportStore.reportForm.metrika_item_7 = newProps.department.extractCount
|
||||
reportStore.reportForm.metrika_item_8 = newProps.department.currentCount
|
||||
|
||||
reportStore.reportForm.metrika_item_9 = newProps.department.deadCount
|
||||
reportStore.reportForm.metrika_item_10 = newProps.department.surgicalCount[1]
|
||||
reportStore.reportForm.metrika_item_11 = newProps.department.surgicalCount[0]
|
||||
|
||||
reportStore.unwantedEvents = newProps.report.unwantedEvents
|
||||
|
||||
reportStore.timestampCurrentRange = [
|
||||
newProps.dates.startAt,
|
||||
newProps.dates.endAt,
|
||||
]
|
||||
}, {
|
||||
deep: true, // важно для глубокого отслеживания
|
||||
immediate: true // выполнить сразу при создании
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
Reference in New Issue
Block a user