# Domain Model

## Entities & Relationships

```
Department (1) ─────< (n) User        (a department has many users)
Department (1) ─────< (n) Procedure   (a department performs many procedures)

Modality (1) ───────< (n) Region      (a modality has many body regions)
Region (1) ─────────< (n) Procedure   (a region has many procedures)

Patient (1) ────────< (n) Study       (a patient can have many studies)
User (1) ───────────< (n) Study       (through study_user pivot)

Study (1) ──────────< (1) Report      (each study has one report)
User (1) ───────────< (n) Report      (reporter assigned_at)

Role (n) ───────────< (n) User        (through role_user pivot)
Role (n) ───────────< (n) Permission  (through permission_role pivot)
```

## Key Attributes

### Patient
| Field | Type | Notes |
|-------|------|-------|
| id | bigint PK | auto |
| mrn | string(20) | unique, `P` + 3-digit zero-padded |
| full_name | string(255) | |
| identity_no | string(20) | unique, nullable |
| date_of_birth | date | nullable |
| gender | string(10) | Male/Female/nullable |
| phone | string(20) | nullable |
| address | text | nullable |
| is_active | boolean | default true |

### Study
| Field | Type | Notes |
|-------|------|-------|
| id | bigint PK | auto |
| patient_id | bigint FK | |
| modality_id | bigint FK | |
| region_id | bigint FK | |
| procedure_id | bigint FK | |
| accession_no | string(20) | unique, collision-safe via retry |
| status | string(20) | Scheduled/In Progress/Completed/Cancelled/Rejected |
| requested_by | string(255) | nullable (requesting doctor name) |
| requesting_department | string(255) | nullable |
| clinical_history | text | nullable |
| notes | text | nullable |
| scheduled_at | datetime | nullable |
| performed_at | datetime | nullable |
| completed_at | datetime | nullable |

### Report
| Field | Type | Notes |
|-------|------|-------|
| id | bigint PK | auto |
| study_id | bigint FK | unique |
| report_body | longtext | Quill HTML |
| status | string(20) | draft/finalized |
| report_requested | boolean | default false |
| reason | text | nullable (for cancel/reject reason) |
| report_image | string(255) | nullable (future) |
| finalized_at | datetime | nullable |
| created_by | bigint FK → User | |

### User (custom fields beyond Laravel defaults)
| Field | Type | Notes |
|-------|------|-------|
| phone | string(20) | nullable |
| nickname | string(100) | nullable |
| avatar | string(255) | path relative to storage |
| designation | string(100) | nullable |
| grade | string(50) | nullable |
| is_active | boolean | default true |

## Status Machine (Study)

```
Scheduled ──→ In Progress ──→ Completed ──→ (report_requested flag)
                                   │              │
                                   ↓              ↓
                              Cancelled      Rejected
```

Valid transitions enforced in `StudyController` update method.

## Status Machine (Report)

```
No report ──→ draft ──→ finalized
                  ↑         │
                  └─────────┘  (no un-finalize)
```

## Study-User Assignment

The `study_user` pivot table tracks which users are assigned to a study:
- `assigned_at` timestamp recorded on assignment
- A study can have multiple assigned users (e.g., radiographer performs, radiologist reports)
- Unused pivot fields: `role` (the role the user played in this study)

## Accession Number Generation

`Study::generateUniqueAccession()`:
- Format: `RIS-YYYYMMDD-XXX` where XXX is random 3-digit number
- Retries up to 10 times on collision
- Called from `Study::creating` boot event

## Medical Record Number (MRN) Generation

`Patient::generateUniqueMrn()`:
- Format: `P` + 3-digit zero-padded sequential number
- Finds highest existing MRN, increments
- Example: P001, P002, ..., P025
