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 enum StudyLevelStatus: int
{ {
case Pending = 0; case Pending = 0;
case StudyArrived = 1 << 1; case Unassigned = 10;
case StudyLocked = 1 << 2; case Assigned = 20;
case StudyUnlocked = 1 << 3; case ReadInProgress = 30;
case ReadCompleted = 40;
} }

View File

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

View File

@ -29,6 +29,13 @@ public function approver(): BelongsTo
return $this->belongsTo(User::class, 'approved_by_id'); 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 public function scopeForStudy(Builder $query, Study|int $study): Builder
{ {
$query->where('study_id', $study instanceof Study ? $study->id : $study); $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; return;
} }
if ($study_status < StudyLevelStatus::StudyArrived->value) { if ($study_status < StudyLevelStatus::Unassigned->value) {
$sync->getUpdateQueue()->add($orthanc_uuid); $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)) { if ((bool) data_get($orthanc_src, 'IsStable', false)) {
$study['study_status'] = StudyLevelStatus::StudyArrived->value; $study['study_status'] = StudyLevelStatus::Unassigned->value;
} else { } else {
$study['study_status'] = StudyLevelStatus::Pending->value; $study['study_status'] = StudyLevelStatus::Pending->value;
} }

View File

@ -2,4 +2,128 @@
namespace App\Services\Report; 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> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <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" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
crossorigin="anonymous"> crossorigin="anonymous">
<title>{{ $title }}</title> <title>{{ $title }}</title>
<style> <style>
body { body {
margin-top: 20px; margin-top: 60px;
} font-family: "Rubik", serif;
font-optical-sizing: auto;
body { font-weight: 400;
padding: 50px;
} }
.alert-coupon { .alert-coupon {
@ -20,8 +20,9 @@
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
font-size: 20px; font-size: 20px;
line-height: 1.5; line-height: 1.6;
text-align: center; text-align: center;
font-weight: 600;
} }
p:last-child { p:last-child {
@ -34,7 +35,7 @@
<div class="container"> <div class="container">
<div class="row"> <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"> <div class="alert alert-{{ $color }} alert-coupon">
<h4>{{ $heading }}</h4> <h4>{{ $heading }}</h4>
<p>{{ $message }}</p> <p>{{ $message }}</p>