Files
onboard/app/Http/Controllers/Api/ReportController.php
2026-05-06 22:32:11 +09:00

584 lines
20 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Domain\Reports\ValueObjects\MetrikaConfig;
use App\Http\Controllers\Controller;
use App\Http\Resources\Api\DepartmentPatientOperationResource;
use App\Http\Resources\Mis\FormattedPatientResource;
use App\Models\Department;
use App\Models\MetrikaGroup;
use App\Models\MisLpuDoctor;
use App\Models\MisMedicalHistory;
use App\Models\MisMigrationPatient;
use App\Models\MisMKB;
use App\Models\MisSurgicalOperation;
use App\Models\ObservationPatient;
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\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
class ReportController extends Controller
{
public function __construct(
protected MisPatientService $misPatientService,
protected ReportService $reportService,
protected DateRangeService $dateRangeService,
) {}
public function index(Request $request)
{
$user = Auth::user();
$departmentId = $request->query('departmentId', $user->department->department_id);
$department = Department::where('department_id', $departmentId)->firstOrFail();
$dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user);
$statistics = $this->reportService->getReportStatistics($department, $user, $dateRange);
$reportInfo = $this->reportService->getCurrentReportInfo($department, $user, $dateRange);
$metrikaGroup = MetrikaGroup::whereMetrikaGroupId(2)->first();
$metrikaItems = $metrikaGroup->metrikaItems;
return response()->json([
'department' => [
'department_id' => $department->department_id,
'department_name' => $department->name_full,
'beds' => $department->beds,
...$statistics,
],
'dates' => [
'startAt' => $dateRange->startTimestamp(),
'endAt' => $dateRange->endTimestamp(),
],
'report' => $reportInfo,
'metrikaItems' => $metrikaItems,
'userId' => $reportInfo['userId'],
'userName' => $reportInfo['userName'],
]);
}
public function store(Request $request)
{
$user = Auth::user();
$data = $request->validate([
'metrics' => 'required|array',
'observationPatients' => 'nullable|array',
'departmentId' => 'required|integer',
'unwantedEvents' => 'nullable|array',
'startAt' => 'required|integer',
'endAt' => 'required|integer',
'userId' => 'required|integer',
'reportId' => 'nullable|integer',
'status' => 'nullable|in:draft,submitted',
]);
$report = $this->reportService->storeReport([
...$data,
'dates' => [(int) $data['startAt'], (int) $data['endAt']],
'status' => $data['status'] ?? 'draft',
], $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',
'departmentId' => 'nullable',
'page' => 'nullable|integer|min:1',
'perPage' => 'nullable|integer|min:1|max:1000',
'search' => 'nullable|string|max:255',
]);
$dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user);
$departmentId = $request->get('departmentId', $user->department->department_id);
$department = Department::where('department_id', $departmentId)->first();
$page = (int) ($validated['page'] ?? 1);
$perPage = (int) ($validated['perPage'] ?? 20);
$search = trim((string) ($validated['search'] ?? ''));
$patients = collect($this->reportService->getPatientsByStatus(
$department,
Auth::user(),
$validated['status'],
$dateRange
));
if ($this->resolveBaseStatus($validated['status']) === 'reanimation') {
$this->attachReanimationIndicators($patients, (int) $department->department_id);
}
if ($search !== '') {
$needle = mb_strtolower($search);
$patients = $patients->filter(function ($patient) use ($needle) {
$fullName = mb_strtolower(trim((string) ($patient->fullname ?? "{$patient->FAMILY} {$patient->Name} {$patient->OT}")));
$diagnosisCode = mb_strtolower((string) ($patient->mkb['ds'] ?? ''));
$diagnosisName = mb_strtolower((string) ($patient->mkb['name'] ?? ''));
return str_contains($fullName, $needle)
|| str_contains($diagnosisCode, $needle)
|| str_contains($diagnosisName, $needle);
})->values();
}
$total = $patients->count();
$items = $patients->forPage($page, $perPage)->values();
$data = FormattedPatientResource::collection($items)->resolve();
return response()->json([
'data' => $data,
'meta' => [
'total' => $total,
'page' => $page,
'perPage' => $perPage,
'lastPage' => max((int) ceil($total / max($perPage, 1)), 1),
],
]);
}
public function saveReanimationIndicator(Request $request)
{
$user = Auth::user();
$data = $request->validate([
'departmentId' => 'nullable|integer',
'medical_history_id' => 'required|integer',
'indicator' => 'required|string|max:100',
'comment' => 'nullable|string|max:1000',
'startAt' => 'nullable',
'endAt' => 'nullable',
]);
$departmentId = (int) ($data['departmentId'] ?? $user->department->department_id);
$department = Department::where('department_id', $departmentId)->firstOrFail();
$dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user);
$reportId = $this->getReportsForDateRange(
$department->department_id,
$dateRange->startSql(),
$dateRange->endSql()
)->pluck('report_id')->last();
$indicator = $this->reportService->saveReanimationIndicator(
$user,
$department->department_id,
(int) $data['medical_history_id'],
trim($data['indicator']),
isset($data['comment']) ? trim((string) $data['comment']) : null,
$reportId ? (int) $reportId : null
);
return response()->json($indicator, 201);
}
public function getReanimationIndicatorHistory(Request $request)
{
$user = Auth::user();
$data = $request->validate([
'departmentId' => 'nullable|integer',
'medical_history_id' => 'required|integer',
'limit' => 'nullable|integer|min:1|max:200',
]);
$departmentId = (int) ($data['departmentId'] ?? $user->department->department_id);
$history = $this->reportService->getReanimationIndicatorsHistory(
$departmentId,
(int) $data['medical_history_id'],
(int) ($data['limit'] ?? 50)
);
return response()->json($history);
}
public function getPatientsCount(Request $request)
{
$user = Auth::user();
$validated = $request->validate([
'status' => 'required|string',
'startAt' => 'nullable',
'endAt' => 'nullable',
'departmentId' => 'nullable',
]);
$dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user);
$departmentId = $request->get('departmentId', $user->department->department_id);
$department = Department::where('department_id', $departmentId)->first();
$count = $this->reportService->getPatientsCountByStatus(
$department,
$user,
$validated['status'],
$dateRange,
);
return response()->json($count);
}
public function getPatientsCounts(Request $request)
{
$user = Auth::user();
$request->validate([
'startAt' => 'nullable',
'endAt' => 'nullable',
'departmentId' => 'nullable',
'force' => 'nullable|boolean',
]);
$dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user);
$departmentId = $request->get('departmentId', $user->department->department_id);
$department = Department::where('department_id', $departmentId)->firstOrFail();
$cacheKey = sprintf(
'report:patients-counts:%d:%d:%s:%s',
$user->id,
$department->department_id,
$dateRange->startSql(),
$dateRange->endSql()
);
$force = (bool) $request->boolean('force', false);
if ($force) {
$counts = $this->reportService->getPatientsCountsMap($department, $user, $dateRange);
Cache::put($cacheKey, $counts, now()->addSeconds(30));
} else {
$counts = Cache::remember($cacheKey, now()->addSeconds(30), function () use ($department, $user, $dateRange) {
return $this->reportService->getPatientsCountsMap($department, $user, $dateRange);
});
}
return response()->json([
'counts' => $counts,
]);
}
public function removeObservation(
Request $request,
) {
$data = $request->validate([
'id' => 'required|string',
]);
$this->reportService->removeObservationPatient($data['id']);
return response()->json()->setStatusCode(200);
}
public function createManualPatient(Request $request)
{
$user = Auth::user();
$data = $request->validate([
'departmentId' => 'required|integer',
'report_id' => 'nullable|integer',
'startAt' => 'required_without:report_id',
'endAt' => 'required_without:report_id',
'user_id' => 'nullable|integer',
'full_name' => 'required|string|max:255',
'birth_date' => 'required|date',
'patient_kind' => 'required|in:plan,emergency',
'diagnosis_code' => 'nullable|string|max:255',
'diagnosis_name' => 'nullable|string|max:1000',
'admitted_at' => 'nullable|date',
]);
$department = Department::where('department_id', $data['departmentId'])->firstOrFail();
$patient = $this->reportService->createManualPatient($department, $user, $data);
return response()->json([
'patient' => $patient,
'report_id' => $patient->rf_report_id,
], 201);
}
public function setManualPatientOutcome(Request $request, int $departmentPatientId)
{
$user = Auth::user();
$data = $request->validate([
'outcome_type' => 'required|in:discharged,transferred,deceased',
'outcome_at' => 'nullable|date',
]);
return response()->json(
$this->reportService->setManualPatientOutcome($user, $departmentPatientId, $data)
);
}
public function updateManualPatient(Request $request, int $departmentPatientId)
{
$user = Auth::user();
$data = $request->validate([
'full_name' => 'required|string|max:255',
'birth_date' => 'required|date',
'patient_kind' => 'required|in:plan,emergency',
'manual_status' => 'nullable|in:current,discharged,deceased',
'outcome_at' => 'nullable|date',
'diagnosis_code' => 'nullable|string|max:255',
'diagnosis_name' => 'nullable|string|max:1000',
'admitted_at' => 'nullable|date',
'startAt' => 'nullable',
'endAt' => 'nullable',
]);
return response()->json(
$this->reportService->updateManualPatient(
$user,
$departmentPatientId,
$data
)
);
}
public function linkManualPatient(Request $request, int $departmentPatientId)
{
$data = $request->validate([
'medical_history_id' => 'required|integer',
]);
return response()->json(
$this->reportService->linkManualPatientToMis($departmentPatientId, $data['medical_history_id'])
);
}
public function getManualPatientOperations(int $departmentPatientId)
{
$user = Auth::user();
$operations = $this->reportService->getManualPatientOperations($user, $departmentPatientId);
return response()->json(
DepartmentPatientOperationResource::collection($operations)
);
}
public function createManualPatientOperation(Request $request, int $departmentPatientId)
{
$user = Auth::user();
$data = $request->validate([
'service_id' => 'required|integer',
'urgency' => 'required|in:plan,emergency',
'started_at' => 'required|date',
'ended_at' => 'required|date|after_or_equal:started_at',
]);
$operation = $this->reportService->createManualPatientOperation($user, $departmentPatientId, $data);
return response()->json(new DepartmentPatientOperationResource($operation), 201);
}
public function updateManualPatientOperation(Request $request, int $departmentPatientId, int $operationId)
{
$user = Auth::user();
$data = $request->validate([
'service_id' => 'required|integer',
'urgency' => 'required|in:plan,emergency',
'started_at' => 'required|date',
'ended_at' => 'required|date|after_or_equal:started_at',
]);
$operation = $this->reportService->updateManualPatientOperation($user, $departmentPatientId, $operationId, $data);
return response()->json(new DepartmentPatientOperationResource($operation));
}
public function deleteManualPatientOperation(int $departmentPatientId, int $operationId)
{
$user = Auth::user();
$this->reportService->deleteManualPatientOperation($user, $departmentPatientId, $operationId);
return response()->json()->setStatusCode(204);
}
public function searchMisPatients(Request $request)
{
$data = $request->validate([
'departmentId' => 'required|integer',
'query' => 'required|string|min:2',
]);
$department = Department::where('department_id', $data['departmentId'])->firstOrFail();
$patients = $this->reportService->searchMisPatientsForDepartment($department, $data['query']);
return response()->json(FormattedPatientResource::collection($patients));
}
public function searchMkb(Request $request)
{
$data = $request->validate([
'query' => 'required|string|min:1|max:255',
]);
$query = trim($data['query']);
$needle = mb_strtolower($query, 'UTF-8');
$like = "%{$needle}%";
$items = MisMKB::query()
->select(['MKBID', 'DS', 'NAME'])
->where(function ($builder) use ($like) {
$builder->whereRaw('LOWER("DS") LIKE ?', [$like])
->orWhereRaw('LOWER("NAME") LIKE ?', [$like]);
})
->orderBy('DS')
->limit(30)
->get()
->map(fn (MisMKB $item) => [
'id' => $item->MKBID,
'code' => $item->DS,
'name' => $item->NAME,
'label' => trim(($item->DS ? "{$item->DS} " : '').($item->NAME ?? '')),
])
->values();
return response()->json($items);
}
public function searchMedicalServices(Request $request)
{
$data = $request->validate([
'query' => 'required|string|min:2|max:255',
]);
$query = trim($data['query']);
$items = \App\Models\MisServiceMedical::query()
->select(['ServiceMedicalID', 'ServiceMedicalCode', 'ServiceMedicalName'])
->where(function ($builder) use ($query) {
$builder->where('ServiceMedicalCode', 'like', "%{$query}%")
->orWhere('ServiceMedicalName', 'like', "%{$query}%");
})
->orderBy('ServiceMedicalCode')
->limit(30)
->get()
->map(fn (\App\Models\MisServiceMedical $item) => [
'id' => $item->ServiceMedicalID,
'code' => $item->ServiceMedicalCode,
'name' => $item->ServiceMedicalName,
'label' => trim(($item->ServiceMedicalCode ? "{$item->ServiceMedicalCode} " : '').($item->ServiceMedicalName ?? '')),
])
->values();
return response()->json($items);
}
// api/report/unwanted-event
public function removeUnwantedEvent(UnwantedEvent $unwantedEvent, Request $request)
{
$unwantedEvent->delete();
return response()->json()->setStatusCode(200);
}
public function getDepartmentUsers(Request $request)
{
$user = Auth::user();
$departmentId = $user->department->rf_mis_department_id;
$users = MisLpuDoctor::select(['LPUDoctorID', 'FAM_V', 'IM_V', 'OT_V'])
->whereHas('prvds', function ($query) use ($departmentId) {
$query->where('rf_DepartmentID', $departmentId)
->whereDate('D_END', '2222-01-01 00:00:00.000000');
})
->active()
->whereNotIn('LPUDoctorID', [0, 1])
->orderBy('FAM_V')
->get();
return response()->json([
...$users,
])->setStatusCode(200);
}
/**
* Получить все отчеты в промежутке дат (для агрегации данных)
*/
private function getReportsForDateRange($departmentId, $startDate, $endDate)
{
if (Carbon::parse($startDate)->diffInDays(Carbon::parse($endDate)) > 1.0) {
return Report::where('rf_department_id', $departmentId)
->withinPeriod($startDate, $endDate)
->orderBy('period_end', 'ASC')
->get();
} else {
return Report::where('rf_department_id', $departmentId)
->exactPeriod($startDate, $endDate)
->orderBy('period_end', 'ASC')
->get();
}
}
private function resolveBaseStatus(string $status): string
{
if (str_starts_with($status, 'mis-')) {
return substr($status, 4);
}
if (str_starts_with($status, 'special-')) {
return substr($status, 8);
}
return $status;
}
private function attachReanimationIndicators(Collection $patients, int $departmentId): void
{
if ($patients->isEmpty()) {
return;
}
$medicalHistoryIds = $patients
->map(function ($patient) {
return (int) ($patient->medicalHistoryId ?? $patient->MedicalHistoryID ?? 0);
})
->filter()
->unique()
->values()
->all();
if (empty($medicalHistoryIds)) {
return;
}
$latestIndicators = $this->reportService->getLatestReanimationIndicators($departmentId, $medicalHistoryIds);
$patients->transform(function ($patient) use ($latestIndicators) {
$medicalHistoryId = (int) ($patient->medicalHistoryId ?? $patient->MedicalHistoryID ?? 0);
$indicator = $medicalHistoryId ? $latestIndicators->get($medicalHistoryId) : null;
$patient->reanimation_indicator = $indicator?->indicator;
$patient->reanimation_comment = $indicator?->comment;
return $patient;
});
}
public function checkReport(Request $request)
{
$request->validate([
'department_id' => 'required|integer|exists:departments,department_id',
]);
$report = Report::where('rf_department_id', $request->department_id)
->exactPeriod(now('Asia/Yakutsk')->subDay()->setTime(7, 0)->format('Y-m-d H:i:s'), now('Asia/Yakutsk')->setTime(7, 0)->format('Y-m-d H:i:s'))
->first();
return response()->json([
'report_id' => $report?->report_id,
'exists' => (bool) $report,
]);
}
}