hasOne(StudyDetails::class); } public function attachments(): HasMany { return $this->hasMany(StudyAttachment::class); } public function reports(): HasMany { return $this->hasMany(StudyReport::class); } public function shares(): HasMany { return $this->hasMany(SharedStudy::class); } public function scopeActive($query) { return $query->where('is_archived', false); } public function scopeArchived($query) { return $query->where('is_archived', true); } public function scopeUnlocked($query) { return $query->where('is_locked', false); } public function getHistoryLink(): string { $user = auth()->user(); if ($user->may(Permission::StudyHistoryEdit)) { return route('staff.history.edit', $this->hash); } if ($user->may(Permission::StudyHistoryView)) { return route('staff.history.view', $this->hash); } return '#'; } public function getMetadataLink(): string { $user = auth()->user(); if ($user->may(Permission::StudyMetadataEdit)) { return route('staff.meta.edit', $this->hash); } if ($user->may(Permission::StudyMetadataView)) { return route('staff.meta.view', $this->hash); } return '#'; } public function sexAge(): string { $dob = $this->patient_birthdate; $age = $dob ? (int) Carbon::make($dob)->diffInYears() . 'Y' : null; if (blank($age) && blank($this->patient_sex)) { return '~'; } return sprintf('%s / %s', $age ?? '~', $this->patient_sex); } public function numInstances(): string { return "{$this->image_count} / {$this->series_count}"; } public function readingPhysician(): BelongsTo { return $this->belongsTo(User::class, 'reporting_physician_id'); } public function assignedPhysician(): BelongsTo { return $this->belongsTo(User::class, 'assigned_physician_id'); } public function isAssigned(int $rad_id): bool { return $this->assigned_physician_id === $rad_id; } public function isActive(): bool { return $this->is_archived === false; } public function getReportStatusLedAttribute(): string { $color = match ($this->report_status) { ReportStatus::Unread => 'bg-white', ReportStatus::Opened => 'bg-secondary', ReportStatus::Preliminary => 'bg-info', ReportStatus::Finalized => 'bg-primary', ReportStatus::Approved => 'bg-success', default => 'bg-light', }; // $icon = match ($this->report_status) { ReportStatus::Unread => 'spinner text-muted', ReportStatus::Preliminary => 'pen-to-square', ReportStatus::Finalized => 'badge-check', ReportStatus::Approved => 'shield-check', default => 'spinner text-muted', }; return sprintf('', $color, $icon); } public function getArchiveLink(): ?string { if (auth()->user()->may(Permission::StudyDownload)) { return PacsUrlGen::archive($this->study_instance_uid); } return null; } public function getStoneLink(): ?string { if (auth()->user()->may(Permission::StudyDownload)) { return PacsUrlGen::stoneViewer($this->study_instance_uid); } return null; } public function getOhifLink(): ?string { if (auth()->user()->may(Permission::StudyDownload)) { return PacsUrlGen::ohifViewer($this->study_instance_uid); } return null; } public function getOhifSegmentationLink(): ?string { if (auth()->user()->may(Permission::StudyDownload)) { return PacsUrlGen::ohifSegmentation($this->study_instance_uid); } return null; } public function getOhifMprLink(): ?string { if (auth()->user()->may(Permission::StudyDownload)) { return PacsUrlGen::ohifViewerMpr($this->study_instance_uid); } return null; } public function links(): array { return [ 'history' => $this->getHistoryLink(), 'metadata' => $this->getMetadataLink(), 'zip' => $this->getArchiveLink(), 'stone' => $this->getStoneLink(), 'ohif' => $this->getOhifLink(), 'ohif.mpr' => $this->getOhifMprLink(), 'ohif.seg' => $this->getOhifSegmentationLink(), ]; } public function allowed(): array { return [ 'zip' => filled($this->getArchiveLink()), 'stone' => filled($this->getStoneLink()), 'ohif' => filled($this->getOhifLink()), 'ohif.mpr' => filled($this->getOhifMprLink()), 'ohif.seg' => filled($this->getOhifSegmentationLink()), ]; } public function sanitizedPatientName(): string { $name = preg_replace('/[^[:alnum:][:space:]]/u', ' ', $this->patient_name); return Str::of($name) ->slug(' ') ->title(); } public function sanitizedStudyDescription(): string { $name = preg_replace('/[^[:alnum:][:space:]\/\-\&]/u', ' ', $this->study_description); $name = Str::of($name)->title(); $lut = [ 'ct' => 'CT', 'mr' => 'MR', 'mri' => 'MRI', 'hrct' => 'HRCT', 'kub' => 'KUB', 'ap' => 'AP', 'pa' => 'PA', 'with' => 'w/', ]; foreach ($lut as $search => $replace) { $name = preg_replace("/\b{$search}\b/i", $replace, $name); } return $name; } public function toArray(): array { return array_merge(parent::toArray(), [ 'disk_size_human' => human_filesize($this->disk_size), 'reader_name' => $this->readingPhysician?->display_name, 'assigned_physician_name' => $this->assignedPhysician?->display_name, 'reader_photo' => $this->readingPhysician?->profile_photo_url, 'report_status_led' => $this->getReportStatusLedAttribute(), 'priority_icon' => $this->getPriorityIcon(), 'sex_age' => $this->sexAge(), 'num_instances' => $this->numInstances(), 'links' => $this->links(), 'allowed' => $this->allowed(), ]); } public function getPriorityIcon(): string { $tpl = ' '; return match ($this->priority) { Priority::Stat => sprintf($tpl, 'danger', 'light-emergency-on'), Priority::High => sprintf($tpl, 'primary', 'bolt'), Priority::Low => sprintf($tpl, 'light', 'chevrons-down'), default => '', }; } public function registerMediaConversions(?Media $media = null): void { // $media->extension $this->addMediaConversion('tn') ->width(48) ->height(48) ->sharpen(10) ->performOnCollections(Study::MEDIA_COLLECTION) ->nonQueued(); } public function isLocked(): bool { return $this->locked_at !== null; } public function lockStudy(?User $user = null): void { $this->update( [ 'locking_physician_id' => $user?->id ?? auth()->id(), 'locked_at' => now(), ] ); } public function unlockStudy(): void { $this->update( [ 'locked_at' => null, ] ); } public function registerMediaCollections(): void { $this->addMediaCollection(self::MEDIA_COLLECTION) ->useFallbackUrl(asset(self::FALLBACK_IMAGE)) ->useFallbackUrl(asset(self::FALLBACK_IMAGE), 'tn') ->useFallbackPath(public_path(self::FALLBACK_IMAGE)) ->useFallbackPath(public_path(self::FALLBACK_IMAGE), 'tn'); } public function getGenderIconAttribute(): string { return match (strtolower($this->patient_sex ?? '')) { 'f' => 'fa-venus text-danger', 'm' => 'fa-mars text-primary', default => 'fa-genderless', }; } public function getGenderNameAttribute(): string { return match (strtolower($this->patient_sex ?? '')) { 'f' => 'Female', 'm' => 'Male', default => 'Other', }; } protected function casts(): array { return [ 'is_archived' => 'boolean', 'study_status' => StudyLevelStatus::class, 'report_status' => ReportStatus::class, 'priority' => Priority::class, 'received_at' => 'immutable_datetime', 'reported_at' => 'immutable_datetime', 'assigned_at' => 'immutable_datetime', 'study_date' => 'immutable_datetime', 'patient_birthdate' => 'immutable_date', ]; } }