""" Operating Theatre app models for hospital management system. Provides surgical scheduling, OR management, and perioperative workflows. """ import uuid from django.db import models from django.core.validators import RegexValidator, MinValueValidator, MaxValueValidator from django.utils import timezone from django.conf import settings from datetime import timedelta, datetime, date, time from decimal import Decimal import json class OperatingRoom(models.Model): """ Operating room model for OR configuration and management. """ ROOM_TYPE_CHOICES = [ ('GENERAL', 'General Surgery'), ('CARDIAC', 'Cardiac Surgery'), ('NEURO', 'Neurosurgery'), ('ORTHOPEDIC', 'Orthopedic Surgery'), ('TRAUMA', 'Trauma Surgery'), ('PEDIATRIC', 'Pediatric Surgery'), ('OBSTETRIC', 'Obstetric Surgery'), ('OPHTHALMOLOGY', 'Ophthalmology'), ('ENT', 'ENT Surgery'), ('UROLOGY', 'Urology'), ('PLASTIC', 'Plastic Surgery'), ('VASCULAR', 'Vascular Surgery'), ('THORACIC', 'Thoracic Surgery'), ('TRANSPLANT', 'Transplant Surgery'), ('ROBOTIC', 'Robotic Surgery'), ('HYBRID', 'Hybrid OR'), ('AMBULATORY', 'Ambulatory Surgery'), ('EMERGENCY', 'Emergency Surgery'), ] STATUS_CHOICES = [ ('AVAILABLE', 'Available'), ('OCCUPIED', 'Occupied'), ('CLEANING', 'Cleaning'), ('MAINTENANCE', 'Maintenance'), ('SETUP', 'Setup'), ('TURNOVER', 'Turnover'), ('OUT_OF_ORDER', 'Out of Order'), ('CLOSED', 'Closed'), ] # Tenant relationship tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, related_name='operating_rooms', help_text='Organization tenant' ) # Room Information room_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique room identifier' ) room_number = models.CharField( max_length=20, help_text='Operating room number' ) room_name = models.CharField( max_length=100, help_text='Operating room name' ) # Room Type and Capabilities room_type = models.CharField( max_length=30, choices=ROOM_TYPE_CHOICES, help_text='Operating room type' ) # Room Status status = models.CharField( max_length=20, choices=STATUS_CHOICES, default='AVAILABLE', help_text='Current room status' ) # Physical Characteristics floor_number = models.PositiveIntegerField( help_text='Floor number' ) room_size = models.FloatField( blank=True, null=True, help_text='Room size in square meters' ) ceiling_height = models.FloatField( blank=True, null=True, help_text='Ceiling height in meters' ) # Environmental Controls temperature_min = models.FloatField( default=18.0, help_text='Minimum temperature in Celsius' ) temperature_max = models.FloatField( default=26.0, help_text='Maximum temperature in Celsius' ) humidity_min = models.FloatField( default=30.0, help_text='Minimum humidity percentage' ) humidity_max = models.FloatField( default=60.0, help_text='Maximum humidity percentage' ) air_changes_per_hour = models.PositiveIntegerField( default=20, help_text='Air changes per hour' ) positive_pressure = models.BooleanField( default=True, help_text='Positive pressure room' ) # Equipment and Features equipment_list = models.JSONField( default=list, help_text='Available equipment list' ) special_features = models.JSONField( default=list, help_text='Special features and capabilities' ) # Imaging Capabilities has_c_arm = models.BooleanField( default=False, help_text='C-arm fluoroscopy available' ) has_ct = models.BooleanField( default=False, help_text='Intraoperative CT available' ) has_mri = models.BooleanField( default=False, help_text='Intraoperative MRI available' ) has_ultrasound = models.BooleanField( default=False, help_text='Ultrasound available' ) has_neuromonitoring = models.BooleanField( default=False, help_text='Neuromonitoring available' ) # Surgical Capabilities supports_robotic = models.BooleanField( default=False, help_text='Robotic surgery capable' ) supports_laparoscopic = models.BooleanField( default=True, help_text='Laparoscopic surgery capable' ) supports_microscopy = models.BooleanField( default=False, help_text='Surgical microscopy available' ) supports_laser = models.BooleanField( default=False, help_text='Laser surgery capable' ) # Capacity and Scheduling max_case_duration = models.PositiveIntegerField( default=480, help_text='Maximum case duration in minutes' ) turnover_time = models.PositiveIntegerField( default=30, help_text='Standard turnover time in minutes' ) cleaning_time = models.PositiveIntegerField( default=45, help_text='Deep cleaning time in minutes' ) # Staffing Requirements required_nurses = models.PositiveIntegerField( default=2, help_text='Required number of nurses' ) required_techs = models.PositiveIntegerField( default=1, help_text='Required number of technicians' ) # Availability is_active = models.BooleanField( default=True, help_text='Room is active and available for scheduling' ) accepts_emergency = models.BooleanField( default=True, help_text='Accepts emergency cases' ) # Location Information building = models.CharField( max_length=50, blank=True, null=True, help_text='Building name or identifier' ) wing = models.CharField( max_length=50, blank=True, null=True, help_text='Wing or section' ) # 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_operating_rooms', help_text='User who created the room' ) class Meta: db_table = 'operating_theatre_operating_room' verbose_name = 'Operating Room' verbose_name_plural = 'Operating Rooms' ordering = ['room_number'] indexes = [ models.Index(fields=['tenant', 'is_active']), models.Index(fields=['room_type', 'status']), models.Index(fields=['floor_number']), models.Index(fields=['accepts_emergency']), ] unique_together = ['tenant', 'room_number'] def __str__(self): return f"OR {self.room_number} - {self.room_name}" @property def is_available(self): """ Check if room is available for scheduling. """ return self.status == 'AVAILABLE' and self.is_active @property def surgical_cases(self): """ All surgical cases scheduled/assigned to this operating room via its OR blocks. """ return SurgicalCase.objects.filter(or_block__operating_room=self) # (Optional) a clearer alias if you prefer not to shadow the term "surgical_cases" @property def cases(self): return self.surgical_cases @property def current_case(self): """ Get the in-progress surgical case for this room (if any). """ return self.surgical_cases.filter(status='IN_PROGRESS').order_by('-scheduled_start').first() class ORBlock(models.Model): """ OR block model for surgical scheduling and time management. """ BLOCK_TYPE_CHOICES = [ ('SCHEDULED', 'Scheduled Block'), ('EMERGENCY', 'Emergency Block'), ('MAINTENANCE', 'Maintenance Block'), ('CLEANING', 'Deep Cleaning'), ('RESERVED', 'Reserved'), ('BLOCKED', 'Blocked'), ] SERVICE_CHOICES = [ ('GENERAL', 'General Surgery'), ('CARDIAC', 'Cardiac Surgery'), ('NEURO', 'Neurosurgery'), ('ORTHOPEDIC', 'Orthopedic Surgery'), ('TRAUMA', 'Trauma Surgery'), ('PEDIATRIC', 'Pediatric Surgery'), ('OBSTETRIC', 'Obstetric Surgery'), ('OPHTHALMOLOGY', 'Ophthalmology'), ('ENT', 'ENT Surgery'), ('UROLOGY', 'Urology'), ('PLASTIC', 'Plastic Surgery'), ('VASCULAR', 'Vascular Surgery'), ('THORACIC', 'Thoracic Surgery'), ('TRANSPLANT', 'Transplant Surgery'), ] STATUS_CHOICES = [ ('SCHEDULED', 'Scheduled'), ('ACTIVE', 'Active'), ('COMPLETED', 'Completed'), ('CANCELLED', 'Cancelled'), ('DELAYED', 'Delayed'), ] # Operating Room relationship operating_room = models.ForeignKey( OperatingRoom, on_delete=models.CASCADE, related_name='or_blocks', help_text='Operating room' ) # Block Information block_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique block identifier' ) # Block Timing date = models.DateField( help_text='Block date' ) start_time = models.TimeField( help_text='Block start time' ) end_time = models.TimeField( help_text='Block end time' ) # Block Type block_type = models.CharField( max_length=20, choices=BLOCK_TYPE_CHOICES, default='SCHEDULED', help_text='Block type' ) # Surgeon Assignment primary_surgeon = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='primary_or_blocks', help_text='Primary surgeon assigned to block' ) assistant_surgeons = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=True, related_name='assistant_or_blocks', help_text='Assistant surgeons' ) # Service Assignment service = models.CharField( max_length=30, choices=SERVICE_CHOICES, help_text='Surgical service' ) # Block Status status = models.CharField( max_length=20, choices=STATUS_CHOICES, default='SCHEDULED', help_text='Block status' ) # Utilization allocated_minutes = models.PositiveIntegerField( help_text='Total allocated minutes' ) used_minutes = models.PositiveIntegerField( default=0, help_text='Minutes actually used' ) # Special Requirements special_equipment = models.JSONField( default=list, help_text='Special equipment requirements' ) special_setup = models.TextField( blank=True, null=True, help_text='Special setup requirements' ) # Notes notes = models.TextField( blank=True, null=True, help_text='Block notes and comments' ) # 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_or_blocks', help_text='User who created the block' ) class Meta: db_table = 'operating_theatre_or_block' verbose_name = 'OR Block' verbose_name_plural = 'OR Blocks' ordering = ['date', 'start_time'] indexes = [ models.Index(fields=['operating_room', 'date']), models.Index(fields=['primary_surgeon', 'date']), models.Index(fields=['service', 'date']), models.Index(fields=['status']), ] def __str__(self): return f"{self.operating_room.room_number} - {self.date} {self.start_time}-{self.end_time}" def save(self, *args, **kwargs): """ Calculate allocated minutes from start and end times. """ if self.start_time and self.end_time: start_datetime = datetime.combine(date.today(), self.start_time) end_datetime = datetime.combine(date.today(), self.end_time) if end_datetime < start_datetime: end_datetime += timedelta(days=1) delta = end_datetime - start_datetime self.allocated_minutes = int(delta.total_seconds() / 60) super().save(*args, **kwargs) @property def utilization_percentage(self): """ Calculate block utilization percentage. """ if self.allocated_minutes > 0: return round((self.used_minutes / self.allocated_minutes) * 100, 1) return 0 @property def tenant(self): """ Get tenant from operating room. """ return self.operating_room.tenant class SurgicalCase(models.Model): """ Surgical case model for individual surgical procedures. """ CASE_TYPE_CHOICES = [ ('ELECTIVE', 'Elective'), ('URGENT', 'Urgent'), ('EMERGENCY', 'Emergency'), ('TRAUMA', 'Trauma'), ('TRANSPLANT', 'Transplant'), ] APPROACH_CHOICES = [ ('OPEN', 'Open'), ('LAPAROSCOPIC', 'Laparoscopic'), ('ROBOTIC', 'Robotic'), ('ENDOSCOPIC', 'Endoscopic'), ('PERCUTANEOUS', 'Percutaneous'), ('HYBRID', 'Hybrid'), ] ANESTHESIA_TYPE_CHOICES = [ ('GENERAL', 'General'), ('REGIONAL', 'Regional'), ('LOCAL', 'Local'), ('SEDATION', 'Sedation'), ('SPINAL', 'Spinal'), ('EPIDURAL', 'Epidural'), ('COMBINED', 'Combined'), ] STATUS_CHOICES = [ ('SCHEDULED', 'Scheduled'), ('DELAYED', 'Delayed'), ('IN_PROGRESS', 'In Progress'), ('COMPLETED', 'Completed'), ('CANCELLED', 'Cancelled'), ('POSTPONED', 'Postponed'), ] PATIENT_POSITION_CHOICES = [ ('SUPINE', 'Supine'), ('PRONE', 'Prone'), ('LATERAL', 'Lateral'), ('LITHOTOMY', 'Lithotomy'), ('TRENDELENBURG', 'Trendelenburg'), ('REVERSE_TREND', 'Reverse Trendelenburg'), ('SITTING', 'Sitting'), ('JACKKNIFE', 'Jackknife'), ] # OR Block relationship or_block = models.ForeignKey( ORBlock, on_delete=models.CASCADE, related_name='surgical_cases', help_text='OR block assignment' ) # Case Information case_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique case identifier' ) case_number = models.CharField( max_length=20, unique=True, help_text='Surgical case number' ) # Patient Information patient = models.ForeignKey( 'patients.PatientProfile', on_delete=models.CASCADE, related_name='surgical_cases', help_text='Patient' ) # Surgical Team primary_surgeon = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='primary_surgical_cases', help_text='Primary surgeon' ) assistant_surgeons = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=True, related_name='assistant_surgical_cases', help_text='Assistant surgeons' ) anesthesiologist = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='anesthesia_cases', help_text='Anesthesiologist' ) circulating_nurse = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='circulating_cases', help_text='Circulating nurse' ) scrub_nurse = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='scrub_cases', help_text='Scrub nurse' ) # Procedure Information primary_procedure = models.CharField( max_length=200, help_text='Primary surgical procedure' ) secondary_procedures = models.JSONField( default=list, help_text='Secondary procedures' ) procedure_codes = models.JSONField( default=list, help_text='CPT procedure codes' ) # Case Classification case_type = models.CharField( max_length=20, choices=CASE_TYPE_CHOICES, default='ELECTIVE', help_text='Case type' ) # Surgical Approach approach = models.CharField( max_length=20, choices=APPROACH_CHOICES, help_text='Surgical approach' ) # Anesthesia anesthesia_type = models.CharField( max_length=20, choices=ANESTHESIA_TYPE_CHOICES, help_text='Anesthesia type' ) # Timing scheduled_start = models.DateTimeField( help_text='Scheduled start time' ) estimated_duration = models.PositiveIntegerField( help_text='Estimated duration in minutes' ) actual_start = models.DateTimeField( blank=True, null=True, help_text='Actual start time' ) actual_end = models.DateTimeField( blank=True, null=True, help_text='Actual end time' ) # Case Status status = models.CharField( max_length=20, choices=STATUS_CHOICES, default='SCHEDULED', help_text='Case status' ) # Clinical Information diagnosis = models.CharField( max_length=200, help_text='Primary diagnosis' ) diagnosis_codes = models.JSONField( default=list, help_text='ICD-10 diagnosis codes' ) clinical_notes = models.TextField( blank=True, null=True, help_text='Clinical notes and history' ) # Special Requirements special_equipment = models.JSONField( default=list, help_text='Special equipment requirements' ) blood_products = models.JSONField( default=list, help_text='Blood product requirements' ) implants = models.JSONField( default=list, help_text='Implant requirements' ) # Patient Positioning patient_position = models.CharField( max_length=20, choices=PATIENT_POSITION_CHOICES, blank=True, null=True, help_text='Patient positioning' ) # Complications and Outcomes complications = models.JSONField( default=list, help_text='Intraoperative complications' ) estimated_blood_loss = models.PositiveIntegerField( blank=True, null=True, help_text='Estimated blood loss in mL' ) # Related Information encounter = models.ForeignKey( 'emr.Encounter', on_delete=models.SET_NULL, null=True, blank=True, related_name='surgical_cases', help_text='Related encounter' ) admission = models.ForeignKey( 'inpatients.Admission', on_delete=models.SET_NULL, null=True, blank=True, related_name='surgical_cases', help_text='Related admission' ) # 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_surgical_cases', help_text='User who created the case' ) class Meta: db_table = 'operating_theatre_surgical_case' verbose_name = 'Surgical Case' verbose_name_plural = 'Surgical Cases' ordering = ['scheduled_start'] indexes = [ models.Index(fields=['or_block', 'status']), models.Index(fields=['patient', 'scheduled_start']), models.Index(fields=['primary_surgeon', 'scheduled_start']), models.Index(fields=['case_type', 'status']), models.Index(fields=['scheduled_start']), models.Index(fields=['case_number']), ] def __str__(self): return f"{self.case_number} - {self.primary_procedure}" def save(self, *args, **kwargs): """ Generate case number if not provided. """ if not self.case_number: # Generate case number (simple implementation) tenant = self.or_block.operating_room.tenant today = timezone.now().date() last_case = SurgicalCase.objects.filter( or_block__operating_room__tenant=tenant, created_at__date=today ).order_by('-id').first() if last_case: last_number = int(last_case.case_number.split('-')[-1]) self.case_number = f"SURG-{today.strftime('%Y%m%d')}-{last_number + 1:04d}" else: self.case_number = f"SURG-{today.strftime('%Y%m%d')}-0001" super().save(*args, **kwargs) @property def actual_duration(self): """ Calculate actual case duration in minutes. """ if self.actual_start and self.actual_end: delta = self.actual_end - self.actual_start return int(delta.total_seconds() / 60) return None @property def is_emergency(self): """ Check if case is emergency. """ return self.case_type in ['EMERGENCY', 'TRAUMA'] @property def operating_room(self): """ Get operating room from OR block. """ return self.or_block.operating_room @property def tenant(self): """ Get tenant from OR block. """ return self.or_block.operating_room.tenant class SurgicalNote(models.Model): """ Surgical note model for perioperative documentation. """ CONDITION_CHOICES = [ ('STABLE', 'Stable'), ('CRITICAL', 'Critical'), ('GUARDED', 'Guarded'), ('FAIR', 'Fair'), ('GOOD', 'Good'), ('EXCELLENT', 'Excellent'), ] DISPOSITION_CHOICES = [ ('RECOVERY', 'Recovery Room'), ('ICU', 'Intensive Care Unit'), ('WARD', 'Ward'), ('DISCHARGE', 'Discharge'), ('MORGUE', 'Morgue'), ] STATUS_CHOICES = [ ('DRAFT', 'Draft'), ('COMPLETED', 'Completed'), ('SIGNED', 'Signed'), ('AMENDED', 'Amended'), ] # Surgical Case relationship surgical_case = models.OneToOneField( SurgicalCase, on_delete=models.CASCADE, related_name='surgical_notes', help_text='Related surgical case' ) # Note Information note_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique note identifier' ) # Surgeon Information surgeon = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='surgeon_surgical_notes', help_text='Operating surgeon' ) # Preoperative Information preoperative_diagnosis = models.TextField( help_text='Preoperative diagnosis' ) planned_procedure = models.TextField( help_text='Planned surgical procedure' ) indication = models.TextField( help_text='Indication for surgery' ) # Intraoperative Information procedure_performed = models.TextField( help_text='Actual procedure performed' ) surgical_approach = models.TextField( help_text='Surgical approach and technique' ) findings = models.TextField( help_text='Intraoperative findings' ) technique = models.TextField( help_text='Detailed surgical technique' ) # Postoperative Information postoperative_diagnosis = models.TextField( help_text='Postoperative diagnosis' ) condition = models.CharField( max_length=20, choices=CONDITION_CHOICES, help_text='Patient condition post-surgery' ) disposition = models.CharField( max_length=30, choices=DISPOSITION_CHOICES, help_text='Patient disposition' ) # Complications and Blood Loss complications = models.TextField( blank=True, null=True, help_text='Intraoperative complications' ) estimated_blood_loss = models.PositiveIntegerField( blank=True, null=True, help_text='Estimated blood loss in mL' ) blood_transfusion = models.TextField( blank=True, null=True, help_text='Blood transfusion details' ) # Specimens and Pathology specimens = models.TextField( blank=True, null=True, help_text='Specimens sent to pathology' ) # Implants and Devices implants = models.TextField( blank=True, null=True, help_text='Implants and devices used' ) # Drains and Tubes drains = models.TextField( blank=True, null=True, help_text='Drains and tubes placed' ) # Closure closure = models.TextField( blank=True, null=True, help_text='Wound closure technique' ) # Postoperative Instructions postop_instructions = models.TextField( blank=True, null=True, help_text='Postoperative instructions' ) follow_up = models.TextField( blank=True, null=True, help_text='Follow-up instructions' ) # Note Status status = models.CharField( max_length=20, choices=STATUS_CHOICES, default='DRAFT', help_text='Note status' ) # Signatures signed_datetime = models.DateTimeField( blank=True, null=True, help_text='Date and time note was signed' ) # Template template_used = models.ForeignKey( 'SurgicalNoteTemplate', on_delete=models.SET_NULL, null=True, blank=True, related_name='surgical_notes', help_text='Template used for note' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'operating_theatre_surgical_note' verbose_name = 'Surgical Note' verbose_name_plural = 'Surgical Notes' ordering = ['-created_at'] indexes = [ models.Index(fields=['surgical_case']), models.Index(fields=['surgeon']), models.Index(fields=['status']), models.Index(fields=['signed_datetime']), ] def __str__(self): return f"Surgical Note - {self.surgical_case.case_number}" @property def patient(self): """ Get patient from surgical case. """ return self.surgical_case.patient @property def is_signed(self): """ Check if note is signed. """ return self.status == 'SIGNED' and self.signed_datetime is not None class EquipmentUsage(models.Model): """ Equipment usage model for tracking surgical equipment. """ EQUIPMENT_TYPE_CHOICES = [ ('SURGICAL_INSTRUMENT', 'Surgical Instrument'), ('MONITORING_DEVICE', 'Monitoring Device'), ('ANESTHESIA_MACHINE', 'Anesthesia Machine'), ('VENTILATOR', 'Ventilator'), ('ELECTROCAUTERY', 'Electrocautery'), ('LASER', 'Laser'), ('MICROSCOPE', 'Microscope'), ('C_ARM', 'C-Arm'), ('ULTRASOUND', 'Ultrasound'), ('ROBOT', 'Surgical Robot'), ('IMPLANT', 'Implant'), ('DISPOSABLE', 'Disposable'), ('OTHER', 'Other'), ] UNIT_OF_MEASURE_CHOICES = [ ('EACH', 'Each'), ('SET', 'Set'), ('PACK', 'Pack'), ('BOX', 'Box'), ('UNIT', 'Unit'), ('PIECE', 'Piece'), ] # Surgical Case relationship surgical_case = models.ForeignKey( SurgicalCase, on_delete=models.CASCADE, related_name='equipment_usage', help_text='Related surgical case' ) # Equipment Information usage_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique usage identifier' ) equipment_name = models.CharField( max_length=100, help_text='Equipment name' ) equipment_type = models.CharField( max_length=50, choices=EQUIPMENT_TYPE_CHOICES, help_text='Equipment type' ) # Equipment Details manufacturer = models.CharField( max_length=100, blank=True, null=True, help_text='Equipment manufacturer' ) model = models.CharField( max_length=100, blank=True, null=True, help_text='Equipment model' ) serial_number = models.CharField( max_length=100, blank=True, null=True, help_text='Equipment serial number' ) # Usage Information quantity_used = models.PositiveIntegerField( default=1, help_text='Quantity used' ) unit_of_measure = models.CharField( max_length=20, choices=UNIT_OF_MEASURE_CHOICES, default='EACH', help_text='Unit of measure' ) # Timing start_time = models.DateTimeField( blank=True, null=True, help_text='Equipment usage start time' ) end_time = models.DateTimeField( blank=True, null=True, help_text='Equipment usage end time' ) # Cost Information unit_cost = models.DecimalField( max_digits=10, decimal_places=2, blank=True, null=True, help_text='Unit cost' ) total_cost = models.DecimalField( max_digits=10, decimal_places=2, blank=True, null=True, help_text='Total cost' ) # Quality and Safety lot_number = models.CharField( max_length=50, blank=True, null=True, help_text='Lot or batch number' ) expiration_date = models.DateField( blank=True, null=True, help_text='Expiration date' ) sterilization_date = models.DateField( blank=True, null=True, help_text='Sterilization date' ) # Usage Notes notes = models.TextField( blank=True, null=True, help_text='Usage notes and comments' ) # Staff Information recorded_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='recorded_equipment_usage', help_text='Staff member who recorded usage' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'operating_theatre_equipment_usage' verbose_name = 'Equipment Usage' verbose_name_plural = 'Equipment Usage' ordering = ['-created_at'] indexes = [ models.Index(fields=['surgical_case']), models.Index(fields=['equipment_type']), models.Index(fields=['equipment_name']), models.Index(fields=['recorded_by']), ] def __str__(self): return f"{self.equipment_name} - {self.surgical_case.case_number}" def save(self, *args, **kwargs): """ Calculate total cost from unit cost and quantity. """ if self.unit_cost and self.quantity_used: self.total_cost = self.unit_cost * self.quantity_used super().save(*args, **kwargs) @property def duration_minutes(self): """ Calculate usage duration in minutes. """ if self.start_time and self.end_time: delta = self.end_time - self.start_time return int(delta.total_seconds() / 60) return None @property def patient(self): """ Get patient from surgical case. """ return self.surgical_case.patient class SurgicalNoteTemplate(models.Model): """ Surgical note template model for standardized documentation. """ SPECIALTY_CHOICES = [ ('ALL', 'All Specialties'), ('GENERAL', 'General Surgery'), ('CARDIAC', 'Cardiac Surgery'), ('NEURO', 'Neurosurgery'), ('ORTHOPEDIC', 'Orthopedic Surgery'), ('TRAUMA', 'Trauma Surgery'), ('PEDIATRIC', 'Pediatric Surgery'), ('OBSTETRIC', 'Obstetric Surgery'), ('OPHTHALMOLOGY', 'Ophthalmology'), ('ENT', 'ENT Surgery'), ('UROLOGY', 'Urology'), ('PLASTIC', 'Plastic Surgery'), ('VASCULAR', 'Vascular Surgery'), ('THORACIC', 'Thoracic Surgery'), ('TRANSPLANT', 'Transplant Surgery'), ] # Tenant relationship tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, related_name='surgical_note_templates', help_text='Organization tenant' ) # Template Information template_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique template identifier' ) name = models.CharField( max_length=100, help_text='Template name' ) description = models.TextField( blank=True, null=True, help_text='Template description' ) # Template Scope procedure_type = models.CharField( max_length=100, blank=True, null=True, help_text='Applicable procedure type' ) specialty = models.CharField( max_length=30, choices=SPECIALTY_CHOICES, default='ALL', help_text='Applicable specialty' ) # Template Content preoperative_diagnosis_template = models.TextField( blank=True, null=True, help_text='Preoperative diagnosis template' ) planned_procedure_template = models.TextField( blank=True, null=True, help_text='Planned procedure template' ) indication_template = models.TextField( blank=True, null=True, help_text='Indication template' ) procedure_performed_template = models.TextField( blank=True, null=True, help_text='Procedure performed template' ) surgical_approach_template = models.TextField( blank=True, null=True, help_text='Surgical approach template' ) findings_template = models.TextField( blank=True, null=True, help_text='Findings template' ) technique_template = models.TextField( blank=True, null=True, help_text='Technique template' ) postoperative_diagnosis_template = models.TextField( blank=True, null=True, help_text='Postoperative diagnosis template' ) complications_template = models.TextField( blank=True, null=True, help_text='Complications template' ) specimens_template = models.TextField( blank=True, null=True, help_text='Specimens template' ) implants_template = models.TextField( blank=True, null=True, help_text='Implants template' ) closure_template = models.TextField( blank=True, null=True, help_text='Closure template' ) postop_instructions_template = models.TextField( blank=True, null=True, help_text='Postoperative instructions template' ) # Template Status is_active = models.BooleanField( default=True, help_text='Template is active' ) is_default = models.BooleanField( default=False, help_text='Default template for specialty' ) # Usage Statistics usage_count = models.PositiveIntegerField( default=0, help_text='Number of times template has been used' ) # 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_surgical_note_templates', help_text='User who created the template' ) class Meta: db_table = 'operating_theatre_surgical_note_template' verbose_name = 'Surgical Note Template' verbose_name_plural = 'Surgical Note Templates' ordering = ['specialty', 'name'] indexes = [ models.Index(fields=['tenant', 'is_active']), models.Index(fields=['specialty']), models.Index(fields=['is_default']), ] unique_together = ['tenant', 'name'] def __str__(self): return f"{self.name} ({self.specialty})" def save(self, *args, **kwargs): """ Ensure only one default template per specialty. """ if self.is_default: # Remove default flag from other templates SurgicalNoteTemplate.objects.filter( tenant=self.tenant, specialty=self.specialty, is_default=True ).exclude(pk=self.pk).update(is_default=False) super().save(*args, **kwargs)