854 lines
23 KiB
Python
854 lines
23 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.
|
|
"""
|
|
|
|
class OrganizationType(models.TextChoices):
|
|
HOSPITAL = 'HOSPITAL', 'Hospital'
|
|
CLINIC = 'CLINIC', 'Clinic'
|
|
HEALTH_SYSTEM = 'HEALTH_SYSTEM', 'Health System'
|
|
AMBULATORY = 'AMBULATORY', 'Ambulatory Care'
|
|
SPECIALTY = 'SPECIALTY', 'Specialty Practice'
|
|
URGENT_CARE = 'URGENT_CARE', 'Urgent Care'
|
|
REHABILITATION = 'REHABILITATION', 'Rehabilitation Center'
|
|
LONG_TERM_CARE = 'LONG_TERM_CARE', 'Long-term Care'
|
|
|
|
class SubscriptionPlan(models.TextChoices):
|
|
BASIC = 'BASIC', 'Basic'
|
|
STANDARD = 'STANDARD', 'Standard'
|
|
PREMIUM = 'PREMIUM', 'Premium'
|
|
ENTERPRISE = '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=OrganizationType.choices,
|
|
default=OrganizationType.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='Saudi Arabia',
|
|
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='SAR',
|
|
help_text='Organization currency code'
|
|
)
|
|
|
|
# Subscription and Billing
|
|
subscription_plan = models.CharField(
|
|
max_length=50,
|
|
choices=SubscriptionPlan.choices,
|
|
default=SubscriptionPlan.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.
|
|
"""
|
|
|
|
class RiskLevel(models.TextChoices):
|
|
LOW = 'LOW', 'Low'
|
|
MEDIUM = 'MEDIUM', 'Medium'
|
|
HIGH = 'HIGH', 'High'
|
|
CRITICAL = 'CRITICAL', 'Critical'
|
|
|
|
class EventType(models.TextChoices):
|
|
CREATE = 'CREATE', 'Create'
|
|
READ = 'READ', 'Read'
|
|
UPDATE = 'UPDATE', 'Update'
|
|
DELETE = 'DELETE', 'Delete'
|
|
LOGIN = 'LOGIN', 'Login'
|
|
LOGOUT = 'LOGOUT', 'Logout'
|
|
ACCESS = 'ACCESS', 'Access'
|
|
EXPORT = 'EXPORT', 'Export'
|
|
PRINT = 'PRINT', 'Print'
|
|
SHARE = 'SHARE', 'Share'
|
|
SYSTEM = 'SYSTEM', 'System Event'
|
|
ERROR = 'ERROR', 'Error'
|
|
SECURITY = 'SECURITY', 'Security Event'
|
|
|
|
class EventCategory(models.TextChoices):
|
|
AUTHENTICATION = 'AUTHENTICATION', 'Authentication'
|
|
AUTHORIZATION = 'AUTHORIZATION', 'Authorization'
|
|
DATA_ACCESS = 'DATA_ACCESS', 'Data Access'
|
|
DATA_MODIFICATION = 'DATA_MODIFICATION', 'Data Modification'
|
|
SYSTEM_ADMINISTRATION = 'SYSTEM_ADMINISTRATION', 'System Administration'
|
|
PATIENT_DATA = 'PATIENT_DATA', 'Patient Data'
|
|
CLINICAL_DATA = 'CLINICAL_DATA', 'Clinical Data'
|
|
FINANCIAL_DATA = 'FINANCIAL_DATA', 'Financial Data'
|
|
SECURITY = 'SECURITY', 'Security'
|
|
INTEGRATION = 'INTEGRATION', 'Integration'
|
|
REPORTING = '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=EventType.choices,
|
|
)
|
|
event_category = models.CharField(
|
|
max_length=50,
|
|
choices=EventCategory.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=RiskLevel.choices,
|
|
default=RiskLevel.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.
|
|
"""
|
|
|
|
class DataType(models.TextChoices):
|
|
STRING = 'STRING', 'String'
|
|
INTEGER = 'INTEGER', 'Integer'
|
|
FLOAT = 'FLOAT', 'Float'
|
|
BOOLEAN = 'BOOLEAN', 'Boolean'
|
|
JSON = 'JSON', 'JSON'
|
|
DATE = 'DATE', 'Date'
|
|
DATETIME = '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=DataType.choices,
|
|
default=DataType.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.
|
|
"""
|
|
|
|
class TargetAudience(models.TextChoices):
|
|
ALL_USERS = 'ALL_USERS', 'All Users'
|
|
ADMINISTRATORS = 'ADMINISTRATORS', 'Administrators'
|
|
CLINICAL_STAFF = 'CLINICAL_STAFF', 'Clinical Staff'
|
|
SUPPORT_STAFF = 'SUPPORT_STAFF', 'Support Staff'
|
|
SPECIFIC_ROLES = 'SPECIFIC_ROLES', 'Specific Roles'
|
|
SPECIFIC_USERS = 'SPECIFIC_USERS', 'Specific Users'
|
|
|
|
class NotificationType(models.TextChoices):
|
|
INFO = 'INFO', 'Information'
|
|
WARNING = 'WARNING', 'Warning'
|
|
ERROR = 'ERROR', 'Error'
|
|
SUCCESS = 'SUCCESS', 'Success'
|
|
MAINTENANCE = 'MAINTENANCE', 'Maintenance'
|
|
SECURITY = 'SECURITY', 'Security Alert'
|
|
FEATURE = 'FEATURE', 'New Feature'
|
|
UPDATE = 'UPDATE', 'System Update'
|
|
|
|
class NotificationPriority(models.TextChoices):
|
|
LOW = 'LOW', 'Low'
|
|
MEDIUM = 'MEDIUM', 'Medium'
|
|
HIGH = 'HIGH', 'High'
|
|
URGENT = '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=NotificationType.choices,
|
|
)
|
|
|
|
# Priority
|
|
priority = models.CharField(
|
|
max_length=20,
|
|
choices=NotificationPriority.choices,
|
|
default=NotificationPriority.MEDIUM
|
|
)
|
|
|
|
# Targeting
|
|
target_audience = models.CharField(
|
|
max_length=30,
|
|
choices=TargetAudience.choices,
|
|
default=TargetAudience.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.
|
|
"""
|
|
|
|
class IntegrationStatus(models.TextChoices):
|
|
SUCCESS = 'SUCCESS', 'Success'
|
|
FAILED = 'FAILED', 'Failed'
|
|
PENDING = 'PENDING', 'Pending'
|
|
TIMEOUT = 'TIMEOUT', 'Timeout'
|
|
RETRY = 'RETRY', 'Retry'
|
|
|
|
class IntegrationType(models.TextChoices):
|
|
HL7 = 'HL7', 'HL7 Message'
|
|
DICOM = 'DICOM', 'DICOM Communication'
|
|
API = 'API', 'API Call'
|
|
DATABASE = 'DATABASE', 'Database Sync'
|
|
FILE_TRANSFER = 'FILE_TRANSFER', 'File Transfer'
|
|
WEBHOOK = 'WEBHOOK', 'Webhook'
|
|
EMAIL = 'EMAIL', 'Email'
|
|
SMS = '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=IntegrationType.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=IntegrationStatus.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}"
|
|
|
|
|
|
|
|
|
|
|