1101 lines
29 KiB
Python
1101 lines
29 KiB
Python
"""
|
|
Core app models for hospital management system.
|
|
Provides foundational infrastructure including tenant management,
|
|
audit logging, system configuration, and integration utilities.
|
|
"""
|
|
|
|
import uuid
|
|
from django.db import models
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
from django.utils import timezone
|
|
from django.core.validators import RegexValidator
|
|
from django.conf import settings
|
|
|
|
|
|
class Tenant(models.Model):
|
|
"""
|
|
Multi-tenant support for hospital organizations.
|
|
Each tenant represents a separate hospital or healthcare organization.
|
|
"""
|
|
|
|
ORGANIZATION_TYPE_CHOICES = [
|
|
('HOSPITAL', 'Hospital'),
|
|
('CLINIC', 'Clinic'),
|
|
('HEALTH_SYSTEM', 'Health System'),
|
|
('AMBULATORY', 'Ambulatory Care'),
|
|
('SPECIALTY', 'Specialty Practice'),
|
|
('URGENT_CARE', 'Urgent Care'),
|
|
('REHABILITATION', 'Rehabilitation Center'),
|
|
('LONG_TERM_CARE', 'Long-term Care'),
|
|
]
|
|
|
|
SUBSCRIPTION_PLAN_CHOICES = [
|
|
('BASIC', 'Basic'),
|
|
('STANDARD', 'Standard'),
|
|
('PREMIUM', 'Premium'),
|
|
('ENTERPRISE', 'Enterprise'),
|
|
]
|
|
# Tenant Information
|
|
tenant_id = models.UUIDField(
|
|
default=uuid.uuid4,
|
|
unique=True,
|
|
editable=False,
|
|
help_text='Unique tenant identifier'
|
|
)
|
|
name = models.CharField(
|
|
max_length=200,
|
|
help_text='Organization name'
|
|
)
|
|
display_name = models.CharField(
|
|
max_length=200,
|
|
help_text='Display name for the organization'
|
|
)
|
|
description = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Organization description'
|
|
)
|
|
|
|
# Organization Details
|
|
organization_type = models.CharField(
|
|
max_length=50,
|
|
choices=ORGANIZATION_TYPE_CHOICES,
|
|
default='HOSPITAL'
|
|
)
|
|
|
|
# Contact Information
|
|
address_line1 = models.CharField(
|
|
max_length=200,
|
|
help_text='Address line 1'
|
|
)
|
|
address_line2 = models.CharField(
|
|
max_length=200,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Address line 2'
|
|
)
|
|
city = models.CharField(
|
|
max_length=100,
|
|
help_text='City'
|
|
)
|
|
state = models.CharField(
|
|
max_length=100,
|
|
help_text='State or province'
|
|
)
|
|
postal_code = models.CharField(
|
|
max_length=20,
|
|
help_text='Postal code'
|
|
)
|
|
country = models.CharField(
|
|
max_length=100,
|
|
default='United States',
|
|
help_text='Country'
|
|
)
|
|
|
|
# Contact Details
|
|
phone_number = models.CharField(
|
|
max_length=20,
|
|
validators=[RegexValidator(
|
|
regex=r'^\+?1?\d{9,15}$',
|
|
message='Phone number must be entered in the format: "+999999999". Up to 15 digits allowed.'
|
|
)],
|
|
help_text='Primary phone number'
|
|
)
|
|
email = models.EmailField(
|
|
help_text='Primary email address'
|
|
)
|
|
website = models.URLField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Organization website'
|
|
)
|
|
|
|
# Licensing and Accreditation
|
|
license_number = models.CharField(
|
|
max_length=100,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Healthcare license number'
|
|
)
|
|
accreditation_body = models.CharField(
|
|
max_length=100,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Accreditation body (e.g., Joint Commission)'
|
|
)
|
|
accreditation_number = models.CharField(
|
|
max_length=100,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Accreditation number'
|
|
)
|
|
accreditation_expiry = models.DateField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Accreditation expiry date'
|
|
)
|
|
|
|
# Configuration
|
|
timezone = models.CharField(
|
|
max_length=50,
|
|
default='UTC',
|
|
help_text='Organization timezone'
|
|
)
|
|
locale = models.CharField(
|
|
max_length=10,
|
|
default='en-US',
|
|
help_text='Organization locale'
|
|
)
|
|
currency = models.CharField(
|
|
max_length=3,
|
|
default='USD',
|
|
help_text='Organization currency code'
|
|
)
|
|
|
|
# Subscription and Billing
|
|
subscription_plan = models.CharField(
|
|
max_length=50,
|
|
choices=SUBSCRIPTION_PLAN_CHOICES,
|
|
default='BASIC'
|
|
)
|
|
max_users = models.PositiveIntegerField(
|
|
default=50,
|
|
help_text='Maximum number of users allowed'
|
|
)
|
|
max_patients = models.PositiveIntegerField(
|
|
default=1000,
|
|
help_text='Maximum number of patients allowed'
|
|
)
|
|
|
|
# Status
|
|
is_active = models.BooleanField(
|
|
default=True,
|
|
help_text='Tenant is active'
|
|
)
|
|
is_trial = models.BooleanField(
|
|
default=False,
|
|
help_text='Tenant is on trial'
|
|
)
|
|
trial_expires_at = models.DateTimeField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Trial expiration date'
|
|
)
|
|
|
|
# Metadata
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = 'core_tenant'
|
|
verbose_name = 'Tenant'
|
|
verbose_name_plural = 'Tenants'
|
|
ordering = ['name']
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
@property
|
|
def is_trial_expired(self):
|
|
"""Check if trial has expired."""
|
|
if not self.is_trial or not self.trial_expires_at:
|
|
return False
|
|
return timezone.now() > self.trial_expires_at
|
|
|
|
def get_user_count(self):
|
|
"""Get current user count for this tenant."""
|
|
User = get_user_model()
|
|
return User.objects.filter(tenant=self).count()
|
|
|
|
def get_patient_count(self):
|
|
"""Get current patient count for this tenant."""
|
|
from patients.models import PatientProfile
|
|
return PatientProfile.objects.filter(tenant=self).count()
|
|
|
|
|
|
class AuditLogEntry(models.Model):
|
|
"""
|
|
Comprehensive audit logging for HIPAA/GDPR compliance.
|
|
Tracks all user actions and system events.
|
|
"""
|
|
RISK_LEVEL_CHOICES = [
|
|
('LOW', 'Low'),
|
|
('MEDIUM', 'Medium'),
|
|
('HIGH', 'High'),
|
|
('CRITICAL', 'Critical'),
|
|
]
|
|
|
|
EVENT_TYPE_CHOICES = [
|
|
('CREATE', 'Create'),
|
|
('READ', 'Read'),
|
|
('UPDATE', 'Update'),
|
|
('DELETE', 'Delete'),
|
|
('LOGIN', 'Login'),
|
|
('LOGOUT', 'Logout'),
|
|
('ACCESS', 'Access'),
|
|
('EXPORT', 'Export'),
|
|
('PRINT', 'Print'),
|
|
('SHARE', 'Share'),
|
|
('SYSTEM', 'System Event'),
|
|
('ERROR', 'Error'),
|
|
('SECURITY', 'Security Event'),
|
|
]
|
|
|
|
EVENT_CATEGORY_CHOICES = [
|
|
('AUTHENTICATION', 'Authentication'),
|
|
('AUTHORIZATION', 'Authorization'),
|
|
('DATA_ACCESS', 'Data Access'),
|
|
('DATA_MODIFICATION', 'Data Modification'),
|
|
('SYSTEM_ADMINISTRATION', 'System Administration'),
|
|
('PATIENT_DATA', 'Patient Data'),
|
|
('CLINICAL_DATA', 'Clinical Data'),
|
|
('FINANCIAL_DATA', 'Financial Data'),
|
|
('SECURITY', 'Security'),
|
|
('INTEGRATION', 'Integration'),
|
|
('REPORTING', 'Reporting'),
|
|
]
|
|
|
|
# Tenant
|
|
tenant = models.ForeignKey(
|
|
Tenant,
|
|
on_delete=models.CASCADE,
|
|
related_name='audit_logs'
|
|
)
|
|
|
|
# Log Information
|
|
log_id = models.UUIDField(
|
|
default=uuid.uuid4,
|
|
unique=True,
|
|
editable=False,
|
|
help_text='Unique log identifier'
|
|
)
|
|
|
|
# Event Information
|
|
event_type = models.CharField(
|
|
max_length=50,
|
|
choices=EVENT_TYPE_CHOICES
|
|
)
|
|
event_category = models.CharField(
|
|
max_length=50,
|
|
choices=EVENT_CATEGORY_CHOICES
|
|
)
|
|
|
|
# User Information
|
|
user = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='audit_logs'
|
|
)
|
|
user_email = models.EmailField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='User email at time of event'
|
|
)
|
|
user_role = models.CharField(
|
|
max_length=50,
|
|
blank=True,
|
|
null=True,
|
|
help_text='User role at time of event'
|
|
)
|
|
|
|
# Session Information
|
|
session_key = models.CharField(
|
|
max_length=40,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Session key'
|
|
)
|
|
ip_address = models.GenericIPAddressField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='IP address'
|
|
)
|
|
user_agent = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='User agent string'
|
|
)
|
|
|
|
# Object Information
|
|
content_type = models.ForeignKey(
|
|
ContentType,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True
|
|
)
|
|
object_id = models.PositiveIntegerField(
|
|
null=True,
|
|
blank=True
|
|
)
|
|
content_object = GenericForeignKey('content_type', 'object_id')
|
|
object_repr = models.CharField(
|
|
max_length=200,
|
|
blank=True,
|
|
null=True,
|
|
help_text='String representation of the object'
|
|
)
|
|
|
|
# Event Details
|
|
action = models.CharField(
|
|
max_length=200,
|
|
help_text='Action performed'
|
|
)
|
|
description = models.TextField(
|
|
help_text='Detailed description of the event'
|
|
)
|
|
|
|
# Data Changes
|
|
changes = models.JSONField(
|
|
default=dict,
|
|
help_text='Field changes (before/after values)'
|
|
)
|
|
additional_data = models.JSONField(
|
|
default=dict,
|
|
help_text='Additional event data'
|
|
)
|
|
|
|
# Patient Context
|
|
patient_id = models.CharField(
|
|
max_length=50,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Patient identifier if applicable'
|
|
)
|
|
patient_mrn = models.CharField(
|
|
max_length=50,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Patient MRN if applicable'
|
|
)
|
|
|
|
# Risk Assessment
|
|
risk_level = models.CharField(
|
|
max_length=20,
|
|
choices=RISK_LEVEL_CHOICES,
|
|
default='LOW'
|
|
)
|
|
|
|
# Compliance Flags
|
|
hipaa_relevant = models.BooleanField(
|
|
default=False,
|
|
help_text='Event is HIPAA relevant'
|
|
)
|
|
gdpr_relevant = models.BooleanField(
|
|
default=False,
|
|
help_text='Event is GDPR relevant'
|
|
)
|
|
|
|
# Status
|
|
is_successful = models.BooleanField(
|
|
default=True,
|
|
help_text='Event was successful'
|
|
)
|
|
error_message = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Error message if event failed'
|
|
)
|
|
|
|
# Metadata
|
|
timestamp = models.DateTimeField(
|
|
default=timezone.now,
|
|
help_text='Event timestamp'
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'core_audit_log_entry'
|
|
verbose_name = 'Audit Log Entry'
|
|
verbose_name_plural = 'Audit Log Entries'
|
|
ordering = ['-timestamp']
|
|
indexes = [
|
|
models.Index(fields=['tenant', 'event_type', 'timestamp']),
|
|
models.Index(fields=['user', 'timestamp']),
|
|
models.Index(fields=['patient_mrn', 'timestamp']),
|
|
models.Index(fields=['content_type', 'object_id']),
|
|
models.Index(fields=['risk_level', 'timestamp']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.event_type} - {self.action} by {self.user_email or 'System'}"
|
|
|
|
|
|
class SystemConfiguration(models.Model):
|
|
"""
|
|
System configuration settings for tenant-specific and global configurations.
|
|
"""
|
|
DATA_TYPE_CHOICES = [
|
|
('STRING', 'String'),
|
|
('INTEGER', 'Integer'),
|
|
('FLOAT', 'Float'),
|
|
('BOOLEAN', 'Boolean'),
|
|
('JSON', 'JSON'),
|
|
('DATE', 'Date'),
|
|
('DATETIME', 'DateTime'),
|
|
]
|
|
# Tenant (null for global configurations)
|
|
tenant = models.ForeignKey(
|
|
Tenant,
|
|
on_delete=models.CASCADE,
|
|
null=True,
|
|
blank=True,
|
|
related_name='configurations'
|
|
)
|
|
|
|
# Configuration Information
|
|
key = models.CharField(
|
|
max_length=200,
|
|
help_text='Configuration key'
|
|
)
|
|
value = models.TextField(
|
|
help_text='Configuration value'
|
|
)
|
|
data_type = models.CharField(
|
|
max_length=20,
|
|
choices=DATA_TYPE_CHOICES,
|
|
default='STRING'
|
|
)
|
|
|
|
# Configuration Metadata
|
|
category = models.CharField(
|
|
max_length=100,
|
|
help_text='Configuration category'
|
|
)
|
|
description = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Configuration description'
|
|
)
|
|
|
|
# Validation
|
|
validation_rules = models.JSONField(
|
|
default=dict,
|
|
help_text='Validation rules for the configuration value'
|
|
)
|
|
default_value = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Default value'
|
|
)
|
|
|
|
# Access Control
|
|
is_sensitive = models.BooleanField(
|
|
default=False,
|
|
help_text='Configuration contains sensitive data'
|
|
)
|
|
is_encrypted = models.BooleanField(
|
|
default=False,
|
|
help_text='Configuration value is encrypted'
|
|
)
|
|
required_permission = models.CharField(
|
|
max_length=100,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Permission required to modify this configuration'
|
|
)
|
|
|
|
# Status
|
|
is_active = models.BooleanField(
|
|
default=True,
|
|
help_text='Configuration is active'
|
|
)
|
|
is_readonly = models.BooleanField(
|
|
default=False,
|
|
help_text='Configuration is read-only'
|
|
)
|
|
|
|
# Metadata
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
updated_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='updated_configurations'
|
|
)
|
|
|
|
class Meta:
|
|
db_table = 'core_system_configuration'
|
|
verbose_name = 'System Configuration'
|
|
verbose_name_plural = 'System Configurations'
|
|
unique_together = ['tenant', 'key']
|
|
ordering = ['category', 'key']
|
|
|
|
def __str__(self):
|
|
tenant_name = self.tenant.name if self.tenant else 'Global'
|
|
return f"{tenant_name} - {self.key}"
|
|
|
|
def get_typed_value(self):
|
|
"""Get the configuration value converted to its proper type."""
|
|
if self.data_type == 'INTEGER':
|
|
return int(self.value)
|
|
elif self.data_type == 'FLOAT':
|
|
return float(self.value)
|
|
elif self.data_type == 'BOOLEAN':
|
|
return self.value.lower() in ('true', '1', 'yes', 'on')
|
|
elif self.data_type == 'JSON':
|
|
import json
|
|
return json.loads(self.value)
|
|
elif self.data_type == 'DATE':
|
|
from datetime import datetime
|
|
return datetime.strptime(self.value, '%Y-%m-%d').date()
|
|
elif self.data_type == 'DATETIME':
|
|
from datetime import datetime
|
|
return datetime.fromisoformat(self.value)
|
|
else:
|
|
return self.value
|
|
|
|
|
|
class SystemNotification(models.Model):
|
|
"""
|
|
System-wide notifications and announcements.
|
|
"""
|
|
TARGET_AUDIENCE = [
|
|
('ALL_USERS', 'All Users'),
|
|
('ADMINISTRATORS', 'Administrators'),
|
|
('CLINICAL_STAFF', 'Clinical Staff'),
|
|
('SUPPORT_STAFF', 'Support Staff'),
|
|
('SPECIFIC_ROLES', 'Specific Roles'),
|
|
('SPECIFIC_USERS', 'Specific Users'),
|
|
]
|
|
|
|
NOTIFICATION_TYPE_CHOICES = [
|
|
('INFO', 'Information'),
|
|
('WARNING', 'Warning'),
|
|
('ERROR', 'Error'),
|
|
('SUCCESS', 'Success'),
|
|
('MAINTENANCE', 'Maintenance'),
|
|
('SECURITY', 'Security Alert'),
|
|
('FEATURE', 'New Feature'),
|
|
('UPDATE', 'System Update'),
|
|
]
|
|
|
|
PRIORITY_CHOICES = [
|
|
('LOW', 'Low'),
|
|
('MEDIUM', 'Medium'),
|
|
('HIGH', 'High'),
|
|
('URGENT', 'Urgent'),
|
|
]
|
|
|
|
# Tenant (null for global notifications)
|
|
tenant = models.ForeignKey(
|
|
Tenant,
|
|
on_delete=models.CASCADE,
|
|
null=True,
|
|
blank=True,
|
|
related_name='notifications'
|
|
)
|
|
|
|
# Notification Information
|
|
notification_id = models.UUIDField(
|
|
default=uuid.uuid4,
|
|
unique=True,
|
|
editable=False,
|
|
help_text='Unique notification identifier'
|
|
)
|
|
title = models.CharField(
|
|
max_length=200,
|
|
help_text='Notification title'
|
|
)
|
|
message = models.TextField(
|
|
help_text='Notification message'
|
|
)
|
|
|
|
# Notification Type
|
|
notification_type = models.CharField(
|
|
max_length=30,
|
|
choices=NOTIFICATION_TYPE_CHOICES,
|
|
)
|
|
|
|
# Priority
|
|
priority = models.CharField(
|
|
max_length=20,
|
|
choices=PRIORITY_CHOICES,
|
|
default='MEDIUM'
|
|
)
|
|
|
|
# Targeting
|
|
target_audience = models.CharField(
|
|
max_length=30,
|
|
choices=TARGET_AUDIENCE,
|
|
default='ALL_USERS'
|
|
)
|
|
target_roles = models.JSONField(
|
|
default=list,
|
|
help_text='Target user roles (if target_audience is SPECIFIC_ROLES)'
|
|
)
|
|
target_users = models.ManyToManyField(
|
|
settings.AUTH_USER_MODEL,
|
|
blank=True,
|
|
related_name='targeted_notifications'
|
|
)
|
|
|
|
# Display Settings
|
|
is_dismissible = models.BooleanField(
|
|
default=True,
|
|
help_text='Users can dismiss this notification'
|
|
)
|
|
auto_dismiss_after = models.PositiveIntegerField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Auto-dismiss after X seconds'
|
|
)
|
|
show_on_login = models.BooleanField(
|
|
default=False,
|
|
help_text='Show notification on user login'
|
|
)
|
|
|
|
# Scheduling
|
|
start_date = models.DateTimeField(
|
|
default=timezone.now,
|
|
help_text='Notification start date'
|
|
)
|
|
end_date = models.DateTimeField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Notification end date'
|
|
)
|
|
|
|
# Actions
|
|
action_url = models.URLField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Action URL for the notification'
|
|
)
|
|
action_text = models.CharField(
|
|
max_length=100,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Action button text'
|
|
)
|
|
|
|
# Status
|
|
is_active = models.BooleanField(
|
|
default=True,
|
|
help_text='Notification is active'
|
|
)
|
|
|
|
# Metadata
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
created_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
related_name='created_notifications'
|
|
)
|
|
|
|
class Meta:
|
|
db_table = 'core_system_notification'
|
|
verbose_name = 'System Notification'
|
|
verbose_name_plural = 'System Notifications'
|
|
ordering = ['-priority', '-created_at']
|
|
|
|
def __str__(self):
|
|
return self.title
|
|
|
|
@property
|
|
def is_visible(self):
|
|
"""Check if notification should be visible now."""
|
|
now = timezone.now()
|
|
if not self.is_active:
|
|
return False
|
|
if now < self.start_date:
|
|
return False
|
|
if self.end_date and now > self.end_date:
|
|
return False
|
|
return True
|
|
|
|
|
|
class IntegrationLog(models.Model):
|
|
"""
|
|
Integration logging for external system communications.
|
|
"""
|
|
STATUS_CHOICES = [
|
|
('SUCCESS', 'Success'),
|
|
('FAILED', 'Failed'),
|
|
('PENDING', 'Pending'),
|
|
('TIMEOUT', 'Timeout'),
|
|
('RETRY', 'Retry'),
|
|
]
|
|
|
|
INTEGRATION_TYPE_CHOICES = [
|
|
('HL7', 'HL7 Message'),
|
|
('DICOM', 'DICOM Communication'),
|
|
('API', 'API Call'),
|
|
('DATABASE', 'Database Sync'),
|
|
('FILE_TRANSFER', 'File Transfer'),
|
|
('WEBHOOK', 'Webhook'),
|
|
('EMAIL', 'Email'),
|
|
('SMS', 'SMS'),
|
|
]
|
|
|
|
# Tenant
|
|
tenant = models.ForeignKey(
|
|
Tenant,
|
|
on_delete=models.CASCADE,
|
|
related_name='integration_logs'
|
|
)
|
|
|
|
# Log Information
|
|
log_id = models.UUIDField(
|
|
default=uuid.uuid4,
|
|
unique=True,
|
|
editable=False,
|
|
help_text='Unique log identifier'
|
|
)
|
|
|
|
# Integration Information
|
|
integration_type = models.CharField(
|
|
max_length=30,
|
|
choices=INTEGRATION_TYPE_CHOICES
|
|
)
|
|
direction = models.CharField(
|
|
max_length=10,
|
|
choices=[
|
|
('INBOUND', 'Inbound'),
|
|
('OUTBOUND', 'Outbound'),
|
|
]
|
|
)
|
|
|
|
# External System
|
|
external_system = models.CharField(
|
|
max_length=200,
|
|
help_text='External system name'
|
|
)
|
|
endpoint = models.CharField(
|
|
max_length=500,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Integration endpoint'
|
|
)
|
|
|
|
# Message Information
|
|
message_type = models.CharField(
|
|
max_length=100,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Message type (e.g., HL7 message type)'
|
|
)
|
|
message_id = models.CharField(
|
|
max_length=200,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Message identifier'
|
|
)
|
|
correlation_id = models.UUIDField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Correlation ID for tracking related messages'
|
|
)
|
|
|
|
# Content
|
|
request_data = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Request data sent'
|
|
)
|
|
response_data = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Response data received'
|
|
)
|
|
|
|
# Status
|
|
status = models.CharField(
|
|
max_length=20,
|
|
choices=STATUS_CHOICES,
|
|
)
|
|
|
|
# Error Information
|
|
error_code = models.CharField(
|
|
max_length=50,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Error code'
|
|
)
|
|
error_message = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Error message'
|
|
)
|
|
|
|
# Performance
|
|
processing_time_ms = models.PositiveIntegerField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Processing time in milliseconds'
|
|
)
|
|
|
|
# Metadata
|
|
timestamp = models.DateTimeField(
|
|
default=timezone.now,
|
|
help_text='Log timestamp'
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'core_integration_log'
|
|
verbose_name = 'Integration Log'
|
|
verbose_name_plural = 'Integration Logs'
|
|
ordering = ['-timestamp']
|
|
indexes = [
|
|
models.Index(fields=['tenant', 'integration_type', 'timestamp']),
|
|
models.Index(fields=['external_system', 'status']),
|
|
models.Index(fields=['correlation_id']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.integration_type} - {self.external_system} - {self.status}"
|
|
|
|
|
|
|
|
|
|
class Department(models.Model):
|
|
"""
|
|
Hospital department model for organizational structure.
|
|
Represents different departments within a healthcare organization.
|
|
"""
|
|
|
|
DEPARTMENT_TYPE_CHOICES = [
|
|
('CLINICAL', 'Clinical Department'),
|
|
('ANCILLARY', 'Ancillary Services'),
|
|
('SUPPORT', 'Support Services'),
|
|
('ADMINISTRATIVE', 'Administrative'),
|
|
('DIAGNOSTIC', 'Diagnostic Services'),
|
|
('THERAPEUTIC', 'Therapeutic Services'),
|
|
('EMERGENCY', 'Emergency Services'),
|
|
('SURGICAL', 'Surgical Services'),
|
|
('MEDICAL', 'Medical Services'),
|
|
('NURSING', 'Nursing Services'),
|
|
('PHARMACY', 'Pharmacy'),
|
|
('LABORATORY', 'Laboratory'),
|
|
('RADIOLOGY', 'Radiology'),
|
|
('REHABILITATION', 'Rehabilitation'),
|
|
('MENTAL_HEALTH', 'Mental Health'),
|
|
('PEDIATRIC', 'Pediatric'),
|
|
('OBSTETRIC', 'Obstetric'),
|
|
('ONCOLOGY', 'Oncology'),
|
|
('CARDIOLOGY', 'Cardiology'),
|
|
('NEUROLOGY', 'Neurology'),
|
|
('ORTHOPEDIC', 'Orthopedic'),
|
|
('OTHER', 'Other'),
|
|
]
|
|
|
|
# Tenant relationship
|
|
tenant = models.ForeignKey(
|
|
Tenant,
|
|
on_delete=models.CASCADE,
|
|
related_name='departments',
|
|
help_text='Organization tenant'
|
|
)
|
|
|
|
# Department Information
|
|
department_id = models.UUIDField(
|
|
default=uuid.uuid4,
|
|
unique=True,
|
|
editable=False,
|
|
help_text='Unique department identifier'
|
|
)
|
|
code = models.CharField(
|
|
max_length=20,
|
|
help_text='Department code (e.g., CARD, EMER, SURG)'
|
|
)
|
|
name = models.CharField(
|
|
max_length=100,
|
|
help_text='Department name'
|
|
)
|
|
description = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Department description'
|
|
)
|
|
|
|
# Department Classification
|
|
department_type = models.CharField(
|
|
max_length=30,
|
|
choices=DEPARTMENT_TYPE_CHOICES,
|
|
help_text='Type of department'
|
|
)
|
|
|
|
# Organizational Structure
|
|
parent_department = models.ForeignKey(
|
|
'self',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='sub_departments',
|
|
help_text='Parent department (for hierarchical structure)'
|
|
)
|
|
|
|
# Management
|
|
department_head = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='headed_departments',
|
|
help_text='Department head/manager'
|
|
)
|
|
|
|
# Contact Information
|
|
phone = models.CharField(
|
|
max_length=20,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Department phone number'
|
|
)
|
|
extension = models.CharField(
|
|
max_length=10,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Phone extension'
|
|
)
|
|
email = models.EmailField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Department email'
|
|
)
|
|
|
|
# Location
|
|
building = models.CharField(
|
|
max_length=50,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Building name or number'
|
|
)
|
|
floor = models.CharField(
|
|
max_length=20,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Floor number or name'
|
|
)
|
|
wing = models.CharField(
|
|
max_length=20,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Wing or section'
|
|
)
|
|
room_numbers = models.CharField(
|
|
max_length=100,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Room numbers (e.g., 101-110, 201A-205C)'
|
|
)
|
|
|
|
# Operational Information
|
|
is_active = models.BooleanField(
|
|
default=True,
|
|
help_text='Department is active'
|
|
)
|
|
is_24_hour = models.BooleanField(
|
|
default=False,
|
|
help_text='Department operates 24 hours'
|
|
)
|
|
operating_hours = models.JSONField(
|
|
default=dict,
|
|
blank=True,
|
|
help_text='Operating hours by day of week'
|
|
)
|
|
|
|
# Budget and Cost Center
|
|
cost_center_code = models.CharField(
|
|
max_length=20,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Cost center code for financial tracking'
|
|
)
|
|
budget_code = models.CharField(
|
|
max_length=20,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Budget code'
|
|
)
|
|
|
|
# Staffing
|
|
authorized_positions = models.PositiveIntegerField(
|
|
default=0,
|
|
help_text='Number of authorized positions'
|
|
)
|
|
current_staff_count = models.PositiveIntegerField(
|
|
default=0,
|
|
help_text='Current number of staff members'
|
|
)
|
|
|
|
# Quality and Compliance
|
|
accreditation_required = models.BooleanField(
|
|
default=False,
|
|
help_text='Department requires special accreditation'
|
|
)
|
|
accreditation_body = models.CharField(
|
|
max_length=100,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Accrediting body (e.g., Joint Commission, CAP)'
|
|
)
|
|
last_inspection_date = models.DateField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Last inspection date'
|
|
)
|
|
next_inspection_date = models.DateField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Next scheduled inspection date'
|
|
)
|
|
|
|
# Metadata
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
created_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='created_departments',
|
|
help_text='User who created the department'
|
|
)
|
|
|
|
class Meta:
|
|
db_table = 'core_department'
|
|
verbose_name = 'Department'
|
|
verbose_name_plural = 'Departments'
|
|
ordering = ['name']
|
|
indexes = [
|
|
models.Index(fields=['tenant', 'department_type']),
|
|
models.Index(fields=['code']),
|
|
models.Index(fields=['is_active']),
|
|
models.Index(fields=['parent_department']),
|
|
]
|
|
unique_together = ['tenant', 'code']
|
|
|
|
def __str__(self):
|
|
return f"{self.name} ({self.code})"
|
|
|
|
@property
|
|
def full_name(self):
|
|
"""Return full department name with parent if applicable"""
|
|
if self.parent_department:
|
|
return f"{self.parent_department.name} - {self.name}"
|
|
return self.name
|
|
|
|
@property
|
|
def staffing_percentage(self):
|
|
"""Calculate current staffing percentage"""
|
|
if self.authorized_positions > 0:
|
|
return (self.current_staff_count / self.authorized_positions) * 100
|
|
return 0
|
|
|
|
def get_all_sub_departments(self):
|
|
"""Get all sub-departments recursively"""
|
|
sub_departments = []
|
|
for sub_dept in self.sub_departments.all():
|
|
sub_departments.append(sub_dept)
|
|
sub_departments.extend(sub_dept.get_all_sub_departments())
|
|
return sub_departments
|
|
|