Marwan Alwali 263292f6be update
2025-11-04 00:50:06 +03:00

11367 lines
384 KiB
Plaintext
Raw Permalink 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})
# """
# Accounts app views for hospital management system with comprehensive CRUD operations.
# """
#
# from django.shortcuts import render, get_object_or_404, redirect
# from django.contrib.auth import authenticate, login, logout
# from django.contrib.auth.decorators import login_required
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
# from django.views.generic import (
# TemplateView, ListView, DetailView, CreateView, UpdateView, DeleteView
# )
# from django.http import JsonResponse
# from django.contrib import messages
# from django.db.models import Q, Count
# from django.utils import timezone
# from django.urls import reverse_lazy, reverse
# from django.core.paginator import Paginator
# from datetime import timedelta
# from .models import *
# from .forms import *
# from core.utils import AuditLogger
#
# # ============================================================================
# # USER VIEWS (FULL CRUD - Master Data)
# # ============================================================================
#
# from core.utils import AuditLogger
#
#
# # ============================================================================
# # USER VIEWS (FULL CRUD - Master Data)
# # ============================================================================
#
# class UserListView(LoginRequiredMixin, ListView):
# """
# User listing view.
# """
# model = User
# template_name = 'accounts/user_list.html'
# context_object_name = 'users'
# paginate_by = 25
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return User.objects.none()
#
# queryset = User.objects.filter(tenant=tenant)
#
# # Apply filters
# role = self.request.GET.get('role')
# if role:
# queryset = queryset.filter(role=role)
#
# department = self.request.GET.get('department')
# if department:
# queryset = queryset.filter(department=department)
#
# status = self.request.GET.get('status')
# if status == 'active':
# queryset = queryset.filter(is_active=True)
# elif status == 'inactive':
# queryset = queryset.filter(is_active=False)
# elif status == 'pending':
# queryset = queryset.filter(is_approved=False)
#
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(username__icontains=search) |
# Q(email__icontains=search) |
# Q(first_name__icontains=search) |
# Q(last_name__icontains=search) |
# Q(employee_id__icontains=search)
# )
#
# return queryset.order_by('last_name', 'first_name')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# tenant = self.request.user.tenant
#
# if tenant:
# # Get filter options
# context.update({
# 'roles': User.objects.filter(tenant=tenant).values_list('role', flat=True).distinct(),
# 'departments': User.objects.filter(tenant=tenant).values_list('department', flat=True).distinct(),
# 'search_form': AccountsSearchForm(self.request.GET),
# })
#
# return context
#
#
# class UserDetailView(LoginRequiredMixin, DetailView):
# """
# User detail view.
# """
# model = User
# template_name = 'accounts/user_detail.html'
# context_object_name = 'user_profile'
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return User.objects.none()
# return User.objects.filter(tenant=tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# user_profile = self.get_object()
#
# # Get user's sessions
# context['active_sessions'] = UserSession.objects.filter(
# user=user_profile,
# is_active=True
# ).order_by('-created_at')
#
# # Get user's two-factor devices
# context['two_factor_devices'] = TwoFactorDevice.objects.filter(
# user=user_profile,
# is_active=True
# ).order_by('-created_at')
#
# # Get user's social accounts
# context['social_accounts'] = SocialAccount.objects.filter(
# user=user_profile,
# is_active=True
# ).order_by('-created_at')
#
# # Get password history
# context['password_history'] = PasswordHistory.objects.filter(
# user=user_profile
# ).order_by('-created_at')[:5]
#
# # Get recent audit logs
# from core.models import AuditLogEntry
# context['recent_activity'] = AuditLogEntry.objects.filter(
# tenant=user_profile.tenant,
# user=user_profile
# ).order_by('-timestamp')[:10]
#
# return context
#
#
# class UserCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create new user.
# """
# model = User
# form_class = UserCreateForm
# template_name = 'accounts/user_create.html'
# permission_required = 'accounts.add_user'
# success_url = reverse_lazy('accounts:user_list')
#
# def form_valid(self, form):
# # Set tenant
# form.instance.tenant = self.request.user.tenant
# response = super().form_valid(form)
#
# # Log user creation
# AuditLogger.log_event(
# tenant=form.instance.tenant,
# event_type='CREATE',
# event_category='USER_MANAGEMENT',
# action='Create User',
# description=f'Created new user: {self.object.username}',
# user=self.request.user,
# content_object=self.object,
# request=self.request
# )
#
# messages.success(self.request, f'User "{self.object.username}" created successfully.')
# return response
#
#
# class UserUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update user information.
# """
# model = User
# form_class = UserForm
# template_name = 'accounts/user_form.html'
# permission_required = 'accounts.change_user'
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return User.objects.none()
# return User.objects.filter(tenant=tenant)
#
# def get_success_url(self):
# return reverse('accounts:user_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log user update
# AuditLogger.log_event(
# tenant=self.object.tenant,
# event_type='UPDATE',
# event_category='USER_MANAGEMENT',
# action='Update User',
# description=f'Updated user: {self.object.username}',
# user=self.request.user,
# content_object=self.object,
# request=self.request
# )
#
# messages.success(self.request, f'User "{self.object.username}" updated successfully.')
# return response
#
#
# class UserDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
# """
# Delete user (soft delete to inactive).
# """
# model = User
# template_name = 'accounts/user_confirm_delete.html'
# permission_required = 'accounts.delete_user'
# success_url = reverse_lazy('accounts:user_list')
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return User.objects.none()
# return User.objects.filter(tenant=tenant)
#
# def delete(self, request, *args, **kwargs):
# self.object = self.get_object()
#
# # Soft delete - set to inactive
# self.object.is_active = False
# self.object.save()
#
# # Log user deletion
# AuditLogger.log_event(
# tenant=self.object.tenant,
# event_type='DELETE',
# event_category='USER_MANAGEMENT',
# action='Deactivate User',
# description=f'Deactivated user: {self.object.username}',
# user=request.user,
# content_object=self.object,
# request=request
# )
#
# messages.success(request, f'User "{self.object.username}" deactivated successfully.')
# return redirect(self.success_url)
#
#
# # ============================================================================
# # TWO FACTOR DEVICE VIEWS (FULL CRUD - Security Data)
# # ============================================================================
#
# class TwoFactorDeviceListView(LoginRequiredMixin, ListView):
# """
# List two-factor devices.
# """
# model = TwoFactorDevice
# template_name = 'accounts/two_factor_device_list.html'
# context_object_name = 'devices'
# paginate_by = 20
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return TwoFactorDevice.objects.none()
#
# queryset = TwoFactorDevice.objects.filter(user__tenant=tenant)
#
# # Apply filters
# user_id = self.request.GET.get('user_id')
# if user_id:
# queryset = queryset.filter(user_id=user_id)
#
# device_type = self.request.GET.get('device_type')
# if device_type:
# queryset = queryset.filter(device_type=device_type)
#
# status = self.request.GET.get('status')
# if status == 'active':
# queryset = queryset.filter(is_active=True)
# elif status == 'inactive':
# queryset = queryset.filter(is_active=False)
#
# return queryset.order_by('-created_at')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# tenant = self.request.user.tenant
#
# if tenant:
# context.update({
# 'device_types': TwoFactorDevice.DEVICE_TYPE_CHOICES,
# 'users': User.objects.filter(tenant=tenant, is_active=True).order_by('last_name', 'first_name'),
# })
#
# return context
#
#
# class TwoFactorDeviceDetailView(LoginRequiredMixin, DetailView):
# """
# Display two-factor device details.
# """
# model = TwoFactorDevice
# template_name = 'accounts/two_factor_device_detail.html'
# context_object_name = 'device'
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return TwoFactorDevice.objects.none()
# return TwoFactorDevice.objects.filter(user__tenant=tenant)
#
#
# class TwoFactorDeviceCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create new two-factor device.
# """
# model = TwoFactorDevice
# form_class = TwoFactorDeviceForm
# template_name = 'accounts/two_factor_device_form.html'
# permission_required = 'accounts.add_twofactordevice'
# success_url = reverse_lazy('accounts:two_factor_device_list')
#
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Generate secret key for TOTP devices
# if form.instance.device_type == 'TOTP':
# import secrets
# form.instance.secret_key = secrets.token_urlsafe(32)
# form.instance.save()
#
# # Log device creation
# AuditLogger.log_event(
# tenant=self.request.user.tenant,
# event_type='CREATE',
# event_category='SECURITY',
# action='Create Two-Factor Device',
# description=f'Created two-factor device: {self.object.name}',
# user=self.request.user,
# content_object=self.object,
# request=self.request
# )
#
# messages.success(self.request, f'Two-factor device "{self.object.name}" created successfully.')
# return response
#
#
# class TwoFactorDeviceUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update two-factor device.
# """
# model = TwoFactorDevice
# form_class = TwoFactorDeviceForm
# template_name = 'accounts/two_factor_device_form.html'
# permission_required = 'accounts.change_twofactordevice'
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return TwoFactorDevice.objects.none()
# return TwoFactorDevice.objects.filter(user__tenant=tenant)
#
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def get_success_url(self):
# return reverse('accounts:two_factor_device_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log device update
# AuditLogger.log_event(
# tenant=self.request.user.tenant,
# event_type='UPDATE',
# event_category='SECURITY',
# action='Update Two-Factor Device',
# description=f'Updated two-factor device: {self.object.name}',
# user=self.request.user,
# content_object=self.object,
# request=self.request
# )
#
# messages.success(self.request, f'Two-factor device "{self.object.name}" updated successfully.')
# return response
#
#
# class TwoFactorDeviceDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
# """
# Delete two-factor device.
# """
# model = TwoFactorDevice
# template_name = 'accounts/two_factor_device_confirm_delete.html'
# permission_required = 'accounts.delete_twofactordevice'
# success_url = reverse_lazy('accounts:two_factor_device_list')
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return TwoFactorDevice.objects.none()
# return TwoFactorDevice.objects.filter(user__tenant=tenant)
#
# def delete(self, request, *args, **kwargs):
# self.object = self.get_object()
# device_name = self.object.name
#
# # Log device deletion
# AuditLogger.log_event(
# tenant=getattr(request, 'tenant', None),
# event_type='DELETE',
# event_category='SECURITY',
# action='Delete Two-Factor Device',
# description=f'Deleted two-factor device: {device_name}',
# user=request.user,
# content_object=self.object,
# request=request
# )
#
# messages.success(request, f'Two-factor device "{device_name}" deleted successfully.')
# return super().delete(request, *args, **kwargs)
#
#
# # ============================================================================
# # SOCIAL ACCOUNT VIEWS (FULL CRUD - Integration Data)
# # ============================================================================
#
# class SocialAccountListView(LoginRequiredMixin, ListView):
# """
# List social accounts.
# """
# model = SocialAccount
# template_name = 'accounts/social_account_list.html'
# context_object_name = 'social_accounts'
# paginate_by = 20
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return SocialAccount.objects.none()
#
# queryset = SocialAccount.objects.filter(user__tenant=tenant)
#
# # Apply filters
# user_id = self.request.GET.get('user_id')
# if user_id:
# queryset = queryset.filter(user_id=user_id)
#
# provider = self.request.GET.get('provider')
# if provider:
# queryset = queryset.filter(provider=provider)
#
# status = self.request.GET.get('status')
# if status == 'active':
# queryset = queryset.filter(is_active=True)
# elif status == 'inactive':
# queryset = queryset.filter(is_active=False)
#
# return queryset.order_by('-created_at')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# tenant = self.request.user.tenant
#
# if tenant:
# context.update({
# 'providers': SocialAccount.objects.filter(
# user__tenant=tenant
# ).values_list('provider', flat=True).distinct(),
# 'users': User.objects.filter(tenant=tenant, is_active=True).order_by('last_name', 'first_name'),
# })
#
# return context
#
#
# class SocialAccountDetailView(LoginRequiredMixin, DetailView):
# """
# Display social account details.
# """
# model = SocialAccount
# template_name = 'accounts/social_account_detail.html'
# context_object_name = 'social_account'
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return SocialAccount.objects.none()
# return SocialAccount.objects.filter(user__tenant=tenant)
#
#
# class SocialAccountCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create new social account.
# """
# model = SocialAccount
# form_class = SocialAccountForm
# template_name = 'accounts/social_account_form.html'
# permission_required = 'accounts.add_socialaccount'
# success_url = reverse_lazy('accounts:social_account_list')
#
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log social account creation
# AuditLogger.log_event(
# tenant=self.request.user.tenant,
# event_type='CREATE',
# event_category='INTEGRATION',
# action='Create Social Account',
# description=f'Created social account: {self.object.provider}',
# user=self.request.user,
# content_object=self.object,
# request=self.request
# )
#
# messages.success(self.request, f'Social account "{self.object.provider}" created successfully.')
# return response
#
#
# class SocialAccountUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update social account.
# """
# model = SocialAccount
# form_class = SocialAccountForm
# template_name = 'accounts/social_account_form.html'
# permission_required = 'accounts.change_socialaccount'
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return SocialAccount.objects.none()
# return SocialAccount.objects.filter(user__tenant=tenant)
#
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def get_success_url(self):
# return reverse('accounts:social_account_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log social account update
# AuditLogger.log_event(
# tenant=self.request.user.tenant,
# event_type='UPDATE',
# event_category='INTEGRATION',
# action='Update Social Account',
# description=f'Updated social account: {self.object.provider}',
# user=self.request.user,
# content_object=self.object,
# request=self.request
# )
#
# messages.success(self.request, f'Social account "{self.object.provider}" updated successfully.')
# return response
#
#
# class SocialAccountDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
# """
# Delete social account.
# """
# model = SocialAccount
# template_name = 'accounts/social_account_confirm_delete.html'
# permission_required = 'accounts.delete_socialaccount'
# success_url = reverse_lazy('accounts:social_account_list')
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return SocialAccount.objects.none()
# return SocialAccount.objects.filter(user__tenant=tenant)
#
# def delete(self, request, *args, **kwargs):
# self.object = self.get_object()
# provider = self.object.provider
#
# # Log social account deletion
# AuditLogger.log_event(
# tenant=getattr(request, 'tenant', None),
# event_type='DELETE',
# event_category='INTEGRATION',
# action='Delete Social Account',
# description=f'Deleted social account: {provider}',
# user=request.user,
# content_object=self.object,
# request=request
# )
#
# messages.success(request, f'Social account "{provider}" deleted successfully.')
# return super().delete(request, *args, **kwargs)
#
#
# # ============================================================================
# # USER SESSION VIEWS (READ-ONLY - System Generated)
# # ============================================================================
#
# class UserSessionListView(LoginRequiredMixin, ListView):
# """
# List user sessions.
# """
# model = UserSession
# template_name = 'accounts/user_session_list.html'
# context_object_name = 'sessions'
# paginate_by = 50
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return UserSession.objects.none()
#
# queryset = UserSession.objects.filter(user__tenant=tenant)
#
# # Apply filters
# user_id = self.request.GET.get('user_id')
# if user_id:
# queryset = queryset.filter(user_id=user_id)
#
# status = self.request.GET.get('status')
# if status == 'active':
# queryset = queryset.filter(is_active=True)
# elif status == 'expired':
# queryset = queryset.filter(expires_at__lt=timezone.now())
#
# device_type = self.request.GET.get('device_type')
# if device_type:
# queryset = queryset.filter(device_type=device_type)
#
# date_from = self.request.GET.get('date_from')
# if date_from:
# queryset = queryset.filter(created_at__gte=date_from)
#
# date_to = self.request.GET.get('date_to')
# if date_to:
# queryset = queryset.filter(created_at__lte=date_to)
#
# return queryset.order_by('-created_at')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# tenant = self.request.user.tenant
#
# if tenant:
# context.update({
# 'device_types': UserSession.objects.filter(
# user__tenant=tenant
# ).values_list('device_type', flat=True).distinct(),
# 'users': User.objects.filter(tenant=tenant, is_active=True).order_by('last_name', 'first_name'),
# })
#
# return context
#
#
# class UserSessionDetailView(LoginRequiredMixin, DetailView):
# """
# Display user session details.
# """
# model = UserSession
# template_name = 'accounts/user_session_detail.html'
# context_object_name = 'session'
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return UserSession.objects.none()
# return UserSession.objects.filter(user__tenant=tenant)
#
#
# # ============================================================================
# # PASSWORD HISTORY VIEWS (READ-ONLY - System Generated)
# # ============================================================================
#
# class PasswordHistoryListView(LoginRequiredMixin, ListView):
# """
# List password history.
# """
# model = PasswordHistory
# template_name = 'accounts/password_history_list.html'
# context_object_name = 'password_history'
# paginate_by = 50
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return PasswordHistory.objects.none()
#
# queryset = PasswordHistory.objects.filter(user__tenant=tenant)
#
# # Apply filters
# user_id = self.request.GET.get('user_id')
# if user_id:
# queryset = queryset.filter(user_id=user_id)
#
# date_from = self.request.GET.get('date_from')
# if date_from:
# queryset = queryset.filter(created_at__gte=date_from)
#
# date_to = self.request.GET.get('date_to')
# if date_to:
# queryset = queryset.filter(created_at__lte=date_to)
#
# return queryset.order_by('-created_at')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# tenant = self.request.user.tenant
#
# if tenant:
# context.update({
# 'users': User.objects.filter(tenant=tenant, is_active=True).order_by('last_name', 'first_name'),
# })
#
# return context
#
#
# class PasswordHistoryDetailView(LoginRequiredMixin, DetailView):
# """
# Display password history details.
# """
# model = PasswordHistory
# template_name = 'accounts/password_history_detail.html'
# context_object_name = 'password_history'
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return PasswordHistory.objects.none()
# return PasswordHistory.objects.filter(user__tenant=tenant)
#
#
# # ============================================================================
# # PROFILE AND SESSION MANAGEMENT VIEWS
# # ============================================================================
#
# class UserProfileView(LoginRequiredMixin, TemplateView):
# """
# User profile management view.
# """
# template_name = 'accounts/user_profile.html'
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# user = self.request.user
#
# # Get user's sessions
# context['active_sessions'] = UserSession.objects.filter(
# user=user,
# is_active=True
# ).order_by('-created_at')
#
# # Get user's two-factor devices
# context['two_factor_devices'] = TwoFactorDevice.objects.filter(
# user=user,
# is_active=True
# ).order_by('-created_at')
#
# # Get user's social accounts
# context['social_accounts'] = SocialAccount.objects.filter(
# user=user,
# is_active=True
# ).order_by('-created_at')
#
# # Get password history
# context['password_history'] = PasswordHistory.objects.filter(
# user=user
# ).order_by('-created_at')[:5]
#
# return context
#
#
# class SessionManagementView(LoginRequiredMixin, ListView):
# """
# Session management view.
# """
# model = UserSession
# template_name = 'accounts/session_management.html'
# context_object_name = 'sessions'
# paginate_by = 50
#
# def get_queryset(self):
# tenant = self.request.user.tenant
# if not tenant:
# return UserSession.objects.none()
#
# queryset = UserSession.objects.filter(user__tenant=tenant)
#
# # Apply filters
# user_id = self.request.GET.get('user_id')
# if user_id:
# queryset = queryset.filter(user_id=user_id)
#
# status = self.request.GET.get('status')
# if status == 'active':
# queryset = queryset.filter(is_active=True)
# elif status == 'expired':
# queryset = queryset.filter(expires_at__lt=timezone.now())
#
# device_type = self.request.GET.get('device_type')
# if device_type:
# queryset = queryset.filter(device_type=device_type)
#
# return queryset.order_by('-created_at')
#
#
# # ============================================================================
# # HTMX VIEWS FOR REAL-TIME UPDATES
# # ============================================================================
#
# @login_required
# def user_search(request):
# """
# HTMX view for user search.
# """
# tenant = getattr(request, 'tenant', None)
# if not tenant:
# return JsonResponse({'error': 'No tenant found'}, status=400)
#
# search_query = request.GET.get('search', '')
# queryset = User.objects.filter(tenant=tenant)
#
# if search_query:
# queryset = queryset.filter(
# Q(username__icontains=search_query) |
# Q(email__icontains=search_query) |
# Q(first_name__icontains=search_query) |
# Q(last_name__icontains=search_query) |
# Q(employee_id__icontains=search_query)
# )
#
# users = queryset.order_by('last_name', 'first_name')[:20]
#
# return render(request, 'accounts/partials/user_list.html', {
# 'users': users
# })
#
#
# @login_required
# def user_stats(request):
# """
# HTMX view for user statistics.
# """
# tenant = getattr(request, 'tenant', None)
# if not tenant:
# return JsonResponse({'error': 'No tenant found'}, status=400)
#
# # Calculate user statistics
# total_users = User.objects.filter(tenant=tenant).count()
# active_users = User.objects.filter(tenant=tenant, is_active=True).count()
# pending_approval = User.objects.filter(tenant=tenant, is_approved=False).count()
#
# # Active sessions in last 24 hours
# yesterday = timezone.now() - timedelta(days=1)
# active_sessions = UserSession.objects.filter(
# user__tenant=tenant,
# last_activity_at__gte=yesterday,
# is_active=True
# ).count()
#
# # Users by role
# role_stats = User.objects.filter(tenant=tenant).values('role').annotate(
# count=Count('id')
# ).order_by('-count')[:5]
#
# # Two-factor adoption
# two_factor_users = User.objects.filter(
# tenant=tenant,
# twofactordevice__is_active=True
# ).distinct().count()
#
# stats = {
# 'total_users': total_users,
# 'active_users': active_users,
# 'pending_approval': pending_approval,
# 'active_sessions': active_sessions,
# 'two_factor_users': two_factor_users,
# 'role_stats': role_stats,
# }
#
# return render(request, 'accounts/partials/user_stats.html', {'stats': stats})
#
#
# @login_required
# def session_list(request):
# """
# HTMX view for session list.
# """
# tenant = getattr(request, 'tenant', None)
# if not tenant:
# return JsonResponse({'error': 'No tenant found'}, status=400)
#
# user_id = request.GET.get('user_id')
# if user_id:
# sessions = UserSession.objects.filter(
# user_id=user_id,
# user__tenant=tenant
# ).order_by('-created_at')[:10]
# else:
# sessions = UserSession.objects.filter(
# user__tenant=tenant,
# is_active=True
# ).order_by('-created_at')[:20]
#
# return render(request, 'accounts/partials/session_list.html', {
# 'sessions': sessions
# })
#
#
# @login_required
# def two_factor_setup(request):
# """
# HTMX view for two-factor authentication setup.
# """
# user = request.user
#
# if request.method == 'POST':
# device_type = request.POST.get('device_type')
# device_name = request.POST.get('device_name')
#
# if device_type and device_name:
# # Create new two-factor device
# device = TwoFactorDevice.objects.create(
# user=user,
# name=device_name,
# device_type=device_type,
# phone_number=request.POST.get('phone_number'),
# email_address=request.POST.get('email_address'),
# )
#
# # Generate secret key for TOTP devices
# if device_type == 'TOTP':
# import secrets
# device.secret_key = secrets.token_urlsafe(32)
# device.save()
#
# # Log the setup
# AuditLogger.log_event(
# tenant=getattr(request, 'tenant', None),
# event_type='CREATE',
# event_category='SECURITY',
# action='Setup Two-Factor Device',
# description=f'User setup two-factor device: {device_name}',
# user=request.user,
# content_object=device,
# request=request
# )
#
# return JsonResponse({'status': 'created', 'device_id': str(device.device_id)})
#
# return render(request, 'accounts/partials/two_factor_setup.html')
#
#
# # ============================================================================
# # ACTION VIEWS FOR WORKFLOW OPERATIONS
# # ============================================================================
#
# @login_required
# def end_session(request, session_id):
# """
# End a user session.
# """
# tenant = getattr(request, 'tenant', None)
# if not tenant:
# return JsonResponse({'error': 'No tenant found'}, status=400)
#
# session = get_object_or_404(
# UserSession,
# session_id=session_id,
# user__tenant=tenant
# )
#
# # End the session
# session.end_session()
#
# # Log the action
# AuditLogger.log_event(
# tenant=tenant,
# event_type='UPDATE',
# event_category='AUTHENTICATION',
# action='End User Session',
# description=f'Ended session for user: {session.user.username}',
# user=request.user,
# content_object=session,
# additional_data={
# 'target_user': session.user.username,
# 'session_ip': session.ip_address,
# },
# request=request
# )
#
# messages.success(request, f'Session for {session.user.username} ended successfully.')
# return redirect('accounts:session_management')
#
#
# @login_required
# def user_profile_update(request):
# """
# HTMX view for user profile update.
# """
# if request.method == 'POST':
# user = request.user
#
# # Update basic information
# user.first_name = request.POST.get('first_name', user.first_name)
# user.last_name = request.POST.get('last_name', user.last_name)
# user.email = request.POST.get('email', user.email)
# user.phone_number = request.POST.get('phone_number', user.phone_number)
# user.mobile_number = request.POST.get('mobile_number', user.mobile_number)
# user.bio = request.POST.get('bio', user.bio)
#
# # Update preferences
# user.timezone = request.POST.get('timezone', user.timezone)
# user.language = request.POST.get('language', user.language)
# user.theme = request.POST.get('theme', user.theme)
#
# user.save()
#
# # Log the update
# AuditLogger.log_event(
# tenant=getattr(request, 'tenant', None),
# event_type='UPDATE',
# event_category='DATA_MODIFICATION',
# action='Update User Profile',
# description=f'User updated their profile: {user.username}',
# user=request.user,
# content_object=user,
# request=request
# )
#
# messages.success(request, 'Profile updated successfully.')
# return JsonResponse({'status': 'success'})
#
# return JsonResponse({'error': 'Invalid request'}, status=400)
#
#
# @login_required
# def remove_two_factor_device(request, device_id):
# """
# Remove a two-factor device.
# """
# device = get_object_or_404(
# TwoFactorDevice,
# device_id=device_id,
# user=request.user
# )
#
# device_name = device.name
# device.delete()
#
# # Log the removal
# AuditLogger.log_event(
# tenant=getattr(request, 'tenant', None),
# event_type='DELETE',
# event_category='SECURITY',
# action='Remove Two-Factor Device',
# description=f'User removed two-factor device: {device_name}',
# user=request.user,
# additional_data={'device_name': device_name},
# request=request
# )
#
# messages.success(request, f'Two-factor device "{device_name}" removed successfully.')
# return redirect('accounts:user_profile')
#
#
# @login_required
# def user_activity_log(request, user_id):
# """
# HTMX view for user activity log.
# """
# tenant = getattr(request, 'tenant', None)
# if not tenant:
# return JsonResponse({'error': 'No tenant found'}, status=400)
#
# user_profile = get_object_or_404(User, id=user_id, tenant=tenant)
#
# # Get recent audit logs for this user
# from core.models import AuditLogEntry
# audit_logs = AuditLogEntry.objects.filter(
# tenant=tenant,
# user=user_profile
# ).order_by('-timestamp')[:20]
#
# return render(request, 'accounts/partials/user_activity_log.html', {
# 'audit_logs': audit_logs,
# 'user_profile': user_profile
# })
#
#
# @login_required
# def approve_user(request, pk):
# """
# Approve a user account.
# """
# tenant = getattr(request, 'tenant', None)
# if not tenant:
# messages.error(request, 'No tenant found.')
# return redirect('accounts:user_list')
#
# user = get_object_or_404(User, pk=pk, tenant=tenant)
#
# user.is_approved = True
# user.save()
#
# # Log approval
# AuditLogger.log_event(
# tenant=tenant,
# event_type='UPDATE',
# event_category='USER_MANAGEMENT',
# action='Approve User',
# description=f'Approved user: {user.username}',
# user=request.user,
# content_object=user,
# request=request
# )
#
# messages.success(request, f'User "{user.username}" approved successfully.')
# return redirect('accounts:user_detail', pk=pk)
#
#
# @login_required
# def activate_user(request, pk):
# """
# Activate a user account.
# """
# tenant = getattr(request, 'tenant', None)
# if not tenant:
# messages.error(request, 'No tenant found.')
# return redirect('accounts:user_list')
#
# user = get_object_or_404(User, pk=pk, tenant=tenant)
#
# user.is_active = True
# user.save()
#
# # Log activation
# AuditLogger.log_event(
# tenant=tenant,
# event_type='UPDATE',
# event_category='USER_MANAGEMENT',
# action='Activate User',
# description=f'Activated user: {user.username}',
# user=request.user,
# content_object=user,
# request=request
# )
#
# messages.success(request, f'User "{user.username}" activated successfully.')
# return redirect('accounts:user_detail', pk=pk)
#
#
# @login_required
# def reset_user_password(request, pk):
# """
# Reset user password.
# """
# tenant = getattr(request, 'tenant', None)
# if not tenant:
# messages.error(request, 'No tenant found.')
# return redirect('accounts:user_list')
#
# user = get_object_or_404(User, pk=pk, tenant=tenant)
#
# # Generate temporary password
# import secrets
# import string
# temp_password = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(12))
#
# # Set password and force change on next login
# user.set_password(temp_password)
# user.password_change_required = True
# user.save()
#
# # Log password reset
# AuditLogger.log_event(
# tenant=tenant,
# event_type='UPDATE',
# event_category='SECURITY',
# action='Reset User Password',
# description=f'Reset password for user: {user.username}',
# user=request.user,
# content_object=user,
# request=request
# )
#
# messages.success(request, f'Password reset for "{user.username}". Temporary password: {temp_password}')
# return redirect('accounts:user_detail', pk=pk)
#
# def available_slots(request):
# """
# HTMX view for available slots.
# """
# tenant = getattr(request, 'tenant', None)
# if not tenant:
# return JsonResponse({'error': 'No tenant found'}, status=400)
#
# provider = request.GET.get('provider')
# date_str = request.GET.get('date')
#
# if not provider or not date_str:
# return render(request, 'appointments/partials/available_slots.html', {'slots': []})
#
# try:
# selected_date = datetime.strptime(date_str, '%Y-%m-%d').date()
# except ValueError:
# return render(request, 'appointments/partials/available_slots.html', {'slots': []})
#
# # Get available slots for the provider on the selected date
# slots = SlotAvailability.objects.filter(
# provider_id=provider,
# provider__tenant=tenant,
# date=selected_date,
# # is_available=True
# ).order_by('start_time')
#
# return render(request, 'appointments/partials/available_slots.html', {
# 'slots': slots,
# 'selected_date': selected_date
# })
# class AppointmentListView(LoginRequiredMixin, ListView):
# """
# List view for appointments.
# """
# model = AppointmentRequest
# template_name = 'appointments/requests/appointment_list.html'
# context_object_name = 'appointments'
# paginate_by = 20
#
# def get_queryset(self):
# return AppointmentRequest.objects.filter(
# tenant=self.request.user.tenant
# ).order_by('-created_at')
#
#
# class AppointmentDetailView(LoginRequiredMixin, DetailView):
# """
# Detail view for appointments.
# """
# model = AppointmentRequest
# template_name = 'appointments/requests/appointment_request_detail.html'
# context_object_name = 'appointment'
#
# def get_queryset(self):
# return AppointmentRequest.objects.filter(
# tenant=self.request.user.tenant
# )
#
# from django.shortcuts import render, get_object_or_404, redirect
# from django.views.generic import (
# ListView, DetailView, CreateView, UpdateView, DeleteView
# )
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
# from django.contrib.auth.decorators import login_required
# from django.http import JsonResponse, HttpResponseForbidden
# from django.urls import reverse, reverse_lazy
# from django.utils import timezone
# from django.utils.translation import gettext_lazy as _
# from django.contrib import messages
# from django.db.models import Q, Count, Avg
# from django.template.loader import render_to_string
# from datetime import datetime, timedelta
#
# from .models import (
# AppointmentRequest, SlotAvailability, WaitingQueue,
# QueueEntry, TelemedicineSession, AppointmentTemplate
# )
# from .forms import (
# AppointmentRequestForm, SlotAvailabilityForm, WaitingQueueForm,
# QueueEntryForm, TelemedicineSessionForm, AppointmentTemplateForm,
# AppointmentSearchForm
# )
# from patients.models import PatientProfile
# from accounts.models import User
# from core.utils import get_tenant_from_request
#
#
# class TenantRequiredMixin:
# """Mixin to require a tenant for views."""
#
# def dispatch(self, request, *args, **kwargs):
# """Check for tenant before dispatching."""
# self.tenant = get_tenant_from_request(request)
# if not self.tenant:
# messages.error(request, _("No tenant found. Please contact an administrator."))
# return redirect('core:dashboard')
# return super().dispatch(request, *args, **kwargs)
#
#
# # --- AppointmentRequest Views ---
#
# class AppointmentRequestListView(LoginRequiredMixin, TenantRequiredMixin, ListView):
# """List view for appointment requests."""
# model = AppointmentRequest
# template_name = 'appointments/appointment_list.html'
# context_object_name = 'appointments'
# paginate_by = 20
#
# def get_queryset(self):
# """Get filtered queryset based on search parameters."""
# queryset = AppointmentRequest.objects.filter(tenant=self.tenant)
#
# # Initialize the search form
# self.form = AppointmentSearchForm(self.request.GET, tenant=self.tenant)
#
# # Apply filters if the form is valid
# if self.form.is_valid():
# data = self.form.cleaned_data
#
# # Text search
# if data.get('search'):
# search_term = data['search']
# queryset = queryset.filter(
# Q(patient__first_name__icontains=search_term) |
# Q(patient__last_name__icontains=search_term) |
# Q(provider__first_name__icontains=search_term) |
# Q(provider__last_name__icontains=search_term) |
# Q(reason__icontains=search_term)
# )
#
# # Status filter
# if data.get('status'):
# queryset = queryset.filter(status=data['status'])
#
# # Appointment type filter
# if data.get('appointment_type'):
# queryset = queryset.filter(appointment_type=data['appointment_type'])
#
# # Provider filter
# if data.get('provider'):
# queryset = queryset.filter(provider=data['provider'])
#
# # Department filter
# if data.get('department'):
# queryset = queryset.filter(department=data['department'])
#
# # Date range filters
# if data.get('date_from'):
# queryset = queryset.filter(scheduled_datetime__date__gte=data['date_from'])
#
# if data.get('date_to'):
# queryset = queryset.filter(scheduled_datetime__date__lte=data['date_to'])
#
# # Default ordering
# return queryset.select_related('patient', 'provider', 'department').order_by(
# 'scheduled_datetime'
# )
#
# def get_context_data(self, **kwargs):
# """Add search form and filters to context."""
# context = super().get_context_data(**kwargs)
# context['form'] = self.form
#
# # Add filter summaries
# context['active_filters'] = {}
# if self.form.is_valid():
# data = self.form.cleaned_data
# for field, value in data.items():
# if value:
# if field == 'provider' and value:
# context['active_filters'][field] = value.get_full_name()
# elif field == 'department' and value:
# context['active_filters'][field] = value.name
# elif field in ['status', 'appointment_type'] and value:
# # Get display value for choice fields
# choices_dict = dict(getattr(AppointmentRequest, f"{field.upper()}_CHOICES", []))
# context['active_filters'][field] = choices_dict.get(value, value)
# else:
# context['active_filters'][field] = value
#
# # Add appointment counts by status
# context['appointment_counts'] = {
# 'total': AppointmentRequest.objects.filter(tenant=self.tenant).count(),
# 'upcoming': AppointmentRequest.objects.filter(
# tenant=self.tenant,
# scheduled_datetime__gte=timezone.now(),
# status__in=['SCHEDULED', 'CONFIRMED']
# ).count(),
# 'today': AppointmentRequest.objects.filter(
# tenant=self.tenant,
# scheduled_datetime__date=timezone.now().date()
# ).count(),
# 'pending': AppointmentRequest.objects.filter(
# tenant=self.tenant,
# status='REQUESTED'
# ).count(),
# }
#
# return context
#
#
# class AppointmentRequestDetailView(LoginRequiredMixin, TenantRequiredMixin, DetailView):
# """Detail view for an appointment request."""
# model = AppointmentRequest
# template_name = 'appointments/appointment_detail.html'
# context_object_name = 'appointment'
#
# def get_queryset(self):
# """Filter by tenant."""
# return AppointmentRequest.objects.filter(
# tenant=self.tenant
# ).select_related(
# 'patient', 'provider', 'department', 'telemedicine_session',
# 'template', 'requested_by', 'cancelled_by'
# )
#
# def get_context_data(self, **kwargs):
# """Add additional context data."""
# context = super().get_context_data(**kwargs)
#
# # Add related queue entries
# context['queue_entries'] = self.object.queue_entries.all()
#
# # Check if patient has other appointments
# context['other_appointments'] = AppointmentRequest.objects.filter(
# tenant=self.tenant,
# patient=self.object.patient
# ).exclude(
# pk=self.object.pk
# ).order_by('-scheduled_datetime')[:5]
#
# return context
#
#
# class AppointmentRequestCreateView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, CreateView):
# """Create view for a new appointment request."""
# model = AppointmentRequest
# form_class = AppointmentRequestForm
# template_name = 'appointments/appointment_form.html'
# permission_required = 'appointments.add_appointmentrequest'
#
# def get_form_kwargs(self):
# """Add the current user to form kwargs."""
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def get_initial(self):
# """Pre-populate form with initial values."""
# initial = super().get_initial()
#
# # Pre-populate patient if provided in query params
# patient_id = self.request.GET.get('patient_id')
# if patient_id:
# try:
# patient = PatientProfile.objects.get(
# tenant=self.tenant,
# patient_id=patient_id
# )
# initial['patient'] = patient
# except PatientProfile.DoesNotExist:
# pass
#
# # Pre-populate provider if provided in query params
# provider_id = self.request.GET.get('provider_id')
# if provider_id:
# try:
# provider = User.objects.get(
# tenant=self.tenant,
# id=provider_id
# )
# initial['provider'] = provider
# except User.DoesNotExist:
# pass
#
# # Pre-populate date/time if provided
# date_str = self.request.GET.get('date')
# time_str = self.request.GET.get('time')
# if date_str:
# try:
# if time_str:
# # Combine date and time
# date_time = datetime.strptime(
# f"{date_str} {time_str}",
# '%Y-%m-%d %H:%M'
# )
# else:
# # Just date, use default time (9:00 AM)
# date_time = datetime.strptime(
# f"{date_str} 09:00",
# '%Y-%m-%d %H:%M'
# )
#
# # Convert to timezone-aware datetime
# date_time = timezone.make_aware(date_time)
# initial['scheduled_datetime'] = date_time
# except ValueError:
# pass
#
# # Set default status
# initial['status'] = 'SCHEDULED'
#
# # Set default appointment type
# initial['appointment_type'] = 'IN_PERSON'
#
# # Set default duration
# initial['duration_minutes'] = 30
#
# return initial
#
# def form_valid(self, form):
# """Process the valid form."""
# # Set tenant
# form.instance.tenant = self.tenant
#
# # Set requested_by to current user if not set
# if not form.instance.requested_by:
# form.instance.requested_by = self.request.user
#
# # Save the form
# response = super().form_valid(form)
#
# # Log the creation
# messages.success(
# self.request,
# _("Appointment for {} with {} has been scheduled for {}.").format(
# form.instance.patient.get_full_name(),
# form.instance.provider.get_full_name(),
# form.instance.scheduled_datetime.strftime('%Y-%m-%d %H:%M')
# )
# )
#
# return response
#
# def get_success_url(self):
# """Redirect to appointment detail view."""
# return reverse('appointments:appointment_detail', kwargs={'pk': self.object.pk})
#
#
# class AppointmentRequestUpdateView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, UpdateView):
# """Update view for an appointment request."""
# model = AppointmentRequest
# form_class = AppointmentRequestForm
# template_name = 'appointments/appointment_form.html'
# permission_required = 'appointments.change_appointmentrequest'
#
# def get_queryset(self):
# """Filter by tenant."""
# return AppointmentRequest.objects.filter(tenant=self.tenant)
#
# def get_form_kwargs(self):
# """Add the current user to form kwargs."""
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# """Process the valid form."""
# # Check if status changed
# old_status = AppointmentRequest.objects.get(pk=self.object.pk).status
# new_status = form.cleaned_data.get('status')
#
# # Save the form
# response = super().form_valid(form)
#
# # Add appropriate message
# if old_status != new_status:
# messages.success(
# self.request,
# _("Appointment status updated from {} to {}.").format(
# dict(AppointmentRequest.STATUS_CHOICES).get(old_status, old_status),
# dict(AppointmentRequest.STATUS_CHOICES).get(new_status, new_status),
# )
# )
# else:
# messages.success(
# self.request,
# _("Appointment updated successfully.")
# )
#
# return response
#
# def get_success_url(self):
# """Redirect to appointment detail view."""
# return reverse('appointments:appointment_detail', kwargs={'pk': self.object.pk})
#
#
# class AppointmentRequestDeleteView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, DeleteView):
# """Delete view for an appointment request."""
# model = AppointmentRequest
# template_name = 'appointments/appointment_confirm_delete.html'
# permission_required = 'appointments.delete_appointmentrequest'
# success_url = reverse_lazy('appointments:appointment_list')
#
# def get_queryset(self):
# """Filter by tenant."""
# return AppointmentRequest.objects.filter(tenant=self.tenant)
#
# def delete(self, request, *args, **kwargs):
# """Override delete to add custom message."""
# self.object = self.get_object()
# success_url = self.get_success_url()
#
# # Store patient name for message
# patient_name = self.object.patient.get_full_name() if self.object.patient else "Unknown"
# appointment_date = self.object.scheduled_datetime.strftime(
# '%Y-%m-%d %H:%M') if self.object.scheduled_datetime else "Unscheduled"
#
# # Delete the object
# self.object.delete()
#
# # Add success message
# messages.success(
# request,
# _("Appointment for {} on {} has been deleted.").format(
# patient_name, appointment_date
# )
# )
#
# return redirect(success_url)
#
#
# # --- SlotAvailability Views ---
#
# class SlotAvailabilityListView(LoginRequiredMixin, TenantRequiredMixin, ListView):
# """List view for provider availability slots."""
# model = SlotAvailability
# template_name = 'appointments/slot_availability_list.html'
# context_object_name = 'slots'
# paginate_by = 20
#
# def get_queryset(self):
# """Filter by tenant and provider."""
# queryset = SlotAvailability.objects.filter(tenant=self.tenant)
#
# # Filter by provider if specified
# provider_id = self.request.GET.get('provider_id')
# if provider_id:
# queryset = queryset.filter(provider_id=provider_id)
#
# # Filter by date range
# date_from = self.request.GET.get('date_from')
# if date_from:
# try:
# date_from = datetime.strptime(date_from, '%Y-%m-%d').date()
# queryset = queryset.filter(start_time__date__gte=date_from)
# except ValueError:
# pass
#
# date_to = self.request.GET.get('date_to')
# if date_to:
# try:
# date_to = datetime.strptime(date_to, '%Y-%m-%d').date()
# queryset = queryset.filter(start_time__date__lte=date_to)
# except ValueError:
# pass
#
# # Default ordering
# return queryset.select_related('provider').order_by('start_time')
#
# def get_context_data(self, **kwargs):
# """Add filter context."""
# context = super().get_context_data(**kwargs)
#
# # Add provider filter
# provider_id = self.request.GET.get('provider_id')
# if provider_id:
# try:
# context['selected_provider'] = User.objects.get(id=provider_id)
# except User.DoesNotExist:
# pass
#
# # Add providers for filter dropdown
# context['providers'] = User.objects.filter(
# tenant=self.tenant,
# role__in=['DOCTOR', 'NURSE', 'PHYSICIAN_ASSISTANT']
# ).order_by('first_name', 'last_name')
#
# # Add date filters
# context['date_from'] = self.request.GET.get('date_from', '')
# context['date_to'] = self.request.GET.get('date_to', '')
#
# return context
#
#
# class SlotAvailabilityDetailView(LoginRequiredMixin, TenantRequiredMixin, DetailView):
# """Detail view for a provider availability slot."""
# model = SlotAvailability
# template_name = 'appointments/slot_availability_detail.html'
# context_object_name = 'slot'
#
# def get_queryset(self):
# """Filter by tenant."""
# return SlotAvailability.objects.filter(tenant=self.tenant).select_related('provider')
#
# def get_context_data(self, **kwargs):
# """Add additional context data."""
# context = super().get_context_data(**kwargs)
#
# # Get appointments during this slot
# context['appointments'] = self.object.get_appointments()
#
# return context
#
#
# class SlotAvailabilityCreateView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, CreateView):
# """Create view for a new availability slot."""
# model = SlotAvailability
# form_class = SlotAvailabilityForm
# template_name = 'appointments/slot_availability_form.html'
# permission_required = 'appointments.add_slotavailability'
#
# def get_form_kwargs(self):
# """Add the current user to form kwargs."""
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def get_initial(self):
# """Pre-populate form with initial values."""
# initial = super().get_initial()
#
# # Pre-populate provider if provided in query params
# provider_id = self.request.GET.get('provider_id')
# if provider_id:
# try:
# provider = User.objects.get(tenant=self.tenant, id=provider_id)
# initial['provider'] = provider
# except User.DoesNotExist:
# pass
#
# # Pre-populate date/time if provided
# date_str = self.request.GET.get('date')
# if date_str:
# try:
# # Parse date and set default times (9:00 AM - 5:00 PM)
# date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
# start_time = timezone.make_aware(datetime.combine(date_obj, datetime.min.time().replace(hour=9)))
# end_time = timezone.make_aware(datetime.combine(date_obj, datetime.min.time().replace(hour=17)))
#
# initial['start_time'] = start_time
# initial['end_time'] = end_time
# except ValueError:
# pass
#
# # Set default availability
# initial['is_available'] = True
#
# return initial
#
# def form_valid(self, form):
# """Process the valid form."""
# # Set tenant
# form.instance.tenant = self.tenant
#
# # Save the form
# response = super().form_valid(form)
#
# # Add success message
# messages.success(
# self.request,
# _("Availability slot for {} from {} to {} has been created.").format(
# form.instance.provider.get_full_name(),
# form.instance.start_time.strftime('%Y-%m-%d %H:%M'),
# form.instance.end_time.strftime('%H:%M')
# )
# )
#
# return response
#
# def get_success_url(self):
# """Redirect to slot list view."""
# return reverse('appointments:slot_availability_list')
#
#
# class SlotAvailabilityUpdateView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, UpdateView):
# """Update view for an availability slot."""
# model = SlotAvailability
# form_class = SlotAvailabilityForm
# template_name = 'appointments/slot_availability_form.html'
# permission_required = 'appointments.change_slotavailability'
#
# def get_queryset(self):
# """Filter by tenant."""
# return SlotAvailability.objects.filter(tenant=self.tenant)
#
# def get_form_kwargs(self):
# """Add the current user to form kwargs."""
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# """Process the valid form."""
# # Save the form
# response = super().form_valid(form)
#
# # Add success message
# messages.success(
# self.request,
# _("Availability slot updated successfully.")
# )
#
# return response
#
# def get_success_url(self):
# """Redirect to slot detail view."""
# return reverse('appointments:slot_availability_detail', kwargs={'pk': self.object.pk})
#
#
# class SlotAvailabilityDeleteView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, DeleteView):
# """Delete view for an availability slot."""
# model = SlotAvailability
# template_name = 'appointments/slot_availability_confirm_delete.html'
# permission_required = 'appointments.delete_slotavailability'
# success_url = reverse_lazy('appointments:slot_availability_list')
#
# def get_queryset(self):
# """Filter by tenant."""
# return SlotAvailability.objects.filter(tenant=self.tenant)
#
# def delete(self, request, *args, **kwargs):
# """Override delete to add custom message."""
# self.object = self.get_object()
# success_url = self.get_success_url()
#
# # Store information for message
# provider_name = self.object.provider.get_full_name() if self.object.provider else "Unknown"
# slot_date = self.object.start_time.strftime('%Y-%m-%d')
#
# # Delete the object
# self.object.delete()
#
# # Add success message
# messages.success(
# request,
# _("Availability slot for {} on {} has been deleted.").format(
# provider_name, slot_date
# )
# )
#
# return redirect(success_url)
#
#
# # --- WaitingQueue Views ---
#
# class WaitingQueueListView(LoginRequiredMixin, TenantRequiredMixin, ListView):
# """List view for waiting queues."""
# model = WaitingQueue
# template_name = 'appointments/waiting_queue_list.html'
# context_object_name = 'queues'
#
# def get_queryset(self):
# """Filter by tenant."""
# return WaitingQueue.objects.filter(
# tenant=self.tenant
# ).annotate(
# waiting_count=Count('entries', filter=Q(entries__status='WAITING'))
# ).select_related('department', 'provider').order_by('name')
#
# def get_context_data(self, **kwargs):
# """Add department filter context."""
# context = super().get_context_data(**kwargs)
#
# # Add department filter
# department_id = self.request.GET.get('department_id')
# if department_id:
# context['queues'] = context['queues'].filter(department_id=department_id)
# try:
# context['selected_department'] = department_id
# except:
# pass
#
# return context
#
#
# class WaitingQueueDetailView(LoginRequiredMixin, TenantRequiredMixin, DetailView):
# """Detail view for a waiting queue."""
# model = WaitingQueue
# template_name = 'appointments/waiting_queue_detail.html'
# context_object_name = 'queue'
#
# def get_queryset(self):
# """Filter by tenant."""
# return WaitingQueue.objects.filter(tenant=self.tenant).select_related('department', 'provider')
#
# def get_context_data(self, **kwargs):
# """Add entries to context."""
# context = super().get_context_data(**kwargs)
#
# # Get waiting entries
# context['waiting_entries'] = self.object.entries.filter(
# status='WAITING'
# ).select_related('patient').order_by('priority', 'added_time')
#
# # Get called/in-progress entries
# context['active_entries'] = self.object.entries.filter(
# status__in=['CALLED', 'IN_PROGRESS']
# ).select_related('patient').order_by('called_time')
#
# # Get completed entries from today
# today = timezone.now().date()
# context['completed_entries'] = self.object.entries.filter(
# status='COMPLETED',
# completed_time__date=today
# ).select_related('patient').order_by('-completed_time')
#
# # Get statistics
# context['stats'] = {
# 'avg_wait_time': self.object.entries.filter(
# status__in=['COMPLETED', 'CALLED', 'IN_PROGRESS'],
# wait_time__isnull=False
# ).aggregate(avg=Avg('wait_time'))['avg'] or 0,
#
# 'today_count': self.object.entries.filter(
# added_time__date=today
# ).count(),
#
# 'completed_count': self.object.entries.filter(
# status='COMPLETED',
# completed_time__date=today
# ).count(),
# }
#
# # Add form for adding a new entry
# context['entry_form'] = QueueEntryForm(
# user=self.request.user,
# initial={'queue': self.object, 'priority': 'ROUTINE'}
# )
#
# return context
#
#
# class WaitingQueueCreateView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, CreateView):
# """Create view for a new waiting queue."""
# model = WaitingQueue
# form_class = WaitingQueueForm
# template_name = 'appointments/waiting_queue_form.html'
# permission_required = 'appointments.add_waitingqueue'
#
# def get_form_kwargs(self):
# """Add the current user to form kwargs."""
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# """Process the valid form."""
# # Set tenant
# form.instance.tenant = self.tenant
#
# # Save the form
# response = super().form_valid(form)
#
# # Add success message
# messages.success(
# self.request,
# _("Waiting queue '{}' has been created.").format(
# form.instance.name
# )
# )
#
# return response
#
# def get_success_url(self):
# """Redirect to queue detail view."""
# return reverse('appointments:waiting_queue_detail', kwargs={'pk': self.object.pk})
#
#
# class WaitingQueueUpdateView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, UpdateView):
# """Update view for a waiting queue."""
# model = WaitingQueue
# form_class = WaitingQueueForm
# template_name = 'appointments/waiting_queue_form.html'
# permission_required = 'appointments.change_waitingqueue'
#
# def get_queryset(self):
# """Filter by tenant."""
# return WaitingQueue.objects.filter(tenant=self.tenant)
#
# def get_form_kwargs(self):
# """Add the current user to form kwargs."""
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# """Process the valid form."""
# # Save the form
# response = super().form_valid(form)
#
# # Add success message
# messages.success(
# self.request,
# _("Waiting queue '{}' has been updated.").format(
# form.instance.name
# )
# )
#
# return response
#
# def get_success_url(self):
# """Redirect to queue detail view."""
# return reverse('appointments:waiting_queue_detail', kwargs={'pk': self.object.pk})
#
#
# class WaitingQueueDeleteView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, DeleteView):
# """Delete view for a waiting queue."""
# model = WaitingQueue
# template_name = 'appointments/waiting_queue_confirm_delete.html'
# permission_required = 'appointments.delete_waitingqueue'
# success_url = reverse_lazy('appointments:waiting_queue_list')
#
# def get_queryset(self):
# """Filter by tenant."""
# return WaitingQueue.objects.filter(tenant=self.tenant)
#
# def delete(self, request, *args, **kwargs):
# """Override delete to add custom message."""
# self.object = self.get_object()
# success_url = self.get_success_url()
#
# # Store information for message
# queue_name = self.object.name
#
# # Check if there are active entries
# active_entries = self.object.entries.filter(
# status__in=['WAITING', 'CALLED', 'IN_PROGRESS']
# ).exists()
#
# if active_entries:
# messages.error(
# request,
# _("Cannot delete queue '{}' because it has active entries.").format(queue_name)
# )
# return redirect('appointments:waiting_queue_detail', pk=self.object.pk)
#
# # Delete the object
# self.object.delete()
#
# # Add success message
# messages.success(
# request,
# _("Waiting queue '{}' has been deleted.").format(queue_name)
# )
#
# return redirect(success_url)
#
#
# # --- Calendar Views ---
#
# @login_required
# def calendar_view(request):
# """View for the appointment calendar."""
# tenant = get_tenant_from_request(request)
# if not tenant:
# messages.error(request, _("No tenant found. Please contact an administrator."))
# return redirect('core:dashboard')
#
# # Get providers for filter
# providers = User.objects.filter(
# tenant=tenant,
# role__in=['DOCTOR', 'NURSE', 'PHYSICIAN_ASSISTANT']
# ).order_by('first_name', 'last_name')
#
# # Get selected date (default to today)
# date_str = request.GET.get('date')
# if not date_str:
# date_str = timezone.now().date().strftime('%Y-%m-%d')
#
# try:
# selected_date = datetime.strptime(date_str, '%Y-%m-%d').date()
# except ValueError:
# selected_date = timezone.now().date()
#
# # Get selected provider (optional)
# provider_id = request.GET.get('provider_id')
# selected_provider = None
# if provider_id:
# try:
# selected_provider = User.objects.get(id=provider_id, tenant=tenant)
# except User.DoesNotExist:
# pass
#
# # Calendar navigation
# prev_date = selected_date - timedelta(days=1)
# next_date = selected_date + timedelta(days=1)
#
# # Week navigation
# start_of_week = selected_date - timedelta(days=selected_date.weekday())
# week_dates = [start_of_week + timedelta(days=i) for i in range(7)]
#
# context = {
# 'providers': providers,
# 'selected_date': selected_date,
# 'selected_provider': selected_provider,
# 'prev_date': prev_date,
# 'next_date': next_date,
# 'week_dates': week_dates,
# }
#
# return render(request, 'appointments/calendar.html', context)
#
#
# @login_required
# def calendar_appointments(request):
# """
# HTMX view for calendar appointments.
# """
# tenant = get_tenant_from_request(request)
# if not tenant:
# return JsonResponse({'error': 'No tenant found'}, status=400)
#
# date_str = request.GET.get('date')
# provider_id = request.GET.get('provider_id')
#
# if not date_str:
# date_str = timezone.now().date().strftime('%Y-%m-%d')
#
# try:
# selected_date = datetime.strptime(date_str, '%Y-%m-%d').date()
# except ValueError:
# selected_date = timezone.now().date()
#
# # Get appointments for the selected date
# queryset = AppointmentRequest.objects.filter(
# tenant=tenant,
# scheduled_datetime__date=selected_date
# )
#
# if provider_id:
# queryset = queryset.filter(provider_id=provider_id)
#
# appointments = queryset.select_related('patient', 'provider').order_by('scheduled_datetime')
#
# # Get time slots (30-minute intervals from 8:00 to 18:00)
# start_hour = 8
# end_hour = 18
# time_slots = []
#
# for hour in range(start_hour, end_hour):
# for minute in [0, 30]:
# time_slots.append({
# 'time': f"{hour:02d}:{minute:02d}",
# 'datetime': datetime.combine(selected_date, datetime.min.time().replace(hour=hour, minute=minute))
# })
#
# # Organize appointments by time slot
# for slot in time_slots:
# slot_time = timezone.make_aware(slot['datetime'])
# slot_end = slot_time + timedelta(minutes=30)
#
# # Find appointments that overlap with this slot
# slot['appointments'] = [
# appt for appt in appointments if (
# appt.scheduled_datetime < slot_end and
# appt.scheduled_datetime + timedelta(minutes=appt.duration_minutes) > slot_time
# )
# ]
#
# return render(request, 'appointments/partials/calendar_appointments.html', {
# 'appointments': appointments,
# 'selected_date': selected_date,
# 'time_slots': time_slots
# })
#
#
#
# # --- HTMX Queue Management Views ---
#
# @login_required
# def queue_status(request, queue_id):
# """
# HTMX view for queue status updates.
# """
# tenant = get_tenant_from_request(request)
# if not tenant:
# return JsonResponse({'error': 'No tenant found'}, status=400)
#
# queue = get_object_or_404(WaitingQueue, queue_id=queue_id, tenant=tenant)
#
# # Get waiting entries
# waiting_entries = queue.entries.filter(
# status='WAITING'
# ).select_related('patient').order_by('priority', 'added_time')
#
# # Get called/in-progress entries
# active_entries = queue.entries.filter(
# status__in=['CALLED', 'IN_PROGRESS']
# ).select_related('patient').order_by('called_time')
#
# return render(request, 'appointments/partials/queue_status.html', {
# 'queue': queue,
# 'waiting_entries': waiting_entries,
# 'active_entries': active_entries
# })
#
#
# # --- Action Views ---
#
# @login_required
# def cancel_appointment(request, pk):
# """
# View for cancelling an appointment.
# """
# tenant = get_tenant_from_request(request)
# if not tenant:
# messages.error(request, _("No tenant found. Please contact an administrator."))
# return redirect('core:dashboard')
#
# appointment = get_object_or_404(AppointmentRequest, pk=pk, tenant=tenant)
#
# # Check if the user has permission
# if not request.user.has_perm('appointments.change_appointmentrequest'):
# messages.error(request, _("You don't have permission to cancel appointments."))
# return redirect('appointments:appointment_detail', pk=appointment.pk)
#
# if request.method == 'POST':
# reason = request.POST.get('reason', '')
#
# # Cancel the appointment
# appointment.cancel(request.user, reason)
#
# messages.success(request, _("Appointment has been cancelled."))
# return redirect('appointments:appointment_detail', pk=appointment.pk)
#
# return render(request, 'appointments/cancel_appointment.html', {
# 'appointment': appointment
# })
#
#
# def reschedule_appointment(request, pk):
# """
# Reschedule an appointment.
# """
# tenant = request.user.tenant
# appointment = get_object_or_404(AppointmentRequest,pk=pk,tenant=tenant)
# providers = User.objects.filter(tenant=tenant, role__in=['DOCTOR', 'NURSE', 'SPECIALIST']).order_by('last_name', 'first_name')
#
# new_date = request.POST.get('new_date')
# new_time = request.POST.get('new_time')
#
# if new_date and new_time:
# appointment.scheduled_date = new_date
# appointment.scheduled_time = new_time
# appointment.status = 'RESCHEDULED'
# appointment.save()
#
# messages.success(request, 'Appointment has been rescheduled')
# return redirect('appointments:appointment_detail', pk=appointment.pk)
#
# return render(request, 'appointments/reschedule_appointment.html', {
# 'appointment': appointment,
# 'providers': providers
# })
# @login_required
# def reschedule_appointment(request, pk):
# """
# View for rescheduling an appointment.
# """
# tenant = get_tenant_from_request(request)
# if not tenant:
# messages.error(request, _("No tenant found. Please contact an administrator."))
# return redirect('core:dashboard')
#
# appointment = get_object_or_404(AppointmentRequest, pk=pk, tenant=tenant)
#
# # Check if the user has permission
# if not request.user.has_perm('appointments.change_appointmentrequest'):
# messages.error(request, _("You don't have permission to reschedule appointments."))
# return redirect('appointments:appointment_detail', pk=appointment.pk)
#
# # Check if the appointment can be rescheduled
# if appointment.status in ['COMPLETED', 'CANCELLED', 'NO_SHOW']:
# messages.error(request, _("This appointment cannot be rescheduled."))
# return redirect('appointments:appointment_detail', pk=appointment.pk)
#
# if request.method == 'POST':
# form = AppointmentRequestForm(request.POST, instance=appointment, user=request.user)
# if form.is_valid():
# # Update the appointment
# appointment = form.save()
#
# # Set status to RESCHEDULED if it was already scheduled
# if appointment.status in ['SCHEDULED', 'CONFIRMED']:
# appointment.status = 'RESCHEDULED'
# appointment.save()
#
# messages.success(request, _("Appointment has been rescheduled."))
# return redirect('appointments:appointment_detail', pk=appointment.pk)
# else:
# form = AppointmentRequestForm(instance=appointment, user=request.user)
#
# return render(request, 'appointments/appointment_reschedule.html', {
# 'form': form,
# 'appointment': appointment
# })
#
# @login_required
# def start_appointment(request, pk):
# """
# View for starting an appointment.
# """
# tenant = get_tenant_from_request(request)
# if not tenant:
# messages.error(request, _("No tenant found. Please contact an administrator."))
# return redirect('core:dashboard')
#
# appointment = get_object_or_404(AppointmentRequest, pk=pk, tenant=tenant)
#
# # Check if the user has permission
# if not request.user.has_perm('appointments.change_appointmentrequest'):
# messages.error(request, _("You don't have permission to start appointments."))
# return redirect('appointments:appointment_detail', pk=appointment.pk)
#
# # Check if the appointment can be started
# if appointment.status != 'CHECKED_IN':
# messages.error(request, _("This appointment cannot be started."))
# return redirect('appointments:appointment_detail', pk=appointment.pk)
#
# # Start the appointment
# appointment.start_appointment()
#
# messages.success(request, _("Appointment has been started."))
# return redirect('appointments:appointment_detail', pk=appointment.pk)
#
#
# @login_required
# def complete_appointment(request, pk):
# """
# View for completing an appointment.
# """
# tenant = get_tenant_from_request(request)
# if not tenant:
# messages.error(request, _("No tenant found. Please contact an administrator."))
# return redirect('core:dashboard')
#
# appointment = get_object_or_404(AppointmentRequest, pk=pk, tenant=tenant)
#
# # Check if the user has permission
# if not request.user.has_perm('appointments.change_appointmentrequest'):
# messages.error(request, _("You don't have permission to complete appointments."))
# return redirect('appointments:appointment_detail', pk=appointment.pk)
#
# # Check if the appointment can be completed
# if appointment.status != 'IN_PROGRESS':
# messages.error(request, _("This appointment cannot be marked as completed."))
# return redirect('appointments:appointment_detail', pk=appointment.pk)
#
# # Complete the appointment
# appointment.complete_appointment()
#
# messages.success(request, _("Appointment has been completed."))
# return redirect('appointments:appointment_detail', pk=appointment.pk)
# class Department(models.Model):
# """
# Hospital department model for organizational structure.
# Represents different departments within a healthcare organization.
# """
#
# DEPARTMENT_TYPE_CHOICES = [
# ('CLINICAL', 'Clinical Department'),
# ('ANCILLARY', 'Ancillary Services'),
# ('SUPPORT', 'Support Services'),
# ('ADMINISTRATIVE', 'Administrative'),
# ('DIAGNOSTIC', 'Diagnostic Services'),
# ('THERAPEUTIC', 'Therapeutic Services'),
# ('EMERGENCY', 'Emergency Services'),
# ('SURGICAL', 'Surgical Services'),
# ('MEDICAL', 'Medical Services'),
# ('NURSING', 'Nursing Services'),
# ('PHARMACY', 'Pharmacy'),
# ('LABORATORY', 'Laboratory'),
# ('RADIOLOGY', 'Radiology'),
# ('REHABILITATION', 'Rehabilitation'),
# ('MENTAL_HEALTH', 'Mental Health'),
# ('PEDIATRIC', 'Pediatric'),
# ('OBSTETRIC', 'Obstetric'),
# ('ONCOLOGY', 'Oncology'),
# ('CARDIOLOGY', 'Cardiology'),
# ('NEUROLOGY', 'Neurology'),
# ('ORTHOPEDIC', 'Orthopedic'),
# ('OTHER', 'Other'),
# ]
#
# # Tenant relationship
# tenant = models.ForeignKey(
# Tenant,
# on_delete=models.CASCADE,
# related_name='core_departments',
# help_text='Organization tenant'
# )
#
# # Department Information
# department_id = models.UUIDField(
# default=uuid.uuid4,
# unique=True,
# editable=False,
# help_text='Unique department identifier'
# )
# code = models.CharField(
# max_length=20,
# help_text='Department code (e.g., CARD, EMER, SURG)'
# )
# name = models.CharField(
# max_length=100,
# help_text='Department name'
# )
# description = models.TextField(
# blank=True,
# null=True,
# help_text='Department description'
# )
#
# # Department Classification
# department_type = models.CharField(
# max_length=30,
# choices=DEPARTMENT_TYPE_CHOICES,
# help_text='Type of department'
# )
#
# # Organizational Structure
# parent_department = models.ForeignKey(
# 'self',
# on_delete=models.SET_NULL,
# null=True,
# blank=True,
# related_name='sub_departments',
# help_text='Parent department (for hierarchical structure)'
# )
#
# # Management
# department_head = models.ForeignKey(
# settings.AUTH_USER_MODEL,
# on_delete=models.SET_NULL,
# null=True,
# blank=True,
# related_name='headed_departments',
# help_text='Department head/manager'
# )
#
# # Contact Information
# phone = models.CharField(
# max_length=20,
# blank=True,
# null=True,
# help_text='Department phone number'
# )
# extension = models.CharField(
# max_length=10,
# blank=True,
# null=True,
# help_text='Phone extension'
# )
# email = models.EmailField(
# blank=True,
# null=True,
# help_text='Department email'
# )
#
# # Location
# building = models.CharField(
# max_length=50,
# blank=True,
# null=True,
# help_text='Building name or number'
# )
# floor = models.CharField(
# max_length=20,
# blank=True,
# null=True,
# help_text='Floor number or name'
# )
# wing = models.CharField(
# max_length=20,
# blank=True,
# null=True,
# help_text='Wing or section'
# )
# room_numbers = models.CharField(
# max_length=100,
# blank=True,
# null=True,
# help_text='Room numbers (e.g., 101-110, 201A-205C)'
# )
#
# # Operational Information
# is_active = models.BooleanField(
# default=True,
# help_text='Department is active'
# )
# is_24_hour = models.BooleanField(
# default=False,
# help_text='Department operates 24 hours'
# )
# operating_hours = models.JSONField(
# default=dict,
# blank=True,
# help_text='Operating hours by day of week'
# )
#
# # Budget and Cost Center
# cost_center_code = models.CharField(
# max_length=20,
# blank=True,
# null=True,
# help_text='Cost center code for financial tracking'
# )
# budget_code = models.CharField(
# max_length=20,
# blank=True,
# null=True,
# help_text='Budget code'
# )
#
# # Staffing
# authorized_positions = models.PositiveIntegerField(
# default=0,
# help_text='Number of authorized positions'
# )
# current_staff_count = models.PositiveIntegerField(
# default=0,
# help_text='Current number of staff members'
# )
#
# # Quality and Compliance
# accreditation_required = models.BooleanField(
# default=False,
# help_text='Department requires special accreditation'
# )
# accreditation_body = models.CharField(
# max_length=100,
# blank=True,
# null=True,
# help_text='Accrediting body (e.g., Joint Commission, CAP)'
# )
# last_inspection_date = models.DateField(
# blank=True,
# null=True,
# help_text='Last inspection date'
# )
# next_inspection_date = models.DateField(
# blank=True,
# null=True,
# help_text='Next scheduled inspection date'
# )
#
# # Metadata
# created_at = models.DateTimeField(auto_now_add=True)
# updated_at = models.DateTimeField(auto_now=True)
# created_by = models.ForeignKey(
# settings.AUTH_USER_MODEL,
# on_delete=models.SET_NULL,
# null=True,
# blank=True,
# related_name='created_departments',
# help_text='User who created the department'
# )
#
# class Meta:
# db_table = 'core_department'
# verbose_name = 'Department'
# verbose_name_plural = 'Departments'
# ordering = ['name']
# indexes = [
# models.Index(fields=['tenant', 'department_type']),
# models.Index(fields=['code']),
# models.Index(fields=['is_active']),
# models.Index(fields=['parent_department']),
# ]
# unique_together = ['tenant', 'code']
#
# def __str__(self):
# return f"{self.name} ({self.code})"
#
# @property
# def full_name(self):
# """Return full department name with parent if applicable"""
# if self.parent_department:
# return f"{self.parent_department.name} - {self.name}"
# return self.name
#
# @property
# def staffing_percentage(self):
# """Calculate current staffing percentage"""
# if self.authorized_positions > 0:
# return (self.current_staff_count / self.authorized_positions) * 100
# return 0
#
# def get_all_sub_departments(self):
# """Get all sub-departments recursively"""
# sub_departments = []
# for sub_dept in self.sub_departments.all():
# sub_departments.append(sub_dept)
# sub_departments.extend(sub_dept.get_all_sub_departments())
# return sub_departments
# from django.shortcuts import render, redirect, get_object_or_404
# from django.contrib.auth.decorators import login_required, permission_required
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
# from django.contrib import messages
# from django.views.generic import (
# CreateView, UpdateView, DeleteView, DetailView, ListView, FormView
# )
# from django.urls import reverse_lazy, reverse
# from django.http import JsonResponse, HttpResponse
# from django.utils import timezone
# from django.db import transaction
# from django.core.mail import send_mail
# from django.conf import settings
# from django.db.models import Q, Count
# from viewflow.views import CreateProcessView, UpdateProcessView
# from datetime import datetime, timedelta
# import json
#
# from .models import Appointment, AppointmentConfirmation, Queue, TelemedicineSession
# from .forms import (
# AppointmentSchedulingForm, AppointmentConfirmationForm, QueueManagementForm,
# TelemedicineSetupForm, AppointmentRescheduleForm, AppointmentCancellationForm,
# AppointmentCheckInForm, BulkAppointmentForm
# )
# from .flows import AppointmentSchedulingFlow, AppointmentConfirmationFlow, QueueManagementFlow, TelemedicineSetupFlow
# from patients.models import Patient
#
#
# class AppointmentSchedulingView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
# """
# View for appointment scheduling workflow
# """
# model = Appointment
# form_class = AppointmentSchedulingForm
# template_name = 'appointments/appointment_scheduling.html'
# permission_required = 'appointments.can_schedule_appointments'
# flow_class = AppointmentSchedulingFlow
#
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs['tenant'] = self.request.user.tenant
# return kwargs
#
# def form_valid(self, form):
# with transaction.atomic():
# # Create appointment
# appointment = form.save(commit=False)
# appointment.tenant = self.request.user.tenant
# appointment.scheduled_by = self.request.user
# appointment.status = 'scheduled'
# appointment.save()
#
# # Start appointment scheduling workflow
# process = self.flow_class.start.run(
# appointment=appointment,
# send_confirmation=form.cleaned_data.get('send_confirmation', True),
# send_reminder=form.cleaned_data.get('send_reminder', True),
# created_by=self.request.user
# )
#
# messages.success(
# self.request,
# f'Appointment scheduled successfully for {appointment.patient.get_full_name()} '
# f'on {appointment.appointment_date} at {appointment.appointment_time}.'
# )
#
# return redirect('appointments:appointment_detail', pk=appointment.pk)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['title'] = 'Schedule Appointment'
# context['breadcrumbs'] = [
# {'name': 'Home', 'url': reverse('core:dashboard')},
# {'name': 'Appointments', 'url': reverse('appointments:appointment_list')},
# {'name': 'Schedule Appointment', 'url': ''}
# ]
# context['available_slots'] = self.get_available_slots()
# return context
#
# def get_available_slots(self):
# """Get available appointment slots for the next 7 days"""
# slots = []
# today = timezone.now().date()
#
# for i in range(7):
# date = today + timedelta(days=i)
# day_slots = self.get_slots_for_date(date)
# if day_slots:
# slots.append({
# 'date': date,
# 'slots': day_slots
# })
#
# return slots
#
# def get_slots_for_date(self, date):
# """Get available slots for a specific date"""
# # This would implement slot availability logic
# return []
#
#
# class AppointmentConfirmationView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
# """
# View for appointment confirmation workflow
# """
# model = AppointmentConfirmation
# form_class = AppointmentConfirmationForm
# template_name = 'appointments/appointment_confirmation.html'
# permission_required = 'appointments.can_confirm_appointments'
# flow_class = AppointmentConfirmationFlow
#
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
# return kwargs
#
# def form_valid(self, form):
# appointment = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
#
# with transaction.atomic():
# # Create confirmation record
# confirmation = form.save(commit=False)
# confirmation.appointment = appointment
# confirmation.confirmed_by = self.request.user
# confirmation.confirmed_at = timezone.now()
# confirmation.save()
#
# # Update appointment status
# if form.cleaned_data.get('reschedule_requested'):
# appointment.status = 'reschedule_requested'
# appointment.save()
#
# # Start rescheduling process
# messages.info(
# self.request,
# 'Patient requested reschedule. Please process the reschedule request.'
# )
# return redirect('appointments:appointment_reschedule', pk=appointment.pk)
# else:
# appointment.status = 'confirmed'
# appointment.save()
#
# # Start confirmation workflow
# process = self.flow_class.start.run(
# appointment=appointment,
# confirmation=confirmation,
# created_by=self.request.user
# )
#
# messages.success(
# self.request,
# f'Appointment confirmed for {appointment.patient.get_full_name()}.'
# )
#
# return redirect('appointments:appointment_detail', pk=appointment.pk)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
# context['title'] = 'Confirm Appointment'
# return context
#
#
# class QueueManagementView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
# """
# View for queue management workflow
# """
# model = Queue
# form_class = QueueManagementForm
# template_name = 'appointments/queue_management.html'
# permission_required = 'appointments.can_manage_queue'
# flow_class = QueueManagementFlow
#
# def form_valid(self, form):
# appointment = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
#
# with transaction.atomic():
# # Create or update queue entry
# queue_entry, created = Queue.objects.get_or_create(
# appointment=appointment,
# defaults={
# 'tenant': self.request.user.tenant,
# 'patient': appointment.patient,
# 'provider': appointment.provider,
# 'department': appointment.department,
# 'created_by': self.request.user
# }
# )
#
# # Update queue entry with form data
# for field in form.cleaned_data:
# if hasattr(queue_entry, field):
# setattr(queue_entry, field, form.cleaned_data[field])
#
# queue_entry.save()
#
# # Start queue management workflow
# process = self.flow_class.start.run(
# queue_entry=queue_entry,
# appointment=appointment,
# notify_patient=form.cleaned_data.get('notify_patient', True),
# created_by=self.request.user
# )
#
# messages.success(
# self.request,
# f'Queue updated for {appointment.patient.get_full_name()}. '
# f'Position: {queue_entry.position}, Wait time: {queue_entry.estimated_wait_time} minutes.'
# )
#
# return redirect('appointments:queue_list')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
# context['title'] = 'Manage Queue'
# context['current_queue'] = self.get_current_queue()
# return context
#
# def get_current_queue(self):
# """Get current queue status"""
# return Queue.objects.filter(
# tenant=self.request.user.tenant,
# status='waiting'
# ).order_by('priority', 'created_at')
#
#
# class TelemedicineSetupView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
# """
# View for telemedicine setup workflow
# """
# model = TelemedicineSession
# form_class = TelemedicineSetupForm
# template_name = 'appointments/telemedicine_setup.html'
# permission_required = 'appointments.can_setup_telemedicine'
# flow_class = TelemedicineSetupFlow
#
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
# return kwargs
#
# def form_valid(self, form):
# appointment = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
#
# with transaction.atomic():
# # Create telemedicine session
# session = form.save(commit=False)
# session.appointment = appointment
# session.tenant = self.request.user.tenant
# session.created_by = self.request.user
# session.save()
#
# # Update appointment type
# appointment.appointment_type = 'telemedicine'
# appointment.save()
#
# # Start telemedicine setup workflow
# process = self.flow_class.start.run(
# session=session,
# appointment=appointment,
# test_connection=form.cleaned_data.get('test_connection', True),
# send_instructions=form.cleaned_data.get('send_instructions', True),
# created_by=self.request.user
# )
#
# messages.success(
# self.request,
# f'Telemedicine session setup completed for {appointment.patient.get_full_name()}. '
# f'Meeting details have been sent to the patient.'
# )
#
# return redirect('appointments:appointment_detail', pk=appointment.pk)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
# context['title'] = 'Setup Telemedicine'
# return context
#
#
# class AppointmentRescheduleView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
# """
# View for appointment rescheduling
# """
# form_class = AppointmentRescheduleForm
# template_name = 'appointments/appointment_reschedule.html'
# permission_required = 'appointments.can_reschedule_appointments'
#
# def get_success_url(self):
# return reverse('appointments:appointment_detail', kwargs={'pk': self.kwargs['pk']})
#
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs['tenant'] = self.request.user.tenant
# kwargs['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['pk'])
# return kwargs
#
# def form_valid(self, form):
# appointment = get_object_or_404(Appointment, pk=self.kwargs['pk'])
#
# with transaction.atomic():
# # Store original appointment details
# original_date = appointment.appointment_date
# original_time = appointment.appointment_time
# original_provider = appointment.provider
#
# # Update appointment
# appointment.appointment_date = form.cleaned_data['new_date']
# appointment.appointment_time = form.cleaned_data['new_time']
# if form.cleaned_data.get('new_provider'):
# appointment.provider = form.cleaned_data['new_provider']
# appointment.status = 'rescheduled'
# appointment.save()
#
# # Log reschedule
# self.log_reschedule(appointment, original_date, original_time, original_provider, form.cleaned_data)
#
# # Send notifications
# if form.cleaned_data.get('notify_patient'):
# self.send_reschedule_notification(appointment, form.cleaned_data)
#
# messages.success(
# self.request,
# f'Appointment rescheduled successfully. New date: {appointment.appointment_date}, '
# f'New time: {appointment.appointment_time}.'
# )
#
# return super().form_valid(form)
#
# def log_reschedule(self, appointment, original_date, original_time, original_provider, form_data):
# """Log reschedule details"""
# from core.models import AuditLogEntry
# AuditLogEntry.objects.create(
# tenant=appointment.tenant,
# user=self.request.user,
# event_type='APPOINTMENT_RESCHEDULE',
# action='UPDATE',
# object_type='Appointment',
# object_id=str(appointment.id),
# details={
# 'original_date': original_date.isoformat(),
# 'original_time': original_time.isoformat(),
# 'original_provider': original_provider.get_full_name() if original_provider else None,
# 'new_date': form_data['new_date'].isoformat(),
# 'new_time': form_data['new_time'].isoformat(),
# 'new_provider': form_data['new_provider'].get_full_name() if form_data.get('new_provider') else None,
# 'reason': form_data['reschedule_reason'],
# 'notes': form_data.get('notes', '')
# },
# ip_address=self.request.META.get('REMOTE_ADDR'),
# user_agent=self.request.META.get('HTTP_USER_AGENT', '')
# )
#
# def send_reschedule_notification(self, appointment, form_data):
# """Send reschedule notification to patient"""
# if appointment.patient.email:
# send_mail(
# subject='Appointment Rescheduled',
# message=f'Your appointment has been rescheduled to {appointment.appointment_date} at {appointment.appointment_time}.',
# from_email=settings.DEFAULT_FROM_EMAIL,
# recipient_list=[appointment.patient.email],
# fail_silently=True
# )
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['pk'])
# context['title'] = 'Reschedule Appointment'
# return context
#
#
# class AppointmentCancellationView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
# """
# View for appointment cancellation
# """
# form_class = AppointmentCancellationForm
# template_name = 'appointments/appointment_cancellation.html'
# permission_required = 'appointments.can_cancel_appointments'
#
# def get_success_url(self):
# return reverse('appointments:appointment_list')
#
# def form_valid(self, form):
# appointment = get_object_or_404(Appointment, pk=self.kwargs['pk'])
#
# with transaction.atomic():
# # Update appointment status
# appointment.status = 'cancelled'
# appointment.cancelled_at = timezone.now()
# appointment.cancelled_by = self.request.user
# appointment.cancellation_reason = form.cleaned_data['cancellation_reason']
# appointment.cancellation_notes = form.cleaned_data.get('cancellation_notes', '')
# appointment.save()
#
# # Handle follow-up actions
# if form.cleaned_data.get('offer_reschedule'):
# self.offer_reschedule(appointment)
#
# if form.cleaned_data.get('notify_patient'):
# self.send_cancellation_notification(appointment, form.cleaned_data)
#
# if form.cleaned_data.get('refund_required'):
# self.process_refund(appointment)
#
# messages.success(
# self.request,
# f'Appointment cancelled for {appointment.patient.get_full_name()}.'
# )
#
# return super().form_valid(form)
#
# def offer_reschedule(self, appointment):
# """Offer reschedule options to patient"""
# # This would implement reschedule offering logic
# pass
#
# def send_cancellation_notification(self, appointment, form_data):
# """Send cancellation notification to patient"""
# if appointment.patient.email:
# send_mail(
# subject='Appointment Cancelled',
# message=f'Your appointment on {appointment.appointment_date} has been cancelled. Reason: {form_data["cancellation_reason"]}',
# from_email=settings.DEFAULT_FROM_EMAIL,
# recipient_list=[appointment.patient.email],
# fail_silently=True
# )
#
# def process_refund(self, appointment):
# """Process refund if required"""
# # This would implement refund processing logic
# pass
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['pk'])
# context['title'] = 'Cancel Appointment'
# return context
#
#
# class AppointmentCheckInView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
# """
# View for appointment check-in
# """
# form_class = AppointmentCheckInForm
# template_name = 'appointments/appointment_checkin.html'
# permission_required = 'appointments.can_checkin_patients'
#
# def get_success_url(self):
# return reverse('appointments:appointment_detail', kwargs={'pk': self.kwargs['pk']})
#
# def form_valid(self, form):
# appointment = get_object_or_404(Appointment, pk=self.kwargs['pk'])
#
# with transaction.atomic():
# # Update appointment status
# appointment.status = 'checked_in'
# appointment.checked_in_at = timezone.now()
# appointment.checked_in_by = self.request.user
# appointment.arrival_time = form.cleaned_data['arrival_time']
# appointment.save()
#
# # Create queue entry if needed
# if not hasattr(appointment, 'queue_entry'):
# Queue.objects.create(
# appointment=appointment,
# tenant=appointment.tenant,
# patient=appointment.patient,
# provider=appointment.provider,
# department=appointment.department,
# queue_type='check_in',
# priority='normal',
# status='waiting',
# created_by=self.request.user
# )
#
# # Handle pre-visit tasks
# self.process_checkin_tasks(appointment, form.cleaned_data)
#
# messages.success(
# self.request,
# f'{appointment.patient.get_full_name()} checked in successfully.'
# )
#
# return super().form_valid(form)
#
# def process_checkin_tasks(self, appointment, form_data):
# """Process check-in tasks"""
# tasks = []
#
# if form_data.get('insurance_verified'):
# tasks.append('Insurance verified')
# if form_data.get('copay_collected'):
# tasks.append('Copay collected')
# if form_data.get('forms_completed'):
# tasks.append('Forms completed')
# if form_data.get('vitals_required'):
# tasks.append('Vitals required')
#
# # Log completed tasks
# if tasks:
# from core.models import AuditLogEntry
# AuditLogEntry.objects.create(
# tenant=appointment.tenant,
# user=self.request.user,
# event_type='APPOINTMENT_CHECKIN',
# action='UPDATE',
# object_type='Appointment',
# object_id=str(appointment.id),
# details={
# 'completed_tasks': tasks,
# 'special_needs': form_data.get('special_needs', '')
# },
# ip_address=self.request.META.get('REMOTE_ADDR'),
# user_agent=self.request.META.get('HTTP_USER_AGENT', '')
# )
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['pk'])
# context['title'] = 'Check In Patient'
# return context
#
#
# class BulkAppointmentView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
# """
# View for bulk appointment operations
# """
# form_class = BulkAppointmentForm
# template_name = 'appointments/bulk_appointment.html'
# permission_required = 'appointments.can_bulk_manage_appointments'
#
# def get_success_url(self):
# return reverse('appointments:appointment_list')
#
# def form_valid(self, form):
# appointment_ids = form.cleaned_data['appointment_ids'].split(',')
# appointments = Appointment.objects.filter(
# id__in=appointment_ids,
# tenant=self.request.user.tenant
# )
#
# action = form.cleaned_data['action']
#
# with transaction.atomic():
# if action == 'confirm':
# self.bulk_confirm(appointments, form.cleaned_data)
# elif action == 'reschedule':
# self.bulk_reschedule(appointments, form.cleaned_data)
# elif action == 'cancel':
# self.bulk_cancel(appointments, form.cleaned_data)
# elif action == 'send_reminders':
# self.bulk_send_reminders(appointments, form.cleaned_data)
#
# messages.success(
# self.request,
# f'Bulk operation "{action}" completed for {appointments.count()} appointments.'
# )
#
# return super().form_valid(form)
#
# def bulk_confirm(self, appointments, form_data):
# """Bulk confirm appointments"""
# for appointment in appointments:
# appointment.status = 'confirmed'
# appointment.save()
#
# if form_data.get('notify_patients'):
# self.send_confirmation_notification(appointment)
#
# def bulk_reschedule(self, appointments, form_data):
# """Bulk reschedule appointments"""
# new_date = form_data.get('bulk_date')
# if new_date:
# for appointment in appointments:
# appointment.appointment_date = new_date
# appointment.status = 'rescheduled'
# appointment.save()
#
# if form_data.get('notify_patients'):
# self.send_reschedule_notification(appointment, form_data)
#
# def bulk_cancel(self, appointments, form_data):
# """Bulk cancel appointments"""
# for appointment in appointments:
# appointment.status = 'cancelled'
# appointment.cancelled_at = timezone.now()
# appointment.cancelled_by = self.request.user
# appointment.cancellation_reason = 'bulk_cancellation'
# appointment.cancellation_notes = form_data.get('bulk_reason', '')
# appointment.save()
#
# if form_data.get('notify_patients'):
# self.send_cancellation_notification(appointment, form_data)
#
# def bulk_send_reminders(self, appointments, form_data):
# """Bulk send reminders"""
# for appointment in appointments:
# if form_data.get('notify_patients'):
# self.send_reminder_notification(appointment)
#
# def send_confirmation_notification(self, appointment):
# """Send confirmation notification"""
# if appointment.patient.email:
# send_mail(
# subject='Appointment Confirmed',
# message=f'Your appointment on {appointment.appointment_date} at {appointment.appointment_time} has been confirmed.',
# from_email=settings.DEFAULT_FROM_EMAIL,
# recipient_list=[appointment.patient.email],
# fail_silently=True
# )
#
# def send_reminder_notification(self, appointment):
# """Send reminder notification"""
# if appointment.patient.email:
# send_mail(
# subject='Appointment Reminder',
# message=f'Reminder: You have an appointment on {appointment.appointment_date} at {appointment.appointment_time}.',
# from_email=settings.DEFAULT_FROM_EMAIL,
# recipient_list=[appointment.patient.email],
# fail_silently=True
# )
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['title'] = 'Bulk Appointment Operations'
# return context
#
#
# class AppointmentListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
# """
# View for listing appointments
# """
# model = Appointment
# template_name = 'appointments/appointment_list.html'
# context_object_name = 'appointments'
# permission_required = 'appointments.view_appointment'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = Appointment.objects.filter(tenant=self.request.user.tenant)
#
# # Apply filters
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(patient__first_name__icontains=search) |
# Q(patient__last_name__icontains=search) |
# Q(provider__first_name__icontains=search) |
# Q(provider__last_name__icontains=search) |
# Q(reason__icontains=search)
# )
#
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# date_from = self.request.GET.get('date_from')
# if date_from:
# queryset = queryset.filter(appointment_date__gte=date_from)
#
# date_to = self.request.GET.get('date_to')
# if date_to:
# queryset = queryset.filter(appointment_date__lte=date_to)
#
# provider = self.request.GET.get('provider')
# if provider:
# queryset = queryset.filter(provider_id=provider)
#
# return queryset.order_by('appointment_date', 'appointment_time')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['title'] = 'Appointments'
# context['providers'] = self.get_providers()
# context['search'] = self.request.GET.get('search', '')
# context['selected_status'] = self.request.GET.get('status', '')
# context['selected_provider'] = self.request.GET.get('provider', '')
# context['date_from'] = self.request.GET.get('date_from', '')
# context['date_to'] = self.request.GET.get('date_to', '')
# return context
#
# def get_providers(self):
# """Get providers for filter"""
# from django.contrib.auth.models import User
# return User.objects.filter(
# tenant=self.request.user.tenant,
# groups__name__in=['Doctors', 'Nurses', 'Specialists']
# ).distinct()
#
#
# class AppointmentDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
# """
# View for appointment details
# """
# model = Appointment
# template_name = 'appointments/appointment_detail.html'
# context_object_name = 'appointment'
# permission_required = 'appointments.view_appointment'
#
# def get_queryset(self):
# return Appointment.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# appointment = self.object
# context['title'] = f'Appointment - {appointment.patient.get_full_name()}'
# context['confirmation'] = getattr(appointment, 'confirmation', None)
# context['queue_entry'] = getattr(appointment, 'queue_entry', None)
# context['telemedicine_session'] = getattr(appointment, 'telemedicine_session', None)
# context['can_edit'] = self.request.user.has_perm('appointments.change_appointment')
# context['can_cancel'] = self.request.user.has_perm('appointments.can_cancel_appointments')
# context['can_reschedule'] = self.request.user.has_perm('appointments.can_reschedule_appointments')
# return context
#
#
# class QueueListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
# """
# View for listing queue entries
# """
# model = Queue
# template_name = 'appointments/queue_list.html'
# context_object_name = 'queue_entries'
# permission_required = 'appointments.view_queue'
#
# def get_queryset(self):
# queryset = Queue.objects.filter(tenant=self.request.user.tenant)
#
# # Apply filters
# status = self.request.GET.get('status', 'waiting')
# if status:
# queryset = queryset.filter(status=status)
#
# department = self.request.GET.get('department')
# if department:
# queryset = queryset.filter(department_id=department)
#
# return queryset.order_by('priority', 'created_at')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['title'] = 'Patient Queue'
# context['departments'] = self.get_departments()
# context['selected_status'] = self.request.GET.get('status', 'waiting')
# context['selected_department'] = self.request.GET.get('department', '')
# context['queue_stats'] = self.get_queue_stats()
# return context
#
# def get_departments(self):
# """Get departments for filter"""
# from core.models import Department
# return Department.objects.filter(tenant=self.request.user.tenant)
#
# def get_queue_stats(self):
# """Get queue statistics"""
# return {
# 'total_waiting': Queue.objects.filter(
# tenant=self.request.user.tenant,
# status='waiting'
# ).count(),
# 'average_wait_time': 25, # Would be calculated
# 'longest_wait': 45 # Would be calculated
# }
#
#
# # AJAX Views
# @login_required
# @permission_required('appointments.view_appointment')
# def appointment_availability_ajax(request):
# """AJAX view to check appointment availability"""
# date = request.GET.get('date')
# provider_id = request.GET.get('provider_id')
#
# if not date or not provider_id:
# return JsonResponse({'success': False, 'message': 'Missing parameters'})
#
# try:
# from django.contrib.auth.models import User
# provider = User.objects.get(id=provider_id, tenant=request.user.tenant)
# appointment_date = datetime.strptime(date, '%Y-%m-%d').date()
#
# # Get existing appointments for the date
# existing_appointments = Appointment.objects.filter(
# provider=provider,
# appointment_date=appointment_date,
# status__in=['scheduled', 'confirmed', 'in_progress']
# )
#
# # Generate available slots
# available_slots = generate_available_slots(appointment_date, existing_appointments)
#
# return JsonResponse({
# 'success': True,
# 'slots': available_slots
# })
# except Exception as e:
# return JsonResponse({'success': False, 'message': str(e)})
#
#
# @login_required
# @permission_required('appointments.view_patient')
# def patient_search_ajax(request):
# """AJAX view for patient search"""
# query = request.GET.get('q', '')
# if len(query) < 2:
# return JsonResponse({'patients': []})
#
# patients = Patient.objects.filter(
# tenant=request.user.tenant
# ).filter(
# Q(first_name__icontains=query) |
# Q(last_name__icontains=query) |
# Q(patient_id__icontains=query) |
# Q(phone_number__icontains=query)
# )[:10]
#
# patient_data = [
# {
# 'id': patient.id,
# 'name': patient.get_full_name(),
# 'patient_id': patient.patient_id,
# 'phone': patient.phone_number,
# 'email': patient.email
# }
# for patient in patients
# ]
#
# return JsonResponse({'patients': patient_data})
#
#
# @login_required
# @permission_required('appointments.can_manage_queue')
# def update_queue_position_ajax(request):
# """AJAX view to update queue position"""
# if request.method == 'POST':
# try:
# data = json.loads(request.body)
# queue_id = data.get('queue_id')
# new_position = data.get('new_position')
#
# queue_entry = Queue.objects.get(
# id=queue_id,
# tenant=request.user.tenant
# )
#
# queue_entry.position = new_position
# queue_entry.save()
#
# return JsonResponse({
# 'success': True,
# 'message': 'Queue position updated successfully.'
# })
# except Queue.DoesNotExist:
# return JsonResponse({
# 'success': False,
# 'message': 'Queue entry not found.'
# })
# except Exception as e:
# return JsonResponse({
# 'success': False,
# 'message': str(e)
# })
#
# return JsonResponse({'success': False, 'message': 'Invalid request.'})
#
#
# def generate_available_slots(date, existing_appointments):
# """Generate available appointment slots for a date"""
# slots = []
# start_time = datetime.strptime('09:00', '%H:%M').time()
# end_time = datetime.strptime('17:00', '%H:%M').time()
# slot_duration = 30 # minutes
#
# current_time = datetime.combine(date, start_time)
# end_datetime = datetime.combine(date, end_time)
#
# while current_time < end_datetime:
# slot_time = current_time.time()
#
# # Check if slot is available
# is_available = True
# for appointment in existing_appointments:
# appointment_start = datetime.combine(date, appointment.appointment_time)
# appointment_end = appointment_start + timedelta(minutes=appointment.duration)
#
# slot_start = current_time
# slot_end = current_time + timedelta(minutes=slot_duration)
#
# if slot_start < appointment_end and slot_end > appointment_start:
# is_available = False
# break
#
# if is_available:
# slots.append({
# 'time': slot_time.strftime('%H:%M'),
# 'available': True
# })
#
# current_time += timedelta(minutes=slot_duration)
#
# return slots
#
# from django import forms
# from django.core.exceptions import ValidationError
# from django.utils import timezone
# from django.contrib.auth.models import User
# from crispy_forms.helper import FormHelper
# from crispy_forms.layout import Layout, Fieldset, Submit, Row, Column, HTML, Div
# from crispy_forms.bootstrap import FormActions
# from datetime import datetime, timedelta
#
# from .models import Appointment, AppointmentConfirmation, Queue, TelemedicineSession
# from patients.models import Patient
# from core.models import Department
#
#
# class AppointmentSchedulingForm(forms.ModelForm):
# """
# Form for appointment scheduling
# """
# patient_search = forms.CharField(
# required=False,
# widget=forms.TextInput(attrs={
# 'class': 'form-control',
# 'placeholder': 'Search patient by name, ID, or phone...',
# 'data-toggle': 'patient-search'
# })
# )
# preferred_time_1 = forms.TimeField(
# required=False,
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
# )
# preferred_time_2 = forms.TimeField(
# required=False,
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
# )
# preferred_time_3 = forms.TimeField(
# required=False,
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
# )
# special_instructions = forms.CharField(
# required=False,
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
# )
# send_confirmation = forms.BooleanField(
# required=False,
# initial=True,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# send_reminder = forms.BooleanField(
# required=False,
# initial=True,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
#
# class Meta:
# model = Appointment
# fields = [
# 'patient', 'provider', 'department', 'appointment_type',
# 'appointment_date', 'appointment_time', 'duration',
# 'urgency', 'reason', 'notes', 'special_instructions',
# 'send_confirmation', 'send_reminder'
# ]
# widgets = {
# 'patient': forms.Select(attrs={'class': 'form-control'}),
# 'provider': forms.Select(attrs={'class': 'form-control'}),
# 'department': forms.Select(attrs={'class': 'form-control'}),
# 'appointment_type': forms.Select(attrs={'class': 'form-control'}),
# 'appointment_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
# 'appointment_time': forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'}),
# 'duration': forms.NumberInput(attrs={'class': 'form-control'}),
# 'urgency': forms.Select(attrs={'class': 'form-control'}),
# 'reason': forms.TextInput(attrs={'class': 'form-control'}),
# 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
# }
#
# def __init__(self, *args, **kwargs):
# tenant = kwargs.pop('tenant', None)
# super().__init__(*args, **kwargs)
#
# if tenant:
# self.fields['patient'].queryset = Patient.objects.filter(tenant=tenant)
# self.fields['provider'].queryset = User.objects.filter(
# tenant=tenant,
# groups__name__in=['Doctors', 'Nurses', 'Specialists']
# )
# self.fields['department'].queryset = Department.objects.filter(tenant=tenant)
#
# # Set minimum date to today
# self.fields['appointment_date'].widget.attrs['min'] = timezone.now().date().isoformat()
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'Patient Information',
# 'patient_search',
# 'patient'
# ),
# Fieldset(
# 'Appointment Details',
# Row(
# Column('provider', css_class='form-group col-md-6 mb-0'),
# Column('department', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# Row(
# Column('appointment_type', css_class='form-group col-md-6 mb-0'),
# Column('urgency', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# Row(
# Column('appointment_date', css_class='form-group col-md-4 mb-0'),
# Column('appointment_time', css_class='form-group col-md-4 mb-0'),
# Column('duration', css_class='form-group col-md-4 mb-0'),
# css_class='form-row'
# ),
# 'reason',
# 'notes',
# 'special_instructions'
# ),
# Fieldset(
# 'Preferred Times (Alternative Options)',
# Row(
# Column('preferred_time_1', css_class='form-group col-md-4 mb-0'),
# Column('preferred_time_2', css_class='form-group col-md-4 mb-0'),
# Column('preferred_time_3', css_class='form-group col-md-4 mb-0'),
# css_class='form-row'
# )
# ),
# Fieldset(
# 'Notifications',
# HTML('<div class="form-check">'),
# 'send_confirmation',
# HTML(
# '<label class="form-check-label" for="id_send_confirmation">Send appointment confirmation</label>'),
# HTML('</div>'),
# HTML('<div class="form-check">'),
# 'send_reminder',
# HTML('<label class="form-check-label" for="id_send_reminder">Send appointment reminder</label>'),
# HTML('</div>')
# ),
# FormActions(
# Submit('submit', 'Schedule Appointment', css_class='btn btn-primary'),
# HTML('<a href="{% url \'appointments:appointment_list\' %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
# def clean(self):
# cleaned_data = super().clean()
# appointment_date = cleaned_data.get('appointment_date')
# appointment_time = cleaned_data.get('appointment_time')
# provider = cleaned_data.get('provider')
#
# if appointment_date and appointment_time:
# appointment_datetime = datetime.combine(appointment_date, appointment_time)
#
# # Check if appointment is in the past
# if appointment_datetime < timezone.now():
# raise ValidationError('Appointment cannot be scheduled in the past.')
#
# # Check provider availability
# if provider and self.check_provider_conflict(provider, appointment_datetime):
# raise ValidationError('Provider is not available at the selected time.')
#
# return cleaned_data
#
# def check_provider_conflict(self, provider, appointment_datetime):
# """Check if provider has conflicting appointments"""
# duration = self.cleaned_data.get('duration', 30)
# end_time = appointment_datetime + timedelta(minutes=duration)
#
# conflicts = Appointment.objects.filter(
# provider=provider,
# appointment_date=appointment_datetime.date(),
# status__in=['scheduled', 'confirmed', 'in_progress']
# ).exclude(id=self.instance.id if self.instance else None)
#
# for conflict in conflicts:
# conflict_start = datetime.combine(conflict.appointment_date, conflict.appointment_time)
# conflict_end = conflict_start + timedelta(minutes=conflict.duration)
#
# if (appointment_datetime < conflict_end and end_time > conflict_start):
# return True
#
# return False
#
#
# class AppointmentConfirmationForm(forms.ModelForm):
# """
# Form for appointment confirmation
# """
# confirmation_method = forms.ChoiceField(
# choices=[
# ('phone', 'Phone Call'),
# ('email', 'Email'),
# ('sms', 'SMS'),
# ('in_person', 'In Person'),
# ('online', 'Online Portal')
# ],
# required=True,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
# contact_attempts = forms.IntegerField(
# initial=1,
# min_value=1,
# max_value=5,
# widget=forms.NumberInput(attrs={'class': 'form-control'})
# )
# confirmation_notes = forms.CharField(
# required=False,
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
# )
# reschedule_requested = forms.BooleanField(
# required=False,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# new_preferred_date = forms.DateField(
# required=False,
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
# )
# new_preferred_time = forms.TimeField(
# required=False,
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
# )
#
# class Meta:
# model = AppointmentConfirmation
# fields = [
# 'confirmation_method', 'contact_attempts', 'confirmation_notes',
# 'reschedule_requested', 'new_preferred_date', 'new_preferred_time'
# ]
#
# def __init__(self, *args, **kwargs):
# appointment = kwargs.pop('appointment', None)
# super().__init__(*args, **kwargs)
#
# self.appointment = appointment
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'Confirmation Details',
# Row(
# Column('confirmation_method', css_class='form-group col-md-6 mb-0'),
# Column('contact_attempts', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# 'confirmation_notes'
# ),
# Fieldset(
# 'Reschedule Request',
# HTML('<div class="form-check">'),
# 'reschedule_requested',
# HTML(
# '<label class="form-check-label" for="id_reschedule_requested">Patient requested reschedule</label>'),
# HTML('</div>'),
# Row(
# Column('new_preferred_date', css_class='form-group col-md-6 mb-0'),
# Column('new_preferred_time', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# )
# ),
# FormActions(
# Submit('submit', 'Confirm Appointment', css_class='btn btn-primary'),
# HTML(
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
# def clean(self):
# cleaned_data = super().clean()
# reschedule_requested = cleaned_data.get('reschedule_requested')
# new_preferred_date = cleaned_data.get('new_preferred_date')
# new_preferred_time = cleaned_data.get('new_preferred_time')
#
# if reschedule_requested:
# if not new_preferred_date:
# raise ValidationError('New preferred date is required when reschedule is requested.')
# if not new_preferred_time:
# raise ValidationError('New preferred time is required when reschedule is requested.')
#
# return cleaned_data
#
#
# class QueueManagementForm(forms.ModelForm):
# """
# Form for queue management
# """
# estimated_wait_time = forms.IntegerField(
# required=False,
# widget=forms.NumberInput(attrs={'class': 'form-control'})
# )
# priority_adjustment = forms.ChoiceField(
# choices=[
# ('none', 'No Change'),
# ('increase', 'Increase Priority'),
# ('decrease', 'Decrease Priority')
# ],
# required=False,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
# queue_notes = forms.CharField(
# required=False,
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
# )
# notify_patient = forms.BooleanField(
# required=False,
# initial=True,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
#
# class Meta:
# model = Queue
# fields = [
# 'queue_type', 'priority', 'estimated_wait_time',
# 'priority_adjustment', 'queue_notes', 'notify_patient'
# ]
# widgets = {
# 'queue_type': forms.Select(attrs={'class': 'form-control'}),
# 'priority': forms.Select(attrs={'class': 'form-control'})
# }
#
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'Queue Configuration',
# Row(
# Column('queue_type', css_class='form-group col-md-6 mb-0'),
# Column('priority', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# Row(
# Column('estimated_wait_time', css_class='form-group col-md-6 mb-0'),
# Column('priority_adjustment', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# 'queue_notes'
# ),
# Fieldset(
# 'Notifications',
# HTML('<div class="form-check">'),
# 'notify_patient',
# HTML('<label class="form-check-label" for="id_notify_patient">Notify patient of queue status</label>'),
# HTML('</div>')
# ),
# FormActions(
# Submit('submit', 'Update Queue', css_class='btn btn-primary'),
# HTML('<a href="{% url \'appointments:queue_list\' %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
#
# class TelemedicineSetupForm(forms.ModelForm):
# """
# Form for telemedicine session setup
# """
# platform = forms.ChoiceField(
# choices=[
# ('zoom', 'Zoom'),
# ('teams', 'Microsoft Teams'),
# ('webex', 'Cisco Webex'),
# ('meet', 'Google Meet'),
# ('custom', 'Custom Platform')
# ],
# required=True,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
# test_connection = forms.BooleanField(
# required=False,
# initial=True,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# send_instructions = forms.BooleanField(
# required=False,
# initial=True,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# backup_phone = forms.CharField(
# required=False,
# widget=forms.TextInput(attrs={'class': 'form-control'})
# )
# technical_support_contact = forms.CharField(
# required=False,
# widget=forms.TextInput(attrs={'class': 'form-control'})
# )
# session_recording = forms.BooleanField(
# required=False,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
#
# class Meta:
# model = TelemedicineSession
# fields = [
# 'platform', 'meeting_url', 'meeting_id', 'meeting_password',
# 'test_connection', 'send_instructions', 'backup_phone',
# 'technical_support_contact', 'session_recording'
# ]
# widgets = {
# 'meeting_url': forms.URLInput(attrs={'class': 'form-control'}),
# 'meeting_id': forms.TextInput(attrs={'class': 'form-control'}),
# 'meeting_password': forms.TextInput(attrs={'class': 'form-control'})
# }
#
# def __init__(self, *args, **kwargs):
# appointment = kwargs.pop('appointment', None)
# super().__init__(*args, **kwargs)
#
# self.appointment = appointment
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'Platform Configuration',
# 'platform',
# 'meeting_url',
# Row(
# Column('meeting_id', css_class='form-group col-md-6 mb-0'),
# Column('meeting_password', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# )
# ),
# Fieldset(
# 'Support Information',
# Row(
# Column('backup_phone', css_class='form-group col-md-6 mb-0'),
# Column('technical_support_contact', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# )
# ),
# Fieldset(
# 'Session Options',
# HTML('<div class="form-check">'),
# 'test_connection',
# HTML(
# '<label class="form-check-label" for="id_test_connection">Test connection before appointment</label>'),
# HTML('</div>'),
# HTML('<div class="form-check">'),
# 'send_instructions',
# HTML(
# '<label class="form-check-label" for="id_send_instructions">Send setup instructions to patient</label>'),
# HTML('</div>'),
# HTML('<div class="form-check">'),
# 'session_recording',
# HTML(
# '<label class="form-check-label" for="id_session_recording">Record session (with consent)</label>'),
# HTML('</div>')
# ),
# FormActions(
# Submit('submit', 'Setup Telemedicine', css_class='btn btn-primary'),
# HTML(
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
# def clean_meeting_url(self):
# meeting_url = self.cleaned_data.get('meeting_url')
# platform = self.cleaned_data.get('platform')
#
# if platform and meeting_url:
# # Validate URL format based on platform
# if platform == 'zoom' and 'zoom.us' not in meeting_url:
# raise ValidationError('Invalid Zoom meeting URL format.')
# elif platform == 'teams' and 'teams.microsoft.com' not in meeting_url:
# raise ValidationError('Invalid Microsoft Teams meeting URL format.')
# elif platform == 'webex' and 'webex.com' not in meeting_url:
# raise ValidationError('Invalid Webex meeting URL format.')
# elif platform == 'meet' and 'meet.google.com' not in meeting_url:
# raise ValidationError('Invalid Google Meet URL format.')
#
# return meeting_url
#
#
# class AppointmentRescheduleForm(forms.Form):
# """
# Form for appointment rescheduling
# """
# new_date = forms.DateField(
# required=True,
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
# )
# new_time = forms.TimeField(
# required=True,
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
# )
# new_provider = forms.ModelChoiceField(
# queryset=None,
# required=False,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
# reschedule_reason = forms.ChoiceField(
# choices=[
# ('patient_request', 'Patient Request'),
# ('provider_unavailable', 'Provider Unavailable'),
# ('emergency', 'Emergency'),
# ('equipment_issue', 'Equipment Issue'),
# ('other', 'Other')
# ],
# required=True,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
# notes = forms.CharField(
# required=False,
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
# )
# notify_patient = forms.BooleanField(
# required=False,
# initial=True,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
#
# def __init__(self, *args, **kwargs):
# tenant = kwargs.pop('tenant', None)
# appointment = kwargs.pop('appointment', None)
# super().__init__(*args, **kwargs)
#
# if tenant:
# self.fields['new_provider'].queryset = User.objects.filter(
# tenant=tenant,
# groups__name__in=['Doctors', 'Nurses', 'Specialists']
# )
#
# # Set minimum date to today
# self.fields['new_date'].widget.attrs['min'] = timezone.now().date().isoformat()
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'New Appointment Details',
# Row(
# Column('new_date', css_class='form-group col-md-6 mb-0'),
# Column('new_time', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# 'new_provider'
# ),
# Fieldset(
# 'Reschedule Information',
# 'reschedule_reason',
# 'notes'
# ),
# Fieldset(
# 'Notifications',
# HTML('<div class="form-check">'),
# 'notify_patient',
# HTML('<label class="form-check-label" for="id_notify_patient">Notify patient of reschedule</label>'),
# HTML('</div>')
# ),
# FormActions(
# Submit('submit', 'Reschedule Appointment', css_class='btn btn-primary'),
# HTML(
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
#
# class AppointmentCancellationForm(forms.Form):
# """
# Form for appointment cancellation
# """
# cancellation_reason = forms.ChoiceField(
# choices=[
# ('patient_request', 'Patient Request'),
# ('provider_unavailable', 'Provider Unavailable'),
# ('patient_no_show', 'Patient No Show'),
# ('emergency', 'Emergency'),
# ('equipment_failure', 'Equipment Failure'),
# ('other', 'Other')
# ],
# required=True,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
# cancellation_notes = forms.CharField(
# required=False,
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
# )
# offer_reschedule = forms.BooleanField(
# required=False,
# initial=True,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# notify_patient = forms.BooleanField(
# required=False,
# initial=True,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# refund_required = forms.BooleanField(
# required=False,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
#
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'Cancellation Details',
# 'cancellation_reason',
# 'cancellation_notes'
# ),
# Fieldset(
# 'Follow-up Actions',
# HTML('<div class="form-check">'),
# 'offer_reschedule',
# HTML(
# '<label class="form-check-label" for="id_offer_reschedule">Offer to reschedule appointment</label>'),
# HTML('</div>'),
# HTML('<div class="form-check">'),
# 'notify_patient',
# HTML('<label class="form-check-label" for="id_notify_patient">Notify patient of cancellation</label>'),
# HTML('</div>'),
# HTML('<div class="form-check">'),
# 'refund_required',
# HTML('<label class="form-check-label" for="id_refund_required">Refund required</label>'),
# HTML('</div>')
# ),
# FormActions(
# Submit('submit', 'Cancel Appointment', css_class='btn btn-danger'),
# HTML(
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Back</a>')
# )
# )
#
#
# class AppointmentCheckInForm(forms.Form):
# """
# Form for appointment check-in
# """
# arrival_time = forms.TimeField(
# initial=timezone.now().time(),
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
# )
# insurance_verified = forms.BooleanField(
# required=False,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# copay_collected = forms.BooleanField(
# required=False,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# forms_completed = forms.BooleanField(
# required=False,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# vitals_required = forms.BooleanField(
# required=False,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# special_needs = forms.CharField(
# required=False,
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 2})
# )
#
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'Check-in Information',
# 'arrival_time',
# 'special_needs'
# ),
# Fieldset(
# 'Pre-visit Tasks',
# HTML('<div class="form-check">'),
# 'insurance_verified',
# HTML('<label class="form-check-label" for="id_insurance_verified">Insurance verified</label>'),
# HTML('</div>'),
# HTML('<div class="form-check">'),
# 'copay_collected',
# HTML('<label class="form-check-label" for="id_copay_collected">Copay collected</label>'),
# HTML('</div>'),
# HTML('<div class="form-check">'),
# 'forms_completed',
# HTML('<label class="form-check-label" for="id_forms_completed">Forms completed</label>'),
# HTML('</div>'),
# HTML('<div class="form-check">'),
# 'vitals_required',
# HTML('<label class="form-check-label" for="id_vitals_required">Vitals required</label>'),
# HTML('</div>')
# ),
# FormActions(
# Submit('submit', 'Check In Patient', css_class='btn btn-primary'),
# HTML(
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
#
# class BulkAppointmentForm(forms.Form):
# """
# Form for bulk appointment operations
# """
# action = forms.ChoiceField(
# choices=[
# ('confirm', 'Confirm Appointments'),
# ('reschedule', 'Reschedule Appointments'),
# ('cancel', 'Cancel Appointments'),
# ('send_reminders', 'Send Reminders')
# ],
# required=True,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
# appointment_ids = forms.CharField(
# widget=forms.HiddenInput()
# )
# bulk_date = forms.DateField(
# required=False,
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
# )
# bulk_reason = forms.CharField(
# required=False,
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
# )
# notify_patients = forms.BooleanField(
# required=False,
# initial=True,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
#
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# 'appointment_ids',
# Fieldset(
# 'Bulk Operation',
# 'action',
# 'bulk_date',
# 'bulk_reason'
# ),
# Fieldset(
# 'Notifications',
# HTML('<div class="form-check">'),
# 'notify_patients',
# HTML('<label class="form-check-label" for="id_notify_patients">Notify patients</label>'),
# HTML('</div>')
# ),
# FormActions(
# Submit('submit', 'Execute Bulk Operation', css_class='btn btn-primary'),
# HTML('<a href="{% url \'appointments:appointment_list\' %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
# path('queue/', views.QueueManagementView.as_view(), name='queue_management'),
class QueueManagementView(LoginRequiredMixin, ListView):
# """
# Queue management view for appointments.
# """
# model = WaitingQueue
# template_name = 'appointments/queue_management.html'
# context_object_name = 'queues'
#
# def get_queryset(self):
# return WaitingQueue.objects.filter(
# tenant=self.request.user.tenant,
# is_active=True,
# )
#
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['queue'] = AppointmentRequest.objects.filter(
# tenant=self.request.user.tenant,
# status__in=['CONFIRMED', 'CHECKED_IN']
# ).order_by('scheduled_datetime')
# return context
{% extends 'base.html' %}
{% load static custom_filters %}
{% block title %}Bed Management - Hospital HMS{% endblock %}
{% block css %}
<style>
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--success-gradient: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
--warning-gradient: linear-gradient(135deg, #fcb045 0%, #fd1d1d 100%);
--info-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--danger-gradient: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
--maintenance-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
--cleaning-gradient: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
--shadow-light: 0 2px 10px rgba(0,0,0,0.1);
--shadow-medium: 0 4px 20px rgba(0,0,0,0.15);
--shadow-heavy: 0 8px 30px rgba(0,0,0,0.2);
--border-radius: 12px;
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.management-header {
background: var(--primary-gradient);
color: white;
padding: 2rem;
border-radius: var(--border-radius);
margin-bottom: 2rem;
box-shadow: var(--shadow-medium);
position: relative;
overflow: hidden;
}
.management-header::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
animation: rotate 20s linear infinite;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
border-radius: var(--border-radius);
padding: 1.5rem;
box-shadow: var(--shadow-light);
border: 1px solid rgba(0,0,0,0.05);
transition: var(--transition);
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: var(--primary-gradient);
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-heavy);
}
.stat-icon {
width: 60px;
height: 60px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
margin-bottom: 1rem;
position: relative;
}
.stat-icon.total { background: var(--primary-gradient); color: white; }
.stat-icon.available { background: var(--success-gradient); color: white; }
.stat-icon.occupied { background: var(--danger-gradient); color: white; }
.stat-icon.maintenance { background: var(--warning-gradient); color: white; }
.stat-number {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 0.5rem;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.stat-label {
font-size: 0.9rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #6c757d;
margin-bottom: 0.5rem;
}
.stat-trend {
font-size: 0.8rem;
display: flex;
align-items: center;
gap: 0.25rem;
}
.trend-up { color: #28a745; }
.trend-down { color: #dc3545; }
.main-content {
display: grid;
grid-template-columns: 1fr 320px;
gap: 2rem;
}
.filters-section {
background: white;
border-radius: var(--border-radius);
padding: 1.5rem;
box-shadow: var(--shadow-light);
margin-bottom: 2rem;
border: 1px solid rgba(0,0,0,0.05);
}
.filters-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 1rem;
}
.filter-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.filter-label {
font-size: 0.85rem;
font-weight: 600;
color: #495057;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.filter-input {
padding: 0.5rem;
border: 1px solid #dee2e6;
border-radius: 6px;
font-size: 0.9rem;
transition: var(--transition);
}
.filter-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
}
.search-input {
position: relative;
}
.search-input i {
position: absolute;
right: 0.75rem;
top: 50%;
transform: translateY(-50%);
color: #6c757d;
}
.ward-section {
background: white;
border-radius: var(--border-radius);
padding: 1.5rem;
margin-bottom: 2rem;
box-shadow: var(--shadow-light);
border: 1px solid rgba(0,0,0,0.05);
transition: var(--transition);
}
.ward-section:hover {
box-shadow: var(--shadow-medium);
}
.ward-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid #f0f0f0;
}
.ward-title {
font-size: 1.25rem;
font-weight: 600;
color: #495057;
margin: 0;
}
.ward-stats {
display: flex;
gap: 1rem;
align-items: center;
}
.ward-stat {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.85rem;
color: #6c757d;
}
.beds-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 1rem;
}
.bed-card {
background: white;
border-radius: 10px;
padding: 1rem;
box-shadow: var(--shadow-light);
border: 2px solid transparent;
transition: var(--transition);
cursor: pointer;
position: relative;
overflow: hidden;
}
.bed-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: var(--primary-gradient);
}
.bed-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-medium);
}
.bed-card.selected {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.bed-card.available {
background: linear-gradient(135deg, #f8fff8 0%, #e8f5e8 100%);
border-left: 4px solid #28a745;
}
.bed-card.available::before {
background: var(--success-gradient);
}
.bed-card.occupied {
background: linear-gradient(135deg, #fff8f8 0%, #feeaea 100%);
border-left: 4px solid #dc3545;
}
.bed-card.occupied::before {
background: var(--danger-gradient);
}
.bed-card.maintenance {
background: linear-gradient(135deg, #fffbf0 0%, #fef3c7 100%);
border-left: 4px solid #f59e0b;
}
.bed-card.maintenance::before {
background: var(--warning-gradient);
}
.bed-card.cleaning {
background: linear-gradient(135deg, #f0fdff 0%, #cffafe 100%);
border-left: 4px solid #06b6d4;
}
.bed-card.cleaning::before {
background: var(--cleaning-gradient);
}
.bed-card.blocked {
background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
border-left: 4px solid #ef4444;
}
.bed-card.out_of_order {
background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
border-left: 4px solid #6b7280;
}
.bed-icon-container {
text-align: center;
margin-bottom: 1rem;
}
.bed-icon {
width: 50px;
height: 50px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
margin-bottom: 0.5rem;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.bed-card.available .bed-icon { background: var(--success-gradient); color: white; }
.bed-card.occupied .bed-icon { background: var(--danger-gradient); color: white; }
.bed-card.maintenance .bed-icon { background: var(--warning-gradient); color: white; }
.bed-card.cleaning .bed-icon { background: var(--cleaning-gradient); color: white; }
.bed-card.blocked .bed-icon { background: var(--danger-gradient); color: white; }
.bed-card.out_of_order .bed-icon { background: #f3f4f6; color: #6b7280; }
.bed-number {
font-size: 1.1rem;
font-weight: 700;
color: #1f2937;
text-align: center;
margin-bottom: 0.25rem;
}
.bed-room {
font-size: 0.8rem;
color: #6b7280;
text-align: center;
margin-bottom: 0.5rem;
}
.bed-patient {
font-size: 0.85rem;
color: #374151;
text-align: center;
margin-bottom: 0.25rem;
font-weight: 500;
}
.bed-time {
font-size: 0.75rem;
color: #9ca3af;
text-align: center;
}
.bed-status {
position: absolute;
top: 0.5rem;
right: 0.5rem;
padding: 0.25rem 0.5rem;
border-radius: 12px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.bed-card.available .bed-status { background: #dcfce7; color: #166534; }
.bed-card.occupied .bed-status { background: #fee2e2; color: #991b1b; }
.bed-card.maintenance .bed-status { background: #fef3c7; color: #92400e; }
.bed-card.cleaning .bed-status { background: #cffafe; color: #0e7490; }
.bed-card.blocked .bed-status { background: #fee2e2; color: #991b1b; }
.bed-card.out_of_order .bed-status { background: #f3f4f6; color: #374151; }
.bed-actions {
position: absolute;
bottom: 0.5rem;
left: 0.5rem;
right: 0.5rem;
opacity: 0;
transition: var(--transition);
}
.bed-card:hover .bed-actions {
opacity: 1;
}
.action-buttons {
display: flex;
gap: 0.25rem;
justify-content: center;
}
.action-btn {
width: 28px;
height: 28px;
border-radius: 6px;
border: none;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
cursor: pointer;
transition: var(--transition);
}
.action-btn:hover {
transform: scale(1.1);
}
.action-btn.view { background: #e3f2fd; color: #1976d2; }
.action-btn.edit { background: #f3e5f5; color: #7b1fa2; }
.action-btn.assign { background: #e8f5e8; color: #388e3c; }
.action-btn.discharge { background: #fff3e0; color: #f57c00; }
.action-btn.more { background: #f5f5f5; color: #616161; }
.sidebar {
position: sticky;
top: 2rem;
}
.sidebar-panel {
background: white;
border-radius: var(--border-radius);
padding: 1.5rem;
box-shadow: var(--shadow-light);
border: 1px solid rgba(0,0,0,0.05);
margin-bottom: 1.5rem;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid #f0f0f0;
}
.panel-title {
font-size: 1rem;
font-weight: 600;
color: #1f2937;
margin: 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.panel-icon {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
}
.panel-icon.primary { background: var(--primary-gradient); color: white; }
.panel-icon.success { background: var(--success-gradient); color: white; }
.panel-icon.warning { background: var(--warning-gradient); color: white; }
.occupancy-display {
text-align: center;
margin-bottom: 1rem;
}
.occupancy-percentage {
font-size: 2rem;
font-weight: 700;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
}
.occupancy-label {
font-size: 0.9rem;
color: #6b7280;
margin-bottom: 1rem;
}
.occupancy-bar {
height: 12px;
background: #e5e7eb;
border-radius: 6px;
overflow: hidden;
margin-bottom: 0.5rem;
}
.occupancy-fill {
height: 100%;
background: var(--primary-gradient);
border-radius: 6px;
transition: width 0.5s ease;
}
.occupancy-stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
font-size: 0.8rem;
color: #6b7280;
}
.ward-breakdown {
space-y: 0.75rem;
}
.ward-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
border-radius: 6px;
background: #f9fafb;
margin-bottom: 0.5rem;
}
.ward-name {
font-weight: 500;
color: #374151;
}
.ward-occupancy {
display: flex;
align-items: center;
gap: 0.5rem;
}
.ward-bar {
width: 60px;
height: 6px;
background: #e5e7eb;
border-radius: 3px;
overflow: hidden;
}
.ward-fill {
height: 100%;
border-radius: 3px;
transition: width 0.3s ease;
}
.ward-percentage {
font-size: 0.75rem;
font-weight: 600;
color: #6b7280;
min-width: 35px;
text-align: right;
}
.quick-actions {
display: grid;
gap: 0.75rem;
}
.quick-btn {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: white;
color: #374151;
text-decoration: none;
transition: var(--transition);
font-weight: 500;
}
.quick-btn:hover {
border-color: #667eea;
background: #f8faff;
color: #667eea;
transform: translateX(2px);
}
.quick-btn i {
font-size: 1.1rem;
width: 20px;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255,255,255,0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@media (max-width: 1024px) {
.main-content {
grid-template-columns: 1fr;
}
.sidebar {
position: static;
}
}
@media (max-width: 768px) {
.stats-grid {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.beds-grid {
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
.filters-grid {
grid-template-columns: 1fr;
}
.ward-stats {
flex-direction: column;
gap: 0.5rem;
align-items: flex-start;
}
}
@media (max-width: 576px) {
.beds-grid {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
}
.management-header {
padding: 1.5rem;
}
.stat-card {
padding: 1rem;
}
}
</style>
{% endblock %}
{% block content %}
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div>
<h1 class="h2">
<i class="fas fa-bed me-2"></i>Bed<span class="fw-light">Management</span>
</h1>
<p class="text-muted">Real-time bed occupancy and management dashboard</p>
</div>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<a href="{% url 'inpatients:bed_create' %}" class="btn btn-primary btn-action">
<i class="fas fa-plus me-1"></i>Add Bed
</a>
<button class="btn btn-outline-info btn-action" onclick="viewFloorPlan()">
<i class="fas fa-map me-1"></i>Floor Plan
</button>
<button class="btn btn-outline-success btn-action" onclick="generateReport()">
<i class="fas fa-chart-bar me-1"></i>Reports
</button>
</div>
</div>
</div>
<!-- Management Header -->
<div class="management-header">
<div class="row">
<div class="col-lg-8">
<h3 class="mb-2">Hospital Bed Management System</h3>
<p class="mb-0 opacity-75">Monitor and manage bed occupancy across all wards in real-time</p>
</div>
<div class="col-lg-4 text-end">
<div class="d-flex align-items-center justify-content-end gap-3">
<div>
<div class="fw-bold fs-4">{{ total_beds }}</div>
<small class="text-white-50">Total Beds</small>
</div>
<div class="vr bg-white opacity-25"></div>
<div>
<div class="fw-bold fs-4 text-success">{{ available_beds }}</div>
<small class="text-white-50">Available</small>
</div>
<div class="vr bg-white opacity-25"></div>
<div>
<div class="fw-bold fs-4 text-danger">{{ occupied_beds }}</div>
<small class="text-white-50">Occupied</small>
</div>
</div>
</div>
</div>
</div>
<!-- Statistics Cards -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon total">
<i class="fas fa-bed"></i>
</div>
<div class="stat-number">{{ total_beds }}</div>
<div class="stat-label">Total Beds</div>
<div class="stat-trend">
<i class="fas fa-arrow-up trend-up"></i>
<span>100% Capacity</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon available">
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-number">{{ available_beds }}</div>
<div class="stat-label">Available Beds</div>
<div class="stat-trend">
<i class="fas fa-arrow-{% if available_beds > total_beds|div:2 %}up trend-up{% else %}down trend-down{% endif %}"></i>
<span>{{ available_beds|div:total_beds|mul:100|floatformat:0 }}% Free</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon occupied">
<i class="fas fa-users"></i>
</div>
<div class="stat-number">{{ occupied_beds }}</div>
<div class="stat-label">Occupied Beds</div>
<div class="stat-trend">
<i class="fas fa-arrow-{% if occupied_beds > total_beds|div:2 %}up trend-up{% else %}down trend-down{% endif %}"></i>
<span>{{ occupied_beds|div:total_beds|mul:100|floatformat:0 }}% Utilized</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon maintenance">
<i class="fas fa-tools"></i>
</div>
<div class="stat-number">{{ maintenance_beds }}</div>
<div class="stat-label">Maintenance</div>
<div class="stat-trend">
<i class="fas fa-arrow-{% if maintenance_beds > 0 %}up trend-down{% else %}down trend-up{% endif %}"></i>
<span>{{ maintenance_beds|div:total_beds|mul:100|floatformat:0 }}% Down</span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Beds Section -->
<div class="beds-section">
<!-- Filters -->
<div class="filters-section">
<div class="filters-grid">
<div class="filter-group">
<label class="filter-label">Ward</label>
<select class="filter-input" id="wardFilter">
<option value="">All Wards</option>
{% for ward in wards %}
<option value="{{ ward.id }}">{{ ward.name }}</option>
{% endfor %}
</select>
</div>
<div class="filter-group">
<label class="filter-label">Status</label>
<select class="filter-input" id="statusFilter">
<option value="">All Statuses</option>
<option value="AVAILABLE">Available</option>
<option value="OCCUPIED">Occupied</option>
<option value="MAINTENANCE">Maintenance</option>
<option value="BLOCKED">Blocked</option>
<option value="CLEANING">Cleaning</option>
<option value="OUT_OF_ORDER">Out of Order</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">Bed Type</label>
<select class="filter-input" id="bedTypeFilter">
<option value="">All Types</option>
<option value="STANDARD">Standard</option>
<option value="ICU">ICU</option>
<option value="ISOLATION">Isolation</option>
<option value="PEDIATRIC">Pediatric</option>
<option value="MATERNITY">Maternity</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">Search</label>
<div class="search-input">
<input type="text" class="filter-input" placeholder="Search beds..." id="bedSearch" />
<i class="fas fa-search"></i>
</div>
</div>
</div>
</div>
<!-- Ward Sections -->
{% for ward in wards %}
<div class="ward-section" data-ward="{{ ward.id }}">
<div class="ward-header">
<h5 class="ward-title">
<i class="fas fa-hospital me-2 text-primary"></i>{{ ward.name }}
</h5>
<div class="ward-stats">
<div class="ward-stat">
<i class="fas fa-bed me-1"></i>
<span>{{ ward.beds.count }} beds</span>
</div>
<div class="ward-stat">
<i class="fas fa-chart-pie me-1"></i>
<span>{{ ward.occupancy_rate|floatformat:0 }}% occupied</span>
</div>
</div>
</div>
<div class="beds-grid">
{% for bed in ward.beds.all %}
<div class="bed-card {{ bed.status|lower }}"
data-bed-id="{{ bed.id }}"
data-status="{{ bed.status|lower }}"
data-type="{{ bed.bed_type }}"
onclick="selectBed('{{ bed.id }}')">
<div class="bed-icon-container">
<div class="bed-icon">
<i class="fas fa-bed"></i>
</div>
<div class="bed-number">{{ bed.bed_number }}</div>
<div class="bed-room">Room {{ bed.room_number }}</div>
</div>
{% if bed.current_admission %}
<div class="bed-patient">{{ bed.current_admission.patient.get_full_name }}</div>
<div class="bed-time">{{ bed.occupied_since|timesince }} ago</div>
{% endif %}
<div class="bed-status">
{{ bed.get_status_display }}
</div>
<div class="bed-actions">
<div class="action-buttons">
<a href="{% url 'inpatients:bed_detail' bed.id %}" class="action-btn view" title="View Details">
<i class="fas fa-eye"></i>
</a>
<a href="{% url 'inpatients:bed_update' bed.id %}" class="action-btn edit" title="Edit Bed">
<i class="fas fa-edit"></i>
</a>
{% if bed.status == 'AVAILABLE' %}
<a href="{% url 'inpatients:admission_create' %}" class="action-btn assign" title="Assign Patient">
<i class="fas fa-user-plus"></i>
</a>
{% elif bed.current_admission %}
<a href="{% url 'inpatients:discharge_patient' bed.current_admission.id %}" class="action-btn discharge" title="Discharge">
<i class="fas fa-sign-out-alt"></i>
</a>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
<!-- Sidebar -->
<div class="sidebar">
<!-- Occupancy Overview -->
<div class="sidebar-panel">
<div class="panel-header">
<h6 class="panel-title">
<div class="panel-icon primary">
<i class="fas fa-chart-pie"></i>
</div>
Occupancy Overview
</h6>
</div>
<div class="occupancy-display">
<div class="occupancy-percentage">{{ occupancy_rate|floatformat:0 }}%</div>
<div class="occupancy-label">Bed Utilization Rate</div>
<div class="occupancy-bar">
<div class="occupancy-fill" style="width: {{ occupancy_rate }}%"></div>
</div>
<div class="occupancy-stats">
<div>Occupied: {{ occupied_beds }}</div>
<div>Available: {{ available_beds }}</div>
</div>
</div>
</div>
<!-- Ward Breakdown -->
<div class="sidebar-panel">
<div class="panel-header">
<h6 class="panel-title">
<div class="panel-icon success">
<i class="fas fa-building"></i>
</div>
Ward Breakdown
</h6>
</div>
<div class="ward-breakdown">
{% for ward in wards %}
<div class="ward-item">
<div class="ward-name">{{ ward.name }}</div>
<div class="ward-occupancy">
<div class="ward-bar">
<div class="ward-fill" style="width: {{ ward.occupancy_rate }}%; background: {% if ward.occupancy_rate >= 90 %}#dc3545{% elif ward.occupancy_rate >= 75 %}#fd7e14{% elif ward.occupancy_rate >= 50 %}#0dcaf0{% else %}#198754{% endif %}"></div>
</div>
<div class="ward-percentage">{{ ward.occupancy_rate|floatformat:0 }}%</div>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Quick Actions -->
<div class="sidebar-panel">
<div class="panel-header">
<h6 class="panel-title">
<span class="panel-icon warning">
<i class="fas fa-bolt"></i>
</span>
Quick Actions
</h6>
</div>
<div class="quick-actions">
<a href="#" class="quick-btn" onclick="bulkUpdate()">
<i class="fas fa-edit"></i>
<span>Bulk Update</span>
</a>
<a href="#" class="quick-btn" onclick="exportData()">
<i class="fas fa-download"></i>
<span>Export Data</span>
</a>
<a href="#" class="quick-btn" onclick="scheduleMaintenance()">
<i class="fas fa-tools"></i>
<span>Schedule Maintenance</span>
</a>
<a href="#" class="quick-btn" onclick="viewAlerts()">
<i class="fas fa-bell"></i>
<span>View Alerts</span>
</a>
</div>
</div>
</div>
</div>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay" style="display: none;">
<div class="loading-spinner"></div>
</div>
<!-- Bed Details Modal -->
<div class="modal fade" id="bedDetailsModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Bed Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="bedDetailsContent">
<!-- Content loaded dynamically -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="editCurrentBed()">Edit Bed</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script>
$(document).ready(function() {
setupEventHandlers();
setupFilters();
updateBedStatuses();
// Auto-refresh every 30 seconds
setInterval(updateBedStatuses, 30000);
});
function setupEventHandlers() {
// Filter handlers
$('#wardFilter, #statusFilter, #bedTypeFilter').on('change', function() {
filterBeds();
});
$('#bedSearch').on('input', function() {
filterBeds();
});
}
function setupFilters() {
$('.bed-card').each(function() {
$(this).data('original-display', $(this).css('display'));
});
}
function selectBed(bedId) {
// Remove previous selection
$('.bed-card').removeClass('selected');
// Add selection
$(`.bed-card[data-bed-id="${bedId}"]`).addClass('selected');
// Store selected bed
window.selectedBedId = bedId;
}
function editBed(bedId) {
window.location.href = `{% url 'inpatients:bed_update' 0 %}`.replace('0', bedId);
}
function editCurrentBed() {
if (window.selectedBedId) {
editBed(window.selectedBedId);
}
}
function viewBedDetails(bedId) {
$('#loadingOverlay').show();
$.get('', {bed_id: bedId}, function(data) {
$('#loadingOverlay').hide();
if (data.success) {
$('#bedDetailsContent').html(data.html);
$('#bedDetailsModal').modal('show');
window.selectedBedId = bedId;
} else {
showNotification('Failed to load bed details', 'error');
}
}).fail(function() {
$('#loadingOverlay').hide();
showNotification('Network error occurred', 'error');
});
}
function filterBeds() {
const wardFilter = $('#wardFilter').val();
const statusFilter = $('#statusFilter').val();
const typeFilter = $('#bedTypeFilter').val();
const searchTerm = $('#bedSearch').val().toLowerCase();
$('.bed-card').each(function() {
const card = $(this);
const bedId = card.data('bed-id');
const status = card.data('status');
const type = card.data('type');
const text = card.text().toLowerCase();
const wardId = card.closest('.ward-section').data('ward');
const matchesWard = !wardFilter || wardId == wardFilter;
const matchesStatus = !statusFilter || status === statusFilter.toLowerCase();
const matchesType = !typeFilter || type === typeFilter;
const matchesSearch = !searchTerm || text.includes(searchTerm);
if (matchesWard && matchesStatus && matchesType && matchesSearch) {
card.show();
} else {
card.hide();
}
});
// Hide empty ward sections
$('.ward-section').each(function() {
const section = $(this);
const visibleBeds = section.find('.bed-card:visible').length;
if (visibleBeds === 0) {
section.hide();
} else {
section.show();
}
});
}
function updateBedStatuses() {
$.get('', {action: 'update_status'}, function(data) {
if (data.success && data.beds) {
data.beds.forEach(function(bed) {
const bedCard = $(`.bed-card[data-bed-id="${bed.id}"]`);
if (bedCard.length) {
// Update status classes
bedCard.removeClass('available occupied maintenance cleaning blocked out_of_order')
.addClass(bed.status.toLowerCase());
// Update status badge
bedCard.find('.bed-status').text(bed.status_display);
// Update patient info if changed
if (bed.patient_name) {
bedCard.find('.bed-patient').text(bed.patient_name);
bedCard.find('.bed-time').text(bed.occupied_duration);
} else {
bedCard.find('.bed-patient').text('');
bedCard.find('.bed-time').text('');
}
}
});
// Update statistics
if (data.stats) {
$('.stat-number').eq(0).text(data.stats.total_beds);
$('.stat-number').eq(1).text(data.stats.available_beds);
$('.stat-number').eq(2).text(data.stats.occupied_beds);
$('.stat-number').eq(3).text(data.stats.maintenance_beds);
// Update occupancy rate
const occupancyRate = data.stats.occupancy_rate;
$('.occupancy-percentage').text(occupancyRate + '%');
$('.occupancy-fill').css('width', occupancyRate + '%');
$('.occupancy-stats').html(`
<div>Occupied: ${data.stats.occupied_beds}</div>
<div>Available: ${data.stats.available_beds}</div>
`);
}
}
}).fail(function() {
console.log('Failed to update bed statuses');
});
}
function showNotification(message, type = 'info') {
// Simple notification - you can replace with a proper notification library
const colors = {
success: '#28a745',
error: '#dc3545',
warning: '#ffc107',
info: '#17a2b8'
};
const notification = $(`
<div style="
position: fixed;
top: 20px;
right: 20px;
background: ${colors[type]};
color: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10000;
font-weight: 500;
">
${message}
</div>
`);
$('body').append(notification);
setTimeout(() => {
notification.fadeOut(() => notification.remove());
}, 3000);
}
// Placeholder functions for buttons
function viewFloorPlan() {
showNotification('Floor plan view coming soon!', 'info');
}
function generateReport() {
showNotification('Report generation started...', 'success');
// Simulate report generation
setTimeout(() => {
showNotification('Report generated successfully!', 'success');
}, 2000);
}
function bulkUpdate() {
showNotification('Bulk update feature coming soon!', 'info');
}
function exportData() {
showNotification('Data export started...', 'success');
// Simulate export
setTimeout(() => {
showNotification('Data exported successfully!', 'success');
}, 1500);
}
function scheduleMaintenance() {
showNotification('Maintenance scheduling opened', 'info');
}
function viewAlerts() {
showNotification('No active alerts at this time', 'success');
}
</script>
{% endblock %}