419 lines
13 KiB
Python

"""
Organizations models - Hospital, Department, Physician, Employee, Patient
"""
from django.db import models
from apps.core.models import TimeStampedModel, UUIDModel, StatusChoices
class Organization(UUIDModel, TimeStampedModel):
"""Top-level healthcare organization/company"""
name = models.CharField(max_length=200)
name_ar = models.CharField(max_length=200, blank=True, verbose_name="Name (Arabic)")
code = models.CharField(max_length=50, unique=True, db_index=True)
# Contact information
phone = models.CharField(max_length=20, blank=True)
email = models.EmailField(blank=True)
address = models.TextField(blank=True)
city = models.CharField(max_length=100, blank=True)
# Status
status = models.CharField(
max_length=20,
choices=StatusChoices.choices,
default=StatusChoices.ACTIVE,
db_index=True
)
# Branding and metadata
logo = models.ImageField(upload_to='organizations/logos/', null=True, blank=True)
website = models.URLField(blank=True)
license_number = models.CharField(max_length=100, blank=True)
class Meta:
ordering = ['name']
verbose_name = 'Organization'
verbose_name_plural = 'Organizations'
def __str__(self):
return self.name
class Hospital(UUIDModel, TimeStampedModel):
"""Hospital/Facility model"""
organization = models.ForeignKey(
Organization,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='hospitals',
help_text="Parent organization (null for backward compatibility)"
)
name = models.CharField(max_length=200)
name_ar = models.CharField(max_length=200, blank=True, verbose_name="Name (Arabic)")
code = models.CharField(max_length=50, unique=True, db_index=True)
# Contact information
address = models.TextField(blank=True)
city = models.CharField(max_length=100, blank=True)
phone = models.CharField(max_length=20, blank=True)
email = models.EmailField(blank=True)
# Status
status = models.CharField(
max_length=20,
choices=StatusChoices.choices,
default=StatusChoices.ACTIVE,
db_index=True
)
# Executive leadership
ceo = models.ForeignKey(
'accounts.User',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='hospitals_as_ceo',
verbose_name='CEO',
help_text="Chief Executive Officer"
)
medical_director = models.ForeignKey(
'accounts.User',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='hospitals_as_medical_director',
verbose_name='Medical Director',
help_text="Medical Director"
)
coo = models.ForeignKey(
'accounts.User',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='hospitals_as_coo',
verbose_name='COO',
help_text="Chief Operating Officer"
)
cfo = models.ForeignKey(
'accounts.User',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='hospitals_as_cfo',
verbose_name='CFO',
help_text="Chief Financial Officer"
)
# Metadata
license_number = models.CharField(max_length=100, blank=True)
capacity = models.IntegerField(null=True, blank=True, help_text="Bed capacity")
metadata = models.JSONField(default=dict, blank=True, help_text="Hospital configuration settings")
class Meta:
ordering = ['name']
verbose_name_plural = 'Hospitals'
def __str__(self):
return self.name
class Department(UUIDModel, TimeStampedModel):
"""Department within a hospital"""
hospital = models.ForeignKey(Hospital, on_delete=models.CASCADE, related_name='departments')
name = models.CharField(max_length=200)
name_ar = models.CharField(max_length=200, blank=True, verbose_name="Name (Arabic)")
code = models.CharField(max_length=50, db_index=True)
# Hierarchy
parent = models.ForeignKey(
'self',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='sub_departments'
)
# Manager
manager = models.ForeignKey(
'accounts.User',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='managed_departments'
)
# Contact
phone = models.CharField(max_length=20, blank=True)
email = models.EmailField(blank=True)
location = models.CharField(max_length=200, blank=True, help_text="Building/Floor/Room")
# Status
status = models.CharField(
max_length=20,
choices=StatusChoices.choices,
default=StatusChoices.ACTIVE,
db_index=True
)
class Meta:
ordering = ['hospital', 'name']
unique_together = [['hospital', 'code']]
def __str__(self):
return f"{self.hospital.name} - {self.name}"
class Staff(UUIDModel, TimeStampedModel):
class StaffType(models.TextChoices):
PHYSICIAN = 'physician', 'Physician'
NURSE = 'nurse', 'Nurse'
ADMIN = 'admin', 'Administrative'
OTHER = 'other', 'Other'
# Link to User (Keep it optional for external/temp staff)
user = models.OneToOneField(
'accounts.User',
on_delete=models.SET_NULL,
null=True, blank=True,
related_name='staff_profile'
)
# Unified Identity (AI will search these 4 fields)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
first_name_ar = models.CharField(max_length=100, blank=True)
last_name_ar = models.CharField(max_length=100, blank=True)
# Role Logic
staff_type = models.CharField(max_length=20, choices=StaffType.choices)
job_title = models.CharField(max_length=200) # "Cardiologist", "Senior Nurse", etc.
# Professional Data (Nullable for non-physicians)
license_number = models.CharField(max_length=100, unique=True, null=True, blank=True)
specialization = models.CharField(max_length=200, blank=True)
email = models.EmailField(blank=True)
phone = models.CharField(max_length=20, blank=True, verbose_name="Phone Number")
employee_id = models.CharField(max_length=50, unique=True, db_index=True)
# Original name from CSV (preserves exact format)
name = models.CharField(max_length=300, blank=True, verbose_name="Full Name (Original)")
# Organization
hospital = models.ForeignKey(Hospital, on_delete=models.CASCADE, related_name='staff')
department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True, blank=True, related_name='staff')
# Additional fields from CSV import
country = models.CharField(max_length=100, blank=True, verbose_name="Country")
location = models.CharField(max_length=200, blank=True, verbose_name="Location")
gender = models.CharField(
max_length=10,
choices=[('male', 'Male'), ('female', 'Female'), ('other', 'Other')],
blank=True
)
department_name = models.CharField(max_length=200, blank=True, verbose_name="Department (Original)")
section = models.CharField(max_length=200, blank=True, verbose_name="Section")
subsection = models.CharField(max_length=200, blank=True, verbose_name="Subsection")
# Self-referential manager field for hierarchy
report_to = models.ForeignKey(
'self',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='direct_reports',
verbose_name="Reports To"
)
status = models.CharField(max_length=20, choices=StatusChoices.choices, default=StatusChoices.ACTIVE)
def __str__(self):
# Use original name if available, otherwise use first_name + last_name
if self.name:
return self.name
prefix = "Dr. " if self.staff_type == self.StaffType.PHYSICIAN else ""
return f"{prefix}{self.first_name} {self.last_name}"
def get_full_name(self):
"""Get full name including Arabic if available"""
if self.first_name_ar and self.last_name_ar:
return f"{self.first_name} {self.last_name} ({self.first_name_ar} {self.last_name_ar})"
return f"{self.first_name} {self.last_name}"
def get_org_info(self):
"""Get organization and department information"""
parts = [self.hospital.name]
if self.department:
parts.append(self.department.name)
if self.department_name:
parts.append(self.department_name)
return " - ".join(parts)
# TODO Add Section
# class Physician(UUIDModel, TimeStampedModel):
# """Physician/Doctor model"""
# # Link to user account (optional - some physicians may not have system access)
# user = models.OneToOneField(
# 'accounts.User',
# on_delete=models.SET_NULL,
# null=True,
# blank=True,
# related_name='physician_profile'
# )
# # Basic information
# first_name = models.CharField(max_length=100)
# last_name = models.CharField(max_length=100)
# first_name_ar = models.CharField(max_length=100, blank=True)
# last_name_ar = models.CharField(max_length=100, blank=True)
# # Professional information
# license_number = models.CharField(max_length=100, unique=True, db_index=True)
# specialization = models.CharField(max_length=200)
# # Organization
# hospital = models.ForeignKey(Hospital, on_delete=models.CASCADE, related_name='physicians')
# department = models.ForeignKey(
# Department,
# on_delete=models.SET_NULL,
# null=True,
# blank=True,
# related_name='physicians'
# )
# # Contact
# phone = models.CharField(max_length=20, blank=True)
# email = models.EmailField(blank=True)
# # Status
# status = models.CharField(
# max_length=20,
# choices=StatusChoices.choices,
# default=StatusChoices.ACTIVE,
# db_index=True
# )
# class Meta:
# ordering = ['last_name', 'first_name']
# def __str__(self):
# return f"Dr. {self.first_name} {self.last_name}"
# def get_full_name(self):
# return f"{self.first_name} {self.last_name}"
# class Employee(UUIDModel, TimeStampedModel):
# """Employee model (non-physician staff)"""
# user = models.OneToOneField(
# 'accounts.User',
# on_delete=models.CASCADE,
# related_name='employee_profile'
# )
# # Organization
# hospital = models.ForeignKey(Hospital, on_delete=models.CASCADE, related_name='employees')
# department = models.ForeignKey(
# Department,
# on_delete=models.SET_NULL,
# null=True,
# blank=True,
# related_name='employees'
# )
# # Job information
# employee_id = models.CharField(max_length=50, unique=True, db_index=True)
# job_title = models.CharField(max_length=200)
# hire_date = models.DateField(null=True, blank=True)
# # Status
# status = models.CharField(
# max_length=20,
# choices=StatusChoices.choices,
# default=StatusChoices.ACTIVE,
# db_index=True
# )
# class Meta:
# ordering = ['user__last_name', 'user__first_name']
# def __str__(self):
# return f"{self.user.get_full_name()} - {self.job_title}"
class Patient(UUIDModel, TimeStampedModel):
"""Patient model"""
# Basic information
mrn = models.CharField(max_length=50, unique=True, db_index=True, verbose_name="Medical Record Number")
national_id = models.CharField(max_length=50, blank=True, db_index=True)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
first_name_ar = models.CharField(max_length=100, blank=True)
last_name_ar = models.CharField(max_length=100, blank=True)
# Demographics
date_of_birth = models.DateField(null=True, blank=True)
gender = models.CharField(
max_length=10,
choices=[('male', 'Male'), ('female', 'Female'), ('other', 'Other')],
blank=True
)
# Contact
phone = models.CharField(max_length=20, blank=True)
email = models.EmailField(blank=True)
address = models.TextField(blank=True)
city = models.CharField(max_length=100, blank=True)
# Primary hospital
primary_hospital = models.ForeignKey(
Hospital,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='patients'
)
# Status
status = models.CharField(
max_length=20,
choices=StatusChoices.choices,
default=StatusChoices.ACTIVE,
db_index=True
)
class Meta:
ordering = ['last_name', 'first_name']
def __str__(self):
return f"{self.first_name} {self.last_name} (MRN: {self.mrn})"
def get_full_name(self):
return f"{self.first_name} {self.last_name}"
@staticmethod
def generate_mrn():
"""
Generate a unique Medical Record Number (MRN).
Returns:
str: A unique MRN in the format: PTN-YYYYMMDD-XXXXXX
where XXXXXX is a random 6-digit number
"""
import random
from datetime import datetime
# Generate MRN with date prefix for better traceability
date_prefix = datetime.now().strftime('%Y%m%d')
random_suffix = random.randint(100000, 999999)
mrn = f"PTN-{date_prefix}-{random_suffix}"
# Ensure uniqueness (in case of collision)
while Patient.objects.filter(mrn=mrn).exists():
random_suffix = random.randint(100000, 999999)
mrn = f"PTN-{date_prefix}-{random_suffix}"
return mrn