11367 lines
384 KiB
Plaintext
11367 lines
384 KiB
Plaintext
# 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 %}
|