2025-10-03 20:11:25 +03:00

5851 lines
198 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# import random
# import uuid
# from datetime import datetime, timedelta
# from decimal import Decimal
# from django.utils import timezone as django_timezone
# from django.contrib.auth import get_user_model
#
# from core.models import Tenant
# from inventory.models import InventoryItem, InventoryStock, InventoryLocation, PurchaseOrder, PurchaseOrderItem, \
# Supplier
#
# User = get_user_model()
#
# # Saudi Arabian Inventory Data
# SAUDI_MEDICAL_CATEGORIES = [
# 'Pharmaceuticals',
# 'Medical Devices',
# 'Surgical Instruments',
# 'Laboratory Supplies',
# 'PPE & Safety',
# 'IV Therapy',
# 'Emergency Supplies'
# ]
#
# SAUDI_SUPPLIERS = [
# 'Saudi Medical Supply Co.',
# 'Gulf Medical Equipment',
# 'Arabian Healthcare Supplies',
# 'Riyadh Medical Trading',
# 'Al-Dawaa Medical',
# 'Nahdi Medical Company',
# 'United Pharmaceuticals'
# ]
#
# SAUDI_CITIES = ['Riyadh', 'Jeddah', 'Dammam', 'Medina', 'Taif', 'Khobar']
#
# MEDICAL_ITEMS = [
# {'name': 'Paracetamol 500mg', 'category': 'Pharmaceuticals', 'unit': 'TAB'},
# {'name': 'Disposable Syringe 5ml', 'category': 'Medical Devices', 'unit': 'PCS'},
# {'name': 'Surgical Gloves Size M', 'category': 'PPE & Safety', 'unit': 'PAIR'},
# {'name': 'Blood Collection Tube', 'category': 'Laboratory Supplies', 'unit': 'PCS'},
# {'name': 'IV Bag Normal Saline', 'category': 'IV Therapy', 'unit': 'BAG'},
# {'name': 'Emergency Oxygen Mask', 'category': 'Emergency Supplies', 'unit': 'PCS'}
# ]
#
#
# def create_saudi_suppliers(tenants):
# """Create Saudi suppliers"""
# suppliers = []
#
# for tenant in tenants:
# print(f"Creating suppliers for {tenant.name}...")
#
# for i, supplier_name in enumerate(SAUDI_SUPPLIERS):
# supplier_code = f"SUP-{tenant.id}-{i + 1:03d}"
#
# try:
# supplier = Supplier.objects.create(
# tenant=tenant,
# supplier_code=supplier_code,
# name=supplier_name,
# supplier_type='DISTRIBUTOR',
# city=random.choice(SAUDI_CITIES),
# country='Saudi Arabia',
# is_active=True
# )
# suppliers.append(supplier)
# print(f" ✓ Created supplier: {supplier_name}")
#
# except Exception as e:
# print(f" ✗ Error creating supplier {supplier_name}: {e}")
# continue
#
# print(f"Created {len(suppliers)} suppliers")
# return suppliers
#
#
# def create_saudi_inventory_locations(tenants):
# """Create Saudi inventory locations"""
# locations = []
#
# storage_rooms = ['Pharmacy', 'Central Supply', 'OR Storage', 'ICU Supply', 'Ward Storage']
#
# for tenant in tenants:
# print(f"Creating locations for {tenant.name}...")
#
# for i, room in enumerate(storage_rooms):
# location_code = f"LOC-{tenant.id}-{i + 1:03d}"
#
# try:
# location = InventoryLocation.objects.create(
# tenant=tenant,
# location_code=location_code,
# name=f"{room} - {tenant.city}",
# description=f"Storage location in {room}",
# location_type='WAREHOUSE',
# building='Main Hospital',
# floor='Ground Floor',
# room=room,
# is_active=True
# )
# locations.append(location)
# print(f" ✓ Created location: {location.name}")
#
# except Exception as e:
# print(f" ✗ Error creating location {room}: {e}")
# continue
#
# print(f"Created {len(locations)} locations")
# return locations
#
#
# def create_saudi_inventory_items(tenants):
# """Create Saudi inventory items"""
# items = []
#
# for tenant in tenants:
# print(f"Creating items for {tenant.name}...")
#
# for i, item_data in enumerate(MEDICAL_ITEMS):
# item_code = f"ITM-{tenant.id}-{i + 1:03d}"
#
# try:
# item = InventoryItem.objects.create(
# tenant=tenant,
# item_code=item_code,
# item_name=item_data['name'],
# description=f"Medical item: {item_data['name']}",
# category=item_data['category'],
# subcategory=item_data['category'],
# item_type='STOCK',
# manufacturer='Saudi Medical Industries',
# unit_of_measure=item_data['unit'],
# package_size=1,
# unit_cost=Decimal(str(random.uniform(10, 100))),
# list_price=Decimal(str(random.uniform(15, 150))),
# has_expiration=item_data['category'] == 'Pharmaceuticals',
# is_active=True,
# is_tracked=True,
# reorder_point=random.randint(10, 50),
# reorder_quantity=random.randint(100, 500),
# max_stock_level=random.randint(500, 1000)
# )
# items.append(item)
# print(f" ✓ Created item: {item.item_name}")
#
# except Exception as e:
# print(f" ✗ Error creating item {item_data['name']}: {e}")
# continue
#
# print(f"Created {len(items)} items")
# return items
#
#
# def create_saudi_inventory_stock(items, locations):
# """Create Saudi inventory stock entries"""
# stocks = []
#
# for item in items:
# print(f"Creating stock for {item.item_name}...")
#
# # Get locations for this tenant
# tenant_locations = [loc for loc in locations if loc.tenant == item.tenant]
# if not tenant_locations:
# continue
#
# location = random.choice(tenant_locations)
#
# try:
# stock = InventoryStock.objects.create(
# inventory_item=item,
# location=location,
# quantity_on_hand=random.randint(50, 500),
# quantity_reserved=random.randint(0, 20),
# received_date=django_timezone.now().date() - timedelta(days=random.randint(1, 90)),
# expiration_date=django_timezone.now().date() + timedelta(days=365) if item.has_expiration else None,
# unit_cost=item.unit_cost,
# quality_status='AVAILABLE'
# )
# stocks.append(stock)
# print(f" ✓ Created stock for: {item.item_name}")
#
# except Exception as e:
# print(f" ✗ Error creating stock for {item.item_name}: {e}")
# continue
#
# print(f"Created {len(stocks)} stock entries")
# return stocks
#
#
# def create_saudi_purchase_orders(tenants, suppliers):
# """Create Saudi purchase orders"""
# orders = []
#
# for tenant in tenants:
# print(f"Creating purchase orders for {tenant.name}...")
#
# # Get suppliers for this tenant
# tenant_suppliers = [supplier for supplier in suppliers if supplier.tenant == tenant]
# if not tenant_suppliers:
# print(f" No suppliers found for {tenant.name}, skipping...")
# continue
#
# # Get delivery locations
# try:
# locations = InventoryLocation.objects.filter(tenant=tenant)
# delivery_location = locations.first() if locations.exists() else None
# except:
# delivery_location = None
#
# for i in range(3): # Create 3 orders per tenant
# po_number = f"PO-{tenant.id}-{django_timezone.now().year}-{i + 1:04d}"
# supplier = random.choice(tenant_suppliers)
#
# try:
# order = PurchaseOrder.objects.create(
# tenant=tenant,
# po_number=po_number,
# supplier=supplier,
# order_date=django_timezone.now().date() - timedelta(days=random.randint(1, 30)),
# requested_delivery_date=django_timezone.now().date() + timedelta(days=random.randint(7, 30)),
# order_type='STANDARD',
# priority='NORMAL',
# subtotal=Decimal(str(random.uniform(1000, 10000))),
# tax_amount=Decimal('0.00'),
# shipping_amount=Decimal('0.00'),
# total_amount=Decimal(str(random.uniform(1000, 10000))),
# status='DRAFT',
# delivery_location=delivery_location,
# payment_terms='NET_30'
# )
# orders.append(order)
# print(f" ✓ Created PO: {po_number}")
#
# except Exception as e:
# print(f" ✗ Error creating PO {po_number}: {e}")
# continue
#
# print(f"Created {len(orders)} purchase orders")
# return orders
#
#
# def create_saudi_purchase_order_items(orders, items):
# """Create Saudi purchase order items"""
# po_items = []
#
# for order in orders:
# print(f"Creating items for PO {order.po_number}...")
#
# # Get items for this tenant
# tenant_items = [item for item in items if item.tenant == order.tenant]
# if not tenant_items:
# continue
#
# # Create 2-3 items per order
# num_items = min(3, len(tenant_items))
# selected_items = random.sample(tenant_items, num_items)
#
# for line_num, item in enumerate(selected_items, 1):
# quantity_ordered = random.randint(10, 100)
# unit_price = item.unit_cost * Decimal(str(random.uniform(0.9, 1.1)))
# total_price = unit_price * quantity_ordered
#
# try:
# po_item = PurchaseOrderItem.objects.create(
# purchase_order=order,
# line_number=line_num,
# inventory_item=item,
# quantity_ordered=quantity_ordered,
# quantity_received=0,
# unit_price=unit_price,
# total_price=total_price,
# requested_delivery_date=order.requested_delivery_date,
# status='PENDING'
# )
# po_items.append(po_item)
# print(f" ✓ Created PO item: {item.item_name}")
#
# except Exception as e:
# print(f" ✗ Error creating PO item for {item.item_name}: {e}")
# continue
#
# print(f"Created {len(po_items)} purchase order items")
# return po_items
#
#
# def main():
# """Main function to create all Saudi inventory data"""
# print("🏥 Starting Saudi Inventory Data Generation...")
#
# # Get tenants
# try:
# tenants = list(Tenant.objects.filter(is_active=True)[:5]) # Limit to first 5 tenants
# if not tenants:
# print("❌ No active tenants found. Please run core_data.py first.")
# return
#
# print(f"📋 Found {len(tenants)} active tenants")
# except Exception as e:
# print(f"❌ Error getting tenants: {e}")
# return
#
# # Create data step by step
# print("\n1⃣ Creating Suppliers...")
# suppliers = create_saudi_suppliers(tenants)
# if not suppliers:
# print("❌ No suppliers created. Stopping.")
# return
#
# print("\n2⃣ Creating Locations...")
# locations = create_saudi_inventory_locations(tenants)
# if not locations:
# print("❌ No locations created. Stopping.")
# return
#
# print("\n3⃣ Creating Items...")
# items = create_saudi_inventory_items(tenants)
# if not items:
# print("❌ No items created. Stopping.")
# return
#
# print("\n4⃣ Creating Stock...")
# stocks = create_saudi_inventory_stock(items, locations)
#
# print("\n5⃣ Creating Purchase Orders...")
# orders = create_saudi_purchase_orders(tenants, suppliers)
#
# print("\n6⃣ Creating Purchase Order Items...")
# po_items = create_saudi_purchase_order_items(orders, items)
#
# print("\n🎉 Saudi Inventory Data Generation Complete!")
# print(f"📊 Summary:")
# print(f" - Suppliers: {len(suppliers)}")
# print(f" - Locations: {len(locations)}")
# print(f" - Items: {len(items)}")
# print(f" - Stock Entries: {len(stocks)}")
# print(f" - Purchase Orders: {len(orders)}")
# print(f" - PO Items: {len(po_items)}")
#
#
# if __name__ == "__main__":
# main()
class Employee(models.Model):
# """
# Employee model for hospital staff management.
# """
# GENDER_CHOICES = [
# ('MALE', 'Male'),
# ('FEMALE', 'Female'),
# ('OTHER', 'Other'),
# ('UNKNOWN', 'Unknown'),
# ]
# MARITAL_STATUS_CHOICES = [
# ('SINGLE', 'Single'),
# ('MARRIED', 'Married'),
# ('DIVORCED', 'Divorced'),
# ('WIDOWED', 'Widowed'),
# ('SEPARATED', 'Separated'),
# ('OTHER', 'Other'),
# ]
# EMPLOYMENT_TYPE_CHOICES = [
# ('FULL_TIME', 'Full Time'),
# ('PART_TIME', 'Part Time'),
# ('CONTRACT', 'Contract'),
# ('TEMPORARY', 'Temporary'),
# ('INTERN', 'Intern'),
# ('VOLUNTEER', 'Volunteer'),
# ('PER_DIEM', 'Per Diem'),
# ('CONSULTANT', 'Consultant'),
# ]
# EMPLOYMENT_STATUS_CHOICES = [
# ('ACTIVE', 'Active'),
# ('INACTIVE', 'Inactive'),
# ('TERMINATED', 'Terminated'),
# ('SUSPENDED', 'Suspended'),
# ('LEAVE', 'On Leave'),
# ('RETIRED', 'Retired'),
# ]
# # Tenant relationship
# tenant = models.ForeignKey(
# 'core.Tenant',
# on_delete=models.CASCADE,
# related_name='employees',
# help_text='Organization tenant'
# )
#
# # User relationship (optional - for employees who have system access)
# user = models.OneToOneField(
# settings.AUTH_USER_MODEL,
# on_delete=models.SET_NULL,
# null=True,
# blank=True,
# related_name='employee_profile',
# help_text='Associated user account'
# )
#
# # Employee Information
# employee_id = models.UUIDField(
# default=uuid.uuid4,
# unique=True,
# editable=False,
# help_text='Unique employee identifier'
# )
# employee_number = models.CharField(
# max_length=20,
# help_text='Employee number'
# )
#
# # Personal Information
# first_name = models.CharField(
# max_length=50,
# help_text='First name'
# )
# last_name = models.CharField(
# max_length=50,
# help_text='Last name'
# )
# middle_name = models.CharField(
# max_length=50,
# blank=True,
# null=True,
# help_text='Middle name'
# )
# preferred_name = models.CharField(
# max_length=50,
# blank=True,
# null=True,
# help_text='Preferred name'
# )
#
# # Contact Information
# email = models.EmailField(
# blank=True,
# null=True,
# help_text='Email address'
# )
# phone = models.CharField(
# max_length=20,
# blank=True,
# null=True,
# help_text='Phone number'
# )
# mobile_phone = models.CharField(
# max_length=20,
# blank=True,
# null=True,
# help_text='Mobile phone number'
# )
#
# # Address Information
# address_line_1 = models.CharField(
# max_length=100,
# blank=True,
# null=True,
# help_text='Address line 1'
# )
# address_line_2 = models.CharField(
# max_length=100,
# blank=True,
# null=True,
# help_text='Address line 2'
# )
# city = models.CharField(
# max_length=50,
# blank=True,
# null=True,
# help_text='City'
# )
# state = models.CharField(
# max_length=50,
# blank=True,
# null=True,
# help_text='State/Province'
# )
# postal_code = models.CharField(
# max_length=20,
# blank=True,
# null=True,
# help_text='Postal/ZIP code'
# )
# country = models.CharField(
# max_length=50,
# blank=True,
# null=True,
# help_text='Country'
# )
# national_id = models.CharField(
# max_length=10,
# blank=True,
# null=True,
# unique=True,
# help_text='National ID'
# )
# # Personal Details
# date_of_birth = models.DateField(
# blank=True,
# null=True,
# help_text='Date of birth'
# )
# gender = models.CharField(
# max_length=10,
# choices=GENDER_CHOICES,
# blank=True,
# null=True,
# help_text='Gender'
# )
# marital_status = models.CharField(
# max_length=20,
# choices=MARITAL_STATUS_CHOICES,
# blank=True,
# null=True,
# help_text='Marital status'
# )
#
# # Employment Information
# department = models.ForeignKey(
# 'Department',
# on_delete=models.SET_NULL,
# null=True,
# blank=True,
# related_name='employees',
# help_text='Department'
# )
# job_title = models.CharField(
# max_length=100,
# help_text='Job title'
# )
# employment_type = models.CharField(
# max_length=20,
# choices=EMPLOYMENT_TYPE_CHOICES,
# help_text='Employment type'
# )
# employment_status = models.CharField(
# max_length=20,
# choices=EMPLOYMENT_STATUS_CHOICES,
# default='ACTIVE',
# help_text='Employment status'
# )
#
# # Employment Dates
# hire_date = models.DateField(
# help_text='Hire date'
# )
# termination_date = models.DateField(
# blank=True,
# null=True,
# help_text='Termination date'
# )
#
# # Supervisor Information
# supervisor = models.ForeignKey(
# 'self',
# on_delete=models.SET_NULL,
# null=True,
# blank=True,
# related_name='direct_reports',
# help_text='Direct supervisor'
# )
#
# # Work Schedule Information
# standard_hours_per_week = models.DecimalField(
# max_digits=5,
# decimal_places=2,
# default=Decimal('40.00'),
# help_text='Standard hours per week'
# )
# fte_percentage = models.DecimalField(
# max_digits=5,
# decimal_places=2,
# default=Decimal('100.00'),
# validators=[MinValueValidator(0), MaxValueValidator(100)],
# help_text='FTE percentage'
# )
#
# # Compensation Information
# hourly_rate = models.DecimalField(
# max_digits=10,
# decimal_places=2,
# blank=True,
# null=True,
# help_text='Hourly rate'
# )
# annual_salary = models.DecimalField(
# max_digits=12,
# decimal_places=2,
# blank=True,
# null=True,
# help_text='Annual salary'
# )
#
# # Professional Information
# license_number = models.CharField(
# max_length=50,
# blank=True,
# null=True,
# help_text='Professional license number'
# )
# license_expiry_date = models.DateField(
# blank=True,
# null=True,
# help_text='License expiry date'
# )
# certifications = models.JSONField(
# default=list,
# help_text='Professional certifications'
# )
#
# # Emergency Contact
# emergency_contact_name = models.CharField(
# max_length=100,
# blank=True,
# null=True,
# help_text='Emergency contact name'
# )
# emergency_contact_relationship = models.CharField(
# max_length=50,
# blank=True,
# null=True,
# help_text='Emergency contact relationship'
# )
# emergency_contact_phone = models.CharField(
# max_length=20,
# blank=True,
# null=True,
# help_text='Emergency contact phone'
# )
#
# # Notes
# notes = models.TextField(
# blank=True,
# null=True,
# help_text='Employee notes'
# )
#
# # 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_employees',
# help_text='User who created the employee record'
# )
#
# class Meta:
# db_table = 'hr_employee'
# verbose_name = 'Employee'
# verbose_name_plural = 'Employees'
# ordering = ['last_name', 'first_name']
# indexes = [
# models.Index(fields=['tenant', 'employment_status']),
# models.Index(fields=['employee_number']),
# models.Index(fields=['last_name', 'first_name']),
# models.Index(fields=['department']),
# models.Index(fields=['hire_date']),
# ]
# unique_together = ['tenant', 'employee_number']
#
# def __str__(self):
# return f"{self.employee_number} - {self.get_full_name()}"
#
# def get_full_name(self):
# """
# Get employee's full name.
# """
# if self.middle_name:
# return f"{self.first_name} {self.middle_name} {self.last_name}"
# return f"{self.first_name} {self.last_name}"
#
# def get_display_name(self):
# """
# Get employee's display name (preferred name if available).
# """
# if self.preferred_name:
# return f"{self.preferred_name} {self.last_name}"
# return self.get_full_name()
#
# @property
# def age(self):
# """
# Calculate employee's age.
# """
# if self.date_of_birth:
# today = date.today()
# return today.year - self.date_of_birth.year - ((today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day))
# return None
#
# @property
# def years_of_service(self):
# """
# Calculate years of service.
# """
# if self.hire_date:
# end_date = self.termination_date or date.today()
# return (end_date - self.hire_date).days / 365.25
# return 0
#
# @property
# def is_license_expired(self):
# """
# Check if professional license is expired.
# """
# if self.license_expiry_date:
# return self.license_expiry_date < date.today()
# return False
#
# @property
# def full_address(self):
# """
# Get full address.
# """
# parts = [
# self.address_line_1,
# self.address_line_2,
# f"{self.city}, {self.state} {self.postal_code}",
# self.country
# ]
# return "\n".join([part for part in parts if part])
# class TrainingRecord(models.Model):
# """
# Training record model for employee training and certifications.
# """
#
# class TrainingType(models.TextChoices):
# ORIENTATION = 'ORIENTATION', 'Orientation'
# MANDATORY = 'MANDATORY', 'Mandatory Training'
# CONTINUING_ED = 'CONTINUING_ED', 'Continuing Education'
# CERTIFICATION = 'CERTIFICATION', 'Certification'
# SKILLS = 'SKILLS', 'Skills Training'
# SAFETY = 'SAFETY', 'Safety Training'
# COMPLIANCE = 'COMPLIANCE', 'Compliance Training'
# LEADERSHIP = 'LEADERSHIP', 'Leadership Development'
# TECHNICAL = 'TECHNICAL', 'Technical Training'
# OTHER = 'OTHER', 'Other'
#
# class TrainingStatus(models.TextChoices):
# SCHEDULED = 'SCHEDULED', 'Scheduled'
# IN_PROGRESS = 'IN_PROGRESS', 'In Progress'
# COMPLETED = 'COMPLETED', 'Completed'
# CANCELLED = 'CANCELLED', 'Cancelled'
# NO_SHOW = 'NO_SHOW', 'No Show'
# FAILED = 'FAILED', 'Failed'
#
# # Employee relationship
# employee = models.ForeignKey(
# Employee,
# on_delete=models.CASCADE,
# related_name='training_records',
# help_text='Employee'
# )
#
# # Training Information
# record_id = models.UUIDField(
# default=uuid.uuid4,
# unique=True,
# editable=False,
# help_text='Unique training record identifier'
# )
# training_name = models.CharField(
# max_length=200,
# help_text='Training name'
# )
# training_description = models.TextField(
# blank=True,
# null=True,
# help_text='Training description'
# )
#
# # Training Type
# training_type = models.CharField(
# max_length=20,
# choices=TrainingType.choices,
# help_text='Training type'
# )
#
# # Training Provider
# training_provider = models.CharField(
# max_length=200,
# blank=True,
# null=True,
# help_text='Training provider'
# )
# instructor = models.CharField(
# max_length=100,
# blank=True,
# null=True,
# help_text='Instructor name'
# )
#
# # Training Dates
# training_date = models.DateField(
# help_text='Training date'
# )
# completion_date = models.DateField(
# blank=True,
# null=True,
# help_text='Completion date'
# )
# expiry_date = models.DateField(
# blank=True,
# null=True,
# help_text='Certification expiry date'
# )
#
# # Training Details
# duration_hours = models.DecimalField(
# max_digits=5,
# decimal_places=2,
# default=Decimal('0.00'),
# help_text='Training duration in hours'
# )
# credits_earned = models.DecimalField(
# max_digits=5,
# decimal_places=2,
# default=Decimal('0.00'),
# help_text='Credits earned'
# )
#
# # Training Status
# status = models.CharField(
# max_length=20,
# choices=TrainingStatus.choices,
# default='SCHEDULED',
# help_text='Training status'
# )
#
# # Results
# score = models.DecimalField(
# max_digits=5,
# decimal_places=2,
# blank=True,
# null=True,
# help_text='Training score/grade'
# )
# passed = models.BooleanField(
# default=False,
# help_text='Training passed'
# )
# is_certified = models.BooleanField(
# default=False,
# help_text='Training is certified'
# )
# # Certification Information
# certificate_number = models.CharField(
# max_length=50,
# blank=True,
# null=True,
# help_text='Certificate number'
# )
# certification_body = models.CharField(
# max_length=200,
# blank=True,
# null=True,
# help_text='Certification body'
# )
#
# # Cost Information
# training_cost = models.DecimalField(
# max_digits=10,
# decimal_places=2,
# default=Decimal('0.00'),
# help_text='Training cost'
# )
#
# # Notes
# notes = models.TextField(
# blank=True,
# null=True,
# help_text='Training notes'
# )
#
# # 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_training_records',
# help_text='User who created the training record'
# )
#
# class Meta:
# db_table = 'hr_training_record'
# verbose_name = 'Training Record'
# verbose_name_plural = 'Training Records'
# ordering = ['-training_date']
# indexes = [
# models.Index(fields=['employee', 'training_date']),
# models.Index(fields=['training_type']),
# models.Index(fields=['status']),
# models.Index(fields=['expiry_date']),
# ]
#
# def __str__(self):
# return f"{self.employee.get_full_name()} - {self.training_name}"
#
# @property
# def tenant(self):
# """
# Get tenant from employee.
# """
# return self.employee.tenant
#
# @property
# def is_expired(self):
# """
# Check if certification is expired.
# """
# if self.expiry_date:
# return self.expiry_date < date.today()
# return False
#
# @property
# def days_to_expiry(self):
# """
# Calculate days to expiry.
# """
# if self.expiry_date:
# return (self.expiry_date - date.today()).days
# return None
#
# @property
# def is_due_for_renewal(self):
# """
# Check if certification is due for renewal (within 30 days).
# """
# if self.expiry_date:
# return (self.expiry_date - date.today()).days <= 30
# return False
# class Certification(models.Model):
# tenant = models.ForeignKey('core.Tenant', on_delete=models.PROTECT, related_name='certifications')
# name = models.CharField(max_length=100)
# issuer = models.CharField(max_length=150, blank=True, null=True)
# is_clinical = models.BooleanField(default=False)
#
# class Meta:
# unique_together = [('tenant', 'name')]
#
# class EmployeeCertification(models.Model):
# employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name='employee_certifications')
# certification = models.ForeignKey(Certification, on_delete=models.PROTECT)
# credential_id = models.CharField(max_length=100, blank=True, null=True)
# issued_on = models.DateField(blank=True, null=True)
# expires_on = models.DateField(blank=True, null=True)
#
# class Meta:
# constraints = [
# models.UniqueConstraint(fields=['employee', 'certification'], name='uq_employee_cert_once')
# ]
# class DrugInteraction(models.Model):
# """
# Drug interaction model for tracking medication interactions.
# """
#
# class InteractionSeverity(models.TextChoices):
# MINOR = 'MINOR', 'Minor'
# MODERATE = 'MODERATE', 'Moderate'
# MAJOR = 'MAJOR', 'Major'
# CONTRAINDICATED = 'CONTRAINDICATED', 'Contraindicated'
#
# # Tenant relationship
# tenant = models.ForeignKey(
# 'core.Tenant',
# on_delete=models.CASCADE,
# related_name='drug_interactions',
# help_text='Organization tenant'
# )
#
# # Patient relationship
# patient = models.ForeignKey(
# 'patients.PatientProfile',
# on_delete=models.CASCADE,
# related_name='drug_interactions',
# help_text='Patient'
# )
#
# # Interaction details
# interaction_id = models.UUIDField(
# default=uuid.uuid4,
# unique=True,
# editable=False,
# help_text='Unique interaction identifier'
# )
#
# drug1 = models.CharField(
# max_length=100,
# help_text='First drug name'
# )
# drug2 = models.CharField(
# max_length=100,
# help_text='Second drug name'
# )
#
# severity = models.CharField(
# max_length=20,
# choices=InteractionSeverity.choices,
# help_text='Interaction severity'
# )
#
# description = models.TextField(
# help_text='Interaction description and clinical significance'
# )
#
# # Management
# management = models.TextField(
# blank=True,
# null=True,
# help_text='Management recommendations'
# )
#
# # Status
# resolved = models.BooleanField(
# default=False,
# help_text='Interaction has been resolved'
# )
# resolved_at = models.DateTimeField(
# blank=True,
# null=True,
# help_text='Date interaction was resolved'
# )
# resolved_by = models.ForeignKey(
# settings.AUTH_USER_MODEL,
# on_delete=models.SET_NULL,
# null=True,
# blank=True,
# related_name='resolved_interactions',
# help_text='Provider who resolved the interaction'
# )
#
# # Metadata
# created_at = models.DateTimeField(auto_now_add=True)
# updated_at = models.DateTimeField(auto_now=True)
# detected_at = models.DateTimeField(
# default=timezone.now,
# help_text='When interaction was detected'
# )
#
# class Meta:
# db_table = 'emr_drug_interaction'
# verbose_name = 'Drug Interaction'
# verbose_name_plural = 'Drug Interactions'
# ordering = ['-detected_at']
# indexes = [
# models.Index(fields=['tenant', 'resolved']),
# models.Index(fields=['patient', 'resolved']),
# models.Index(fields=['severity']),
# models.Index(fields=['detected_at']),
# ]
#
# def __str__(self):
# return f"{self.patient.get_full_name()} - {self.drug1} + {self.drug2}"
# class RiskAssessment(models.Model):
# """
# Risk assessment model for tracking patient risk scores.
# """
#
# class RiskLevel(models.TextChoices):
# LOW = 'LOW', 'Low Risk'
# MODERATE = 'MODERATE', 'Moderate Risk'
# HIGH = 'HIGH', 'High Risk'
# CRITICAL = 'CRITICAL', 'Critical Risk'
#
# # Tenant relationship
# tenant = models.ForeignKey(
# 'core.Tenant',
# on_delete=models.CASCADE,
# related_name='risk_assessments',
# help_text='Organization tenant'
# )
#
# # Patient relationship
# patient = models.ForeignKey(
# 'patients.PatientProfile',
# on_delete=models.CASCADE,
# related_name='risk_assessments',
# help_text='Patient'
# )
#
# # Assessment details
# assessment_id = models.UUIDField(
# default=uuid.uuid4,
# unique=True,
# editable=False,
# help_text='Unique assessment identifier'
# )
#
# assessment_type = models.CharField(
# max_length=100,
# help_text='Type of risk assessment (e.g., Fall Risk, Cardiac Risk)'
# )
#
# score = models.DecimalField(
# max_digits=5,
# decimal_places=2,
# help_text='Risk score value'
# )
#
# risk_level = models.CharField(
# max_length=20,
# choices=RiskLevel.choices,
# help_text='Calculated risk level'
# )
#
# description = models.TextField(
# blank=True,
# null=True,
# help_text='Assessment description and interpretation'
# )
#
# # Assessment details
# assessment_date = models.DateTimeField(
# default=timezone.now,
# help_text='Date and time of assessment'
# )
#
# factors = models.JSONField(
# default=dict,
# help_text='Risk factors considered'
# )
#
# recommendations = models.JSONField(
# default=list,
# help_text='Recommendations based on assessment'
# )
#
# # Provider information
# assessed_by = models.ForeignKey(
# settings.AUTH_USER_MODEL,
# on_delete=models.CASCADE,
# related_name='risk_assessments',
# help_text='Provider who performed assessment'
# )
#
# # Metadata
# created_at = models.DateTimeField(auto_now_add=True)
# updated_at = models.DateTimeField(auto_now=True)
#
# class Meta:
# db_table = 'emr_risk_assessment'
# verbose_name = 'Risk Assessment'
# verbose_name_plural = 'Risk Assessments'
# ordering = ['-assessment_date']
# indexes = [
# models.Index(fields=['tenant']),
# models.Index(fields=['patient']),
# models.Index(fields=['assessment_type']),
# models.Index(fields=['risk_level']),
# models.Index(fields=['assessment_date']),
# ]
#
# def __str__(self):
# return f"{self.patient.get_full_name()} - {self.assessment_type}: {self.score}"
#
# # ClinicalNote --------------------------------------------------------------------
#
# class ClinicalNoteListView(LoginRequiredMixin, TenantMixin, ListView):
# model = ClinicalNote
# template_name = 'emr/clinical_note_list.html'
# context_object_name = 'notes'
# paginate_by = 20
#
#
# class ClinicalNoteDetailView(LoginRequiredMixin, TenantMixin, DetailView):
# model = ClinicalNote
# template_name = 'emr/clinical_note_detail.html'
# context_object_name = 'note'
#
#
# class ClinicalNoteCreateView(
# LoginRequiredMixin, FormKwargsMixin,
# SuccessMessageMixin, CreateView
# ):
# model = ClinicalNote
# form_class = ClinicalNoteForm
# template_name = 'emr/clinical_note_form.html'
# success_message = _('Clinical note created successfully.')
#
# def form_valid(self, form):
# form.instance.author = self.request.user
# response = super().form_valid(form)
# AuditLogEntry.objects.create(
# tenant=self.request.user.tenant,
# user=self.request.user,
# action='CREATE',
# model_name='ClinicalNote',
# object_id=str(self.object.pk),
# changes={'status': 'Clinical note created'}
# )
# return response
#
# def get_success_url(self):
# return reverse_lazy('emr:clinical_note_detail', kwargs={'pk': self.object.pk})
#
#
# class ClinicalNoteUpdateView(
# LoginRequiredMixin, FormKwargsMixin,
# SuccessMessageMixin, TenantMixin, UpdateView
# ):
# model = ClinicalNote
# form_class = ClinicalNoteForm
# template_name = 'emr/clinical_note_form.html'
# success_message = _('Clinical note updated successfully.')
#
# def form_valid(self, form):
# response = super().form_valid(form)
# AuditLogEntry.objects.create(
# tenant=self.request.user.tenant,
# user=self.request.user,
# action='UPDATE',
# model_name='ClinicalNote',
# object_id=str(self.object.pk),
# changes={'status': 'Clinical note updated'}
# )
# return response
#
# def get_success_url(self):
# return reverse_lazy('emr:clinical_note_detail', kwargs={'pk': self.object.pk})
#
#
# class ClinicalNoteDeleteView(
# LoginRequiredMixin, TenantMixin,
# SuccessMessageMixin, DeleteView
# ):
# model = ClinicalNote
# template_name = 'emr/clinical_note_confirm_delete.html'
# success_url = reverse_lazy('emr:clinical_note_list')
# success_message = _('Clinical note deleted successfully.')
#
# def delete(self, request, *args, **kwargs):
# cn = self.get_object()
# AuditLogEntry.objects.create(
# tenant=request.user.tenant,
# user=request.user,
# action='DELETE',
# model_name='ClinicalNote',
# object_id=str(cn.pk),
# changes={'status': 'Clinical note deleted'}
# )
# messages.success(request, self.success_message)
# return super().delete(request, *args, **kwargs)
#
#
# NoteTemplate -------------------------------------------------------------------
#
# # encounters/views.py
#
# from django.shortcuts import get_object_or_404, redirect, render
# from django.urls import reverse_lazy
# from django.contrib.auth.decorators import login_required
# from django.contrib.auth.mixins import LoginRequiredMixin
# from django.contrib import messages
# from django.views.generic import (
# TemplateView, ListView, DetailView, CreateView, UpdateView, DeleteView
# )
# from django.views.generic.edit import FormMixin
# from django.contrib.messages.views import SuccessMessageMixin
# from django.db.models import Q, Avg
# from django.utils import timezone
# from django.http import JsonResponse
#
# from .models import *
# from .forms import *
# from core.models import AuditLogEntry
# from patients.models import PatientProfile
# from django.utils.translation import gettext_lazy as _
#
#
# # Mixins -------------------------------------------------------------------------
#
# class TenantMixin:
# def get_queryset(self):
# qs = super().get_queryset()
# tenant = getattr(self.request.user, 'tenant', None)
# if tenant and not self.request.user.is_superuser:
# # Models with patient FK:
# if hasattr(qs.model, 'patient'):
# return qs.filter(patient__tenant=tenant)
# # NoteTemplate uses tenant directly:
# return qs.filter(tenant=tenant)
# return qs
#
# def get_object(self, queryset=None):
# qs = queryset or self.get_queryset()
# return super().get_object(qs)
#
#
# class FormKwargsMixin:
# def get_form_kwargs(self):
# kw = super().get_form_kwargs()
# kw['user'] = self.request.user
# kw['tenant'] = getattr(self.request.user, 'tenant', None)
# return kw
#
#
# # Dashboard ----------------------------------------------------------------------
#
# class DashboardView(LoginRequiredMixin, TemplateView):
# template_name = 'emr/dashboard.html'
#
# def get_context_data(self, **kwargs):
# ctx = super().get_context_data(**kwargs)
# tenant = getattr(self.request.user, 'tenant', None)
# today = timezone.now().date()
# week_ago = today - timezone.timedelta(days=7)
#
# enc = Encounter.objects.filter(patient__tenant=tenant)
# vs = VitalSigns.objects.filter(encounter__patient__tenant=tenant)
# pb = ProblemList.objects.filter(patient__tenant=tenant)
# cp = CarePlan.objects.filter(patient__tenant=tenant)
# cn = ClinicalNote.objects.filter(encounter__patient__tenant=tenant)
#
# ctx.update({
# 'total_encounters': enc.count(),
# 'encounters_today': enc.filter(scheduled_datetime__date=today).count(),
# 'encounters_this_week': enc.filter(scheduled_datetime__date__gte=week_ago).count(),
# 'active_encounters': enc.filter(status='IN_PROGRESS').count(),
#
# 'total_vital_signs': vs.count(),
# 'vital_signs_today': vs.filter(recorded_at__date=today).count(),
# 'avg_temp_week': vs.filter(recorded_at__date__gte=week_ago).aggregate(Avg('temperature'))['temperature__avg'],
# 'avg_hr_week': vs.filter(recorded_at__date__gte=week_ago).aggregate(Avg('heart_rate'))['heart_rate__avg'],
#
# 'total_problems': pb.count(),
# 'active_problems': pb.filter(status='ACTIVE').count(),
# 'resolved_problems': pb.filter(status='RESOLVED').count(),
#
# 'total_care_plans': cp.count(),
# 'active_care_plans': cp.filter(status='ACTIVE').count(),
# 'completed_care_plans': cp.filter(status='COMPLETED').count(),
#
# 'total_notes': cn.count(),
# 'notes_today': cn.filter(created_at__date=today).count(),
#
# 'recent_encounters': enc.select_related('patient','provider').order_by('-scheduled_datetime')[:5],
# 'recent_vitals': vs.select_related('encounter','recorded_by').order_by('-recorded_at')[:5],
# 'recent_notes': cn.select_related('encounter','author').order_by('-created_at')[:5],
# })
# return ctx
#
#
# # Encounter ----------------------------------------------------------------------
#
# class EncounterListView(LoginRequiredMixin, TenantMixin, FormMixin, ListView):
# model = Encounter
# template_name = 'emr/encounter_list.html'
# context_object_name = 'encounters'
# paginate_by = 20
# form_class = EMRSearchForm
#
# def get_queryset(self):
# qs = super().get_queryset().select_related('patient','provider').order_by('-scheduled_datetime')
# if self.request.GET:
# form = self.get_form()
# if form.is_valid():
# cd = form.cleaned_data
# if cd.get('search'):
# qs = qs.filter(
# Q(patient__first_name__icontains=cd['search']) |
# Q(patient__last_name__icontains=cd['search']) |
# Q(chief_complaint__icontains=cd['search'])
# )
# for fld in ('patient','provider','encounter_type','status'):
# if cd.get(fld):
# qs = qs.filter(**{fld: cd[fld]})
# if cd.get('date_from'):
# qs = qs.filter(scheduled_datetime__date__gte=cd['date_from'])
# if cd.get('date_to'):
# qs = qs.filter(scheduled_datetime__date__lte=cd['date_to'])
# return qs
#
# def get_context_data(self, **kwargs):
# ctx = super().get_context_data(**kwargs)
# ctx['search_form'] = self.get_form()
# ctx['total_count'] = self.get_queryset().count()
# return ctx
#
#
# class EncounterDetailView(LoginRequiredMixin, TenantMixin, DetailView):
# model = Encounter
# template_name = 'emr/encounter_detail.html'
# context_object_name = 'encounter'
#
# def get_context_data(self, **kwargs):
# ctx = super().get_context_data(**kwargs)
# enc = self.object
# ctx.update({
# 'vital_signs': VitalSigns.objects.filter(encounter=enc).order_by('-recorded_at'),
# 'clinical_notes': ClinicalNote.objects.filter(encounter=enc).order_by('-created_at'),
# 'problems': ProblemList.objects.filter(patient=enc.patient, status='ACTIVE'),
# 'care_plans': CarePlan.objects.filter(patient=enc.patient, status='ACTIVE'),
# })
# return ctx
#
#
# class EncounterCreateView(
# LoginRequiredMixin, FormKwargsMixin,
# SuccessMessageMixin, CreateView
# ):
# model = Encounter
# form_class = EncounterForm
# template_name = 'emr/encounter_form.html'
# success_message = _('Encounter for %(patient)s created successfully.')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# response = super().form_valid(form)
# AuditLogEntry.objects.create(
# tenant=self.request.user.tenant,
# user=self.request.user,
# action='CREATE',
# model_name='Encounter',
# object_id=str(self.object.pk),
# changes={'status': 'Encounter created'}
# )
# return response
#
# def get_success_url(self):
# return reverse_lazy('emr:encounter_detail', kwargs={'pk': self.object.pk})
#
#
# class EncounterUpdateView(
# LoginRequiredMixin, FormKwargsMixin,
# SuccessMessageMixin, TenantMixin, UpdateView
# ):
# model = Encounter
# form_class = EncounterForm
# template_name = 'emr/encounter_form.html'
# success_message = _('Encounter for %(patient)s updated successfully.')
#
# def dispatch(self, request, *args, **kwargs):
# enc = self.get_object()
# if enc.status == 'COMPLETED' and not request.user.is_superuser:
# messages.error(request, _('Cannot modify a completed encounter.'))
# return redirect('emr:encounter_detail', pk=enc.pk)
# return super().dispatch(request, *args, **kwargs)
#
# def form_valid(self, form):
# response = super().form_valid(form)
# AuditLogEntry.objects.create(
# tenant=self.request.user.tenant,
# user=self.request.user,
# action='UPDATE',
# model_name='Encounter',
# object_id=str(self.object.pk),
# changes={'status': 'Encounter updated'}
# )
# return response
#
# def get_success_url(self):
# return reverse_lazy('emr:encounter_detail', kwargs={'pk': self.object.pk})
#
#
# class EncounterDeleteView(
# LoginRequiredMixin, TenantMixin,
# SuccessMessageMixin, DeleteView
# ):
# model = Encounter
# template_name = 'emr/encounter_confirm_delete.html'
# success_url = reverse_lazy('emr:encounter_list')
# success_message = _('Encounter deleted successfully.')
#
# def delete(self, request, *args, **kwargs):
# enc = self.get_object()
# AuditLogEntry.objects.create(
# tenant=request.user.tenant,
# user=request.user,
# action='DELETE',
# model_name='Encounter',
# object_id=str(enc.pk),
# changes={'status': 'Encounter deleted'}
# )
# messages.success(request, self.success_message)
# return super().delete(request, *args, **kwargs)
#
#
# # VitalSigns ----------------------------------------------------------------------
#
# class VitalSignsListView(LoginRequiredMixin, TenantMixin, ListView):
# model = VitalSigns
# template_name = 'emr/vital_signs_list.html'
# context_object_name = 'vital_signs'
# paginate_by = 20
#
# def get_queryset(self):
# qs = super().get_queryset().select_related('encounter','recorded_by').order_by('-recorded_at')
# # (Search/filter logic would use a VitalSignsSearchForm — omitted for brevity)
# return qs
#
# class VitalSignsUpdateView(LoginRequiredMixin, FormKwargsMixin, SuccessMessageMixin, TenantMixin, UpdateView):
# model = VitalSigns
# form_class = VitalSignsForm
# template_name = 'emr/vital_signs_form.html'
# success_message = _('Vital signs updated successfully.')
# # implement form_valid, get_success_url, etc.
#
# class VitalSignsDeleteView(LoginRequiredMixin, TenantMixin, SuccessMessageMixin, DeleteView):
# model = VitalSigns
# template_name = 'emr/vital_signs_confirm_delete.html'
# success_url = reverse_lazy('emr:vitalsigns_list')
# success_message = _('Vital signs entry deleted.')
#
# """
# Radiology app views with healthcare-focused CRUD operations.
# Implements appropriate access patterns for radiology and imaging workflows.
# """
#
# from django.shortcuts import render, get_object_or_404, redirect
# from django.contrib.auth.decorators import login_required
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
# from django.views.generic import (
# ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView
# )
# from django.http import JsonResponse, HttpResponse
# from django.db.models import Q, Count, Avg, Sum, F
# from django.utils import timezone
# from django.contrib import messages
# from django.urls import reverse_lazy, reverse
# from django.core.paginator import Paginator
# from django.template.loader import render_to_string
# from datetime import datetime, timedelta, date
# import json
#
# from core.utils import AuditLogger
# from .models import (
# ImagingOrder, ImagingStudy, ImagingSeries, DICOMImage,
# RadiologyReport, ReportTemplate
# )
# from .forms import (
# ImagingOrderForm, ImagingStudyForm, RadiologyReportForm,
# ReportTemplateForm, ImagingSeriesForm
# )
#
#
# # ============================================================================
# # DASHBOARD AND OVERVIEW VIEWS
# # ============================================================================
#
# class RadiologyDashboardView(LoginRequiredMixin, TemplateView):
# """
# Main radiology dashboard with key metrics and recent activity.
# """
# template_name = 'radiology/dashboard.html'
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# tenant = self.request.user.tenant
# today = timezone.now().date()
#
# # Dashboard statistics
# context.update({
# 'pending_orders': ImagingOrder.objects.filter(
# tenant=tenant,
# status='PENDING'
# ).count(),
# 'scheduled_studies': ImagingStudy.objects.filter(
# tenant=tenant,
# status='SCHEDULED'
# ).count(),
# 'in_progress_studies': ImagingStudy.objects.filter(
# tenant=tenant,
# status='IN_PROGRESS'
# ).count(),
# 'studies_completed_today': ImagingStudy.objects.filter(
# tenant=tenant,
# completed_datetime__date=today,
# status='COMPLETED'
# ).count(),
# 'reports_pending': RadiologyReport.objects.filter(
# tenant=tenant,
# status='DRAFT'
# ).count(),
# 'reports_signed_today': RadiologyReport.objects.filter(
# tenant=tenant,
# signed_datetime__date=today,
# status='SIGNED'
# ).count(),
# 'critical_findings': RadiologyReport.objects.filter(
# tenant=tenant,
# has_critical_findings=True,
# status='SIGNED',
# signed_datetime__date=today
# ).count(),
# 'total_images_today': DICOMImage.objects.filter(
# tenant=tenant,
# created_at__date=today
# ).count(),
# })
#
# # Recent orders
# context['recent_orders'] = ImagingOrder.objects.filter(
# tenant=tenant
# ).select_related('patient', 'ordering_provider').order_by('-order_datetime')[:10]
#
# # Recent studies
# context['recent_studies'] = ImagingStudy.objects.filter(
# tenant=tenant
# ).select_related('order__patient').order_by('-study_datetime')[:10]
#
# # Recent reports
# context['recent_reports'] = RadiologyReport.objects.filter(
# tenant=tenant,
# status='SIGNED'
# ).select_related('study__order__patient').order_by('-signed_datetime')[:10]
#
# return context
#
#
# # ============================================================================
# # REPORT TEMPLATE VIEWS (FULL CRUD - Master Data)
# # ============================================================================
#
# class ReportTemplateListView(LoginRequiredMixin, ListView):
# """
# List all radiology report templates with filtering and search.
# """
# model = ReportTemplate
# template_name = 'radiology/report_template_list.html'
# context_object_name = 'report_templates'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = ReportTemplate.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(template_name__icontains=search) |
# Q(modality__icontains=search) |
# Q(body_part__icontains=search)
# )
#
# # Filter by modality
# modality = self.request.GET.get('modality')
# if modality:
# queryset = queryset.filter(modality=modality)
#
# # Filter by active status
# active_only = self.request.GET.get('active_only')
# if active_only:
# queryset = queryset.filter(is_active=True)
#
# return queryset.order_by('template_name')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'modalities': ReportTemplate._meta.get_field('modality').choices,
# 'search_query': self.request.GET.get('search', ''),
# })
# return context
#
#
# class ReportTemplateDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a report template.
# """
# model = ReportTemplate
# template_name = 'radiology/report_template_detail.html'
# context_object_name = 'report_template'
#
# def get_queryset(self):
# return ReportTemplate.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# template = self.object
#
# # Get recent reports using this template
# context['recent_reports'] = RadiologyReport.objects.filter(
# template=template,
# tenant=self.request.user.tenant
# ).select_related('study__order__patient').order_by('-created_at')[:10]
#
# return context
#
#
# class ReportTemplateCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new report template.
# """
# model = ReportTemplate
# form_class = ReportTemplateForm
# template_name = 'radiology/report_template_form.html'
# permission_required = 'radiology.add_reporttemplate'
# success_url = reverse_lazy('radiology:report_template_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.created_by = self.request.user
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='REPORT_TEMPLATE_CREATED',
# model='ReportTemplate',
# object_id=str(self.object.id),
# details={
# 'template_name': self.object.template_name,
# 'modality': self.object.modality
# }
# )
#
# messages.success(self.request, f'Report template "{self.object.template_name}" created successfully.')
# return response
#
#
# class ReportTemplateUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update an existing report template.
# """
# model = ReportTemplate
# form_class = ReportTemplateForm
# template_name = 'radiology/report_template_form.html'
# permission_required = 'radiology.change_reporttemplate'
#
# def get_queryset(self):
# return ReportTemplate.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('radiology:report_template_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='REPORT_TEMPLATE_UPDATED',
# model='ReportTemplate',
# object_id=str(self.object.id),
# details={
# 'template_name': self.object.template_name,
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, f'Report template "{self.object.template_name}" updated successfully.')
# return response
#
#
# class ReportTemplateDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
# """
# Delete a report template (soft delete by deactivating).
# """
# model = ReportTemplate
# template_name = 'radiology/report_template_confirm_delete.html'
# permission_required = 'radiology.delete_reporttemplate'
# success_url = reverse_lazy('radiology:report_template_list')
#
# def get_queryset(self):
# return ReportTemplate.objects.filter(tenant=self.request.user.tenant)
#
# def delete(self, request, *args, **kwargs):
# self.object = self.get_object()
#
# # Soft delete by deactivating instead of actual deletion
# self.object.is_active = False
# self.object.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='REPORT_TEMPLATE_DEACTIVATED',
# model='ReportTemplate',
# object_id=str(self.object.id),
# details={'template_name': self.object.template_name}
# )
#
# messages.success(request, f'Report template "{self.object.template_name}" deactivated successfully.')
# return redirect(self.success_url)
#
#
# # ============================================================================
# # IMAGING ORDER VIEWS (RESTRICTED CRUD - Clinical Orders)
# # ============================================================================
#
# class ImagingOrderListView(LoginRequiredMixin, ListView):
# """
# List all imaging orders with filtering and search.
# """
# model = ImagingOrder
# template_name = 'radiology/imaging_order_list.html'
# context_object_name = 'imaging_orders'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = ImagingOrder.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(patient__first_name__icontains=search) |
# Q(patient__last_name__icontains=search) |
# Q(patient__mrn__icontains=search) |
# Q(study_description__icontains=search)
# )
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by modality
# modality = self.request.GET.get('modality')
# if modality:
# queryset = queryset.filter(modality=modality)
#
# # Filter by priority
# priority = self.request.GET.get('priority')
# if priority:
# queryset = queryset.filter(priority=priority)
#
# # Filter by date range
# date_from = self.request.GET.get('date_from')
# date_to = self.request.GET.get('date_to')
# if date_from:
# queryset = queryset.filter(order_datetime__date__gte=date_from)
# if date_to:
# queryset = queryset.filter(order_datetime__date__lte=date_to)
#
# return queryset.select_related(
# 'patient', 'ordering_provider'
# ).order_by('-order_datetime')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'statuses': ImagingOrder._meta.get_field('status').choices,
# 'modalities': ImagingOrder._meta.get_field('modality').choices,
# 'priorities': ImagingOrder._meta.get_field('priority').choices,
# })
# return context
#
#
# class ImagingOrderDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about an imaging order.
# """
# model = ImagingOrder
# template_name = 'radiology/imaging_order_detail.html'
# context_object_name = 'imaging_order'
#
# def get_queryset(self):
# return ImagingOrder.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# imaging_order = self.object
#
# # Get studies for this order
# context['studies'] = imaging_order.studies.all().order_by('-study_datetime')
#
# # Get reports for this order
# context['reports'] = RadiologyReport.objects.filter(
# study__order=imaging_order,
# tenant=self.request.user.tenant
# ).order_by('-created_at')
#
# return context
#
#
# class ImagingOrderCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new imaging order.
# """
# model = ImagingOrder
# form_class = ImagingOrderForm
# template_name = 'radiology/imaging_order_form.html'
# permission_required = 'radiology.add_imagingorder'
# success_url = reverse_lazy('radiology:imaging_order_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.ordering_provider = self.request.user
# form.instance.order_datetime = timezone.now()
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='IMAGING_ORDER_CREATED',
# model='ImagingOrder',
# object_id=str(self.object.order_id),
# details={
# 'patient_name': f"{self.object.patient.first_name} {self.object.patient.last_name}",
# 'modality': self.object.modality,
# 'study_description': self.object.study_description,
# 'priority': self.object.priority
# }
# )
#
# messages.success(self.request, 'Imaging order created successfully.')
# return response
#
#
# class ImagingOrderUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update imaging order (limited to status and notes only).
# """
# model = ImagingOrder
# fields = ['status', 'notes'] # Restricted fields for clinical orders
# template_name = 'radiology/imaging_order_update_form.html'
# permission_required = 'radiology.change_imagingorder'
#
# def get_queryset(self):
# return ImagingOrder.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('radiology:imaging_order_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='IMAGING_ORDER_UPDATED',
# model='ImagingOrder',
# object_id=str(self.object.order_id),
# details={
# 'patient_name': f"{self.object.patient.first_name} {self.object.patient.last_name}",
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, 'Imaging order updated successfully.')
# return response
#
#
# # ============================================================================
# # IMAGING STUDY VIEWS (RESTRICTED CRUD - Clinical Data)
# # ============================================================================
#
# class ImagingStudyListView(LoginRequiredMixin, ListView):
# """
# List all imaging studies with filtering and search.
# """
# model = ImagingStudy
# template_name = 'radiology/imaging_study_list.html'
# context_object_name = 'imaging_studies'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = ImagingStudy.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(study_id__icontains=search) |
# Q(order__patient__first_name__icontains=search) |
# Q(order__patient__last_name__icontains=search) |
# Q(order__patient__mrn__icontains=search)
# )
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by modality
# modality = self.request.GET.get('modality')
# if modality:
# queryset = queryset.filter(order__modality=modality)
#
# return queryset.select_related('order__patient').order_by('-study_datetime')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'statuses': ImagingStudy._meta.get_field('status').choices,
# 'modalities': ImagingOrder._meta.get_field('modality').choices,
# })
# return context
#
#
# class ImagingStudyDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about an imaging study.
# """
# model = ImagingStudy
# template_name = 'radiology/imaging_study_detail.html'
# context_object_name = 'imaging_study'
#
# def get_queryset(self):
# return ImagingStudy.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# imaging_study = self.object
#
# # Get series for this study
# context['series'] = imaging_study.series.all().order_by('series_number')
#
# # Get reports for this study
# context['reports'] = imaging_study.reports.all().order_by('-created_at')
#
# # Get total image count
# context['total_images'] = DICOMImage.objects.filter(
# series__study=imaging_study,
# tenant=self.request.user.tenant
# ).count()
#
# return context
#
#
# class ImagingStudyCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new imaging study.
# """
# model = ImagingStudy
# form_class = ImagingStudyForm
# template_name = 'radiology/imaging_study_form.html'
# permission_required = 'radiology.add_imagingstudy'
# success_url = reverse_lazy('radiology:imaging_study_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.technologist = self.request.user
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='IMAGING_STUDY_CREATED',
# model='ImagingStudy',
# object_id=str(self.object.study_id),
# details={
# 'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
# 'modality': self.object.order.modality
# }
# )
#
# messages.success(self.request, 'Imaging study created successfully.')
# return response
#
#
# class ImagingStudyUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update imaging study (limited to status and technical notes).
# """
# model = ImagingStudy
# fields = ['status', 'technical_notes'] # Restricted fields
# template_name = 'radiology/imaging_study_update_form.html'
# permission_required = 'radiology.change_imagingstudy'
#
# def get_queryset(self):
# return ImagingStudy.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('radiology:imaging_study_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='IMAGING_STUDY_UPDATED',
# model='ImagingStudy',
# object_id=str(self.object.study_id),
# details={
# 'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, 'Imaging study updated successfully.')
# return response
#
#
# # ============================================================================
# # IMAGING SERIES VIEWS (RESTRICTED CRUD - Clinical Data)
# # ============================================================================
#
# class ImagingSeriesListView(LoginRequiredMixin, ListView):
# """
# List all imaging series with filtering and search.
# """
# model = ImagingSeries
# template_name = 'radiology/imaging_series_list.html'
# context_object_name = 'imaging_series'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = ImagingSeries.objects.filter(tenant=self.request.user.tenant)
#
# # Filter by study
# study_id = self.request.GET.get('study')
# if study_id:
# queryset = queryset.filter(study_id=study_id)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(series_description__icontains=search) |
# Q(study__order__patient__first_name__icontains=search) |
# Q(study__order__patient__last_name__icontains=search)
# )
#
# return queryset.select_related('study__order__patient').order_by('-created_at')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'studies': ImagingStudy.objects.filter(
# tenant=self.request.user.tenant
# ).select_related('order__patient').order_by('-study_datetime')[:50],
# })
# return context
#
#
# class ImagingSeriesDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about an imaging series.
# """
# model = ImagingSeries
# template_name = 'radiology/imaging_series_detail.html'
# context_object_name = 'imaging_series'
#
# def get_queryset(self):
# return ImagingSeries.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# imaging_series = self.object
#
# # Get images for this series
# context['images'] = imaging_series.images.all().order_by('instance_number')
#
# return context
#
#
# class ImagingSeriesCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new imaging series.
# """
# model = ImagingSeries
# form_class = ImagingSeriesForm
# template_name = 'radiology/imaging_series_form.html'
# permission_required = 'radiology.add_imagingseries'
# success_url = reverse_lazy('radiology:imaging_series_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='IMAGING_SERIES_CREATED',
# model='ImagingSeries',
# object_id=str(self.object.series_id),
# details={
# 'series_description': self.object.series_description,
# 'study_id': str(self.object.study.study_id)
# }
# )
#
# messages.success(self.request, 'Imaging series created successfully.')
# return response
#
#
# # ============================================================================
# # DICOM IMAGE VIEWS (READ-ONLY - System Generated)
# # ============================================================================
#
# class DICOMImageListView(LoginRequiredMixin, ListView):
# """
# List all DICOM images with filtering and search.
# """
# model = DICOMImage
# template_name = 'radiology/dicom_image_list.html'
# context_object_name = 'dicom_images'
# paginate_by = 50
#
# def get_queryset(self):
# queryset = DICOMImage.objects.filter(tenant=self.request.user.tenant)
#
# # Filter by series
# series_id = self.request.GET.get('series')
# if series_id:
# queryset = queryset.filter(series_id=series_id)
#
# # Filter by study
# study_id = self.request.GET.get('study')
# if study_id:
# queryset = queryset.filter(series__study_id=study_id)
#
# return queryset.select_related('series__study__order__patient').order_by('instance_number')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'series': ImagingSeries.objects.filter(
# tenant=self.request.user.tenant
# ).select_related('study__order__patient').order_by('-created_at')[:50],
# })
# return context
#
#
# class DICOMImageDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a DICOM image.
# """
# model = DICOMImage
# template_name = 'radiology/dicom_image_detail.html'
# context_object_name = 'dicom_image'
#
# def get_queryset(self):
# return DICOMImage.objects.filter(tenant=self.request.user.tenant)
#
#
# # ============================================================================
# # RADIOLOGY REPORT VIEWS (APPEND-ONLY - Clinical Records)
# # ============================================================================
#
# class RadiologyReportListView(LoginRequiredMixin, ListView):
# """
# List all radiology reports with filtering and search.
# """
# model = RadiologyReport
# template_name = 'radiology/radiology_report_list.html'
# context_object_name = 'radiology_reports'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = RadiologyReport.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(study__order__patient__first_name__icontains=search) |
# Q(study__order__patient__last_name__icontains=search) |
# Q(study__order__patient__mrn__icontains=search) |
# Q(findings__icontains=search) |
# Q(impression__icontains=search)
# )
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by critical findings
# critical_only = self.request.GET.get('critical_only')
# if critical_only:
# queryset = queryset.filter(has_critical_findings=True)
#
# # Filter by radiologist
# radiologist_id = self.request.GET.get('radiologist')
# if radiologist_id:
# queryset = queryset.filter(radiologist_id=radiologist_id)
#
# return queryset.select_related(
# 'study__order__patient', 'radiologist', 'template'
# ).order_by('-created_at')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'statuses': RadiologyReport._meta.get_field('status').choices,
# })
# return context
#
#
# class RadiologyReportDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a radiology report.
# """
# model = RadiologyReport
# template_name = 'radiology/radiology_report_detail.html'
# context_object_name = 'radiology_report'
#
# def get_queryset(self):
# return RadiologyReport.objects.filter(tenant=self.request.user.tenant)
#
#
# class RadiologyReportCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new radiology report.
# """
# model = RadiologyReport
# form_class = RadiologyReportForm
# template_name = 'radiology/radiology_report_form.html'
# permission_required = 'radiology.add_radiologyreport'
# success_url = reverse_lazy('radiology:radiology_report_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.radiologist = self.request.user
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='RADIOLOGY_REPORT_CREATED',
# model='RadiologyReport',
# object_id=str(self.object.report_id),
# details={
# 'patient_name': f"{self.object.study.order.patient.first_name} {self.object.study.order.patient.last_name}",
# 'has_critical_findings': self.object.has_critical_findings
# }
# )
#
# messages.success(self.request, 'Radiology report created successfully.')
# return response
#
#
# # Note: No UpdateView or DeleteView for RadiologyReport - Append-only for clinical records
# # Reports can only be amended through addendum process after signing
#
#
# # ============================================================================
# # HTMX VIEWS FOR REAL-TIME UPDATES
# # ============================================================================
#
# @login_required
# def radiology_stats(request):
# """
# HTMX endpoint for radiology statistics.
# """
# tenant = request.user.tenant
# today = timezone.now().date()
#
# stats = {
# 'pending_orders': ImagingOrder.objects.filter(
# tenant=tenant,
# status='PENDING'
# ).count(),
# 'scheduled_studies': ImagingStudy.objects.filter(
# tenant=tenant,
# status='SCHEDULED'
# ).count(),
# 'in_progress_studies': ImagingStudy.objects.filter(
# tenant=tenant,
# status='IN_PROGRESS'
# ).count(),
# 'reports_pending': RadiologyReport.objects.filter(
# tenant=tenant,
# status='DRAFT'
# ).count(),
# 'critical_findings': RadiologyReport.objects.filter(
# tenant=tenant,
# has_critical_findings=True,
# status='SIGNED',
# signed_datetime__date=today
# ).count(),
# }
#
# return render(request, 'radiology/partials/radiology_stats.html', {'stats': stats})
#
#
# @login_required
# def order_search(request):
# """
# HTMX endpoint for imaging order search.
# """
# search = request.GET.get('search', '')
# status = request.GET.get('status', '')
# modality = request.GET.get('modality', '')
#
# queryset = ImagingOrder.objects.filter(tenant=request.user.tenant)
#
# if search:
# queryset = queryset.filter(
# Q(patient__first_name__icontains=search) |
# Q(patient__last_name__icontains=search) |
# Q(patient__mrn__icontains=search) |
# Q(study_description__icontains=search)
# )
#
# if status:
# queryset = queryset.filter(status=status)
#
# if modality:
# queryset = queryset.filter(modality=modality)
#
# orders = queryset.select_related(
# 'patient', 'ordering_provider'
# ).order_by('-order_datetime')[:20]
#
# return render(request, 'radiology/partials/order_list.html', {'orders': orders})
#
#
# # ============================================================================
# # ACTION VIEWS
# # ============================================================================
#
# @login_required
# def schedule_study(request, order_id):
# """
# Schedule an imaging study for an order.
# """
# if request.method == 'POST':
# order = get_object_or_404(
# ImagingOrder,
# id=order_id,
# tenant=request.user.tenant
# )
#
# scheduled_datetime = request.POST.get('scheduled_datetime')
# if scheduled_datetime:
# scheduled_datetime = timezone.datetime.fromisoformat(scheduled_datetime)
#
# # Create or update study
# study, created = ImagingStudy.objects.get_or_create(
# order=order,
# tenant=request.user.tenant,
# defaults={
# 'study_datetime': scheduled_datetime,
# 'status': 'SCHEDULED',
# 'technologist': request.user
# }
# )
#
# if not created:
# study.study_datetime = scheduled_datetime
# study.status = 'SCHEDULED'
# study.save()
#
# # Update order status
# order.status = 'SCHEDULED'
# order.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='STUDY_SCHEDULED',
# model='ImagingOrder',
# object_id=str(order.order_id),
# details={
# 'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
# 'scheduled_time': scheduled_datetime.isoformat()
# }
# )
#
# messages.success(request, 'Study scheduled successfully.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'radiology/partials/order_status.html', {'order': order})
#
# return redirect('radiology:imaging_order_detail', pk=order.pk)
#
# return JsonResponse({'success': False})
#
#
# @login_required
# def start_study(request, study_id):
# """
# Start an imaging study.
# """
# if request.method == 'POST':
# study = get_object_or_404(
# ImagingStudy,
# id=study_id,
# tenant=request.user.tenant
# )
#
# study.status = 'IN_PROGRESS'
# study.started_datetime = timezone.now()
# study.technologist = request.user
# study.save()
#
# # Update order status
# study.order.status = 'IN_PROGRESS'
# study.order.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='STUDY_STARTED',
# model='ImagingStudy',
# object_id=str(study.study_id),
# details={
# 'patient_name': f"{study.order.patient.first_name} {study.order.patient.last_name}",
# 'modality': study.order.modality
# }
# )
#
# messages.success(request, 'Study started successfully.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'radiology/partials/study_status.html', {'study': study})
#
# return redirect('radiology:imaging_study_detail', pk=study.pk)
#
# return JsonResponse({'success': False})
#
#
# @login_required
# def complete_study(request, study_id):
# """
# Complete an imaging study.
# """
# if request.method == 'POST':
# study = get_object_or_404(
# ImagingStudy,
# id=study_id,
# tenant=request.user.tenant
# )
#
# study.status = 'COMPLETED'
# study.completed_datetime = timezone.now()
# study.save()
#
# # Update order status
# study.order.status = 'COMPLETED'
# study.order.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='STUDY_COMPLETED',
# model='ImagingStudy',
# object_id=str(study.study_id),
# details={
# 'patient_name': f"{study.order.patient.first_name} {study.order.patient.last_name}",
# 'modality': study.order.modality
# }
# )
#
# messages.success(request, 'Study completed successfully.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'radiology/partials/study_status.html', {'study': study})
#
# return redirect('radiology:imaging_study_detail', pk=study.pk)
#
# return JsonResponse({'success': False})
#
#
# @login_required
# def sign_report(request, report_id):
# """
# Sign a radiology report.
# """
# if request.method == 'POST':
# report = get_object_or_404(
# RadiologyReport,
# id=report_id,
# tenant=request.user.tenant
# )
#
# # Only allow signing if report is in draft status
# if report.status != 'DRAFT':
# messages.error(request, 'Only draft reports can be signed.')
# return redirect('radiology:radiology_report_detail', pk=report.pk)
#
# report.status = 'SIGNED'
# report.signed_datetime = timezone.now()
# report.signed_by = request.user
# report.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='REPORT_SIGNED',
# model='RadiologyReport',
# object_id=str(report.report_id),
# details={
# 'patient_name': f"{report.study.order.patient.first_name} {report.study.order.patient.last_name}",
# 'has_critical_findings': report.has_critical_findings
# }
# )
#
# messages.success(request, 'Report signed successfully.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'radiology/partials/report_status.html', {'report': report})
#
# return redirect('radiology:radiology_report_detail', pk=report.pk)
#
# return JsonResponse({'success': False})
#
#
# @login_required
# def dictate_report(request, study_id):
# """
# Start dictating a report for a study.
# """
# if request.method == 'POST':
# study = get_object_or_404(
# ImagingStudy,
# id=study_id,
# tenant=request.user.tenant
# )
#
# template_id = request.POST.get('template_id')
# template = None
# if template_id:
# template = get_object_or_404(
# ReportTemplate,
# id=template_id,
# tenant=request.user.tenant
# )
#
# # Create report if it doesn't exist
# report, created = RadiologyReport.objects.get_or_create(
# study=study,
# tenant=request.user.tenant,
# defaults={
# 'radiologist': request.user,
# 'template': template,
# 'status': 'DRAFT'
# }
# )
#
# if created:
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='REPORT_DICTATION_STARTED',
# model='RadiologyReport',
# object_id=str(report.report_id),
# details={
# 'patient_name': f"{study.order.patient.first_name} {study.order.patient.last_name}",
# 'template_used': template.template_name if template else None
# }
# )
#
# messages.success(request, 'Report dictation started.')
#
# return redirect('radiology:radiology_report_detail', pk=report.pk)
#
# return JsonResponse({'success': False})
#
#
#
#
# @login_required
# @require_POST
# def clock_in(request, employee_id):
# """
# Clock in an employee. If HTMX, return the updated controls partial (button -> time).
# Otherwise return JSON.
# """
# # Prefer the URL param; fall back to POST field for safety
# emp_id = employee_id or request.POST.get('employee_id')
#
# try:
# employee = Employee.objects.get(id=emp_id, tenant=request.user.tenant)
# except Employee.DoesNotExist:
# if request.headers.get('HX-Request'):
# return HttpResponse("Employee not found.", status=404)
# return JsonResponse({'success': False, 'message': 'Employee not found.'}, status=404)
#
# today = timezone.now().date()
# open_entry = TimeEntry.objects.filter(
# employee=employee,
# work_date=today,
# clock_out_time__isnull=True
# ).first()
#
# if open_entry:
# # Already clocked in — just re-render the controls so the UI shows the time
# if request.headers.get('HX-Request'):
# html = render_to_string(
# 'hr/partials/clock_controls.html',
# {'employee': employee, 'time_entry': open_entry},
# request=request
# )
# return HttpResponse(html)
# return JsonResponse({'success': False, 'message': 'Employee is already clocked in.'}, status=400)
#
# # Create new time entry
# time_entry = TimeEntry.objects.create(
# employee=employee,
# work_date=today,
# clock_in_time=timezone.now(),
# status='DRAFT'
# )
#
# if request.headers.get('HX-Request'):
# html = render_to_string(
# 'hr/partials/clock_controls.html',
# {'employee': employee, 'time_entry': time_entry},
# request=request
# )
# return HttpResponse(html)
#
# return JsonResponse({
# 'success': True,
# 'message': f'{employee.get_full_name()} clocked in successfully.',
# 'time_entry_id': time_entry.id
# })
#
#
# @login_required
# @require_POST
# def clock_out(request, employee_id):
# """
# Clock out an employee.
# If HTMX, return the updated controls partial (button -> times).
# Otherwise, return JSON.
# """
# emp_id = employee_id or request.POST.get('employee_id')
#
# # Find employee in same tenant
# try:
# employee = Employee.objects.get(id=emp_id, tenant=request.user.tenant)
# except Employee.DoesNotExist:
# if request.headers.get('HX-Request'):
# # Re-render controls without an active entry (will show "Clock In")
# html = render_to_string(
# 'hr/partials/clock_controls.html',
# {'employee': None, 'time_entry': None}, # or pass the real employee if you want the button still visible
# request=request
# )
# return HttpResponse(html, status=404)
# return JsonResponse({'success': False, 'message': 'Employee not found.'}, status=404)
#
# # Locate today's open entry
# today = timezone.now().date()
# time_entry = TimeEntry.objects.filter(
# employee=employee,
# work_date=today,
# clock_out_time__isnull=True
# ).first()
#
# if not time_entry:
# if request.headers.get('HX-Request'):
# # No open entry → re-render to show "Clock In"
# html = render_to_string(
# 'hr/partials/clock_controls.html',
# {'employee': employee, 'time_entry': None},
# request=request
# )
# return HttpResponse(html, status=400)
# return JsonResponse({'success': False, 'message': 'No active clock-in found for this employee.'}, status=400)
#
# # Close the entry
# time_entry.clock_out_time = timezone.now()
# time_entry.save() # if your model computes total_hours on save
#
# if request.headers.get('HX-Request'):
# html = render_to_string(
# 'hr/partials/clock_controls.html',
# {'employee': employee, 'time_entry': time_entry},
# request=request
# )
# return HttpResponse(html)
#
# return JsonResponse({
# 'success': True,
# 'message': f'{employee.get_full_name()} clocked out successfully.',
# 'time_entry_id': time_entry.id,
# # If total_hours is Decimal, this makes it JSON-safe:
# 'hours_worked': float(getattr(time_entry, 'total_hours', 0) or 0)
# })
# Query patterns to use if needed
# # All upcoming sessions for a tenant (next 30 days)
# TrainingSession.objects.filter(
# tenant=request.user.tenant, start_at__gte=timezone.now(),
# start_at__lte=timezone.now() + timedelta(days=30)
# ).select_related('program', 'instructor')
#
# # Employees due for renewal in 30 days
# TrainingCertificates.objects.filter(
# tenant=request.user.tenant,
# expiry_date__lte=date.today() + timedelta(days=30),
# expiry_date__gte=date.today()
# ).select_related('employee', 'program')
#
# # Enroll an employee, respecting capacity
# session = TrainingSession.objects.select_for_update().get(pk=session_pk, tenant=tenant)
# if session.capacity and session.enrollments.count() >= session.capacity:
# status = 'WAITLISTED'
# else:
# status = 'SCHEDULED'
# enrollment = TrainingRecord.objects.create(
# tenant=tenant, employee=emp, program=session.program, session=session,
# status=status, created_by=request.user
# )
#
# # Mark completion + pass (auto-certificate will fire via signal)
# enrollment.status = 'COMPLETED'
# enrollment.passed = True
# enrollment.completion_date = date.today()
# enrollment.credits_earned = enrollment.hours
# enrollment.save()
#
# from django.shortcuts import render, redirect, get_object_or_404
# from django.views.generic import (
# ListView, DetailView, CreateView, UpdateView, DeleteView
# )
# from django.urls import reverse, reverse_lazy
# from django.contrib.auth.decorators import login_required
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
# from django.contrib import messages
# from django.utils import timezone
# from django.http import HttpResponse, JsonResponse
# from django.db.models import Q, Count, Sum, Avg, F, ExpressionWrapper, fields
# from django.template.loader import render_to_string
# from django.core.paginator import Paginator
#
# from .models import (
# Department, Employee, Schedule, ScheduleAssignment,
# TimeEntry, PerformanceReview, TrainingRecord
# )
# from .forms import (
# DepartmentForm, EmployeeForm, ScheduleForm, ScheduleAssignmentForm,
# TimeEntryForm, PerformanceReviewForm, TrainingRecordForm,
# HRSearchForm, TimeEntrySearchForm
# )
#
#
# class DashboardView(LoginRequiredMixin, ListView):
# """
# HR dashboard view displaying summary statistics.
# """
# template_name = 'hr/dashboard.html'
# context_object_name = 'employees'
# paginate_by = 10
#
# def get_queryset(self):
# """
# Get recent employees as primary queryset.
# """
# return Employee.objects.filter(
# tenant=self.request.user.tenant,
# is_active=True
# ).order_by('-hire_date')[:10]
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# tenant = self.request.user.tenant
#
# # Employee statistics
# context['total_employees'] = Employee.objects.filter(
# tenant=tenant
# ).count()
#
# context['active_employees'] = Employee.objects.filter(
# tenant=tenant,
# is_active=True
# ).count()
#
# context['department_stats'] = Department.objects.filter(
# tenant=tenant
# ).annotate(
# employee_count=Count('employees', filter=Q(employees__is_active=True))
# ).order_by('-employee_count')[:5]
#
# # Recent hires
# context['recent_hires'] = Employee.objects.filter(
# tenant=tenant
# ).order_by('-hire_date')[:5]
#
# # Upcoming reviews
# context['upcoming_reviews'] = PerformanceReview.objects.filter(
# tenant=tenant,
# status='PENDING',
# scheduled_date__gte=timezone.now().date()
# ).order_by('scheduled_date')[:5]
#
# # Employees on leave
# context['employees_on_leave'] = Employee.objects.filter(
# tenant=tenant,
# employment_status='LEAVE'
# ).order_by('last_name')
#
# # Training expiring soon
# context['expiring_trainings'] = TrainingRecord.objects.filter(
# tenant=tenant,
# status='COMPLETED',
# expiry_date__gte=timezone.now().date(),
# expiry_date__lte=timezone.now().date() + timezone.timedelta(days=30)
# ).order_by('expiry_date')[:5]
#
# # Time entry approvals pending
# context['pending_time_approvals'] = TimeEntry.objects.filter(
# tenant=tenant,
# is_approved=False,
# entry_date__gte=timezone.now().date() - timezone.timedelta(days=14)
# ).count()
#
# # Search form
# context['search_form'] = HRSearchForm(tenant=tenant)
#
# return context
#
#
# class EmployeeListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
# """
# List view for employees.
# """
# model = Employee
# template_name = 'hr/employee_list.html'
# context_object_name = 'employees'
# paginate_by = 20
# permission_required = 'hr.view_employee'
#
# def get_queryset(self):
# """
# Filter employees by tenant and apply search filters.
# """
# queryset = Employee.objects.filter(tenant=self.request.user.tenant)
#
# # Apply search filters
# form = HRSearchForm(self.request.GET, tenant=self.request.user.tenant)
# if form.is_valid():
# data = form.cleaned_data
#
# # Search term
# if data.get('search'):
# search_term = data['search']
# queryset = queryset.filter(
# Q(first_name__icontains=search_term) |
# Q(last_name__icontains=search_term) |
# Q(employee_number__icontains=search_term) |
# Q(position__icontains=search_term) |
# Q(personal_email__icontains=search_term) |
# Q(work_email__icontains=search_term)
# )
#
# # Department filter
# if data.get('department'):
# queryset = queryset.filter(department=data['department'])
#
# # Employment status filter
# if data.get('employment_status'):
# queryset = queryset.filter(employment_status=data['employment_status'])
#
# # Employment type filter
# if data.get('employment_type'):
# queryset = queryset.filter(employment_type=data['employment_type'])
#
# # Position filter
# if data.get('position'):
# queryset = queryset.filter(position__icontains=data['position'])
#
# # Hire date range
# if data.get('hire_date_from'):
# queryset = queryset.filter(hire_date__gte=data['hire_date_from'])
#
# if data.get('hire_date_to'):
# queryset = queryset.filter(hire_date__lte=data['hire_date_to'])
#
# # Active status
# if data.get('is_active') == 'True':
# queryset = queryset.filter(is_active=True)
# elif data.get('is_active') == 'False':
# queryset = queryset.filter(is_active=False)
#
# return queryset.select_related('department', 'supervisor')
#
# def get_context_data(self, **kwargs):
# """
# Add search form to context.
# """
# context = super().get_context_data(**kwargs)
# context['search_form'] = HRSearchForm(
# self.request.GET or None,
# tenant=self.request.user.tenant
# )
# return context
#
#
# class EmployeeDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
# """
# Detail view for employees.
# """
# model = Employee
# template_name = 'hr/employee_detail.html'
# context_object_name = 'employee'
# permission_required = 'hr.view_employee'
#
# def get_queryset(self):
# """
# Filter employee by tenant.
# """
# return Employee.objects.filter(
# tenant=self.request.user.tenant
# ).select_related(
# 'department', 'supervisor', 'user', 'created_by'
# )
#
# def get_context_data(self, **kwargs):
# """
# Add related objects to context.
# """
# context = super().get_context_data(**kwargs)
# employee = self.object
#
# # Current schedule
# context['current_schedule'] = Schedule.objects.filter(
# employee=employee,
# is_current=True,
# is_active=True
# ).first()
#
# # Recent time entries
# context['recent_time_entries'] = TimeEntry.objects.filter(
# employee=employee
# ).order_by('-entry_date', '-created_at')[:10]
#
# # Performance reviews
# context['performance_reviews'] = PerformanceReview.objects.filter(
# employee=employee
# ).order_by('-scheduled_date')[:5]
#
# # Training records
# context['training_records'] = TrainingRecord.objects.filter(
# employee=employee
# ).order_by('-start_date')[:10]
#
# # Subordinates
# context['subordinates'] = Employee.objects.filter(
# supervisor=employee,
# is_active=True
# ).order_by('last_name', 'first_name')
#
# return context
#
#
# class EmployeeCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create view for employees.
# """
# model = Employee
# form_class = EmployeeForm
# template_name = 'hr/employee_form.html'
# permission_required = 'hr.add_employee'
#
# def get_form_kwargs(self):
# """
# Pass tenant and user to form.
# """
# kwargs = super().get_form_kwargs()
# kwargs['tenant'] = self.request.user.tenant
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# """
# Set tenant and user before saving.
# """
# form.instance.tenant = self.request.user.tenant
# form.instance.created_by = self.request.user
#
# response = super().form_valid(form)
#
# messages.success(
# self.request,
# f"Employee {form.instance.get_full_name()} created successfully."
# )
#
# return response
#
# def get_success_url(self):
# """
# Redirect to employee detail view.
# """
# return reverse('hr:employee_detail', kwargs={'pk': self.object.pk})
#
#
# class EmployeeUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update view for employees.
# """
# model = Employee
# form_class = EmployeeForm
# template_name = 'hr/employee_form.html'
# permission_required = 'hr.change_employee'
#
# def get_queryset(self):
# """
# Filter employee by tenant.
# """
# return Employee.objects.filter(tenant=self.request.user.tenant)
#
# def get_form_kwargs(self):
# """
# Pass tenant and user to form.
# """
# kwargs = super().get_form_kwargs()
# kwargs['tenant'] = self.request.user.tenant
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# """
# Handle successful form validation.
# """
# response = super().form_valid(form)
#
# messages.success(
# self.request,
# f"Employee {form.instance.get_full_name()} updated successfully."
# )
#
# return response
#
# def get_success_url(self):
# """
# Redirect to employee detail view.
# """
# return reverse('hr:employee_detail', kwargs={'pk': self.object.pk})
#
#
# class EmployeeDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
# """
# Delete view for employees.
# """
# model = Employee
# template_name = 'hr/employee_confirm_delete.html'
# permission_required = 'hr.delete_employee'
#
# def get_queryset(self):
# """
# Filter employee by tenant.
# """
# return Employee.objects.filter(tenant=self.request.user.tenant)
#
# def delete(self, request, *args, **kwargs):
# """
# Override delete to show success message.
# """
# self.object = self.get_object()
# name = self.object.get_full_name()
#
# # Check if employee can be safely deleted
# if self.object.managed_departments.exists():
# messages.error(
# request,
# f"Cannot delete {name} because they are assigned as a department manager."
# )
# return redirect('hr:employee_detail', pk=self.object.pk)
#
# if self.object.subordinates.exists():
# messages.error(
# request,
# f"Cannot delete {name} because they are assigned as a supervisor."
# )
# return redirect('hr:employee_detail', pk=self.object.pk)
#
# success_url = self.get_success_url()
# self.object.delete()
#
# messages.success(request, f"Employee {name} deleted successfully.")
#
# return redirect(success_url)
#
# def get_success_url(self):
# """
# Redirect to employee list view.
# """
# return reverse_lazy('hr:employee_list')
#
#
# class DepartmentListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
# """
# List view for departments.
# """
# model = Department
# template_name = 'hr/department_list.html'
# context_object_name = 'departments'
# paginate_by = 20
# permission_required = 'hr.view_department'
#
# def get_queryset(self):
# """
# Filter departments by tenant.
# """
# queryset = Department.objects.filter(tenant=self.request.user.tenant)
#
# # Apply search filters
# search = self.request.GET.get('search', '')
# if search:
# queryset = queryset.filter(
# Q(name__icontains=search) |
# Q(code__icontains=search) |
# Q(description__icontains=search)
# )
#
# department_type = self.request.GET.get('department_type', '')
# if department_type:
# queryset = queryset.filter(department_type=department_type)
#
# is_active = self.request.GET.get('is_active', '')
# if is_active == 'True':
# queryset = queryset.filter(is_active=True)
# elif is_active == 'False':
# queryset = queryset.filter(is_active=False)
#
# return queryset.select_related('manager', 'parent_department')
#
# def get_context_data(self, **kwargs):
# """
# Add filter choices to context.
# """
# context = super().get_context_data(**kwargs)
# context['search'] = self.request.GET.get('search', '')
# context['department_types'] = Department.DEPARTMENT_TYPES
# context['selected_type'] = self.request.GET.get('department_type', '')
# context['selected_active'] = self.request.GET.get('is_active', '')
# return context
#
#
# class DepartmentDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
# """
# Detail view for departments.
# """
# model = Department
# template_name = 'hr/department_detail.html'
# context_object_name = 'department'
# permission_required = 'hr.view_department'
#
# def get_queryset(self):
# """
# Filter department by tenant.
# """
# return Department.objects.filter(
# tenant=self.request.user.tenant
# ).select_related(
# 'manager', 'parent_department', 'created_by'
# )
#
# def get_context_data(self, **kwargs):
# """
# Add related objects to context.
# """
# context = super().get_context_data(**kwargs)
# department = self.object
#
# # Employees in department
# context['employees'] = Employee.objects.filter(
# department=department,
# is_active=True
# ).order_by('last_name', 'first_name')
#
# # Sub-departments
# context['sub_departments'] = Department.objects.filter(
# parent_department=department,
# is_active=True
# ).order_by('name')
#
# return context
#
#
# class DepartmentCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create view for departments.
# """
# model = Department
# form_class = DepartmentForm
# template_name = 'hr/department_form.html'
# permission_required = 'hr.add_department'
#
# def get_form_kwargs(self):
# """
# Pass tenant and user to form.
# """
# kwargs = super().get_form_kwargs()
# kwargs['tenant'] = self.request.user.tenant
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# """
# Set tenant and user before saving.
# """
# form.instance.tenant = self.request.user.tenant
# form.instance.created_by = self.request.user
#
# response = super().form_valid(form)
#
# messages.success(
# self.request,
# f"Department {form.instance.name} created successfully."
# )
#
# return response
#
# def get_success_url(self):
# """
# Redirect to department detail view.
# """
# return reverse('hr:department_detail', kwargs={'pk': self.object.pk})
#
#
# class DepartmentUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update view for departments.
# """
# model = Department
# form_class = DepartmentForm
# template_name = 'hr/department_form.html'
# permission_required = 'hr.change_department'
#
# def get_queryset(self):
# """
# Filter department by tenant.
# """
# return Department.objects.filter(tenant=self.request.user.tenant)
#
# def get_form_kwargs(self):
# """
# Pass tenant and user to form.
# """
# kwargs = super().get_form_kwargs()
# kwargs['tenant'] = self.request.user.tenant
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# """
# Handle successful form validation.
# """
# response = super().form_valid(form)
#
# messages.success(
# self.request,
# f"Department {form.instance.name} updated successfully."
# )
#
# return response
#
# def get_success_url(self):
# """
# Redirect to department detail view.
# """
# return reverse('hr:department_detail', kwargs={'pk': self.object.pk})
#
#
# class DepartmentDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
# """
# Delete view for departments.
# """
# model = Department
# template_name = 'hr/department_confirm_delete.html'
# permission_required = 'hr.delete_department'
#
# def get_queryset(self):
# """
# Filter department by tenant.
# """
# return Department.objects.filter(tenant=self.request.user.tenant)
#
# def delete(self, request, *args, **kwargs):
# """
# Override delete to show success message.
# """
# self.object = self.get_object()
# name = self.object.name
#
# # Check if department can be safely deleted
# if self.object.employees.exists():
# messages.error(
# request,
# f"Cannot delete {name} because it has employees assigned to it."
# )
# return redirect('hr:department_detail', pk=self.object.pk)
#
# if self.object.sub_departments.exists():
# messages.error(
# request,
# f"Cannot delete {name} because it has sub-departments assigned to it."
# )
# return redirect('hr:department_detail', pk=self.object.pk)
#
# success_url = self.get_success_url()
# self.object.delete()
#
# messages.success(request, f"Department {name} deleted successfully.")
#
# return redirect(success_url)
#
# def get_success_url(self):
# """
# Redirect to department list view.
# """
# return reverse_lazy('hr:department_list')
#
#
# class ScheduleListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
# """
# List view for schedules.
# """
# model = Schedule
# template_name = 'hr/schedule_list.html'
# context_object_name = 'schedules'
# paginate_by = 20
# permission_required = 'hr.view_schedule'
#
# def get_queryset(self):
# """
# Filter schedules by tenant and apply search filters.
# """
# queryset = Schedule.objects.filter(
# employee__tenant=self.request.user.tenant
# )
#
# # Apply search filters
# search = self.request.GET.get('search', '')
# if search:
# queryset = queryset.filter(
# Q(name__icontains=search) |
# Q(description__icontains=search) |
# Q(employee__first_name__icontains=search) |
# Q(employee__last_name__icontains=search) |
# Q(employee__employee_number__icontains=search)
# )
#
# employee_id = self.request.GET.get('employee', '')
# if employee_id:
# queryset = queryset.filter(employee_id=employee_id)
#
# schedule_type = self.request.GET.get('schedule_type', '')
# if schedule_type:
# queryset = queryset.filter(schedule_type=schedule_type)
#
# is_active = self.request.GET.get('is_active', '')
# if is_active == 'True':
# queryset = queryset.filter(is_active=True)
# elif is_active == 'False':
# queryset = queryset.filter(is_active=False)
#
# is_current = self.request.GET.get('is_current', '')
# if is_current == 'True':
# queryset = queryset.filter(is_current=True)
#
# return queryset.select_related('employee', 'approved_by')
#
# def get_context_data(self, **kwargs):
# """
# Add filter choices to context.
# """
# context = super().get_context_data(**kwargs)
# tenant = self.request.user.tenant
#
# context['search'] = self.request.GET.get('search', '')
# context['employees'] = Employee.objects.filter(
# tenant=tenant,
# is_active=True
# ).order_by('last_name', 'first_name')
# context['schedule_types'] = Schedule.SCHEDULE_TYPES
# context['selected_employee'] = self.request.GET.get('employee', '')
# context['selected_type'] = self.request.GET.get('schedule_type', '')
# context['selected_active'] = self.request.GET.get('is_active', '')
# context['selected_current'] = self.request.GET.get('is_current', '')
#
# return context
#
#
# class ScheduleDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
# """
# Detail view for schedules.
# """
# model = Schedule
# template_name = 'hr/schedule_detail.html'
# context_object_name = 'schedule'
# permission_required = 'hr.view_schedule'
#
# def get_queryset(self):
# """
# Filter schedule by tenant.
# """
# return Schedule.objects.filter(
# employee__tenant=self.request.user.tenant
# ).select_related(
# 'employee', 'approved_by', 'created_by'
# )
#
# def get_context_data(self, **kwargs):
# """
# Add related objects to context.
# """
# context = super().get_context_data(**kwargs)
# schedule = self.object
#
# # Assignments for this schedule
# context['assignments'] = ScheduleAssignment.objects.filter(
# schedule=schedule
# ).order_by('assignment_date', 'start_time')
#
# # Group assignments by week
# assignments_by_week = {}
# for assignment in context['assignments']:
# # Get week start date (Monday)
# week_start = assignment.assignment_date - timezone.timedelta(
# days=assignment.assignment_date.weekday()
# )
# week_key = week_start.strftime('%Y-%m-%d')
#
# if week_key not in assignments_by_week:
# assignments_by_week[week_key] = []
#
# assignments_by_week[week_key].append(assignment)
#
# context['assignments_by_week'] = assignments_by_week
#
# return context
#
#
# class ScheduleCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create view for schedules.
# """
# model = Schedule
# form_class = ScheduleForm
# template_name = 'hr/schedule_form.html'
# permission_required = 'hr.add_schedule'
#
# def get_form_kwargs(self):
# """
# Pass tenant and user to form.
# """
# kwargs = super().get_form_kwargs()
# kwargs['tenant'] = self.request.user.tenant
# kwargs['user'] = self.request.user
#
# # Pre-select employee if provided in GET parameters
# employee_id = self.request.GET.get('employee', None)
# if employee_id:
# kwargs['initial'] = kwargs.get('initial', {})
# kwargs['initial']['employee'] = employee_id
#
# return kwargs
#
# def form_valid(self, form):
# """
# Set tenant and user before saving.
# """
# form.instance.tenant = self.request.user.tenant
# form.instance.created_by = self.request.user
#
# # Set approval information
# form.instance.approved_by = self.request.user
# form.instance.approval_date = timezone.now()
#
# response = super().form_valid(form)
#
# messages.success(
# self.request,
# f"Schedule '{form.instance.name}' for {form.instance.employee.get_full_name()} created successfully."
# )
#
# return response
#
# def get_success_url(self):
# """
# Redirect to schedule detail view.
# """
# return reverse('hr:schedule_detail', kwargs={'pk': self.object.pk})
#
#
# class ScheduleUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update view for schedules.
# """
# model = Schedule
# form_class = ScheduleForm
# template_name = 'hr/schedule_form.html'
# permission_required = 'hr.change_schedule'
#
# def get_queryset(self):
# """
# Filter schedule by tenant.
# """
# return Schedule.objects.filter(
# employee__tenant=self.request.user.tenant
# )
#
# def get_form_kwargs(self):
# """
# Pass tenant and user to form.
# """
# kwargs = super().get_form_kwargs()
# kwargs['tenant'] = self.request.user.tenant
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# """
# Handle successful form validation.
# """
# # If is_current status changed, update approval information
# if 'is_current' in form.changed_data and form.instance.is_current:
# form.instance.approved_by = self.request.user
# form.instance.approval_date = timezone.now()
#
# response = super().form_valid(form)
#
# messages.success(
# self.request,
# f"Schedule '{form.instance.name}' updated successfully."
# )
#
# return response
#
# def get_success_url(self):
# """
# Redirect to schedule detail view.
# """
# return reverse('hr:schedule_detail', kwargs={'pk': self.object.pk})
#
#
# class ScheduleDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
# """
# Delete view for schedules.
# """
# model = Schedule
# template_name = 'hr/schedule_confirm_delete.html'
# permission_required = 'hr.delete_schedule'
#
# def get_queryset(self):
# """
# Filter schedule by tenant.
# """
# return Schedule.objects.filter(
# employee__tenant=self.request.user.tenant
# )
#
# def delete(self, request, *args, **kwargs):
# """
# Override delete to show success message.
# """
# self.object = self.get_object()
# employee_name = self.object.employee.get_full_name()
# schedule_name = self.object.name
#
# # Check if schedule can be safely deleted
# if self.object.is_current:
# messages.error(
# request,
# f"Cannot delete schedule '{schedule_name}' because it is the current schedule for {employee_name}."
# )
# return redirect('hr:schedule_detail', pk=self.object.pk)
#
# success_url = self.get_success_url()
# self.object.delete()
#
# messages.success(
# request,
# f"Schedule '{schedule_name}' for {employee_name} deleted successfully."
# )
#
# return redirect(success_url)
#
# def get_success_url(self):
# """
# Redirect to schedule list view.
# """
# return reverse_lazy('hr:schedule_list')
#
#
# class TimeEntryListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
# """
# List view for time entries.
# """
# model = TimeEntry
# template_name = 'hr/time_entry_list.html'
# context_object_name = 'time_entries'
# paginate_by = 20
# permission_required = 'hr.view_timeentry'
#
# def get_queryset(self):
# """
# Filter time entries by tenant and apply search filters.
# """
# queryset = TimeEntry.objects.filter(tenant=self.request.user.tenant)
#
# # Apply search filters
# form = TimeEntrySearchForm(self.request.GET, tenant=self.request.user.tenant)
# if form.is_valid():
# data = form.cleaned_data
#
# # Search term
# if data.get('search'):
# search_term = data['search']
# queryset = queryset.filter(
# Q(employee__first_name__icontains=search_term) |
# Q(employee__last_name__icontains=search_term) |
# Q(employee__employee_number__icontains=search_term) |
# Q(description__icontains=search_term)
# )
#
# # Employee filter
# if data.get('employee'):
# queryset = queryset.filter(employee=data['employee'])
#
# # Department filter
# if data.get('department'):
# queryset = queryset.filter(department=data['department'])
#
# # Entry type filter
# if data.get('entry_type'):
# queryset = queryset.filter(entry_type=data['entry_type'])
#
# # Entry date range
# if data.get('entry_date_from'):
# queryset = queryset.filter(entry_date__gte=data['entry_date_from'])
#
# if data.get('entry_date_to'):
# queryset = queryset.filter(entry_date__lte=data['entry_date_to'])
#
# # Approval status
# if data.get('is_approved') == 'True':
# queryset = queryset.filter(is_approved=True)
# elif data.get('is_approved') == 'False':
# queryset = queryset.filter(is_approved=False)
#
# # Payment status
# if data.get('is_paid') == 'True':
# queryset = queryset.filter(is_paid=True)
# elif data.get('is_paid') == 'False':
# queryset = queryset.filter(is_paid=False)
#
# return queryset.select_related(
# 'employee', 'department', 'approved_by'
# ).order_by('-entry_date', '-updated_at')
#
# def get_context_data(self, **kwargs):
# """
# Add search form to context.
# """
# context = super().get_context_data(**kwargs)
# context['search_form'] = TimeEntrySearchForm(
# self.request.GET or None,
# tenant=self.request.user.tenant
# )
#
# # Calculate totals
# entries = self.object_list
# context['total_hours'] = sum(entry.total_hours for entry in entries)
# context['total_approved'] = sum(1 for entry in entries if entry.is_approved)
# context['total_pending'] = sum(1 for entry in entries if not entry.is_approved)
#
# return context
#
#
# class TimeEntryDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
# """
# Detail view for time entries.
# """
# model = TimeEntry
# template_name = 'hr/time_entry_detail.html'
# context_object_name = 'time_entry'
# permission_required = 'hr.view_timeentry'
#
# def get_queryset(self):
# """
# Filter time entry by tenant.
# """
# return TimeEntry.objects.filter(
# tenant=self.request.user.tenant
# ).select_related(
# 'employee', 'department', 'approved_by', 'created_by'
# )
#
#
# class TimeEntryCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create view for time entries.
# """
# model = TimeEntry
# form_class = TimeEntryForm
# template_name = 'hr/time_entry_form.html'
# permission_required = 'hr.add_timeentry'
#
# def get_form_kwargs(self):
# """
# Pass tenant and user to form.
# """
# kwargs = super().get_form_kwargs()
# kwargs['tenant'] = self.request.user.tenant
# kwargs['user'] = self.request.user
#
# # Pre-select employee and date if provided in GET parameters
# initial = {}
# employee_id = self.request.GET.get('employee', None)
# if employee_id:
# initial['employee'] = employee_id
#
# entry_date = self.request.GET.get('entry_date', None)
# if entry_date:
# try:
# initial['entry_date'] = timezone.datetime.strptime(
# entry_date, '%Y-%m-%d'
# ).date()
# except (ValueError, TypeError):
# pass
#
# if initial:
# kwargs['initial'] = initial
#
# return kwargs
#
# def form_valid(self, form):
# """
# Set tenant and user before saving.
# """
# form.instance.tenant = self.request.user.tenant
# form.instance.created_by = self.request.user
#
# response = super().form_valid(form)
#
# messages.success(
# self.request,
# f"Time entry for {form.instance.employee.get_full_name()} on {form.instance.entry_date} created successfully."
# )
#
# return response
#
# def get_success_url(self):
# """
# Redirect to time entry list view or employee detail based on origin.
# """
# if 'employee' in self.request.GET:
# return reverse('hr:employee_detail', kwargs={'pk': self.request.GET['employee']})
# return reverse('hr:time_entry_list')
#
#
# class TimeEntryUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update view for time entries.
# """
# model = TimeEntry
# form_class = TimeEntryForm
# template_name = 'hr/time_entry_form.html'
# permission_required = 'hr.change_timeentry'
#
# def get_queryset(self):
# """
# Filter time entry by tenant.
# """
# return TimeEntry.objects.filter(tenant=self.request.user.tenant)
#
# def get_form_kwargs(self):
# """
# Pass tenant and user to form.
# """
# kwargs = super().get_form_kwargs()
# kwargs['tenant'] = self.request.user.tenant
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# """
# Handle successful form validation.
# """
# response = super().form_valid(form)
#
# messages.success(
# self.request,
# f"Time entry for {form.instance.employee.get_full_name()} on {form.instance.entry_date} updated successfully."
# )
#
# return response
#
# def get_success_url(self):
# """
# Redirect to time entry detail view.
# """
# return reverse('hr:time_entry_detail', kwargs={'pk': self.object.pk})
#
#
# @login_required
# def approve_time_entry(request, pk):
# """
# Approve a time entry.
# """
# time_entry = get_object_or_404(
# TimeEntry,
# pk=pk,
# tenant=request.user.tenant
# )
#
# if request.method == 'POST':
# if not request.user.has_perm('hr.approve_timeentry'):
# messages.error(request, "You don't have permission to approve time entries.")
# return redirect('hr:time_entry_detail', pk=time_entry.pk)
#
# time_entry.is_approved = True
# time_entry.approved_by = request.user
# time_entry.approval_date = timezone.now()
# time_entry.save()
#
# messages.success(
# request,
# f"Time entry for {time_entry.employee.get_full_name()} on {time_entry.entry_date} approved."
# )
#
# redirect_url = request.POST.get('next', reverse('hr:time_entry_detail', kwargs={'pk': time_entry.pk}))
# return redirect(redirect_url)
#
# return redirect('hr:time_entry_detail', pk=time_entry.pk)
#
#
#
#
# class PerformanceReviewListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
# """
# List view for performance reviews.
# """
# model = PerformanceReview
# template_name = 'hr/performance_review_list.html'
# context_object_name = 'reviews'
# paginate_by = 20
# permission_required = 'hr.view_performancereview'
#
# def get_queryset(self):
# """
# Filter reviews by tenant and apply search filters.
# """
# queryset = PerformanceReview.objects.filter(tenant=self.request.user.tenant)
#
# # Apply search filters
# search = self.request.GET.get('search', '')
# if search:
# queryset = queryset.filter(
# Q(employee__first_name__icontains=search) |
# Q(employee__last_name__icontains=search) |
# Q(employee__employee_number__icontains=search) |
# Q(strengths__icontains=search) |
# Q(areas_for_improvement__icontains=search)
# )
#
# employee_id = self.request.GET.get('employee', '')
# if employee_id:
# queryset = queryset.filter(employee_id=employee_id)
#
# review_type = self.request.GET.get('review_type', '')
# if review_type:
# queryset = queryset.filter(review_type=review_type)
#
# status = self.request.GET.get('status', '')
# if status:
# queryset = queryset.filter(status=status)
#
# # Date range filters
# scheduled_from = self.request.GET.get('scheduled_from', '')
# if scheduled_from:
# try:
# scheduled_from = timezone.datetime.strptime(scheduled_from, '%Y-%m-%d').date()
# queryset = queryset.filter(scheduled_date__gte=scheduled_from)
# except (ValueError, TypeError):
# pass
#
# scheduled_to = self.request.GET.get('scheduled_to', '')
# if scheduled_to:
# try:
# scheduled_to = timezone.datetime.strptime(scheduled_to, '%Y-%m-%d').date()
# queryset = queryset.filter(scheduled_date__lte=scheduled_to)
# except (ValueError, TypeError):
# pass
#
# # Rating filter
# rating = self.request.GET.get('rating', '')
# if rating and rating.isdigit():
# queryset = queryset.filter(performance_rating=int(rating))
#
# return queryset.select_related('employee', 'reviewer')
#
# def get_context_data(self, **kwargs):
# """
# Add filter choices to context.
# """
# context = super().get_context_data(**kwargs)
# tenant = self.request.user.tenant
#
# context['search'] = self.request.GET.get('search', '')
# context['employees'] = Employee.objects.filter(
# tenant=tenant
# ).order_by('last_name', 'first_name')
# context['review_types'] = PerformanceReview.REVIEW_TYPES
# context['review_statuses'] = PerformanceReview.REVIEW_STATUS
# context['selected_employee'] = self.request.GET.get('employee', '')
# context['selected_type'] = self.request.GET.get('review_type', '')
# context['selected_status'] = self.request.GET.get('status', '')
# context['scheduled_from'] = self.request.GET.get('scheduled_from', '')
# context['scheduled_to'] = self.request.GET.get('scheduled_to', '')
# context['selected_rating'] = self.request.GET.get('rating', '')
#
# return context
#
#
# class PerformanceReviewDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
# """
# Detail view for performance reviews.
# """
# model = PerformanceReview
# template_name = 'hr/performance_review_detail.html'
# context_object_name = 'review'
# permission_required = 'hr.view_performancereview'
#
# def get_queryset(self):
# """
# Filter review by tenant.
# """
# return PerformanceReview.objects.filter(
# tenant=self.request.user.tenant
# ).select_related(
# 'employee', 'reviewer', 'created_by'
# )
#
#
# class PerformanceReviewCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create view for performance reviews.
# """
# model = PerformanceReview
# form_class = PerformanceReviewForm
# template_name = 'hr/performance_review_form.html'
# permission_required = 'hr.add_performancereview'
#
# def get_form_kwargs(self):
# """
# Pass tenant and user to form.
# """
# kwargs = super().get_form_kwargs()
# kwargs['tenant'] = self.request.user.tenant
# kwargs['user'] = self.request.user
#
# # Pre-select employee if provided in GET parameters
# employee_id = self.request.GET.get('employee', None)
# if employee_id:
# kwargs['initial'] = kwargs.get('initial', {})
# kwargs['initial']['employee'] = employee_id
# kwargs['initial']['reviewer'] = self.request.user.id
#
# return kwargs
#
# def form_valid(self, form):
# """
# Set tenant and user before saving.
# """
# form.instance.tenant = self.request.user.tenant
# form.instance.created_by = self.request.user
#
# response = super().form_valid(form)
#
# messages.success(
# self.request,
# f"Performance review for {form.instance.employee.get_full_name()} created successfully."
# )
#
# return response
#
# def get_success_url(self):
# """
# Redirect to review detail view.
# """
# return reverse('hr:performance_review_detail', kwargs={'pk': self.object.pk})
#
#
# class PerformanceReviewUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update view for performance reviews.
# """
# model = PerformanceReview
# form_class = PerformanceReviewForm
# template_name = 'hr/performance_review_form.html'
# permission_required = 'hr.change_performancereview'
#
# def get_queryset(self):
# """
# Filter review by tenant.
# """
# return PerformanceReview.objects.filter(tenant=self.request.user.tenant)
#
# def get_form_kwargs(self):
# """
# Pass tenant and user to form.
# """
# kwargs = super().get_form_kwargs()
# kwargs['tenant'] = self.request.user.tenant
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# """
# Handle successful form validation.
# """
# response = super().form_valid(form)
#
# messages.success(
# self.request,
# f"Performance review for {form.instance.employee.get_full_name()} updated successfully."
# )
#
# return response
#
# def get_success_url(self):
# """
# Redirect to review detail view.
# """
# return reverse('hr:performance_review_detail', kwargs={'pk': self.object.pk})
#
#
# class TrainingRecordListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
# """
# List view for training records.
# """
# model = TrainingRecord
# template_name = 'hr/training_record_list.html'
# context_object_name = 'training_records'
# paginate_by = 20
# permission_required = 'hr.view_trainingrecord'
#
# def get_queryset(self):
# """
# Filter training records by tenant and apply search filters.
# """
# queryset = TrainingRecord.objects.filter(tenant=self.request.user.tenant)
#
# # Apply search filters
# search = self.request.GET.get('search', '')
# if search:
# queryset = queryset.filter(
# Q(employee__first_name__icontains=search) |
# Q(employee__last_name__icontains=search) |
# Q(employee__employee_number__icontains=search) |
# Q(training_title__icontains=search) |
# Q(training_description__icontains=search) |
# Q(provider__icontains=search) |
# Q(trainer__icontains=search)
# )
#
# employee_id = self.request.GET.get('employee', '')
# if employee_id:
# queryset = queryset.filter(employee_id=employee_id)
#
# training_type = self.request.GET.get('training_type', '')
# if training_type:
# queryset = queryset.filter(training_type=training_type)
#
# status = self.request.GET.get('status', '')
# if status:
# queryset = queryset.filter(status=status)
#
# is_mandatory = self.request.GET.get('is_mandatory', '')
# if is_mandatory == 'True':
# queryset = queryset.filter(is_mandatory=True)
# elif is_mandatory == 'False':
# queryset = queryset.filter(is_mandatory=False)
#
# # Date range filters
# start_from = self.request.GET.get('start_from', '')
# if start_from:
# try:
# start_from = timezone.datetime.strptime(start_from, '%Y-%m-%d').date()
# queryset = queryset.filter(start_date__gte=start_from)
# except (ValueError, TypeError):
# pass
#
# start_to = self.request.GET.get('start_to', '')
# if start_to:
# try:
# start_to = timezone.datetime.strptime(start_to, '%Y-%m-%d').date()
# queryset = queryset.filter(start_date__lte=start_to)
# except (ValueError, TypeError):
# pass
#
# # Expiry status filter
# expiry_status = self.request.GET.get('expiry_status', '')
# today = timezone.now().date()
# if expiry_status == 'valid':
# queryset = queryset.filter(
# status='COMPLETED',
# expiry_date__gt=today
# )
# elif expiry_status == 'expiring':
# queryset = queryset.filter(
# status='COMPLETED',
# expiry_date__gt=today,
# expiry_date__lte=today + timezone.timedelta(days=30)
# )
# elif expiry_status == 'expired':
# queryset = queryset.filter(
# Q(status='EXPIRED') |
# Q(status='COMPLETED', expiry_date__lt=today)
# )
#
# return queryset.select_related('employee')
#
# def get_context_data(self, **kwargs):
# """
# Add filter choices to context.
# """
# context = super().get_context_data(**kwargs)
# tenant = self.request.user.tenant
#
# context['search'] = self.request.GET.get('search', '')
# context['employees'] = Employee.objects.filter(
# tenant=tenant
# ).order_by('last_name', 'first_name')
# context['training_statuses'] = TrainingRecord.TRAINING_STATUS
# context['selected_employee'] = self.request.GET.get('employee', '')
# context['selected_type'] = self.request.GET.get('training_type', '')
# context['selected_status'] = self.request.GET.get('status', '')
# context['selected_mandatory'] = self.request.GET.get('is_mandatory', '')
# context['start_from'] = self.request.GET.get('start_from', '')
# context['start_to'] = self.request.GET.get('start_to', '')
# context['selected_expiry'] = self.request.GET.get('expiry_status', '')
#
# # Collect unique training types from existing records
# context['training_types'] = TrainingRecord.objects.filter(
# tenant=tenant
# ).values_list('training_type', flat=True).distinct().order_by('training_type')
#
# return context
#
#
# class TrainingRecordDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
# """
# Detail view for training records.
# """
# model = TrainingRecord
# template_name = 'hr/training_record_detail.html'
# context_object_name = 'training_record'
# permission_required = 'hr.view_trainingrecord'
#
# def get_queryset(self):
# """
# Filter training record by tenant.
# """
# return TrainingRecord.objects.filter(
# tenant=self.request.user.tenant
# ).select_related(
# 'employee', 'created_by'
# )
#
#
# class TrainingRecordCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create view for training records.
# """
# model = TrainingRecord
# form_class = TrainingRecordForm
# template_name = 'hr/training_record_form.html'
# permission_required = 'hr.add_trainingrecord'
#
# def get_form_kwargs(self):
# """
# Pass tenant and user to form.
# """
# kwargs = super().get_form_kwargs()
# kwargs['tenant'] = self.request.user.tenant
# kwargs['user'] = self.request.user
#
# # Pre-select employee if provided in GET parameters
# employee_id = self.request.GET.get('employee', None)
# if employee_id:
# kwargs['initial'] = kwargs.get('initial', {})
# kwargs['initial']['employee'] = employee_id
#
# return kwargs
#
# def form_valid(self, form):
# """
# Set tenant and user before saving.
# """
# form.instance.tenant = self.request.user.tenant
# form.instance.created_by = self.request.user
#
# response = super().form_valid(form)
#
# messages.success(
# self.request,
# f"Training record '{form.instance.training_title}' for {form.instance.employee.get_full_name()} created successfully."
# )
#
# return response
#
# def get_success_url(self):
# """
# Redirect to training record detail view.
# """
# return reverse('hr:training_record_detail', kwargs={'pk': self.object.pk})
#
#
# class TrainingRecordUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update view for training records.
# """
# model = TrainingRecord
# form_class = TrainingRecordForm
# template_name = 'hr/training_record_form.html'
# permission_required = 'hr.change_trainingrecord'
#
# def get_queryset(self):
# """
# Filter training record by tenant.
# """
# return TrainingRecord.objects.filter(tenant=self.request.user.tenant)
#
# def get_form_kwargs(self):
# """
# Pass tenant and user to form.
# """
# kwargs = super().get_form_kwargs()
# kwargs['tenant'] = self.request.user.tenant
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# """
# Handle successful form validation.
# """
# response = super().form_valid(form)
#
# messages.success(
# self.request,
# f"Training record '{form.instance.training_title}' updated successfully."
# )
#
# return response
#
# def get_success_url(self):
# """
# Redirect to training record detail view.
# """
# return reverse('hr:training_record_detail', kwargs={'pk': self.object.pk})
#
#
# @login_required
# def hr_stats(request):
# """
# Return HR statistics for dashboard updates.
# """
# context = {
# 'total_employees': Employee.objects.filter(tenant=request.user.tenant).count(),
# 'active_employees': Employee.objects.filter(
# tenant=request.user.tenant,
# employment_status='ACTIVE'
# ).count(),
# 'total_departments': Department.objects.filter(tenant=request.user.tenant).count(),
# 'pending_reviews': PerformanceReview.objects.filter(
# tenant=request.user.tenant,
# status='PENDING'
# ).count(),
# 'employees_clocked_in': TimeEntry.objects.filter(
# tenant=request.user.tenant,
# clock_in_time__date=timezone.now().date(),
# clock_out_time__isnull=True
# ).count(),
# }
#
# return render(request, 'hr/partials/hr_stats.html', context)
#
#
# @login_required
# def clock_in(request):
# """
# Clock in the current user.
# """
# # Check if user has an employee profile
# try:
# employee = request.user.employee_profile
# except (AttributeError, Employee.DoesNotExist):
# messages.error(request, "You don't have an employee profile. Please contact HR.")
# return redirect('hr:dashboard')
#
# # Check if already clocked in
# existing_entry = TimeEntry.objects.filter(
# tenant=request.user.tenant,
# employee=employee,
# entry_date=timezone.now().date(),
# clock_out_time__isnull=True
# ).first()
#
# if existing_entry:
# messages.warning(request, "You are already clocked in. Please clock out first.")
# return redirect('hr:dashboard')
#
# # Create new time entry
# time_entry = TimeEntry(
# tenant=request.user.tenant,
# employee=employee,
# entry_date=timezone.now().date(),
# entry_type='REGULAR',
# clock_in_time=timezone.now(),
# department=employee.department,
# created_by=request.user
# )
# time_entry.save()
#
# messages.success(request, f"Clocked in at {time_entry.clock_in_time.strftime('%H:%M:%S')}.")
# return redirect('hr:dashboard')
#
#
# @login_required
# def clock_out(request):
# """
# Clock out the current user.
# """
# # Check if user has an employee profile
# try:
# employee = request.user.employee_profile
# except (AttributeError, Employee.DoesNotExist):
# messages.error(request, "You don't have an employee profile. Please contact HR.")
# return redirect('hr:dashboard')
#
# # Find active time entry
# time_entry = TimeEntry.objects.filter(
# tenant=request.user.tenant,
# employee=employee,
# entry_date=timezone.now().date(),
# clock_out_time__isnull=True
# ).order_by('-clock_in_time').first()
#
# if not time_entry:
# messages.warning(request, "You are not clocked in. Please clock in first.")
# return redirect('hr:dashboard')
#
# # Update time entry
# time_entry.clock_out_time = timezone.now()
# time_entry.save() # This will calculate total_hours in the save method
#
# hours = time_entry.total_hours
# messages.success(
# request,
# f"Clocked out at {time_entry.clock_out_time.strftime('%H:%M:%S')}. "
# f"Total hours: {hours:.2f}"
# )
# return redirect('hr:dashboard')
#
#
#
#
# # ============================================================================
# # ACTION VIEWS
# # ============================================================================
#
# @login_required
# def start_case(request, case_id):
# """
# Start a surgical case.
# """
# if request.method == 'POST':
# case = get_object_or_404(
# SurgicalCase,
# id=case_id,
# tenant=request.user.tenant
# )
#
# case.status = 'IN_PROGRESS'
# case.actual_start_time = timezone.now()
# case.save()
#
# # Update room status
# if case.operating_room:
# case.operating_room.status = 'IN_USE'
# case.operating_room.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='SURGICAL_CASE_STARTED',
# model='SurgicalCase',
# object_id=str(case.case_id),
# details={
# 'patient_name': f"{case.patient.first_name} {case.patient.last_name}",
# 'procedure_name': case.procedure_name
# }
# )
#
# messages.success(request, 'Surgical case started successfully.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'operating_theatre/partials/case_status.html', {'case': case})
#
# return redirect('operating_theatre:surgical_case_detail', pk=case.pk)
#
# return JsonResponse({'success': False})
#
#
# @login_required
# def complete_case(request, case_id):
# """
# Complete a surgical case.
# """
# if request.method == 'POST':
# case = get_object_or_404(
# SurgicalCase,
# id=case_id,
# tenant=request.user.tenant
# )
#
# case.status = 'COMPLETED'
# case.actual_end_time = timezone.now()
# case.save()
#
# # Update room status
# if case.operating_room:
# case.operating_room.status = 'CLEANING'
# case.operating_room.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='SURGICAL_CASE_COMPLETED',
# model='SurgicalCase',
# object_id=str(case.case_id),
# details={
# 'patient_name': f"{case.patient.first_name} {case.patient.last_name}",
# 'procedure_name': case.procedure_name,
# 'duration': str(case.actual_end_time - case.actual_start_time) if case.actual_start_time else None
# }
# )
#
# messages.success(request, 'Surgical case completed successfully.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'operating_theatre/partials/case_status.html', {'case': case})
#
# return redirect('operating_theatre:surgical_case_detail', pk=case.pk)
#
# return JsonResponse({'success': False})
# #
#
# @login_required
# def sign_note(request, note_id):
# """
# Sign a surgical note.
# """
# if request.method == 'POST':
# note = get_object_or_404(
# SurgicalNote,
# id=note_id,
# tenant=request.user.tenant
# )
#
# # Only allow signing if note is in draft status
# if note.status != 'DRAFT':
# messages.error(request, 'Only draft notes can be signed.')
# return redirect('operating_theatre:surgical_note_detail', pk=note.pk)
#
# note.status = 'SIGNED'
# note.signed_datetime = timezone.now()
# note.signed_by = request.user
# note.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='SURGICAL_NOTE_SIGNED',
# model='SurgicalNote',
# object_id=str(note.note_id),
# details={
# 'patient_name': f"{note.surgical_case.patient.first_name} {note.surgical_case.patient.last_name}",
# 'note_type': note.note_type
# }
# )
#
# messages.success(request, 'Surgical note signed successfully.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'operating_theatre/partials/note_status.html', {'note': note})
#
# return redirect('operating_theatre:surgical_note_detail', pk=note.pk)
#
# return JsonResponse({'success': False})
#
#
# @login_required
# def update_room_status(request, room_id):
# """
# Update operating room status.
# """
# if request.method == 'POST':
# room = get_object_or_404(
# OperatingRoom,
# id=room_id,
# tenant=request.user.tenant
# )
#
# new_status = request.POST.get('status')
# if new_status in dict(OperatingRoom._meta.get_field('status').choices):
# old_status = room.status
# room.status = new_status
# room.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='OPERATING_ROOM_STATUS_UPDATED',
# model='OperatingRoom',
# object_id=str(room.id),
# details={
# 'room_number': room.room_number,
# 'old_status': old_status,
# 'new_status': new_status
# }
# )
#
# messages.success(request, f'Room {room.room_number} status updated to {room.get_status_display()}.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'operating_theatre/partials/room_status.html', {'room': room})
#
# return redirect('operating_theatre:operating_room_detail', pk=room.pk)
#
# return JsonResponse({'success': False})
#
#
#
#
#
# """
# Operating Theatre app views with healthcare-focused CRUD operations.
# Implements appropriate access patterns for surgical and OR management workflows.
# """
#
# from django.shortcuts import render, get_object_or_404, redirect
# from django.contrib.auth.decorators import login_required
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
# from django.views.generic import (
# ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView
# )
# from django.http import JsonResponse, HttpResponse
# from django.db.models import Q, Count, Avg, Sum, F
# from django.utils import timezone
# from django.contrib import messages
# from django.urls import reverse_lazy, reverse
# from django.core.paginator import Paginator
# from django.template.loader import render_to_string
# from datetime import datetime, timedelta, date
# import json
#
# from core.utils import AuditLogger
# from .models import (
# OperatingRoom, ORBlock, SurgicalCase, SurgicalNote,
# EquipmentUsage, SurgicalNoteTemplate
# )
# from .forms import (
# OperatingRoomForm, ORBlockForm, SurgicalCaseForm, SurgicalNoteForm,
# EquipmentUsageForm, SurgicalNoteTemplateForm
# )
#
#
# # ============================================================================
# # DASHBOARD AND OVERVIEW VIEWS
# # ============================================================================
#
# class OperatingTheatreDashboardView(LoginRequiredMixin, TemplateView):
# """
# Main operating theatre dashboard with key metrics and recent activity.
# """
# template_name = 'operating_theatre/dashboard.html'
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# tenant = self.request.user.tenant
# today = timezone.now().date()
#
# # Dashboard statistics
# context.update({
# 'total_rooms': OperatingRoom.objects.filter(
# tenant=tenant,
# is_active=True
# ).count(),
# 'rooms_available': OperatingRoom.objects.filter(
# tenant=tenant,
# is_active=True,
# status='AVAILABLE'
# ).count(),
# 'rooms_in_use': OperatingRoom.objects.filter(
# tenant=tenant,
# is_active=True,
# status='IN_USE'
# ).count(),
# 'rooms_maintenance': OperatingRoom.objects.filter(
# tenant=tenant,
# is_active=True,
# status='MAINTENANCE'
# ).count(),
# 'cases_today': SurgicalCase.objects.filter(
# tenant=tenant,
# scheduled_start_time__date=today
# ).count(),
# 'cases_in_progress': SurgicalCase.objects.filter(
# tenant=tenant,
# status='IN_PROGRESS'
# ).count(),
# 'cases_completed_today': SurgicalCase.objects.filter(
# tenant=tenant,
# actual_end_time__date=today,
# status='COMPLETED'
# ).count(),
# 'emergency_cases_today': SurgicalCase.objects.filter(
# tenant=tenant,
# scheduled_start_time__date=today,
# priority='EMERGENCY'
# ).count(),
# 'blocks_today': ORBlock.objects.filter(
# tenant=tenant,
# date=today
# ).count(),
# 'equipment_in_use': EquipmentUsage.objects.filter(
# tenant=tenant,
# status='IN_USE'
# ).count(),
# 'notes_pending': SurgicalNote.objects.filter(
# tenant=tenant,
# status='DRAFT'
# ).count(),
# })
#
# # Recent surgical cases
# context['recent_cases'] = SurgicalCase.objects.filter(
# tenant=tenant
# ).select_related(
# 'patient', 'primary_surgeon', 'operating_room'
# ).order_by('-scheduled_start_time')[:10]
#
# # Today's schedule
# context['todays_schedule'] = SurgicalCase.objects.filter(
# tenant=tenant,
# scheduled_start_time__date=today
# ).select_related(
# 'patient', 'primary_surgeon', 'operating_room'
# ).order_by('scheduled_start_time')
#
# # Room utilization
# context['room_utilization'] = OperatingRoom.objects.filter(
# tenant=tenant,
# is_active=True
# ).annotate(
# cases_today=Count(
# 'surgical_cases',
# filter=Q(surgical_cases__scheduled_start_time__date=today)
# )
# ).order_by('room_number')
#
# return context
#
#
# # ============================================================================
# # OPERATING ROOM VIEWS (FULL CRUD - Master Data)
# # ============================================================================
#
# class OperatingRoomListView(LoginRequiredMixin, ListView):
# """
# List all operating rooms with filtering and search.
# """
# model = OperatingRoom
# template_name = 'operating_theatre/operating_room_list.html'
# context_object_name = 'operating_rooms'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = OperatingRoom.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(room_number__icontains=search) |
# Q(room_name__icontains=search) |
# Q(location__icontains=search)
# )
#
# # Filter by room type
# room_type = self.request.GET.get('room_type')
# if room_type:
# queryset = queryset.filter(room_type=room_type)
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by floor
# floor = self.request.GET.get('floor')
# if floor:
# queryset = queryset.filter(floor=floor)
#
# # Filter by active status
# active_only = self.request.GET.get('active_only')
# if active_only:
# queryset = queryset.filter(is_active=True)
#
# return queryset.order_by('room_number')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'room_types': OperatingRoom._meta.get_field('room_type').choices,
# 'statuses': OperatingRoom._meta.get_field('status').choices,
# 'search_query': self.request.GET.get('search', ''),
# })
# return context
#
#
# class OperatingRoomDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about an operating room.
# """
# model = OperatingRoom
# template_name = 'operating_theatre/operating_room_detail.html'
# context_object_name = 'operating_room'
#
# def get_queryset(self):
# return OperatingRoom.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# operating_room = self.object
# today = timezone.now().date()
#
# # Get today's cases for this room
# context['todays_cases'] = operating_room.surgical_cases.filter(
# scheduled_start_time__date=today
# ).select_related('patient', 'primary_surgeon').order_by('scheduled_start_time')
#
# # Get recent cases
# context['recent_cases'] = operating_room.surgical_cases.all().select_related(
# 'patient', 'primary_surgeon'
# ).order_by('-scheduled_start_time')[:10]
#
# # Get equipment usage
# context['equipment_usage'] = EquipmentUsage.objects.filter(
# operating_room=operating_room,
# tenant=self.request.user.tenant
# ).order_by('-start_time')[:10]
#
# # Room statistics
# context['room_stats'] = {
# 'total_cases': operating_room.surgical_cases.count(),
# 'cases_this_month': operating_room.surgical_cases.filter(
# scheduled_start_time__month=timezone.now().month,
# scheduled_start_time__year=timezone.now().year
# ).count(),
# 'average_case_duration': operating_room.surgical_cases.filter(
# actual_end_time__isnull=False
# ).aggregate(
# avg_duration=Avg(
# F('actual_end_time') - F('actual_start_time')
# )
# )['avg_duration'],
# }
#
# return context
#
#
# class OperatingRoomCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new operating room.
# """
# model = OperatingRoom
# form_class = OperatingRoomForm
# template_name = 'operating_theatre/operating_room_form.html'
# permission_required = 'operating_theatre.add_operatingroom'
# success_url = reverse_lazy('operating_theatre:operating_room_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='OPERATING_ROOM_CREATED',
# model='OperatingRoom',
# object_id=str(self.object.id),
# details={
# 'room_number': self.object.room_number,
# 'room_name': self.object.room_name,
# 'room_type': self.object.room_type
# }
# )
#
# messages.success(self.request, f'Operating room "{self.object.room_number}" created successfully.')
# return response
#
#
# class OperatingRoomUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update an existing operating room.
# """
# model = OperatingRoom
# form_class = OperatingRoomForm
# template_name = 'operating_theatre/operating_room_form.html'
# permission_required = 'operating_theatre.change_operatingroom'
#
# def get_queryset(self):
# return OperatingRoom.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('operating_theatre:operating_room_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='OPERATING_ROOM_UPDATED',
# model='OperatingRoom',
# object_id=str(self.object.id),
# details={
# 'room_number': self.object.room_number,
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, f'Operating room "{self.object.room_number}" updated successfully.')
# return response
#
#
# class OperatingRoomDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
# """
# Delete an operating room (soft delete by deactivating).
# """
# model = OperatingRoom
# template_name = 'operating_theatre/operating_room_confirm_delete.html'
# permission_required = 'operating_theatre.delete_operatingroom'
# success_url = reverse_lazy('operating_theatre:operating_room_list')
#
# def get_queryset(self):
# return OperatingRoom.objects.filter(tenant=self.request.user.tenant)
#
# def delete(self, request, *args, **kwargs):
# self.object = self.get_object()
#
# # Check if room has active cases
# active_cases = self.object.surgical_cases.filter(
# status__in=['SCHEDULED', 'IN_PROGRESS']
# ).count()
#
# if active_cases > 0:
# messages.error(
# request,
# f'Cannot deactivate room "{self.object.room_number}" - it has {active_cases} active cases.'
# )
# return redirect('operating_theatre:operating_room_detail', pk=self.object.pk)
#
# # Soft delete by deactivating
# self.object.is_active = False
# self.object.status = 'OUT_OF_SERVICE'
# self.object.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='OPERATING_ROOM_DEACTIVATED',
# model='OperatingRoom',
# object_id=str(self.object.id),
# details={'room_number': self.object.room_number}
# )
#
# messages.success(request, f'Operating room "{self.object.room_number}" deactivated successfully.')
# return redirect(self.success_url)
#
#
# # ============================================================================
# # SURGICAL NOTE TEMPLATE VIEWS (FULL CRUD - Master Data)
# # ============================================================================
#
# class SurgicalNoteTemplateListView(LoginRequiredMixin, ListView):
# """
# List all surgical note templates with filtering and search.
# """
# model = SurgicalNoteTemplate
# template_name = 'operating_theatre/surgical_note_template_list.html'
# context_object_name = 'surgical_note_templates'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = SurgicalNoteTemplate.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(template_name__icontains=search) |
# Q(procedure_type__icontains=search) |
# Q(specialty__icontains=search)
# )
#
# # Filter by specialty
# specialty = self.request.GET.get('specialty')
# if specialty:
# queryset = queryset.filter(specialty=specialty)
#
# # Filter by active status
# active_only = self.request.GET.get('active_only')
# if active_only:
# queryset = queryset.filter(is_active=True)
#
# return queryset.order_by('template_name')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'specialties': SurgicalNoteTemplate._meta.get_field('specialty').choices,
# 'search_query': self.request.GET.get('search', ''),
# })
# return context
#
#
# class SurgicalNoteTemplateDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a surgical note template.
# """
# model = SurgicalNoteTemplate
# template_name = 'operating_theatre/surgical_note_template_detail.html'
# context_object_name = 'surgical_note_template'
#
# def get_queryset(self):
# return SurgicalNoteTemplate.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# template = self.object
#
# # Get recent notes using this template
# context['recent_notes'] = SurgicalNote.objects.filter(
# template=template,
# tenant=self.request.user.tenant
# ).select_related('surgical_case__patient').order_by('-created_at')[:10]
#
# return context
#
#
# class SurgicalNoteTemplateCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new surgical note template.
# """
# model = SurgicalNoteTemplate
# form_class = SurgicalNoteTemplateForm
# template_name = 'operating_theatre/surgical_note_template_form.html'
# permission_required = 'operating_theatre.add_surgicalnotetemplate'
# success_url = reverse_lazy('operating_theatre:surgical_note_template_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.created_by = self.request.user
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='SURGICAL_NOTE_TEMPLATE_CREATED',
# model='SurgicalNoteTemplate',
# object_id=str(self.object.id),
# details={
# 'template_name': self.object.template_name,
# 'specialty': self.object.specialty
# }
# )
#
# messages.success(self.request, f'Surgical note template "{self.object.template_name}" created successfully.')
# return response
#
#
# class SurgicalNoteTemplateUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update an existing surgical note template.
# """
# model = SurgicalNoteTemplate
# form_class = SurgicalNoteTemplateForm
# template_name = 'operating_theatre/surgical_note_template_form.html'
# permission_required = 'operating_theatre.change_surgicalnotetemplate'
#
# def get_queryset(self):
# return SurgicalNoteTemplate.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('operating_theatre:surgical_note_template_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='SURGICAL_NOTE_TEMPLATE_UPDATED',
# model='SurgicalNoteTemplate',
# object_id=str(self.object.id),
# details={
# 'template_name': self.object.template_name,
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, f'Surgical note template "{self.object.template_name}" updated successfully.')
# return response
#
#
# class SurgicalNoteTemplateDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
# """
# Delete a surgical note template (soft delete by deactivating).
# """
# model = SurgicalNoteTemplate
# template_name = 'operating_theatre/surgical_note_template_confirm_delete.html'
# permission_required = 'operating_theatre.delete_surgicalnotetemplate'
# success_url = reverse_lazy('operating_theatre:surgical_note_template_list')
#
# def get_queryset(self):
# return SurgicalNoteTemplate.objects.filter(tenant=self.request.user.tenant)
#
# def delete(self, request, *args, **kwargs):
# self.object = self.get_object()
#
# # Soft delete by deactivating
# self.object.is_active = False
# self.object.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='SURGICAL_NOTE_TEMPLATE_DEACTIVATED',
# model='SurgicalNoteTemplate',
# object_id=str(self.object.id),
# details={'template_name': self.object.template_name}
# )
#
# messages.success(request, f'Surgical note template "{self.object.template_name}" deactivated successfully.')
# return redirect(self.success_url)
#
#
# # ============================================================================
# # OR BLOCK VIEWS (LIMITED CRUD - Operational Data)
# # ============================================================================
#
# class ORBlockListView(LoginRequiredMixin, ListView):
# """
# List all OR blocks with filtering and search.
# """
# model = ORBlock
# template_name = 'operating_theatre/or_block_list.html'
# context_object_name = 'or_blocks'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = ORBlock.objects.filter(tenant=self.request.user.tenant)
#
# # Filter by date range
# date_from = self.request.GET.get('date_from')
# date_to = self.request.GET.get('date_to')
# if date_from:
# queryset = queryset.filter(date__gte=date_from)
# if date_to:
# queryset = queryset.filter(date__lte=date_to)
#
# # Filter by surgeon
# surgeon_id = self.request.GET.get('surgeon')
# if surgeon_id:
# queryset = queryset.filter(surgeon_id=surgeon_id)
#
# # Filter by operating room
# room_id = self.request.GET.get('room')
# if room_id:
# queryset = queryset.filter(operating_room_id=room_id)
#
# return queryset.select_related(
# 'operating_room', 'surgeon'
# ).order_by('-date', 'start_time')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'operating_rooms': OperatingRoom.objects.filter(
# tenant=self.request.user.tenant,
# is_active=True
# ).order_by('room_number'),
# })
# return context
#
#
# class ORBlockDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about an OR block.
# """
# model = ORBlock
# template_name = 'operating_theatre/or_block_detail.html'
# context_object_name = 'or_block'
#
# def get_queryset(self):
# return ORBlock.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# or_block = self.object
#
# # Get cases scheduled in this block
# context['scheduled_cases'] = SurgicalCase.objects.filter(
# operating_room=or_block.operating_room,
# scheduled_start_time__date=or_block.date,
# scheduled_start_time__time__gte=or_block.start_time,
# scheduled_start_time__time__lt=or_block.end_time,
# tenant=self.request.user.tenant
# ).select_related('patient', 'primary_surgeon').order_by('scheduled_start_time')
#
# # Calculate utilization
# total_block_minutes = (
# timezone.datetime.combine(timezone.now().date(), or_block.end_time) -
# timezone.datetime.combine(timezone.now().date(), or_block.start_time)
# ).total_seconds() / 60
#
# used_minutes = 0
# for case in context['scheduled_cases']:
# if case.estimated_duration_minutes:
# used_minutes += case.estimated_duration_minutes
#
# context['utilization_percentage'] = (
# (used_minutes / total_block_minutes) * 100 if total_block_minutes > 0 else 0
# )
#
# return context
#
#
# class ORBlockCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new OR block.
# """
# model = ORBlock
# form_class = ORBlockForm
# template_name = 'operating_theatre/or_block_form.html'
# permission_required = 'operating_theatre.add_orblock'
# success_url = reverse_lazy('operating_theatre:or_block_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='OR_BLOCK_CREATED',
# model='ORBlock',
# object_id=str(self.object.id),
# details={
# 'date': str(self.object.date),
# 'operating_room': self.object.operating_room.room_number,
# 'surgeon': f"{self.object.surgeon.first_name} {self.object.surgeon.last_name}"
# }
# )
#
# messages.success(self.request, 'OR block created successfully.')
# return response
#
#
# class ORBlockUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update an OR block (limited to notes and time adjustments).
# """
# model = ORBlock
# fields = ['start_time', 'end_time', 'notes'] # Restricted fields
# template_name = 'operating_theatre/or_block_update_form.html'
# permission_required = 'operating_theatre.change_orblock'
#
# def get_queryset(self):
# return ORBlock.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('operating_theatre:or_block_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='OR_BLOCK_UPDATED',
# model='ORBlock',
# object_id=str(self.object.id),
# details={
# 'date': str(self.object.date),
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, 'OR block updated successfully.')
# return response
#
#
# # ============================================================================
# # SURGICAL CASE VIEWS (RESTRICTED CRUD - Clinical Data)
# # ============================================================================
#
# class SurgicalCaseListView(LoginRequiredMixin, ListView):
# """
# List all surgical cases with filtering and search.
# """
# model = SurgicalCase
# template_name = 'operating_theatre/surgical_case_list.html'
# context_object_name = 'surgical_cases'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = SurgicalCase.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(patient__first_name__icontains=search) |
# Q(patient__last_name__icontains=search) |
# Q(patient__mrn__icontains=search) |
# Q(procedure_name__icontains=search)
# )
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by priority
# priority = self.request.GET.get('priority')
# if priority:
# queryset = queryset.filter(priority=priority)
#
# # Filter by surgeon
# surgeon_id = self.request.GET.get('surgeon')
# if surgeon_id:
# queryset = queryset.filter(primary_surgeon_id=surgeon_id)
#
# # Filter by operating room
# room_id = self.request.GET.get('room')
# if room_id:
# queryset = queryset.filter(operating_room_id=room_id)
#
# # Filter by date range
# date_from = self.request.GET.get('date_from')
# date_to = self.request.GET.get('date_to')
# if date_from:
# queryset = queryset.filter(scheduled_start_time__date__gte=date_from)
# if date_to:
# queryset = queryset.filter(scheduled_start_time__date__lte=date_to)
#
# return queryset.select_related(
# 'patient', 'primary_surgeon', 'operating_room'
# ).order_by('-scheduled_start_time')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'statuses': SurgicalCase._meta.get_field('status').choices,
# 'priorities': SurgicalCase._meta.get_field('priority').choices,
# 'operating_rooms': OperatingRoom.objects.filter(
# tenant=self.request.user.tenant,
# is_active=True
# ).order_by('room_number'),
# })
# return context
#
#
# class SurgicalCaseDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a surgical case.
# """
# model = SurgicalCase
# template_name = 'operating_theatre/surgical_case_detail.html'
# context_object_name = 'surgical_case'
#
# def get_queryset(self):
# return SurgicalCase.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# surgical_case = self.object
#
# # Get surgical notes for this case
# context['surgical_notes'] = surgical_case.surgical_notes.all().order_by('-created_at')
#
# # Get equipment usage for this case
# context['equipment_usage'] = EquipmentUsage.objects.filter(
# surgical_case=surgical_case,
# tenant=self.request.user.tenant
# ).order_by('-start_time')
#
# # Calculate actual duration if case is completed
# if surgical_case.actual_start_time and surgical_case.actual_end_time:
# context['actual_duration'] = surgical_case.actual_end_time - surgical_case.actual_start_time
#
# return context
#
#
# class SurgicalCaseCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new surgical case.
# """
# model = SurgicalCase
# form_class = SurgicalCaseForm
# template_name = 'operating_theatre/surgical_case_form.html'
# permission_required = 'operating_theatre.add_surgicalcase'
# success_url = reverse_lazy('operating_theatre:surgical_case_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='SURGICAL_CASE_CREATED',
# model='SurgicalCase',
# object_id=str(self.object.case_id),
# details={
# 'patient_name': f"{self.object.patient.first_name} {self.object.patient.last_name}",
# 'procedure_name': self.object.procedure_name,
# 'priority': self.object.priority
# }
# )
#
# messages.success(self.request, 'Surgical case created successfully.')
# return response
#
#
# class SurgicalCaseUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update surgical case (limited to status and notes after surgery starts).
# """
# model = SurgicalCase
# template_name = 'operating_theatre/surgical_case_update_form.html'
# permission_required = 'operating_theatre.change_surgicalcase'
#
# def get_queryset(self):
# return SurgicalCase.objects.filter(tenant=self.request.user.tenant)
#
# def get_form_class(self):
# # Limit fields based on case status
# if self.object.status in ['IN_PROGRESS', 'COMPLETED']:
# # Limited fields for cases that have started
# class RestrictedSurgicalCaseForm(SurgicalCaseForm):
# class Meta(SurgicalCaseForm.Meta):
# fields = ['status', 'notes', 'complications']
#
# return RestrictedSurgicalCaseForm
# else:
# return SurgicalCaseForm
#
# def get_success_url(self):
# return reverse('operating_theatre:surgical_case_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='SURGICAL_CASE_UPDATED',
# model='SurgicalCase',
# object_id=str(self.object.case_id),
# details={
# 'patient_name': f"{self.object.patient.first_name} {self.object.patient.last_name}",
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, 'Surgical case updated successfully.')
# return response
#
#
# # ============================================================================
# # SURGICAL NOTE VIEWS (APPEND-ONLY - Clinical Records)
# # ============================================================================
#
# class SurgicalNoteListView(LoginRequiredMixin, ListView):
# """
# List all surgical notes with filtering and search.
# """
# model = SurgicalNote
# template_name = 'operating_theatre/surgical_note_list.html'
# context_object_name = 'surgical_notes'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = SurgicalNote.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(surgical_case__patient__first_name__icontains=search) |
# Q(surgical_case__patient__last_name__icontains=search) |
# Q(surgical_case__patient__mrn__icontains=search) |
# Q(surgical_case__procedure_name__icontains=search) |
# Q(note_content__icontains=search)
# )
#
# # Filter by note type
# note_type = self.request.GET.get('note_type')
# if note_type:
# queryset = queryset.filter(note_type=note_type)
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by surgeon
# surgeon_id = self.request.GET.get('surgeon')
# if surgeon_id:
# queryset = queryset.filter(surgeon_id=surgeon_id)
#
# return queryset.select_related(
# 'surgical_case__patient', 'surgeon', 'template'
# ).order_by('-created_at')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'note_types': SurgicalNote._meta.get_field('note_type').choices,
# 'statuses': SurgicalNote._meta.get_field('status').choices,
# })
# return context
#
#
# class SurgicalNoteDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a surgical note.
# """
# model = SurgicalNote
# template_name = 'operating_theatre/surgical_note_detail.html'
# context_object_name = 'surgical_note'
#
# def get_queryset(self):
# return SurgicalNote.objects.filter(tenant=self.request.user.tenant)
#
#
# class SurgicalNoteCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new surgical note.
# """
# model = SurgicalNote
# form_class = SurgicalNoteForm
# template_name = 'operating_theatre/surgical_note_form.html'
# permission_required = 'operating_theatre.add_surgicalnote'
# success_url = reverse_lazy('operating_theatre:surgical_note_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.surgeon = self.request.user
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='SURGICAL_NOTE_CREATED',
# model='SurgicalNote',
# object_id=str(self.object.note_id),
# details={
# 'patient_name': f"{self.object.surgical_case.patient.first_name} {self.object.surgical_case.patient.last_name}",
# 'note_type': self.object.note_type
# }
# )
#
# messages.success(self.request, 'Surgical note created successfully.')
# return response
#
#
# # Note: No UpdateView or DeleteView for SurgicalNote - Append-only for clinical records
#
#
# # ============================================================================
# # EQUIPMENT USAGE VIEWS (LIMITED CRUD - Operational Data)
# # ============================================================================
#
# class EquipmentUsageListView(LoginRequiredMixin, ListView):
# """
# List all equipment usage records with filtering and search.
# """
# model = EquipmentUsage
# template_name = 'operating_theatre/equipment_usage_list.html'
# context_object_name = 'equipment_usage_records'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = EquipmentUsage.objects.filter(tenant=self.request.user.tenant)
#
# # Filter by equipment type
# equipment_type = self.request.GET.get('equipment_type')
# if equipment_type:
# queryset = queryset.filter(equipment_type=equipment_type)
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by operating room
# room_id = self.request.GET.get('room')
# if room_id:
# queryset = queryset.filter(operating_room_id=room_id)
#
# # Filter by date range
# date_from = self.request.GET.get('date_from')
# date_to = self.request.GET.get('date_to')
# if date_from:
# queryset = queryset.filter(start_time__date__gte=date_from)
# if date_to:
# queryset = queryset.filter(start_time__date__lte=date_to)
#
# return queryset.select_related(
# 'operating_room', 'surgical_case__patient'
# ).order_by('-start_time')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'equipment_types': EquipmentUsage._meta.get_field('equipment_type').choices,
# 'statuses': EquipmentUsage._meta.get_field('status').choices,
# 'operating_rooms': OperatingRoom.objects.filter(
# tenant=self.request.user.tenant,
# is_active=True
# ).order_by('room_number'),
# })
# return context
#
#
# class EquipmentUsageDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about equipment usage.
# """
# model = EquipmentUsage
# template_name = 'operating_theatre/equipment_usage_detail.html'
# context_object_name = 'equipment_usage'
#
# def get_queryset(self):
# return EquipmentUsage.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# equipment_usage = self.object
#
# # Calculate usage duration if ended
# if equipment_usage.end_time:
# context['usage_duration'] = equipment_usage.end_time - equipment_usage.start_time
#
# return context
#
#
# class EquipmentUsageCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new equipment usage record.
# """
# model = EquipmentUsage
# form_class = EquipmentUsageForm
# template_name = 'operating_theatre/equipment_usage_form.html'
# permission_required = 'operating_theatre.add_equipmentusage'
# success_url = reverse_lazy('operating_theatre:equipment_usage_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='EQUIPMENT_USAGE_CREATED',
# model='EquipmentUsage',
# object_id=str(self.object.id),
# details={
# 'equipment_name': self.object.equipment_name,
# 'equipment_type': self.object.equipment_type,
# 'operating_room': self.object.operating_room.room_number
# }
# )
#
# messages.success(self.request, 'Equipment usage record created successfully.')
# return response
#
#
# class EquipmentUsageUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update equipment usage record (limited to status and end time).
# """
# model = EquipmentUsage
# fields = ['status', 'end_time', 'notes'] # Restricted fields
# template_name = 'operating_theatre/equipment_usage_update_form.html'
# permission_required = 'operating_theatre.change_equipmentusage'
#
# def get_queryset(self):
# return EquipmentUsage.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('operating_theatre:equipment_usage_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='EQUIPMENT_USAGE_UPDATED',
# model='EquipmentUsage',
# object_id=str(self.object.id),
# details={
# 'equipment_name': self.object.equipment_name,
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, 'Equipment usage record updated successfully.')
# return response
#
#
# # ============================================================================
# # HTMX VIEWS FOR REAL-TIME UPDATES
# # ============================================================================
#
# @login_required
# def operating_theatre_stats(request):
# """
# HTMX endpoint for operating theatre statistics.
# """
# tenant = request.user.tenant
# today = timezone.now().date()
#
# stats = {
# 'rooms_available': OperatingRoom.objects.filter(
# tenant=tenant,
# is_active=True,
# status='AVAILABLE'
# ).count(),
# 'rooms_in_use': OperatingRoom.objects.filter(
# tenant=tenant,
# is_active=True,
# status='IN_USE'
# ).count(),
# 'cases_in_progress': SurgicalCase.objects.filter(
# tenant=tenant,
# status='IN_PROGRESS'
# ).count(),
# 'cases_completed_today': SurgicalCase.objects.filter(
# tenant=tenant,
# actual_end_time__date=today,
# status='COMPLETED'
# ).count(),
# 'emergency_cases_today': SurgicalCase.objects.filter(
# tenant=tenant,
# scheduled_start_time__date=today,
# priority='EMERGENCY'
# ).count(),
# }
#
# return render(request, 'operating_theatre/partials/or_stats.html', {'stats': stats})
#
#
# @login_required
# def start_case(request, case_id):
# """
# Start a surgical case.
# """
# if request.method == 'POST':
# case = get_object_or_404(
# SurgicalCase,
# id=case_id,
# tenant=request.user.tenant
# )
#
# case.status = 'IN_PROGRESS'
# case.actual_start_time = timezone.now()
# case.save()
#
# # Update room status
# if case.operating_room:
# case.operating_room.status = 'IN_USE'
# case.operating_room.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='SURGICAL_CASE_STARTED',
# model='SurgicalCase',
# object_id=str(case.case_id),
# details={
# 'patient_name': f"{case.patient.first_name} {case.patient.last_name}",
# 'procedure_name': case.procedure_name
# }
# )
#
# messages.success(request, 'Surgical case started successfully.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'operating_theatre/partials/case_status.html', {'case': case})
#
# return redirect('operating_theatre:surgical_case_detail', pk=case.pk)
#
# return JsonResponse({'success': False})
#
#
# @login_required
# def complete_case(request, case_id):
# """
# Complete a surgical case.
# """
# if request.method == 'POST':
# case = get_object_or_404(
# SurgicalCase,
# id=case_id,
# tenant=request.user.tenant
# )
#
# case.status = 'COMPLETED'
# case.actual_end_time = timezone.now()
# case.save()
#
# # Update room status
# if case.operating_room:
# case.operating_room.status = 'CLEANING'
# case.operating_room.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='SURGICAL_CASE_COMPLETED',
# model='SurgicalCase',
# object_id=str(case.case_id),
# details={
# 'patient_name': f"{case.patient.first_name} {case.patient.last_name}",
# 'procedure_name': case.procedure_name,
# 'duration': str(case.actual_end_time - case.actual_start_time) if case.actual_start_time else None
# }
# )
#
# messages.success(request, 'Surgical case completed successfully.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'operating_theatre/partials/case_status.html', {'case': case})
#
# return redirect('operating_theatre:surgical_case_detail', pk=case.pk)
#
# return JsonResponse({'success': False})