refactorings

This commit is contained in:
Dr Masroor Ehsan 2025-01-12 01:09:27 +06:00
parent 832948984a
commit 3fa3654938
9 changed files with 186 additions and 95 deletions

View File

@ -5,7 +5,8 @@
enum StudyLevelStatus: int
{
case Pending = 0;
case StudyArrived = 1 << 1;
case StudyLocked = 1 << 2;
case StudyUnlocked = 1 << 3;
case Unassigned = 10;
case Assigned = 20;
case ReadInProgress = 30;
case ReadCompleted = 40;
}

View File

@ -2,19 +2,18 @@
namespace App\Http\Controllers\Staff;
use App\Domain\ACL\Permission;
use App\Domain\Report\ReportStatus;
use App\Http\Controllers\HashidControllerBase;
use App\Http\Requests\StoreReportRequest;
use App\Models\Study;
use App\Models\StudyReport;
use App\Services\AuditTrail\Activity;
use App\Services\Report\ReportManager;
class ReportController extends HashidControllerBase
{
public function popup()
{
abort_unless(me()->may([Permission::ReportEdit, Permission::ReportDictate, Permission::ReportApprove, Permission::ReportDownload]), 403);
ReportManager::ensureDownloadAccess();
$this->decodeKeys();
$study = Study::with(['reports.radiologist', 'reports.study', 'assignedPhysicians'])->findOrFail($this->key);
if (me()->isRadiologist()) {
@ -28,104 +27,60 @@ public function popup()
public function save(StoreReportRequest $request)
{
abort_unless(me()->may([Permission::ReportEdit, Permission::ReportDictate, Permission::ReportApprove]), 403);
ReportManager::ensureEditAccess();
$this->decodeKeys();
$study = Study::findOrFail($this->key);
$manager = ReportManager::make($this->key);
$reportStatus = ReportStatus::from($request->integer('report_status'));
$report = StudyReport::make([
'study_id' => $study->id,
'institute_id' => $study->institute_id,
'facility_id' => $study->facility_id,
'report_status' => $reportStatus->value,
'read_by_id' => me()->id,
]);
$report->saveContent(request('content'));
$report->save();
$report->refresh();
audit()
->on($study)
->did($reportStatus->value >= ReportStatus::Finalized->value ? Activity::Report_Finalize : Activity::Report_Save)
->notes($report->accession_number)
->log();
$report = $manager->createReport(request('content'), $reportStatus);
if ($reportStatus->value === ReportStatus::Finalized->value) {
$report->setStatus($reportStatus);
$study->setReportStatus($reportStatus);
audit()
->on($study)
->did(Activity::Report_Finalize)
->log();
$study->unlockStudy();
audit()
->on($study)
->did(Activity::Study_Unlock)
->log();
$manager->finalizeReport($report);
}
return view('staff.reports.close-window');
return view('staff.content.pages.close-window');
}
public function create()
{
abort_unless(me()->may([Permission::ReportEdit, Permission::ReportDictate, Permission::ReportApprove]), 403);
ReportManager::ensureEditAccess();
$this->decodeKeys();
$study = Study::findOrFail($this->key);
if (! $study->canEditReport()) {
return view('content.pages.notice', [
'color' => 'warning',
'title' => 'Read Completed',
'heading' => 'Read Completed',
'message' => 'Reading has been completed already.',
]);
$manager = ReportManager::make($this->key);
$view = $manager->check();
if ($view) {
return $view;
}
if (! $study->canObtainLock()) {
return view('content.pages.notice', [
'color' => 'danger',
'title' => 'Study Locked',
'heading' => 'Study Locked',
'message' => 'Study is locked by another user.',
]);
}
if ($study->isUnlocked()) {
$study->lockStudy();
audit()
->on($study)
->did(Activity::Study_Lock)
->log();
}
$manager->lockStudyIfRequired();
$study = $manager->getStudy();
$report = $manager->latestReport();
// todo: study_status: Read in Progress
$report = StudyReport::forStudy($study)
->where('report_status', ReportStatus::Preliminary->value)
->select(['id', 'accession_number', 'file_path'])
->latest()
->first();
return view('staff.reports.create', compact('study', 'report'));
}
public function edit(string $uuid)
{
abort_unless(me()->may([Permission::ReportEdit, Permission::ReportDictate, Permission::ReportApprove]), 403);
$report = StudyReport::with(['study', 'radiologist'])->where('accession_number', $uuid)->firstOrFail();
$study = $report->study;
$title = 'View Report';
$close = false;
ReportManager::ensureEditAccess();
$report = StudyReport::with(['study_id', 'id'])->accession($uuid)->firstOrFail();
$manager = ReportManager::make($report->study_id);
return view('staff.reports.create', compact('study', 'report', 'close'));
$view = $manager->check();
if ($view) {
return $view;
}
$manager->lockStudyIfRequired();
$study = $manager->getStudy();
return view('staff.reports.create', compact('study', 'report'));
}
public function view(string $uuid)
{
abort_unless(me()->may([Permission::ReportEdit, Permission::ReportDictate, Permission::ReportApprove, Permission::ReportDownload]), 403);
$report = StudyReport::with(['study', 'radiologist'])->where('accession_number', $uuid)->firstOrFail();
ReportManager::ensureDownloadAccess();
$report = StudyReport::with(['study_id', 'id'])->accession($uuid)->firstOrFail();
$title = 'View Report';
return view('staff.reports.viewer.html-report', compact('report', 'title'));

View File

@ -338,14 +338,17 @@ public function isUnlocked(): bool
return $this->locked_at === null;
}
public function lockStudy(User|int|null $user = null): void
public function lockStudy(User|int|null $user = null, ?StudyLevelStatus $status = null): void
{
$this->update(
[
$params = [
'locking_physician_id' => me($user)->id,
'locked_at' => now(),
]
);
];
if ($status) {
$params['study_status'] = $status->value;
}
$this->update($params);
}
public function unlockStudy(): void

View File

@ -29,6 +29,13 @@ public function approver(): BelongsTo
return $this->belongsTo(User::class, 'approved_by_id');
}
public function scopeAccession(Builder $query, string $uuid): Builder
{
$query->wherewhere('accession_number', $uuid);
return $query;
}
public function scopeForStudy(Builder $query, Study|int $study): Builder
{
$query->where('study_id', $study instanceof Study ? $study->id : $study);

View File

@ -41,7 +41,7 @@ private function checkUpdate(string $orthanc_uuid, StudiesSync $sync, Collection
return;
}
if ($study_status < StudyLevelStatus::StudyArrived->value) {
if ($study_status < StudyLevelStatus::Unassigned->value) {
$sync->getUpdateQueue()->add($orthanc_uuid);
}
}

View File

@ -139,7 +139,7 @@ public function transformData(mixed $orthanc_src): array
];
if ((bool) data_get($orthanc_src, 'IsStable', false)) {
$study['study_status'] = StudyLevelStatus::StudyArrived->value;
$study['study_status'] = StudyLevelStatus::Unassigned->value;
} else {
$study['study_status'] = StudyLevelStatus::Pending->value;
}

View File

@ -2,4 +2,128 @@
namespace App\Services\Report;
final class ReportManager {}
use App\Domain\ACL\Permission;
use App\Domain\Report\ReportStatus;
use App\Models\Study;
use App\Models\StudyReport;
use App\Services\AuditTrail\Activity;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
final class ReportManager
{
public function __construct(private readonly Study $study) {}
public static function make(int $study_id): static
{
return new self(Study::findOrFail($study_id));
}
public function getReports(): Collection
{
return $this->study->reports->sortByDesc('created_at');
}
public function createReport(string $content, ReportStatus $status): StudyReport
{
$report = StudyReport::make([
'study_id' => $this->study->id,
'institute_id' => $this->study->institute_id,
'facility_id' => $this->study->facility_id,
'report_status' => $status->value,
'read_by_id' => me()->id,
]);
$report->saveContent($content);
$report->save();
$report->refresh();
audit()
->on($this->study)
->did(Activity::Report_Save)
->notes($report->accession_number)
->log();
return $report;
}
public function finalizeReport(StudyReport $report): void
{
$report->setStatus(ReportStatus::Finalized);
audit()
->on($this->study)
->did(Activity::Report_Finalize)
->notes($report->accession_number)
->log();
$this->study->setReportStatus(ReportStatus::Finalized);
audit()
->on($this->study)
->did(Activity::Report_Finalize)
->log();
$this->study->unlockStudy();
audit()
->on($this->study)
->did(Activity::Study_Unlock)
->log();
}
public function check(): ?View
{
if (! $this->study->canEditReport()) {
return view('content.pages.notice', [
'color' => 'warning',
'title' => 'Read Completed',
'heading' => 'Read Completed',
'message' => 'The report has been finalized, and the study is now closed for any further modifications.',
]);
}
if (! $this->study->canObtainLock()) {
return view('content.pages.notice', [
'color' => 'danger',
'title' => 'Study Locked',
'heading' => 'Study Locked',
'message' => 'Study is locked by another user.',
]);
}
return null;
}
public function lockStudyIfRequired(): void
{
if ($this->study->isUnlocked()) {
$this->study->lockStudy();
audit()
->on($this->study)
->did(Activity::Study_Lock)
->log();
}
}
public function latestReport(): ?StudyReport
{
return StudyReport::forStudy($this->study)
->where('report_status', ReportStatus::Preliminary->value)
->select(['id', 'accession_number', 'file_path'])
->latest()
->first();
}
public function getStudy(): Study
{
return $this->study;
}
public static function ensureDownloadAccess(): void
{
abort_unless(me()->may([Permission::ReportEdit, Permission::ReportDictate, Permission::ReportApprove, Permission::ReportDownload]), 403);
}
public static function ensureEditAccess(): void
{
abort_unless(me()->may([Permission::ReportEdit, Permission::ReportDictate, Permission::ReportApprove]), 403);
}
}

View File

@ -3,16 +3,16 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css2?family=Rubik:wght@400..600&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
crossorigin="anonymous">
<title>{{ $title }}</title>
<style>
body {
margin-top: 20px;
}
body {
padding: 50px;
margin-top: 60px;
font-family: "Rubik", serif;
font-optical-sizing: auto;
font-weight: 400;
}
.alert-coupon {
@ -20,8 +20,9 @@
h1, h2, h3, h4, h5, h6 {
font-size: 20px;
line-height: 1.5;
line-height: 1.6;
text-align: center;
font-weight: 600;
}
p:last-child {
@ -34,7 +35,7 @@
<div class="container">
<div class="row">
<div class="col-md-6">
<div class="col-7 col-md-7 d-flex justify-content-center">
<div class="alert alert-{{ $color }} alert-coupon">
<h4>{{ $heading }}</h4>
<p>{{ $message }}</p>