343 lines
11 KiB
Python
343 lines
11 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
|
|
)
|
|
|
|
# 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")
|
|
|
|
metadata = models.JSONField(default=dict, blank=True)
|
|
|
|
class Meta:
|
|
ordering = ['name']
|
|
verbose_name_plural = 'Hospitals'
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
<<<<<<< HEAD
|
|
|
|
# TODO: Add branch
|
|
=======
|
|
>>>>>>> 12310a5 (update complain and add ai and sentiment analysis)
|
|
|
|
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)
|
|
employee_id = models.CharField(max_length=50, unique=True, db_index=True)
|
|
|
|
# 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)
|
|
|
|
status = models.CharField(max_length=20, choices=StatusChoices.choices, default=StatusChoices.ACTIVE)
|
|
|
|
def __str__(self):
|
|
prefix = "Dr. " if self.staff_type == self.StaffType.PHYSICIAN else ""
|
|
return f"{prefix}{self.first_name} {self.last_name}"
|
|
|
|
# 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
|