commit
4a79935e4c
@ -238,11 +238,21 @@ public function getColumns(): array
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case WorklistColumn::Organization:
|
case WorklistColumn::Organization:
|
||||||
|
$columns[] = Column::make($col->value)
|
||||||
|
->searchable(false)
|
||||||
|
->title('Org');
|
||||||
|
break;
|
||||||
|
|
||||||
case WorklistColumn::Department:
|
case WorklistColumn::Department:
|
||||||
$columns[] = Column::make($col->value)
|
$columns[] = Column::make($col->value)
|
||||||
->searchable(true)
|
->searchable(false)
|
||||||
->orderable(true)
|
->title('Dept');
|
||||||
->title(formatTitle($col->value));
|
break;
|
||||||
|
|
||||||
|
case WorklistColumn::DicomServer:
|
||||||
|
$columns[] = Column::make($col->value)
|
||||||
|
->searchable(false)
|
||||||
|
->title('Server');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -445,7 +455,9 @@ private function renderCustomColumns(): array
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case WorklistColumn::StudyDescription:
|
case WorklistColumn::StudyDescription:
|
||||||
$columns[$col->value] = static fn (Study $study) => $study->sanitizedStudyDescription();
|
$columns[$col->value] = function (Study $study) {
|
||||||
|
return sprintf('<span data-bs-toggle="tooltip" data-bs-placement="top" title="%s">%s</span>', $study->study_description, str_limit($study->sanitizedStudyDescription(), 20));
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
case WorklistColumn::AssignedPhysician:
|
case WorklistColumn::AssignedPhysician:
|
||||||
$columns[$col->value] = static function (Study $study) {
|
$columns[$col->value] = static function (Study $study) {
|
||||||
@ -500,6 +512,13 @@ private function renderCustomColumns(): array
|
|||||||
case WorklistColumn::ReportButtons:
|
case WorklistColumn::ReportButtons:
|
||||||
$columns[$col->value] = fn (Study $study) => $this->generateReportingButtons($study);
|
$columns[$col->value] = fn (Study $study) => $this->generateReportingButtons($study);
|
||||||
break;
|
break;
|
||||||
|
case WorklistColumn::DicomServer:
|
||||||
|
$columns[$col->value] = function (Study $study) {
|
||||||
|
return sprintf('<span class="fi fi-%s msg-icon me-1"></span>%s',
|
||||||
|
strtolower($study->dicomServer->geo_code),
|
||||||
|
$study->dicomServer->server_name);
|
||||||
|
};
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,21 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Domain\Rule\MatchCondition;
|
|
||||||
use App\Models\Traits\Active;
|
use App\Models\Traits\Active;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
|
||||||
class DicomRoutingRule extends BaseModel
|
class DicomRoutingRule extends BaseModel
|
||||||
{
|
{
|
||||||
use Active;
|
use Active;
|
||||||
|
|
||||||
public function conditions(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(DicomRuleCondition::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function organization(): BelongsTo
|
public function organization(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Organization::class);
|
return $this->belongsTo(Organization::class);
|
||||||
@ -41,7 +34,6 @@ protected function casts(): array
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'is_active' => 'boolean',
|
'is_active' => 'boolean',
|
||||||
'match_condition' => MatchCondition::class,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use App\Domain\Rule\MatchMode;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
|
|
||||||
class DicomRuleCondition extends BaseModel
|
|
||||||
{
|
|
||||||
public function rule(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(DicomRoutingRule::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function casts(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'case_sensitive' => 'boolean',
|
|
||||||
'match_mode' => MatchMode::class,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -36,4 +36,5 @@ enum WorklistColumn: string
|
|||||||
case ReportButtons = 'report_buttons';
|
case ReportButtons = 'report_buttons';
|
||||||
case Organization = 'organization';
|
case Organization = 'organization';
|
||||||
case Department = 'department';
|
case Department = 'department';
|
||||||
|
case DicomServer = 'dicom_server';
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ public static function worklistColumns(User|int|null $usr = null): Collection
|
|||||||
if ($user->isAdmin()) {
|
if ($user->isAdmin()) {
|
||||||
$columns->push(WorklistColumn::Organization);
|
$columns->push(WorklistColumn::Organization);
|
||||||
$columns->push(WorklistColumn::Department);
|
$columns->push(WorklistColumn::Department);
|
||||||
|
$columns->push(WorklistColumn::DicomServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
use App\Services\Pacs\DicomUtils;
|
use App\Services\Pacs\DicomUtils;
|
||||||
use App\Services\Pacs\OrthancRestClient;
|
use App\Services\Pacs\OrthancRestClient;
|
||||||
use App\Services\StudyRouter\DicomStudyRouter;
|
use App\Services\StudyRouter\DicomStudyRouter;
|
||||||
|
use App\Services\StudyRouter\RawDicomTags;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Pipeline\Pipeline;
|
use Illuminate\Pipeline\Pipeline;
|
||||||
@ -283,20 +284,21 @@ private function getStudyDicomTags(string $study_uuid): array
|
|||||||
// randomly sample few instances for tags collection
|
// randomly sample few instances for tags collection
|
||||||
$selectedInstances = count($instances) <= $this->maxInstances ? $instances : array_rand(array_flip($instances), $this->maxInstances);
|
$selectedInstances = count($instances) <= $this->maxInstances ? $instances : array_rand(array_flip($instances), $this->maxInstances);
|
||||||
|
|
||||||
$tags = [];
|
$tags = collect();
|
||||||
foreach ($selectedInstances as $instance) {
|
foreach ($selectedInstances as $instance) {
|
||||||
foreach ($this->fetchInstancesTags($instance) as $key => $value) {
|
foreach ($this->fetchInstancesTags($instance) as $key => $value) {
|
||||||
if ($key == 'MainDicomTags' || $key == 'RequestedTags') {
|
if ($key == 'MainDicomTags' || $key == 'RequestedTags') {
|
||||||
foreach ($value as $tag => $val) {
|
foreach ($value as $tag => $val) {
|
||||||
if (! isset($tags[$tag]) || $tags[$tag] !== $val) {
|
$dcmTag = RawDicomTags::tryFrom($tag);
|
||||||
$tags[$tag] = $val;
|
if ($dcmTag != null && (! $tags->has($dcmTag->name) || $tags->get($dcmTag->name) !== $val)) {
|
||||||
|
$tags->put($dcmTag->name, $val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $tags;
|
return $tags->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function setValue(array &$array, string $key, mixed $value): void
|
private function setValue(array &$array, string $key, mixed $value): void
|
||||||
|
@ -3,16 +3,15 @@
|
|||||||
namespace App\Services\StudyRouter;
|
namespace App\Services\StudyRouter;
|
||||||
|
|
||||||
use App\Domain\ACL\Role;
|
use App\Domain\ACL\Role;
|
||||||
use App\Domain\Rule\MatchCondition;
|
|
||||||
use App\Domain\Rule\MatchMode;
|
|
||||||
use App\Models\AssignmentPanel;
|
use App\Models\AssignmentPanel;
|
||||||
use App\Models\DicomRoutingRule;
|
use App\Models\DicomRoutingRule;
|
||||||
use App\Models\DicomRuleCondition;
|
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\ContentMatcher;
|
use Exception;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||||
|
|
||||||
final class DicomStudyRouter
|
final class DicomStudyRouter
|
||||||
{
|
{
|
||||||
@ -30,22 +29,27 @@ public static function matchStudy(array $dicomHeaders): array
|
|||||||
return self::fallbackRouting();
|
return self::fallbackRouting();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$study = json_decode(json_encode($dicomHeaders)); // convert to object
|
||||||
|
$expression = new ExpressionLanguage;
|
||||||
foreach (self::$rules as $rule) {
|
foreach (self::$rules as $rule) {
|
||||||
$conditions = $rule->conditions()->orderByDesc('priority')->get();
|
try {
|
||||||
$matches = $rule->match_condition === MatchCondition::ALL
|
$matches = (bool) $expression->evaluate($rule->condition, ['study' => $study]);
|
||||||
? $conditions->every(fn ($condition) => self::matchCondition($condition, $dicomHeaders))
|
} catch (Exception $exc) {
|
||||||
: $conditions->contains(fn ($condition) => self::matchCondition($condition, $dicomHeaders));
|
Log::error('Error evaluating rule expression', [
|
||||||
|
'rule_id' => $rule->id,
|
||||||
|
'condition' => $rule->condition,
|
||||||
|
'error' => $exc->getMessage(),
|
||||||
|
]);
|
||||||
|
|
||||||
/*
|
return self::fallbackRouting();
|
||||||
if ($dicomHeaders[RawDicomTags::Modality->value] === 'CR') {
|
|
||||||
dd($rule, $conditions->toArray(), $matches, $dicomHeaders[RawDicomTags::Modality->value]);
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
if ($matches) {
|
if ($matches) {
|
||||||
return [
|
return [
|
||||||
'organization_id' => $rule->organization_id,
|
'organization_id' => $rule->organization_id,
|
||||||
'department_id' => $rule->department_id,
|
'department_id' => $rule->department_id,
|
||||||
'rule_id' => $rule->id,
|
'rule_id' => $rule->id,
|
||||||
|
'rule_name' => $rule->name,
|
||||||
'radiologists' => self::getRadiologists($rule),
|
'radiologists' => self::getRadiologists($rule),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -70,7 +74,6 @@ private static function initialize(): void
|
|||||||
self::$rules = Cache::remember('dicom.rules',
|
self::$rules = Cache::remember('dicom.rules',
|
||||||
now()->addMinutes(self::CACHE_TTL),
|
now()->addMinutes(self::CACHE_TTL),
|
||||||
fn () => DicomRoutingRule::active()
|
fn () => DicomRoutingRule::active()
|
||||||
->with('conditions')
|
|
||||||
->orderByDesc('priority')
|
->orderByDesc('priority')
|
||||||
->get()
|
->get()
|
||||||
);
|
);
|
||||||
@ -92,22 +95,6 @@ private static function initialize(): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function matchCondition(DicomRuleCondition $condition, array $dicomHeaders): bool
|
|
||||||
{
|
|
||||||
$dicomTag = $condition->dicom_tag;
|
|
||||||
$dicomValue = $dicomHeaders[$dicomTag] ?? '';
|
|
||||||
$searchPattern = $condition->search_pattern;
|
|
||||||
|
|
||||||
if (! $condition->case_sensitive) {
|
|
||||||
$dicomValue = strtolower($dicomValue);
|
|
||||||
if ($condition->match_mode !== MatchMode::Regex) {
|
|
||||||
$searchPattern = strtolower($searchPattern);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ContentMatcher::match($dicomValue, $searchPattern, $condition->match_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function getRadiologists(DicomRoutingRule $rule): array
|
private static function getRadiologists(DicomRoutingRule $rule): array
|
||||||
{
|
{
|
||||||
if (! is_null($rule->assignment_panel_id)) {
|
if (! is_null($rule->assignment_panel_id)) {
|
||||||
|
@ -4,62 +4,62 @@
|
|||||||
|
|
||||||
enum RawDicomTags: string
|
enum RawDicomTags: string
|
||||||
{
|
{
|
||||||
case PatientName = '0010,0010'; // Patient's Name
|
case patient_name = '0010,0010'; // Patient's Name
|
||||||
case PatientID = '0010,0020'; // Patient ID
|
case patient_id = '0010,0020'; // Patient ID
|
||||||
case PatientBirthDate = '0010,0030'; // Patient's Birth Date
|
case patient_birth_date = '0010,0030'; // Patient's Birth Date
|
||||||
case PatientSex = '0010,0040'; // Patient's Sex
|
case patient_sex = '0010,0040'; // Patient's Sex
|
||||||
case StudyInstanceUID = '0020,000D'; // Study Instance UID
|
case study_instance_uid = '0020,000D'; // Study Instance UID
|
||||||
case SeriesInstanceUID = '0020,000E'; // Series Instance UID
|
case series_instance_uid = '0020,000E'; // Series Instance UID
|
||||||
case StudyID = '0020,0010'; // Study ID
|
case study_id = '0020,0010'; // Study ID
|
||||||
case SeriesNumber = '0020,0011'; // Series Number
|
case series_number = '0020,0011'; // Series Number
|
||||||
case InstanceNumber = '0020,0013'; // Instance Number
|
case instance_number = '0020,0013'; // Instance Number
|
||||||
case SOPClassUID = '0008,0016'; // SOP Class UID
|
case sop_class_uid = '0008,0016'; // SOP Class UID
|
||||||
case SOPInstanceUID = '0008,0018'; // SOP Instance UID
|
case sop_instance_uid = '0008,0018'; // SOP Instance UID
|
||||||
case StudyDate = '0008,0020'; // Study Date
|
case study_date = '0008,0020'; // Study Date
|
||||||
case StudyTime = '0008,0030'; // Study Time
|
case study_time = '0008,0030'; // Study Time
|
||||||
case AccessionNumber = '0008,0050'; // Accession Number
|
case accession_number = '0008,0050'; // Accession Number
|
||||||
case Modality = '0008,0060'; // Modality
|
case modality = '0008,0060'; // Modality
|
||||||
case Manufacturer = '0008,0070'; // Manufacturer
|
case manufacturer = '0008,0070'; // Manufacturer
|
||||||
case InstitutionName = '0008,0080'; // Institution Name
|
case institution_name = '0008,0080'; // Institution Name
|
||||||
case ReferringPhysicianName = '0008,0090'; // Referring Physician's Name
|
case referring_physician_name = '0008,0090'; // Referring Physician's Name
|
||||||
case StationName = '0008,1010'; // Station Name
|
case station_name = '0008,1010'; // Station Name
|
||||||
case SeriesDescription = '0008,103E'; // Series Description
|
case series_description = '0008,103E'; // Series Description
|
||||||
case ManufacturerModelName = '0008,1090'; // Manufacturer's Model Name
|
case manufacturer_model_name = '0008,1090'; // Manufacturer's Model Name
|
||||||
case PatientAge = '0010,1010'; // Patient's Age
|
case patient_age = '0010,1010'; // Patient's Age
|
||||||
case PatientWeight = '0010,1030'; // Patient's Weight
|
case patient_weight = '0010,1030'; // Patient's Weight
|
||||||
case BodyPartExamined = '0018,0015'; // Body Part Examined
|
case body_part_examined = '0018,0015'; // Body Part Examined
|
||||||
case ProtocolName = '0018,1030'; // Protocol Name
|
case protocol_name = '0018,1030'; // Protocol Name
|
||||||
case SoftwareVersions = '0018,1020'; // Software Versions
|
case software_versions = '0018,1020'; // Software Versions
|
||||||
case AcquisitionDate = '0008,0022'; // Acquisition Date
|
case acquisition_date = '0008,0022'; // Acquisition Date
|
||||||
case AcquisitionTime = '0008,0032'; // Acquisition Time
|
case acquisition_time = '0008,0032'; // Acquisition Time
|
||||||
case ContentDate = '0008,0023'; // Content Date
|
case content_date = '0008,0023'; // Content Date
|
||||||
case ContentTime = '0008,0033'; // Content Time
|
case content_time = '0008,0033'; // Content Time
|
||||||
case AcquisitionDeviceProcessingDescription = '0018,1400'; // Acquisition Device Processing Description
|
case acquisition_device_processing_description = '0018,1400'; // Acquisition Device Processing Description
|
||||||
case InstitutionAddress = '0008,0081'; // Institution Address
|
case institution_address = '0008,0081'; // Institution Address
|
||||||
case StudyDescription = '0008,1030'; // Study Description
|
case study_description = '0008,1030'; // Study Description
|
||||||
case OperatorsName = '0008,1070'; // Operator's Name
|
case operators_name = '0008,1070'; // Operator's Name
|
||||||
case Private10 = '0029,0010'; // Private Tag 10
|
case private_10 = '0029,0010'; // Private Tag 10
|
||||||
case IW_Private = '0009,0010'; // IW Private Tag
|
case iw_private = '0009,0010'; // IW Private Tag
|
||||||
case ImageType = '0008,0008'; // Image Type
|
case image_type = '0008,0008'; // Image Type
|
||||||
case PatientOrientation = '0020,0020'; // Patient Orientation
|
case patient_orientation = '0020,0020'; // Patient Orientation
|
||||||
case ImagePositionPatient = '0020,0032'; // Image Position (Patient)
|
case image_position_patient = '0020,0032'; // Image Position (Patient)
|
||||||
case ImageOrientationPatient = '0020,0037'; // Image Orientation (Patient)
|
case image_orientation_patient = '0020,0037'; // Image Orientation (Patient)
|
||||||
case FrameOfReferenceUID = '0020,0052'; // Frame of Reference UID
|
case frame_of_reference_uid = '0020,0052'; // Frame of Reference UID
|
||||||
case PositionReferenceIndicator = '0020,1040'; // Position Reference Indicator
|
case position_reference_indicator = '0020,1040'; // Position Reference Indicator
|
||||||
case SliceLocation = '0020,1041'; // Slice Location
|
case slice_location = '0020,1041'; // Slice Location
|
||||||
case SamplesPerPixel = '0028,0002'; // Samples per Pixel
|
case samples_per_pixel = '0028,0002'; // Samples per Pixel
|
||||||
case PhotometricInterpretation = '0028,0004'; // Photometric Interpretation
|
case photometric_interpretation = '0028,0004'; // Photometric Interpretation
|
||||||
case Rows = '0028,0010'; // Rows
|
case rows = '0028,0010'; // Rows
|
||||||
case Columns = '0028,0011'; // Columns
|
case columns = '0028,0011'; // Columns
|
||||||
case PixelSpacing = '0028,0030'; // Pixel Spacing
|
case pixel_spacing = '0028,0030'; // Pixel Spacing
|
||||||
case BitsAllocated = '0028,0100'; // Bits Allocated
|
case bits_allocated = '0028,0100'; // Bits Allocated
|
||||||
case BitsStored = '0028,0101'; // Bits Stored
|
case bits_stored = '0028,0101'; // Bits Stored
|
||||||
case HighBit = '0028,0102'; // High Bit
|
case high_bit = '0028,0102'; // High Bit
|
||||||
case PixelRepresentation = '0028,0103'; // Pixel Representation
|
case pixel_representation = '0028,0103'; // Pixel Representation
|
||||||
case WindowCenter = '0028,1050'; // Window Center
|
case window_center = '0028,1050'; // Window Center
|
||||||
case WindowWidth = '0028,1051'; // Window Width
|
case window_width = '0028,1051'; // Window Width
|
||||||
case RescaleIntercept = '0028,1052'; // Rescale Intercept
|
case rescale_intercept = '0028,1052'; // Rescale Intercept
|
||||||
case RescaleSlope = '0028,1053'; // Rescale Slope
|
case rescale_slope = '0028,1053'; // Rescale Slope
|
||||||
case InoWave_Private = '0011,1060';
|
case ino_wave_private = '0011,1060';
|
||||||
case RadFusion_SenderId = '1971,1020';
|
case rad_fusion_sender_id = '1971,1020';
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"sentry/sentry-laravel": "^4.10",
|
"sentry/sentry-laravel": "^4.10",
|
||||||
"spatie/laravel-medialibrary": "^11.11",
|
"spatie/laravel-medialibrary": "^11.11",
|
||||||
"spatie/laravel-permission": "^6.10",
|
"spatie/laravel-permission": "^6.10",
|
||||||
|
"symfony/expression-language": "^7.2",
|
||||||
"vinkla/hashids": "^12.0",
|
"vinkla/hashids": "^12.0",
|
||||||
"yajra/laravel-datatables": "^11.0"
|
"yajra/laravel-datatables": "^11.0"
|
||||||
},
|
},
|
||||||
|
@ -14,6 +14,7 @@ public function up(): void
|
|||||||
$table->id();
|
$table->id();
|
||||||
$table->boolean('is_active')->index();
|
$table->boolean('is_active')->index();
|
||||||
$table->string('server_name')->unique();
|
$table->string('server_name')->unique();
|
||||||
|
$table->string('geo_code', 2)->index();
|
||||||
$table->string('host');
|
$table->string('host');
|
||||||
$table->integer('port');
|
$table->integer('port');
|
||||||
$table->string('rest_api_endpoint');
|
$table->string('rest_api_endpoint');
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Domain\Rule\MatchCondition;
|
|
||||||
use App\Models\AssignmentPanel;
|
use App\Models\AssignmentPanel;
|
||||||
use App\Models\Department;
|
use App\Models\Department;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
@ -21,7 +20,7 @@ public function up(): void
|
|||||||
$table->foreignIdFor(User::class)->nullable()->constrained()->nullOnDelete();
|
$table->foreignIdFor(User::class)->nullable()->constrained()->nullOnDelete();
|
||||||
$table->foreignIdFor(AssignmentPanel::class)->nullable()->constrained()->nullOnDelete();
|
$table->foreignIdFor(AssignmentPanel::class)->nullable()->constrained()->nullOnDelete();
|
||||||
$table->string('name')->nullable();
|
$table->string('name')->nullable();
|
||||||
$table->string('match_condition')->default(MatchCondition::ANY->value);
|
$table->string('condition');
|
||||||
$table->unsignedTinyInteger('priority')->default(0);
|
$table->unsignedTinyInteger('priority')->default(0);
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use App\Domain\Rule\MatchMode;
|
|
||||||
use App\Models\DicomRoutingRule;
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::create('dicom_rule_conditions', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
$table->foreignIdFor(DicomRoutingRule::class)->constrained()->cascadeOnDelete();
|
|
||||||
$table->string('dicom_tag');
|
|
||||||
$table->string('search_pattern');
|
|
||||||
$table->boolean('case_sensitive')->default(false);
|
|
||||||
$table->string('match_mode')->default(MatchMode::Exact->value);
|
|
||||||
$table->unsignedTinyInteger('priority')->default(0);
|
|
||||||
$table->timestamps();
|
|
||||||
|
|
||||||
$table->index(['dicom_routing_rule_id', 'priority']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('dicom_rule_conditions');
|
|
||||||
}
|
|
||||||
};
|
|
@ -2,14 +2,10 @@
|
|||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Domain\Rule\MatchCondition;
|
|
||||||
use App\Domain\Rule\MatchMode;
|
|
||||||
use App\Models\Department;
|
use App\Models\Department;
|
||||||
use App\Models\DicomRoutingRule;
|
use App\Models\DicomRoutingRule;
|
||||||
use App\Models\DicomRuleCondition;
|
|
||||||
use App\Models\DicomServer;
|
use App\Models\DicomServer;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
use App\Services\StudyRouter\RawDicomTags;
|
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
class OrganizationSeeder extends Seeder
|
class OrganizationSeeder extends Seeder
|
||||||
@ -36,14 +32,14 @@ public function run(): void
|
|||||||
$dept_chev_xr = Department::create(
|
$dept_chev_xr = Department::create(
|
||||||
[
|
[
|
||||||
'is_active' => true,
|
'is_active' => true,
|
||||||
'name' => 'Chevron XR',
|
'name' => 'Chev-CR',
|
||||||
'organization_id' => $chev->id,
|
'organization_id' => $chev->id,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$chev_dep_ct_mr = Department::create(
|
$chev_dep_ct_mr = Department::create(
|
||||||
[
|
[
|
||||||
'is_active' => true,
|
'is_active' => true,
|
||||||
'name' => 'Chevron CT/MR',
|
'name' => 'Chev-MR',
|
||||||
'organization_id' => $chev->id,
|
'organization_id' => $chev->id,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@ -61,7 +57,7 @@ public function run(): void
|
|||||||
'name' => 'Chevron MR/CT',
|
'name' => 'Chevron MR/CT',
|
||||||
'organization_id' => $chev->id,
|
'organization_id' => $chev->id,
|
||||||
'department_id' => $chev_dep_ct_mr->id,
|
'department_id' => $chev_dep_ct_mr->id,
|
||||||
'match_condition' => MatchCondition::ALL->value,
|
'condition' => '(study.institution_name ?? "" starts with "chevron") and (study.modality not in ["CR", "DX", "MG"])',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -71,45 +67,16 @@ public function run(): void
|
|||||||
'name' => 'Chevron X-ray',
|
'name' => 'Chevron X-ray',
|
||||||
'organization_id' => $chev->id,
|
'organization_id' => $chev->id,
|
||||||
'department_id' => $dept_chev_xr->id,
|
'department_id' => $dept_chev_xr->id,
|
||||||
'match_condition' => MatchCondition::ALL->value,
|
'condition' => '(study.institution_name ?? "" starts with "chevron") and (study.modality in ["CR", "DX", "MG"])',
|
||||||
'priority' => 10,
|
'priority' => 99,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
DicomRuleCondition::create([
|
|
||||||
'dicom_tag' => RawDicomTags::InstitutionName->value,
|
|
||||||
'search_pattern' => 'chevron',
|
|
||||||
'dicom_routing_rule_id' => $rul_chev_mr_ct->id,
|
|
||||||
'match_mode' => MatchMode::Contains->value,
|
|
||||||
'case_sensitive' => false,
|
|
||||||
]);
|
|
||||||
DicomRuleCondition::create([
|
|
||||||
'dicom_tag' => RawDicomTags::Modality->value,
|
|
||||||
'search_pattern' => 'MR,CT',
|
|
||||||
'dicom_routing_rule_id' => $rul_chev_mr_ct->id,
|
|
||||||
'match_mode' => MatchMode::InList->value,
|
|
||||||
'case_sensitive' => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
DicomRuleCondition::create([
|
|
||||||
'dicom_tag' => RawDicomTags::InstitutionName->value,
|
|
||||||
'search_pattern' => 'chevron',
|
|
||||||
'dicom_routing_rule_id' => $rul_chev_xr->id,
|
|
||||||
'match_mode' => MatchMode::Contains->value,
|
|
||||||
'case_sensitive' => false,
|
|
||||||
]);
|
|
||||||
DicomRuleCondition::create([
|
|
||||||
'dicom_tag' => RawDicomTags::Modality->value,
|
|
||||||
'search_pattern' => 'CR,DX,MG',
|
|
||||||
'dicom_routing_rule_id' => $rul_chev_xr->id,
|
|
||||||
'match_mode' => MatchMode::InList->value,
|
|
||||||
'case_sensitive' => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
DicomServer::create(
|
DicomServer::create(
|
||||||
[
|
[
|
||||||
'is_active' => true,
|
'is_active' => true,
|
||||||
'server_name' => 'Orthanc Main',
|
'server_name' => 'CTG-1',
|
||||||
|
'geo_code' => 'BD',
|
||||||
'host' => 'pacs.mylabctg.com',
|
'host' => 'pacs.mylabctg.com',
|
||||||
'port' => 8042,
|
'port' => 8042,
|
||||||
'rest_api_endpoint' => 'http://pacs.mylabctg.com:8042/',
|
'rest_api_endpoint' => 'http://pacs.mylabctg.com:8042/',
|
||||||
@ -122,8 +89,9 @@ public function run(): void
|
|||||||
DicomServer::create(
|
DicomServer::create(
|
||||||
[
|
[
|
||||||
'is_active' => false,
|
'is_active' => false,
|
||||||
'server_name' => 'Orthanc XR',
|
'server_name' => 'MAA-1',
|
||||||
'host' => 'pacs.mylabctg.com',
|
'host' => 'pacs.mylabctg.com',
|
||||||
|
'geo_code' => 'IN',
|
||||||
'port' => 8043,
|
'port' => 8043,
|
||||||
'rest_api_endpoint' => 'http://pacs.mylabctg.com:8042/',
|
'rest_api_endpoint' => 'http://pacs.mylabctg.com:8042/',
|
||||||
'ae_title' => 'RADFUSION',
|
'ae_title' => 'RADFUSION',
|
||||||
|
@ -27,6 +27,8 @@
|
|||||||
'resources/assets/vendor/libs/bootstrap-datepicker/bootstrap-datepicker.scss',
|
'resources/assets/vendor/libs/bootstrap-datepicker/bootstrap-datepicker.scss',
|
||||||
'resources/assets/vendor/libs/bootstrap-daterangepicker/bootstrap-daterangepicker.scss',
|
'resources/assets/vendor/libs/bootstrap-daterangepicker/bootstrap-daterangepicker.scss',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/7.2.3/css/flag-icons.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('vendor-script')
|
@section('vendor-script')
|
||||||
|
Loading…
Reference in New Issue
Block a user