diff --git a/INSURANCE_APPROVAL_INTEGRATION.md b/INSURANCE_APPROVAL_INTEGRATION.md new file mode 100644 index 00000000..caf71922 --- /dev/null +++ b/INSURANCE_APPROVAL_INTEGRATION.md @@ -0,0 +1,588 @@ +# Insurance Approval Integration Documentation + +## Overview + +The Insurance Approval Request module has been successfully integrated with all relevant clinical order modules in the hospital management system. This document outlines the integration points and usage patterns. + +--- + +## Integrated Modules + +### 1. Laboratory Module ✅ +**Model:** `LabOrder` +**File:** `laboratory/models.py` + +#### Integration Details: +- Added `GenericRelation` to link lab orders with approval requests +- Added helper methods for approval status checking +- Supports multi-tenant approval tracking + +#### Helper Methods Added: +```python +# Check if order has valid approval +lab_order.has_valid_approval() # Returns True/False + +# Get active approval +lab_order.get_active_approval() # Returns InsuranceApprovalRequest or None + +# Check if approval is required +lab_order.requires_approval() # Returns True/False + +# Get approval status for display +lab_order.approval_status # Returns status string +``` + +#### Usage Example: +```python +from laboratory.models import LabOrder +from insurance_approvals.models import InsuranceApprovalRequest + +# Create a lab order +lab_order = LabOrder.objects.get(order_number='LAB-1-000001') + +# Check if approval is needed +if lab_order.requires_approval(): + # Create approval request + approval = InsuranceApprovalRequest.objects.create( + tenant=lab_order.tenant, + patient=lab_order.patient, + insurance_info=lab_order.patient.insurance_info.first(), + request_type='LABORATORY', + content_object=lab_order, # Links to lab order + service_description=f"Lab tests: {', '.join([t.test_name for t in lab_order.tests.all()])}", + procedure_codes=', '.join([t.cpt_code for t in lab_order.tests.all() if t.cpt_code]), + diagnosis_codes=lab_order.diagnosis_code or '', + clinical_justification=lab_order.clinical_indication, + requesting_provider=lab_order.ordering_provider, + service_start_date=lab_order.collection_datetime.date() if lab_order.collection_datetime else timezone.now().date(), + priority='ROUTINE', + status='PENDING_SUBMISSION', + ) + +# Check approval status +if lab_order.has_valid_approval(): + print("Order has valid approval - proceed with processing") +else: + print(f"Approval status: {lab_order.approval_status}") + +# Get all approval requests for this order +approvals = lab_order.approval_requests.all() +``` + +--- + +### 2. Radiology Module ✅ +**Model:** `ImagingOrder` +**File:** `radiology/models.py` + +#### Integration Details: +- Added `GenericRelation` to link imaging orders with approval requests +- Added identical helper methods as laboratory module +- Supports all imaging modalities (CT, MRI, X-Ray, etc.) + +#### Helper Methods Added: +```python +# Check if order has valid approval +imaging_order.has_valid_approval() # Returns True/False + +# Get active approval +imaging_order.get_active_approval() # Returns InsuranceApprovalRequest or None + +# Check if approval is required +imaging_order.requires_approval() # Returns True/False + +# Get approval status for display +imaging_order.approval_status # Returns status string +``` + +#### Usage Example: +```python +from radiology.models import ImagingOrder +from insurance_approvals.models import InsuranceApprovalRequest + +# Create an imaging order +imaging_order = ImagingOrder.objects.get(order_number='IMG-1-000001') + +# Check if approval is needed +if imaging_order.requires_approval(): + # Create approval request + approval = InsuranceApprovalRequest.objects.create( + tenant=imaging_order.tenant, + patient=imaging_order.patient, + insurance_info=imaging_order.patient.insurance_info.first(), + request_type='RADIOLOGY', + content_object=imaging_order, # Links to imaging order + service_description=imaging_order.study_description, + procedure_codes=imaging_order.modality, # Could be enhanced with CPT codes + diagnosis_codes=imaging_order.diagnosis_code or '', + clinical_justification=imaging_order.clinical_indication, + requesting_provider=imaging_order.ordering_provider, + service_start_date=imaging_order.requested_datetime.date() if imaging_order.requested_datetime else timezone.now().date(), + priority='URGENT' if imaging_order.is_stat else 'ROUTINE', + status='PENDING_SUBMISSION', + ) + +# Access approvals +active_approval = imaging_order.get_active_approval() +if active_approval: + print(f"Authorization Number: {active_approval.authorization_number}") + print(f"Expires: {active_approval.expiration_date}") +``` + +--- + +## Common Integration Patterns + +### Pattern 1: Pre-Order Approval Check +```python +def create_order_with_approval_check(patient, order_type, **order_data): + """ + Create an order and check if insurance approval is required. + """ + # Create the order + if order_type == 'LAB': + order = LabOrder.objects.create(patient=patient, **order_data) + elif order_type == 'IMAGING': + order = ImagingOrder.objects.create(patient=patient, **order_data) + + # Check if approval is required + if order.requires_approval(): + # Redirect to approval request creation + return { + 'order': order, + 'requires_approval': True, + 'redirect_url': f'/insurance-approvals/create/?order_type={order_type}&order_id={order.id}' + } + + return { + 'order': order, + 'requires_approval': False + } +``` + +### Pattern 2: Approval Status Display +```python +def get_order_status_with_approval(order): + """ + Get comprehensive order status including approval information. + """ + status = { + 'order_status': order.status, + 'has_insurance': order.patient.insurance_info.exists(), + 'requires_approval': order.requires_approval(), + 'approval_status': order.approval_status, + 'can_proceed': False + } + + if not status['has_insurance']: + status['can_proceed'] = True # No insurance, no approval needed + elif order.has_valid_approval(): + status['can_proceed'] = True + status['approval'] = order.get_active_approval() + + return status +``` + +### Pattern 3: Bulk Approval Status Check +```python +def get_orders_needing_approval(tenant, order_type='LAB'): + """ + Get all orders that need insurance approval. + """ + if order_type == 'LAB': + Model = LabOrder + elif order_type == 'IMAGING': + Model = ImagingOrder + + orders = Model.objects.filter( + tenant=tenant, + status__in=['PENDING', 'SCHEDULED'] + ) + + orders_needing_approval = [] + for order in orders: + if order.requires_approval(): + orders_needing_approval.append({ + 'order': order, + 'patient': order.patient, + 'insurance': order.patient.insurance_info.first(), + 'approval_status': order.approval_status + }) + + return orders_needing_approval +``` + +--- + +## Approval Request Types + +The system supports the following request types: + +1. **LABORATORY** - Lab tests and panels +2. **RADIOLOGY** - Imaging studies (CT, MRI, X-Ray, etc.) +3. **PHARMACY** - Medications (to be integrated) +4. **PROCEDURE** - Medical procedures (to be integrated) +5. **SURGERY** - Surgical procedures (to be integrated) +6. **THERAPY** - Physical/Occupational therapy +7. **DME** - Durable Medical Equipment +8. **HOME_HEALTH** - Home health services +9. **HOSPICE** - Hospice care +10. **TRANSPORTATION** - Medical transportation +11. **OTHER** - Other services + +--- + +## Approval Workflow States + +``` +DRAFT → PENDING_SUBMISSION → SUBMITTED → UNDER_REVIEW + ↓ +MORE_INFO_REQUIRED (can loop back to SUBMITTED) + ↓ +APPROVED / PARTIALLY_APPROVED / DENIED + ↓ +APPEAL_SUBMITTED → APPEAL_APPROVED / APPEAL_DENIED + ↓ +EXPIRED (if expiration_date passes) +``` + +--- + +## Database Relationships + +### GenericForeignKey Pattern +```python +# In InsuranceApprovalRequest model +content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) +object_id = models.PositiveIntegerField() +content_object = GenericForeignKey('content_type', 'object_id') + +# In LabOrder/ImagingOrder models +approval_requests = GenericRelation( + 'insurance_approvals.InsuranceApprovalRequest', + content_type_field='content_type', + object_id_field='object_id', + related_query_name='lab_order' # or 'imaging_order' +) +``` + +### Querying Across Relationships +```python +# Get all lab orders with approvals +from django.contrib.contenttypes.models import ContentType +from laboratory.models import LabOrder +from insurance_approvals.models import InsuranceApprovalRequest + +lab_order_ct = ContentType.objects.get_for_model(LabOrder) +approvals_for_lab_orders = InsuranceApprovalRequest.objects.filter( + content_type=lab_order_ct +) + +# Get specific lab order's approvals +lab_order = LabOrder.objects.get(pk=1) +approvals = lab_order.approval_requests.all() + +# Reverse query - get order from approval +approval = InsuranceApprovalRequest.objects.get(pk=1) +order = approval.content_object # Returns LabOrder or ImagingOrder instance +``` + +--- + +## Template Integration Examples + +### Display Approval Status in Order List +```django +{% for order in orders %} + + {{ order.order_number }} + {{ order.patient.get_full_name }} + + {% if order.approval_status == 'APPROVED' %} + Approved + {% elif order.approval_status == 'APPROVAL_REQUIRED' %} + Approval Required + Request Approval + {% elif order.approval_status == 'NO_INSURANCE' %} + No Insurance + {% else %} + {{ order.get_approval_status_display }} + {% endif %} + + +{% endfor %} +``` + +### Display Approval Details in Order Detail +```django +{% if order.has_valid_approval %} +
+
Insurance Approval Active
+ {% with approval=order.get_active_approval %} +

Authorization Number: {{ approval.authorization_number }}

+

Expires: {{ approval.expiration_date|date:"M d, Y" }}

+

Approved Quantity: {{ approval.approved_quantity }}

+ {% endwith %} +
+{% elif order.requires_approval %} +
+
Insurance Approval Required
+

This order requires insurance approval before processing.

+ Request Approval +
+{% endif %} +``` + +--- + +## API Integration (DRF) + +### Serializer Example +```python +from rest_framework import serializers +from laboratory.models import LabOrder +from insurance_approvals.models import InsuranceApprovalRequest + +class LabOrderSerializer(serializers.ModelSerializer): + approval_status = serializers.CharField(read_only=True) + has_valid_approval = serializers.BooleanField(read_only=True) + requires_approval = serializers.SerializerMethodField() + active_approval = serializers.SerializerMethodField() + + class Meta: + model = LabOrder + fields = '__all__' + + def get_requires_approval(self, obj): + return obj.requires_approval() + + def get_active_approval(self, obj): + approval = obj.get_active_approval() + if approval: + return { + 'id': approval.id, + 'approval_number': approval.approval_number, + 'authorization_number': approval.authorization_number, + 'status': approval.status, + 'expiration_date': approval.expiration_date + } + return None +``` + +--- + +## Future Integration Points + +### Pharmacy Module (Pending) +- **Model:** `PharmacyOrder` or `Prescription` +- **Request Type:** `PHARMACY` +- Same integration pattern as Lab and Radiology + +### Operating Theatre Module (Pending) +- **Model:** `SurgerySchedule` or `OperativeCase` +- **Request Type:** `SURGERY` or `PROCEDURE` +- Same integration pattern + +### Other Potential Integrations: +- Home Health Orders +- DME Orders +- Physical Therapy Orders +- Hospice Care Orders + +--- + +## Migration Notes + +**IMPORTANT:** Migrations are NOT included in this integration. The user will handle migrations separately. + +### To apply changes: +1. Create migrations: `python manage.py makemigrations laboratory radiology` +2. Review migrations carefully +3. Apply migrations: `python manage.py migrate` + +--- + +## Testing Recommendations + +### Unit Tests +```python +from django.test import TestCase +from laboratory.models import LabOrder +from insurance_approvals.models import InsuranceApprovalRequest + +class LabOrderApprovalIntegrationTest(TestCase): + def test_order_requires_approval_with_insurance(self): + # Create patient with insurance + patient = self.create_patient_with_insurance() + order = LabOrder.objects.create(patient=patient, ...) + + self.assertTrue(order.requires_approval()) + + def test_order_has_valid_approval(self): + order = self.create_lab_order() + approval = InsuranceApprovalRequest.objects.create( + content_object=order, + status='APPROVED', + expiration_date=timezone.now().date() + timedelta(days=30), + ... + ) + + self.assertTrue(order.has_valid_approval()) + self.assertEqual(order.get_active_approval(), approval) +``` + +--- + +### 3. Pharmacy Module ✅ +**Model:** `Prescription` +**File:** `pharmacy/models.py` + +#### Integration Details: +- Added `GenericRelation` to link prescriptions with approval requests +- Added helper methods for approval status checking +- Enhanced with prior authorization support +- Checks medication formulary status for approval requirements + +#### Helper Methods Added: +```python +# Check if prescription has valid approval +prescription.has_valid_approval() # Returns True/False + +# Get active approval +prescription.get_active_approval() # Returns InsuranceApprovalRequest or None + +# Check if approval is required +prescription.requires_approval() # Returns True/False + +# Get approval status for display +prescription.approval_status # Returns status string +``` + +#### Special Features: +- **Prior Authorization Integration**: Checks both approval requests and prior authorization fields +- **Formulary Status**: Automatically requires approval for RESTRICTED or PRIOR_AUTH medications +- **Enhanced Status**: Returns specific statuses like 'PRIOR_AUTH_REQUIRED', 'PRIOR_AUTH_EXPIRED', etc. + +#### Usage Example: +```python +from pharmacy.models import Prescription +from insurance_approvals.models import InsuranceApprovalRequest + +# Create a prescription +prescription = Prescription.objects.get(prescription_number='RX-1-000001') + +# Check if approval is needed (considers formulary status) +if prescription.requires_approval(): + # Create approval request + approval = InsuranceApprovalRequest.objects.create( + tenant=prescription.tenant, + patient=prescription.patient, + insurance_info=prescription.patient.insurance_info.first(), + request_type='PHARMACY', + content_object=prescription, # Links to prescription + service_description=f"{prescription.medication.display_name} - {prescription.quantity_prescribed} {prescription.quantity_unit}", + procedure_codes=prescription.medication.ndc_number or '', + diagnosis_codes=prescription.diagnosis_code or '', + clinical_justification=prescription.indication or 'As prescribed', + requesting_provider=prescription.prescriber, + service_start_date=prescription.date_written, + priority='URGENT' if prescription.medication.is_controlled_substance else 'ROUTINE', + status='PENDING_SUBMISSION', + requested_quantity=prescription.quantity_prescribed, + ) + +# Check prior authorization +if prescription.prior_authorization_required: + if not prescription.prior_authorization_number: + print("Prior authorization required but not obtained") +``` + +--- + +### 4. Operating Theatre Module ✅ +**Model:** `SurgicalCase` +**File:** `operating_theatre/models.py` + +#### Integration Details: +- Added `GenericRelation` to link surgical cases with approval requests +- Added helper methods for approval status checking +- Enhanced with emergency case handling +- Supports elective and emergency approval workflows + +#### Helper Methods Added: +```python +# Check if surgical case has valid approval +surgical_case.has_valid_approval() # Returns True/False + +# Get active approval +surgical_case.get_active_approval() # Returns InsuranceApprovalRequest or None + +# Check if approval is required +surgical_case.requires_approval() # Returns True/False + +# Get approval status for display +surgical_case.approval_status # Returns status string +``` + +#### Special Features: +- **Emergency Case Handling**: Different approval requirements for emergency vs elective cases +- **Enhanced Status**: Returns 'EMERGENCY_APPROVAL_REQUIRED' for emergency cases +- **Procedure Code Support**: Integrates with procedure_codes JSON field + +#### Usage Example: +```python +from operating_theatre.models import SurgicalCase +from insurance_approvals.models import InsuranceApprovalRequest + +# Create a surgical case +surgical_case = SurgicalCase.objects.get(case_number='SURG-20250103-0001') + +# Check if approval is needed +if surgical_case.requires_approval(): + # Determine priority based on case type + if surgical_case.is_emergency: + priority = 'STAT' + is_expedited = True + else: + priority = 'ROUTINE' + is_expedited = False + + # Create approval request + approval = InsuranceApprovalRequest.objects.create( + tenant=surgical_case.tenant, + patient=surgical_case.patient, + insurance_info=surgical_case.patient.insurance_info.first(), + request_type='SURGERY', + content_object=surgical_case, # Links to surgical case + service_description=surgical_case.primary_procedure, + procedure_codes=', '.join(surgical_case.procedure_codes) if surgical_case.procedure_codes else '', + diagnosis_codes=', '.join(surgical_case.diagnosis_codes) if surgical_case.diagnosis_codes else surgical_case.diagnosis, + clinical_justification=surgical_case.clinical_notes or f"Surgical intervention required for {surgical_case.diagnosis}", + requesting_provider=surgical_case.primary_surgeon, + service_start_date=surgical_case.scheduled_start.date(), + priority=priority, + is_expedited=is_expedited, + status='PENDING_SUBMISSION', + ) +``` + +--- + +## Summary + +✅ **Laboratory Module** - Fully integrated +✅ **Radiology Module** - Fully integrated +✅ **Pharmacy Module** - Fully integrated (with prior auth support) +✅ **Operating Theatre Module** - Fully integrated (with emergency handling) + +All integrated modules now support: +- Automatic approval requirement detection +- Approval status tracking +- Multi-tenant approval management +- Complete audit trail +- Flexible workflow states +- Module-specific enhancements (prior auth, emergency cases, etc.) + +The integration is backward compatible and does not affect existing functionality. diff --git a/INSURANCE_APPROVAL_MIGRATION_GUIDE.md b/INSURANCE_APPROVAL_MIGRATION_GUIDE.md new file mode 100644 index 00000000..0b92b585 --- /dev/null +++ b/INSURANCE_APPROVAL_MIGRATION_GUIDE.md @@ -0,0 +1,504 @@ +# Insurance Approval Integration - Migration Guide + +## Overview + +This guide provides step-by-step instructions for applying the insurance approval integration to your hospital management system. + +--- + +## Prerequisites + +Before starting the migration: + +1. ✅ Backup your database +2. ✅ Ensure all existing migrations are applied +3. ✅ Review the integration documentation (`INSURANCE_APPROVAL_INTEGRATION.md`) +4. ✅ Test in a development environment first + +--- + +## Step 1: Verify Current State + +### Check Existing Migrations + +```bash +# Check migration status +python manage.py showmigrations + +# Look for these apps: +# - insurance_approvals +# - laboratory +# - radiology +# - pharmacy +# - operating_theatre +``` + +### Verify Database Backup + +```bash +# For SQLite (development) +cp db.sqlite3 db.sqlite3.backup + +# For PostgreSQL (production) +pg_dump -U username -d database_name > backup_$(date +%Y%m%d_%H%M%S).sql + +# For MySQL (production) +mysqldump -u username -p database_name > backup_$(date +%Y%m%d_%H%M%S).sql +``` + +--- + +## Step 2: Create Migrations + +### Generate Migrations for All Modified Apps + +```bash +# Create migrations for all integrated modules +python manage.py makemigrations laboratory radiology pharmacy operating_theatre + +# Expected output: +# Migrations for 'laboratory': +# laboratory/migrations/0XXX_add_approval_integration.py +# - Add field approval_requests to laborder +# Migrations for 'radiology': +# radiology/migrations/0XXX_add_approval_integration.py +# - Add field approval_requests to imagingorder +# Migrations for 'pharmacy': +# pharmacy/migrations/0XXX_add_approval_integration.py +# - Add field approval_requests to prescription +# Migrations for 'operating_theatre': +# operating_theatre/migrations/0XXX_add_approval_integration.py +# - Add field approval_requests to surgicalcase +``` + +--- + +## Step 3: Review Generated Migrations + +### Check Migration Files + +Review each generated migration file to ensure it only adds the GenericRelation field: + +```python +# Example: laboratory/migrations/0XXX_add_approval_integration.py + +from django.db import migrations +import django.contrib.contenttypes.fields + +class Migration(migrations.Migration): + dependencies = [ + ('laboratory', '0XXX_previous_migration'), + ('contenttypes', '0002_remove_content_type_name'), + ('insurance_approvals', '0001_initial'), + ] + + operations = [ + # Note: GenericRelation doesn't create database fields + # This migration may be empty or just update model metadata + ] +``` + +**Important:** GenericRelation fields don't create actual database columns. The migrations might be empty or only update model metadata. + +--- + +## Step 4: Apply Migrations + +### Run Migrations + +```bash +# Apply all pending migrations +python manage.py migrate + +# Expected output: +# Running migrations: +# Applying laboratory.0XXX_add_approval_integration... OK +# Applying radiology.0XXX_add_approval_integration... OK +# Applying pharmacy.0XXX_add_approval_integration... OK +# Applying operating_theatre.0XXX_add_approval_integration... OK +``` + +### Verify Migration Success + +```bash +# Check migration status again +python manage.py showmigrations + +# All migrations should show [X] (applied) +``` + +--- + +## Step 5: Test Integration + +### Test in Django Shell + +```python +python manage.py shell + +# Test Laboratory Integration +from laboratory.models import LabOrder +from insurance_approvals.models import InsuranceApprovalRequest + +# Get a lab order +order = LabOrder.objects.first() + +# Test helper methods +print(f"Requires approval: {order.requires_approval()}") +print(f"Has valid approval: {order.has_valid_approval()}") +print(f"Approval status: {order.approval_status}") + +# Test creating an approval +if order.patient.insurance_info.exists(): + approval = InsuranceApprovalRequest.objects.create( + tenant=order.tenant, + patient=order.patient, + insurance_info=order.patient.insurance_info.first(), + request_type='LABORATORY', + content_object=order, + service_description='Test lab order', + requesting_provider=order.ordering_provider, + service_start_date=order.created_at.date(), + status='PENDING_SUBMISSION', + ) + print(f"Created approval: {approval.approval_number}") + + # Verify relationship + print(f"Order approvals: {order.approval_requests.count()}") + print(f"Approval content object: {approval.content_object}") +``` + +### Test Each Module + +Repeat the above test for: +- ✅ Radiology (ImagingOrder) +- ✅ Pharmacy (Prescription) +- ✅ Operating Theatre (SurgicalCase) + +--- + +## Step 6: Update Application Code + +### Add Approval Checks to Views + +#### Example: Laboratory Order Creation + +```python +# laboratory/views.py + +from django.contrib import messages +from django.shortcuts import redirect +from insurance_approvals.models import InsuranceApprovalRequest + +class LabOrderCreateView(LoginRequiredMixin, CreateView): + model = LabOrder + form_class = LabOrderForm + + def form_valid(self, form): + response = super().form_valid(form) + order = self.object + + # Check if approval is required + if order.requires_approval(): + messages.warning( + self.request, + f"Insurance approval required for order {order.order_number}. " + f"Please submit an approval request." + ) + # Optionally redirect to approval creation + # return redirect('insurance_approvals:create', order_id=order.id) + + return response +``` + +### Add Approval Status to Templates + +#### Example: Order List Template + +```django + + +{% for order in orders %} + + {{ order.order_number }} + {{ order.patient.get_full_name }} + + {% if order.approval_status == 'APPROVED' %} + + Approved + + {% elif order.approval_status == 'APPROVAL_REQUIRED' %} + + Approval Required + + + Request Approval + + {% elif order.approval_status == 'NO_INSURANCE' %} + No Insurance + {% else %} + {{ order.approval_status }} + {% endif %} + + +{% endfor %} +``` + +--- + +## Step 7: Generate Sample Data (Optional) + +### Run Data Generation Script + +```bash +# Generate sample approval data +python insurance_approvals_data.py + +# Expected output: +# 🏥 Creating Insurance Approval Data +# 📋 Found X tenants +# 👥 Found X patients +# +# 1️⃣ Creating Approval Templates... +# Successfully created X approval templates. +# +# 2️⃣ Creating Approval Requests... +# Successfully created X approval requests. +# +# 3️⃣ Creating Status History... +# Successfully created X status history records. +# +# 4️⃣ Creating Communication Logs... +# Successfully created X communication logs. +# +# 🎉 Insurance Approval Data Creation Complete! +``` + +--- + +## Step 8: Configure URLs (If Not Already Done) + +### Add Insurance Approvals URLs + +```python +# hospital_management/urls.py + +from django.urls import path, include + +urlpatterns = [ + # ... existing patterns ... + path('insurance-approvals/', include('insurance_approvals.urls')), +] +``` + +--- + +## Step 9: Test End-to-End Workflow + +### Complete Workflow Test + +1. **Create an Order** + ```python + # Create a lab order for a patient with insurance + order = LabOrder.objects.create( + tenant=tenant, + patient=patient_with_insurance, + ordering_provider=provider, + # ... other fields + ) + ``` + +2. **Check Approval Requirement** + ```python + if order.requires_approval(): + print("✓ Approval requirement detected") + ``` + +3. **Create Approval Request** + ```python + approval = InsuranceApprovalRequest.objects.create( + tenant=order.tenant, + patient=order.patient, + insurance_info=order.patient.insurance_info.first(), + request_type='LABORATORY', + content_object=order, + # ... other fields + ) + ``` + +4. **Update Approval Status** + ```python + approval.status = 'APPROVED' + approval.authorization_number = 'AUTH123456' + approval.expiration_date = timezone.now().date() + timedelta(days=90) + approval.save() + ``` + +5. **Verify Approval** + ```python + assert order.has_valid_approval() + assert order.approval_status == 'APPROVED' + print("✓ Approval workflow complete") + ``` + +--- + +## Troubleshooting + +### Common Issues + +#### Issue 1: Migration Conflicts + +**Problem:** Migration conflicts with existing migrations + +**Solution:** +```bash +# Reset migrations (development only!) +python manage.py migrate laboratory zero +python manage.py migrate radiology zero +python manage.py migrate pharmacy zero +python manage.py migrate operating_theatre zero + +# Delete migration files +rm laboratory/migrations/0XXX_*.py +rm radiology/migrations/0XXX_*.py +rm pharmacy/migrations/0XXX_*.py +rm operating_theatre/migrations/0XXX_*.py + +# Recreate migrations +python manage.py makemigrations +python manage.py migrate +``` + +#### Issue 2: ContentType Not Found + +**Problem:** `ContentType matching query does not exist` + +**Solution:** +```python +# Run in Django shell +from django.contrib.contenttypes.models import ContentType +from laboratory.models import LabOrder + +# Ensure ContentType exists +ct = ContentType.objects.get_for_model(LabOrder) +print(f"ContentType created: {ct}") +``` + +#### Issue 3: Circular Import + +**Problem:** Circular import when accessing approval_requests + +**Solution:** +```python +# In helper methods, import inside the function +def has_valid_approval(self): + from django.utils import timezone # Import inside method + return self.approval_requests.filter( + status__in=['APPROVED', 'PARTIALLY_APPROVED'], + expiration_date__gte=timezone.now().date() + ).exists() +``` + +--- + +## Rollback Procedure + +### If You Need to Rollback + +```bash +# 1. Rollback migrations +python manage.py migrate laboratory 0XXX # Previous migration number +python manage.py migrate radiology 0XXX +python manage.py migrate pharmacy 0XXX +python manage.py migrate operating_theatre 0XXX + +# 2. Restore database backup +# For SQLite +cp db.sqlite3.backup db.sqlite3 + +# For PostgreSQL +psql -U username -d database_name < backup_file.sql + +# For MySQL +mysql -u username -p database_name < backup_file.sql + +# 3. Remove migration files +rm laboratory/migrations/0XXX_add_approval_integration.py +rm radiology/migrations/0XXX_add_approval_integration.py +rm pharmacy/migrations/0XXX_add_approval_integration.py +rm operating_theatre/migrations/0XXX_add_approval_integration.py +``` + +--- + +## Post-Migration Checklist + +- [ ] All migrations applied successfully +- [ ] Helper methods work correctly +- [ ] Approval requests can be created +- [ ] Approval status displays correctly +- [ ] No errors in application logs +- [ ] Templates updated with approval status +- [ ] Views updated with approval checks +- [ ] Sample data generated (if needed) +- [ ] End-to-end workflow tested +- [ ] Documentation reviewed +- [ ] Team trained on new features + +--- + +## Performance Considerations + +### Database Indexes + +The integration uses GenericForeignKey which queries ContentType. Ensure these indexes exist: + +```sql +-- These should already exist from insurance_approvals migrations +CREATE INDEX idx_approval_content_type ON insurance_approvals_insuranceapprovalrequest(content_type_id); +CREATE INDEX idx_approval_object_id ON insurance_approvals_insuranceapprovalrequest(object_id); +CREATE INDEX idx_approval_status ON insurance_approvals_insuranceapprovalrequest(status); +CREATE INDEX idx_approval_expiration ON insurance_approvals_insuranceapprovalrequest(expiration_date); +``` + +### Query Optimization + +```python +# Use select_related for better performance +orders = LabOrder.objects.select_related( + 'patient', + 'patient__insurance_info' +).prefetch_related( + 'approval_requests' +) + +# Check approval status efficiently +for order in orders: + if order.requires_approval(): + # Process... +``` + +--- + +## Support + +For issues or questions: +1. Review `INSURANCE_APPROVAL_INTEGRATION.md` +2. Check this migration guide +3. Review the insurance_approvals README.md +4. Check Django logs for errors + +--- + +## Summary + +✅ **Migration Complete When:** +- All migrations applied +- Helper methods working +- Approval workflow tested +- Templates updated +- No errors in logs + +🎉 **You're ready to use the insurance approval integration!** diff --git a/INSURANCE_APPROVAL_QUICK_REFERENCE.md b/INSURANCE_APPROVAL_QUICK_REFERENCE.md new file mode 100644 index 00000000..aede9f7a --- /dev/null +++ b/INSURANCE_APPROVAL_QUICK_REFERENCE.md @@ -0,0 +1,460 @@ +# Insurance Approval Integration - Quick Reference Card + +## 🚀 Quick Start + +### Check if Order Needs Approval +```python +if order.requires_approval(): + # Create approval request +``` + +### Get Approval Status +```python +status = order.approval_status +# Returns: 'APPROVED', 'APPROVAL_REQUIRED', 'NO_INSURANCE', etc. +``` + +### Check if Has Valid Approval +```python +if order.has_valid_approval(): + # Proceed with order +``` + +### Get Active Approval +```python +approval = order.get_active_approval() +if approval: + print(f"Auth #: {approval.authorization_number}") +``` + +--- + +## 📦 Integrated Models + +| Module | Model | Request Type | +|--------|-------|--------------| +| Laboratory | `LabOrder` | `LABORATORY` | +| Radiology | `ImagingOrder` | `RADIOLOGY` | +| Pharmacy | `Prescription` | `PHARMACY` | +| Operating Theatre | `SurgicalCase` | `SURGERY` | + +--- + +## 🔧 Helper Methods (All Models) + +### `requires_approval()` → bool +Returns `True` if order requires insurance approval + +```python +if lab_order.requires_approval(): + # Patient has insurance but no valid approval +``` + +### `has_valid_approval()` → bool +Returns `True` if order has a valid, non-expired approval + +```python +if imaging_order.has_valid_approval(): + # Can proceed with imaging +``` + +### `get_active_approval()` → InsuranceApprovalRequest | None +Returns the active approval or None + +```python +approval = prescription.get_active_approval() +if approval: + auth_number = approval.authorization_number +``` + +### `approval_status` → str (property) +Returns current approval status as string + +```python +status = surgical_case.approval_status +# 'APPROVED', 'APPROVAL_REQUIRED', 'NO_INSURANCE', etc. +``` + +--- + +## 📝 Creating Approval Requests + +### Laboratory Order +```python +from insurance_approvals.models import InsuranceApprovalRequest + +approval = InsuranceApprovalRequest.objects.create( + tenant=lab_order.tenant, + patient=lab_order.patient, + insurance_info=lab_order.patient.insurance_info.first(), + request_type='LABORATORY', + content_object=lab_order, + service_description=f"Lab tests: {', '.join([t.test_name for t in lab_order.tests.all()])}", + procedure_codes=', '.join([t.cpt_code for t in lab_order.tests.all() if t.cpt_code]), + diagnosis_codes=lab_order.diagnosis_code or '', + clinical_justification=lab_order.clinical_indication, + requesting_provider=lab_order.ordering_provider, + service_start_date=lab_order.collection_datetime.date(), + priority='ROUTINE', + status='PENDING_SUBMISSION', +) +``` + +### Radiology Order +```python +approval = InsuranceApprovalRequest.objects.create( + tenant=imaging_order.tenant, + patient=imaging_order.patient, + insurance_info=imaging_order.patient.insurance_info.first(), + request_type='RADIOLOGY', + content_object=imaging_order, + service_description=imaging_order.study_description, + procedure_codes=imaging_order.modality, + diagnosis_codes=imaging_order.diagnosis_code or '', + clinical_justification=imaging_order.clinical_indication, + requesting_provider=imaging_order.ordering_provider, + service_start_date=imaging_order.requested_datetime.date(), + priority='URGENT' if imaging_order.is_stat else 'ROUTINE', + status='PENDING_SUBMISSION', +) +``` + +### Pharmacy Prescription +```python +approval = InsuranceApprovalRequest.objects.create( + tenant=prescription.tenant, + patient=prescription.patient, + insurance_info=prescription.patient.insurance_info.first(), + request_type='PHARMACY', + content_object=prescription, + service_description=f"{prescription.medication.display_name} - {prescription.quantity_prescribed} {prescription.quantity_unit}", + procedure_codes=prescription.medication.ndc_number or '', + diagnosis_codes=prescription.diagnosis_code or '', + clinical_justification=prescription.indication or 'As prescribed', + requesting_provider=prescription.prescriber, + service_start_date=prescription.date_written, + priority='URGENT' if prescription.medication.is_controlled_substance else 'ROUTINE', + requested_quantity=prescription.quantity_prescribed, + status='PENDING_SUBMISSION', +) +``` + +### Surgical Case +```python +approval = InsuranceApprovalRequest.objects.create( + tenant=surgical_case.tenant, + patient=surgical_case.patient, + insurance_info=surgical_case.patient.insurance_info.first(), + request_type='SURGERY', + content_object=surgical_case, + service_description=surgical_case.primary_procedure, + procedure_codes=', '.join(surgical_case.procedure_codes), + diagnosis_codes=', '.join(surgical_case.diagnosis_codes), + clinical_justification=surgical_case.clinical_notes, + requesting_provider=surgical_case.primary_surgeon, + service_start_date=surgical_case.scheduled_start.date(), + priority='STAT' if surgical_case.is_emergency else 'ROUTINE', + is_expedited=surgical_case.is_emergency, + status='PENDING_SUBMISSION', +) +``` + +--- + +## 🎨 Template Examples + +### Display Approval Status Badge +```django +{% if order.approval_status == 'APPROVED' %} + + Approved + +{% elif order.approval_status == 'APPROVAL_REQUIRED' %} + + Approval Required + +{% elif order.approval_status == 'NO_INSURANCE' %} + No Insurance +{% else %} + {{ order.approval_status }} +{% endif %} +``` + +### Show Approval Details +```django +{% if order.has_valid_approval %} + {% with approval=order.get_active_approval %} +
+ Authorization: {{ approval.authorization_number }}
+ Expires: {{ approval.expiration_date|date:"M d, Y" }}
+ Approved Quantity: {{ approval.approved_quantity }} +
+ {% endwith %} +{% endif %} +``` + +### Request Approval Button +```django +{% if order.requires_approval %} + + Request Approval + +{% endif %} +``` + +--- + +## 📊 Approval Status Values + +| Status | Meaning | +|--------|---------| +| `NO_INSURANCE` | Patient has no insurance | +| `APPROVAL_REQUIRED` | Needs approval but none exists | +| `APPROVED` | Has valid, active approval | +| `PENDING_SUBMISSION` | Request created but not submitted | +| `SUBMITTED` | Request submitted to insurance | +| `UNDER_REVIEW` | Being reviewed by insurance | +| `MORE_INFO_REQUIRED` | Insurance needs more information | +| `PARTIALLY_APPROVED` | Partially approved | +| `DENIED` | Approval denied | +| `EXPIRED` | Approval has expired | +| `PRIOR_AUTH_REQUIRED` | Prior authorization required (Pharmacy) | +| `PRIOR_AUTH_APPROVED` | Prior authorization approved (Pharmacy) | +| `PRIOR_AUTH_EXPIRED` | Prior authorization expired (Pharmacy) | +| `EMERGENCY_APPROVAL_REQUIRED` | Emergency approval needed (Surgery) | + +--- + +## 🔍 Querying Approvals + +### Get All Approvals for an Order +```python +approvals = order.approval_requests.all() +``` + +### Get Active Approvals +```python +from django.utils import timezone + +active_approvals = order.approval_requests.filter( + status__in=['APPROVED', 'PARTIALLY_APPROVED'], + expiration_date__gte=timezone.now().date() +) +``` + +### Get Latest Approval +```python +latest = order.approval_requests.order_by('-created_at').first() +``` + +### Get Approvals by Status +```python +pending = order.approval_requests.filter(status='PENDING_SUBMISSION') +approved = order.approval_requests.filter(status='APPROVED') +denied = order.approval_requests.filter(status='DENIED') +``` + +--- + +## 🔄 Reverse Queries + +### Get Order from Approval +```python +approval = InsuranceApprovalRequest.objects.get(pk=1) +order = approval.content_object # Returns LabOrder, ImagingOrder, etc. +``` + +### Get All Lab Orders with Approvals +```python +from django.contrib.contenttypes.models import ContentType +from laboratory.models import LabOrder + +lab_order_ct = ContentType.objects.get_for_model(LabOrder) +approvals = InsuranceApprovalRequest.objects.filter( + content_type=lab_order_ct +) +``` + +### Filter Orders by Approval Status +```python +# Orders needing approval +orders_needing_approval = [ + order for order in LabOrder.objects.all() + if order.requires_approval() +] + +# Orders with valid approval +orders_with_approval = [ + order for order in LabOrder.objects.all() + if order.has_valid_approval() +] +``` + +--- + +## ⚡ Performance Tips + +### Use select_related and prefetch_related +```python +orders = LabOrder.objects.select_related( + 'patient', + 'patient__insurance_info' +).prefetch_related( + 'approval_requests' +) +``` + +### Bulk Check Approval Status +```python +from django.db.models import Prefetch + +orders = LabOrder.objects.prefetch_related( + Prefetch( + 'approval_requests', + queryset=InsuranceApprovalRequest.objects.filter( + status__in=['APPROVED', 'PARTIALLY_APPROVED'] + ) + ) +) +``` + +--- + +## 🎯 Common Patterns + +### Pattern 1: Create Order with Approval Check +```python +def create_order_with_approval(patient, **order_data): + order = LabOrder.objects.create(patient=patient, **order_data) + + if order.requires_approval(): + return { + 'order': order, + 'needs_approval': True, + 'redirect': f'/insurance-approvals/create/?order_id={order.id}' + } + + return {'order': order, 'needs_approval': False} +``` + +### Pattern 2: Validate Before Processing +```python +def process_order(order): + if order.requires_approval() and not order.has_valid_approval(): + raise ValidationError("Order requires valid insurance approval") + + # Process order... +``` + +### Pattern 3: Auto-Create Approval Request +```python +def auto_request_approval(order): + if not order.requires_approval(): + return None + + return InsuranceApprovalRequest.objects.create( + tenant=order.tenant, + patient=order.patient, + insurance_info=order.patient.insurance_info.first(), + request_type='LABORATORY', + content_object=order, + # ... other fields + ) +``` + +--- + +## 🚨 Error Handling + +### Check for Insurance +```python +if not order.patient.insurance_info.exists(): + # No insurance - no approval needed + pass +``` + +### Handle Missing Approval +```python +try: + approval = order.get_active_approval() + if not approval: + # Create new approval request + approval = create_approval_request(order) +except Exception as e: + logger.error(f"Error getting approval: {e}") +``` + +### Validate Expiration +```python +from django.utils import timezone + +approval = order.get_active_approval() +if approval and approval.expiration_date < timezone.now().date(): + # Approval expired - need new one + pass +``` + +--- + +## 📱 API/DRF Integration + +### Serializer with Approval Status +```python +from rest_framework import serializers + +class LabOrderSerializer(serializers.ModelSerializer): + approval_status = serializers.CharField(read_only=True) + requires_approval = serializers.SerializerMethodField() + active_approval = serializers.SerializerMethodField() + + def get_requires_approval(self, obj): + return obj.requires_approval() + + def get_active_approval(self, obj): + approval = obj.get_active_approval() + return { + 'id': approval.id, + 'authorization_number': approval.authorization_number, + 'expiration_date': approval.expiration_date + } if approval else None +``` + +--- + +## 📚 Related Documentation + +- **Full Integration Guide:** `INSURANCE_APPROVAL_INTEGRATION.md` +- **Migration Guide:** `INSURANCE_APPROVAL_MIGRATION_GUIDE.md` +- **Module README:** `insurance_approvals/README.md` + +--- + +## 💡 Tips + +1. **Always check `requires_approval()` before processing orders** +2. **Use `has_valid_approval()` to verify approval is still valid** +3. **Prefetch approval_requests for better performance** +4. **Handle emergency cases differently (Surgery module)** +5. **Check formulary status for medications (Pharmacy module)** +6. **Use approval templates for consistency** +7. **Track all status changes with ApprovalStatusHistory** +8. **Log communications with insurance companies** + +--- + +## 🎉 Quick Checklist + +- [ ] Order created +- [ ] Check if approval required +- [ ] Create approval request if needed +- [ ] Submit to insurance +- [ ] Track status changes +- [ ] Update when approved +- [ ] Verify before processing +- [ ] Handle expiration + +--- + +**Last Updated:** 2025-01-03 +**Version:** 1.0 diff --git a/__pycache__/hr_data.cpython-312.pyc b/__pycache__/hr_data.cpython-312.pyc index 8739aac3..dccf4fb8 100644 Binary files a/__pycache__/hr_data.cpython-312.pyc and b/__pycache__/hr_data.cpython-312.pyc differ diff --git a/accounts/migrations/0001_initial.py b/accounts/migrations/0001_initial.py deleted file mode 100644 index 561b4057..00000000 --- a/accounts/migrations/0001_initial.py +++ /dev/null @@ -1,472 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.contrib.auth.models -import django.contrib.auth.validators -import django.utils.timezone -import uuid -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("auth", "0012_alter_user_first_name_max_length"), - ] - - operations = [ - migrations.CreateModel( - name="PasswordHistory", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "password_hash", - models.CharField(help_text="Hashed password", max_length=128), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ], - options={ - "verbose_name": "Password History", - "verbose_name_plural": "Password History", - "db_table": "accounts_password_history", - "ordering": ["-created_at"], - }, - ), - migrations.CreateModel( - name="SocialAccount", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "provider", - models.CharField( - choices=[ - ("GOOGLE", "Google"), - ("MICROSOFT", "Microsoft"), - ("APPLE", "Apple"), - ("FACEBOOK", "Facebook"), - ("LINKEDIN", "LinkedIn"), - ("GITHUB", "GitHub"), - ("OKTA", "Okta"), - ("SAML", "SAML"), - ("LDAP", "LDAP"), - ], - max_length=50, - ), - ), - ( - "provider_id", - models.CharField(help_text="Provider user ID", max_length=200), - ), - ( - "provider_email", - models.EmailField( - blank=True, - help_text="Email from provider", - max_length=254, - null=True, - ), - ), - ( - "display_name", - models.CharField( - blank=True, - help_text="Display name from provider", - max_length=200, - null=True, - ), - ), - ( - "profile_url", - models.URLField( - blank=True, help_text="Profile URL from provider", null=True - ), - ), - ( - "avatar_url", - models.URLField( - blank=True, help_text="Avatar URL from provider", null=True - ), - ), - ( - "access_token", - models.TextField( - blank=True, help_text="Access token from provider", null=True - ), - ), - ( - "refresh_token", - models.TextField( - blank=True, help_text="Refresh token from provider", null=True - ), - ), - ( - "token_expires_at", - models.DateTimeField( - blank=True, help_text="Token expiration date", null=True - ), - ), - ( - "is_active", - models.BooleanField( - default=True, help_text="Social account is active" - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "last_login_at", - models.DateTimeField( - blank=True, - help_text="Last login using this social account", - null=True, - ), - ), - ], - options={ - "verbose_name": "Social Account", - "verbose_name_plural": "Social Accounts", - "db_table": "accounts_social_account", - "ordering": ["-created_at"], - }, - ), - migrations.CreateModel( - name="TwoFactorDevice", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "device_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique device identifier", - unique=True, - ), - ), - ("name", models.CharField(help_text="Device name", max_length=100)), - ( - "device_type", - models.CharField( - choices=[ - ("TOTP", "Time-based OTP (Authenticator App)"), - ("SMS", "SMS"), - ("EMAIL", "Email"), - ("HARDWARE", "Hardware Token"), - ("BACKUP", "Backup Codes"), - ], - max_length=20, - ), - ), - ( - "secret_key", - models.CharField( - blank=True, - help_text="Secret key for TOTP devices", - max_length=200, - null=True, - ), - ), - ( - "phone_number", - models.CharField( - blank=True, - help_text="Phone number for SMS devices", - max_length=20, - null=True, - ), - ), - ( - "email_address", - models.EmailField( - blank=True, - help_text="Email address for email devices", - max_length=254, - null=True, - ), - ), - ( - "is_active", - models.BooleanField(default=True, help_text="Device is active"), - ), - ( - "is_verified", - models.BooleanField(default=False, help_text="Device is verified"), - ), - ( - "verified_at", - models.DateTimeField( - blank=True, help_text="Device verification date", null=True - ), - ), - ( - "last_used_at", - models.DateTimeField( - blank=True, help_text="Last time device was used", null=True - ), - ), - ( - "usage_count", - models.PositiveIntegerField( - default=0, help_text="Number of times device was used" - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Two Factor Device", - "verbose_name_plural": "Two Factor Devices", - "db_table": "accounts_two_factor_device", - "ordering": ["-created_at"], - }, - ), - migrations.CreateModel( - name="UserSession", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "session_key", - models.CharField( - help_text="Django session key", max_length=40, unique=True - ), - ), - ( - "session_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique session identifier", - unique=True, - ), - ), - ("ip_address", models.GenericIPAddressField(help_text="IP address")), - ("user_agent", models.TextField(help_text="User agent string")), - ( - "device_type", - models.CharField( - choices=[ - ("DESKTOP", "Desktop"), - ("MOBILE", "Mobile"), - ("TABLET", "Tablet"), - ("UNKNOWN", "Unknown"), - ], - default="UNKNOWN", - max_length=20, - ), - ), - ( - "browser", - models.CharField( - blank=True, - help_text="Browser name and version", - max_length=100, - null=True, - ), - ), - ( - "operating_system", - models.CharField( - blank=True, - help_text="Operating system", - max_length=100, - null=True, - ), - ), - ( - "country", - models.CharField( - blank=True, help_text="Country", max_length=100, null=True - ), - ), - ( - "region", - models.CharField( - blank=True, help_text="Region/State", max_length=100, null=True - ), - ), - ( - "city", - models.CharField( - blank=True, help_text="City", max_length=100, null=True - ), - ), - ( - "is_active", - models.BooleanField(default=True, help_text="Session is active"), - ), - ( - "login_method", - models.CharField( - choices=[ - ("PASSWORD", "Password"), - ("TWO_FACTOR", "Two Factor"), - ("SOCIAL", "Social Login"), - ("SSO", "Single Sign-On"), - ("API_KEY", "API Key"), - ], - default="PASSWORD", - max_length=20, - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("last_activity_at", models.DateTimeField(auto_now=True)), - ( - "expires_at", - models.DateTimeField(help_text="Session expiration time"), - ), - ( - "ended_at", - models.DateTimeField( - blank=True, help_text="Session end time", null=True - ), - ), - ], - options={ - "verbose_name": "User Session", - "verbose_name_plural": "User Sessions", - "db_table": "accounts_user_session", - "ordering": ["-created_at"], - }, - ), - migrations.CreateModel( - name="User", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("password", models.CharField(max_length=128, verbose_name="password")), - ( - "last_login", - models.DateTimeField( - blank=True, null=True, verbose_name="last login" - ), - ), - ( - "is_superuser", - models.BooleanField( - default=False, - help_text="Designates that this user has all permissions without explicitly assigning them.", - verbose_name="superuser status", - ), - ), - ( - "username", - models.CharField( - error_messages={ - "unique": "A user with that username already exists." - }, - help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", - max_length=150, - unique=True, - validators=[ - django.contrib.auth.validators.UnicodeUsernameValidator() - ], - verbose_name="username", - ), - ), - ( - "first_name", - models.CharField( - blank=True, max_length=150, verbose_name="first name" - ), - ), - ( - "last_name", - models.CharField( - blank=True, max_length=150, verbose_name="last name" - ), - ), - ( - "is_staff", - models.BooleanField( - default=False, - help_text="Designates whether the user can log into this admin site.", - verbose_name="staff status", - ), - ), - ( - "is_active", - models.BooleanField( - default=True, - help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", - verbose_name="active", - ), - ), - ( - "date_joined", - models.DateTimeField( - default=django.utils.timezone.now, verbose_name="date joined" - ), - ), - ( - "user_id", - models.UUIDField(default=uuid.uuid4, editable=False, unique=True), - ), - ("email", models.EmailField(max_length=254, unique=True)), - ("force_password_change", models.BooleanField(default=False)), - ("password_expires_at", models.DateTimeField(blank=True, null=True)), - ("failed_login_attempts", models.PositiveIntegerField(default=0)), - ("locked_until", models.DateTimeField(blank=True, null=True)), - ("two_factor_enabled", models.BooleanField(default=False)), - ("max_concurrent_sessions", models.PositiveIntegerField(default=3)), - ("session_timeout_minutes", models.PositiveIntegerField(default=30)), - ("last_password_change", models.DateTimeField(blank=True, null=True)), - ( - "groups", - models.ManyToManyField( - blank=True, - help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", - related_name="user_set", - related_query_name="user", - to="auth.group", - verbose_name="groups", - ), - ), - ], - options={ - "db_table": "accounts_user", - "ordering": ["last_name", "first_name"], - }, - managers=[ - ("objects", django.contrib.auth.models.UserManager()), - ], - ), - ] diff --git a/accounts/migrations/0002_initial.py b/accounts/migrations/0002_initial.py deleted file mode 100644 index c21fcc85..00000000 --- a/accounts/migrations/0002_initial.py +++ /dev/null @@ -1,116 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("accounts", "0001_initial"), - ("auth", "0012_alter_user_first_name_max_length"), - ("core", "0001_initial"), - ] - - operations = [ - migrations.AddField( - model_name="user", - name="tenant", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="users", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="user", - name="user_permissions", - field=models.ManyToManyField( - blank=True, - help_text="Specific permissions for this user.", - related_name="user_set", - related_query_name="user", - to="auth.permission", - verbose_name="user permissions", - ), - ), - migrations.AddField( - model_name="passwordhistory", - name="user", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="password_history", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="socialaccount", - name="user", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="social_accounts", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="twofactordevice", - name="user", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="two_factor_devices", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="usersession", - name="user", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="user_sessions", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddIndex( - model_name="user", - index=models.Index( - fields=["tenant", "email"], name="accounts_us_tenant__162cd2_idx" - ), - ), - migrations.AddIndex( - model_name="user", - index=models.Index( - fields=["tenant", "username"], name="accounts_us_tenant__d92906_idx" - ), - ), - migrations.AddIndex( - model_name="user", - index=models.Index( - fields=["tenant", "is_active"], name="accounts_us_tenant__78e6c9_idx" - ), - ), - migrations.AlterUniqueTogether( - name="socialaccount", - unique_together={("provider", "provider_id")}, - ), - migrations.AddIndex( - model_name="usersession", - index=models.Index( - fields=["user", "is_active"], name="accounts_us_user_id_f3bc3f_idx" - ), - ), - migrations.AddIndex( - model_name="usersession", - index=models.Index( - fields=["session_key"], name="accounts_us_session_5ce38e_idx" - ), - ), - migrations.AddIndex( - model_name="usersession", - index=models.Index( - fields=["ip_address"], name="accounts_us_ip_addr_f7885b_idx" - ), - ), - ] diff --git a/accounts/migrations/__pycache__/0001_initial.cpython-312.pyc b/accounts/migrations/__pycache__/0001_initial.cpython-312.pyc deleted file mode 100644 index affba34f..00000000 Binary files a/accounts/migrations/__pycache__/0001_initial.cpython-312.pyc and /dev/null differ diff --git a/accounts/migrations/__pycache__/0002_initial.cpython-312.pyc b/accounts/migrations/__pycache__/0002_initial.cpython-312.pyc deleted file mode 100644 index ec05b689..00000000 Binary files a/accounts/migrations/__pycache__/0002_initial.cpython-312.pyc and /dev/null differ diff --git a/analytics/migrations/0001_initial.py b/analytics/migrations/0001_initial.py deleted file mode 100644 index 5d0e31fc..00000000 --- a/analytics/migrations/0001_initial.py +++ /dev/null @@ -1,623 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.core.validators -import django.db.models.deletion -import uuid -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="DashboardWidget", - fields=[ - ( - "widget_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ("name", models.CharField(max_length=200)), - ("description", models.TextField(blank=True)), - ( - "widget_type", - models.CharField( - choices=[ - ("CHART", "Chart Widget"), - ("TABLE", "Table Widget"), - ("METRIC", "Metric Widget"), - ("GAUGE", "Gauge Widget"), - ("MAP", "Map Widget"), - ("TEXT", "Text Widget"), - ("IMAGE", "Image Widget"), - ("IFRAME", "IFrame Widget"), - ("CUSTOM", "Custom Widget"), - ], - max_length=20, - ), - ), - ( - "chart_type", - models.CharField( - blank=True, - choices=[ - ("LINE", "Line Chart"), - ("BAR", "Bar Chart"), - ("PIE", "Pie Chart"), - ("DOUGHNUT", "Doughnut Chart"), - ("AREA", "Area Chart"), - ("SCATTER", "Scatter Plot"), - ("HISTOGRAM", "Histogram"), - ("HEATMAP", "Heat Map"), - ("TREEMAP", "Tree Map"), - ("FUNNEL", "Funnel Chart"), - ], - max_length=20, - ), - ), - ( - "query_config", - models.JSONField( - default=dict, help_text="Query configuration for data source" - ), - ), - ("position_x", models.PositiveIntegerField(default=0)), - ("position_y", models.PositiveIntegerField(default=0)), - ( - "width", - models.PositiveIntegerField( - default=4, - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(12), - ], - ), - ), - ( - "height", - models.PositiveIntegerField( - default=4, - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(12), - ], - ), - ), - ( - "display_config", - models.JSONField( - default=dict, help_text="Widget display configuration" - ), - ), - ("color_scheme", models.CharField(default="default", max_length=50)), - ("auto_refresh", models.BooleanField(default=True)), - ( - "refresh_interval", - models.PositiveIntegerField( - default=300, help_text="Refresh interval in seconds" - ), - ), - ("is_active", models.BooleanField(default=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "db_table": "analytics_dashboard_widget", - "ordering": ["position_y", "position_x"], - }, - ), - migrations.CreateModel( - name="DataSource", - fields=[ - ( - "source_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ("name", models.CharField(max_length=200)), - ("description", models.TextField(blank=True)), - ( - "source_type", - models.CharField( - choices=[ - ("DATABASE", "Database Query"), - ("API", "API Endpoint"), - ("FILE", "File Upload"), - ("STREAM", "Real-time Stream"), - ("WEBHOOK", "Webhook"), - ("CUSTOM", "Custom Source"), - ], - max_length=20, - ), - ), - ( - "connection_type", - models.CharField( - choices=[ - ("POSTGRESQL", "PostgreSQL"), - ("MYSQL", "MySQL"), - ("SQLITE", "SQLite"), - ("MONGODB", "MongoDB"), - ("REDIS", "Redis"), - ("REST_API", "REST API"), - ("GRAPHQL", "GraphQL"), - ("WEBSOCKET", "WebSocket"), - ("CSV", "CSV File"), - ("JSON", "JSON File"), - ("XML", "XML File"), - ], - max_length=20, - ), - ), - ( - "connection_config", - models.JSONField( - default=dict, help_text="Connection configuration" - ), - ), - ( - "authentication_config", - models.JSONField( - default=dict, help_text="Authentication configuration" - ), - ), - ( - "query_template", - models.TextField( - blank=True, help_text="SQL query or API endpoint template" - ), - ), - ( - "parameters", - models.JSONField(default=dict, help_text="Query parameters"), - ), - ( - "data_transformation", - models.JSONField( - default=dict, help_text="Data transformation rules" - ), - ), - ( - "cache_duration", - models.PositiveIntegerField( - default=300, help_text="Cache duration in seconds" - ), - ), - ("is_healthy", models.BooleanField(default=True)), - ("last_health_check", models.DateTimeField(blank=True, null=True)), - ( - "health_check_interval", - models.PositiveIntegerField( - default=300, help_text="Health check interval in seconds" - ), - ), - ("is_active", models.BooleanField(default=True)), - ( - "last_test_status", - models.CharField( - choices=[ - ("PENDING", "Pending"), - ("RUNNING", "Running"), - ("SUCCESS", "Success"), - ("FAILURE", "Failure"), - ], - default="PENDING", - max_length=20, - ), - ), - ("last_test_start_at", models.DateTimeField(blank=True, null=True)), - ("last_test_end_at", models.DateTimeField(blank=True, null=True)), - ( - "last_test_duration_seconds", - models.PositiveIntegerField(blank=True, null=True), - ), - ("last_test_error_message", models.TextField(blank=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "db_table": "analytics_data_source", - }, - ), - migrations.CreateModel( - name="MetricDefinition", - fields=[ - ( - "metric_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ("name", models.CharField(max_length=200)), - ("description", models.TextField(blank=True)), - ( - "metric_type", - models.CharField( - choices=[ - ("COUNT", "Count"), - ("SUM", "Sum"), - ("AVERAGE", "Average"), - ("PERCENTAGE", "Percentage"), - ("RATIO", "Ratio"), - ("RATE", "Rate"), - ("DURATION", "Duration"), - ("CUSTOM", "Custom Calculation"), - ], - max_length=20, - ), - ), - ( - "calculation_config", - models.JSONField( - default=dict, help_text="Metric calculation configuration" - ), - ), - ( - "aggregation_period", - models.CharField( - choices=[ - ("REAL_TIME", "Real-time"), - ("HOURLY", "Hourly"), - ("DAILY", "Daily"), - ("WEEKLY", "Weekly"), - ("MONTHLY", "Monthly"), - ("QUARTERLY", "Quarterly"), - ("YEARLY", "Yearly"), - ], - max_length=20, - ), - ), - ( - "aggregation_config", - models.JSONField( - default=dict, help_text="Aggregation configuration" - ), - ), - ( - "target_value", - models.DecimalField( - blank=True, decimal_places=4, max_digits=15, null=True - ), - ), - ( - "warning_threshold", - models.DecimalField( - blank=True, decimal_places=4, max_digits=15, null=True - ), - ), - ( - "critical_threshold", - models.DecimalField( - blank=True, decimal_places=4, max_digits=15, null=True - ), - ), - ("unit_of_measure", models.CharField(blank=True, max_length=50)), - ( - "decimal_places", - models.PositiveIntegerField( - default=2, - validators=[django.core.validators.MaxValueValidator(10)], - ), - ), - ("display_format", models.CharField(default="number", max_length=50)), - ("is_active", models.BooleanField(default=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "db_table": "analytics_metric_definition", - }, - ), - migrations.CreateModel( - name="MetricValue", - fields=[ - ( - "value_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ("value", models.DecimalField(decimal_places=4, max_digits=15)), - ("period_start", models.DateTimeField()), - ("period_end", models.DateTimeField()), - ( - "dimensions", - models.JSONField( - default=dict, - help_text="Metric dimensions (e.g., department, provider)", - ), - ), - ( - "metadata", - models.JSONField(default=dict, help_text="Additional metadata"), - ), - ( - "data_quality_score", - models.DecimalField( - blank=True, - decimal_places=2, - max_digits=5, - null=True, - validators=[ - django.core.validators.MinValueValidator(0), - django.core.validators.MaxValueValidator(100), - ], - ), - ), - ( - "confidence_level", - models.DecimalField( - blank=True, - decimal_places=2, - max_digits=5, - null=True, - validators=[ - django.core.validators.MinValueValidator(0), - django.core.validators.MaxValueValidator(100), - ], - ), - ), - ("calculation_timestamp", models.DateTimeField(auto_now_add=True)), - ( - "calculation_duration_ms", - models.PositiveIntegerField(blank=True, null=True), - ), - ], - options={ - "db_table": "analytics_metric_value", - "ordering": ["-period_start"], - }, - ), - migrations.CreateModel( - name="Report", - fields=[ - ( - "report_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ("name", models.CharField(max_length=200)), - ("description", models.TextField(blank=True)), - ( - "report_type", - models.CharField( - choices=[ - ("OPERATIONAL", "Operational Report"), - ("FINANCIAL", "Financial Report"), - ("CLINICAL", "Clinical Report"), - ("QUALITY", "Quality Report"), - ("COMPLIANCE", "Compliance Report"), - ("PERFORMANCE", "Performance Report"), - ("CUSTOM", "Custom Report"), - ], - max_length=20, - ), - ), - ( - "query_config", - models.JSONField( - default=dict, help_text="Query configuration for report" - ), - ), - ( - "output_format", - models.CharField( - choices=[ - ("PDF", "PDF Document"), - ("EXCEL", "Excel Spreadsheet"), - ("CSV", "CSV File"), - ("JSON", "JSON Data"), - ("HTML", "HTML Page"), - ("EMAIL", "Email Report"), - ], - max_length=20, - ), - ), - ( - "template_config", - models.JSONField( - default=dict, help_text="Report template configuration" - ), - ), - ( - "schedule_type", - models.CharField( - choices=[ - ("MANUAL", "Manual Execution"), - ("DAILY", "Daily"), - ("WEEKLY", "Weekly"), - ("MONTHLY", "Monthly"), - ("QUARTERLY", "Quarterly"), - ("YEARLY", "Yearly"), - ("CUSTOM", "Custom Schedule"), - ], - default="MANUAL", - max_length=20, - ), - ), - ( - "schedule_config", - models.JSONField(default=dict, help_text="Schedule configuration"), - ), - ("next_execution", models.DateTimeField(blank=True, null=True)), - ( - "recipients", - models.JSONField(default=list, help_text="Report recipients"), - ), - ( - "distribution_config", - models.JSONField( - default=dict, help_text="Distribution configuration" - ), - ), - ("is_active", models.BooleanField(default=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "db_table": "analytics_report", - }, - ), - migrations.CreateModel( - name="ReportExecution", - fields=[ - ( - "execution_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ( - "execution_type", - models.CharField( - choices=[ - ("MANUAL", "Manual"), - ("SCHEDULED", "Scheduled"), - ("API", "API Triggered"), - ], - default="MANUAL", - max_length=20, - ), - ), - ("started_at", models.DateTimeField(auto_now_add=True)), - ("completed_at", models.DateTimeField(blank=True, null=True)), - ( - "duration_seconds", - models.PositiveIntegerField(blank=True, null=True), - ), - ( - "status", - models.CharField( - choices=[ - ("PENDING", "Pending"), - ("RUNNING", "Running"), - ("COMPLETED", "Completed"), - ("FAILED", "Failed"), - ("CANCELLED", "Cancelled"), - ], - default="PENDING", - max_length=20, - ), - ), - ("error_message", models.TextField(blank=True)), - ("output_file_path", models.CharField(blank=True, max_length=500)), - ( - "output_size_bytes", - models.PositiveBigIntegerField(blank=True, null=True), - ), - ("record_count", models.PositiveIntegerField(blank=True, null=True)), - ( - "execution_parameters", - models.JSONField(default=dict, help_text="Execution parameters"), - ), - ], - options={ - "db_table": "analytics_report_execution", - "ordering": ["-started_at"], - }, - ), - migrations.CreateModel( - name="Dashboard", - fields=[ - ( - "dashboard_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ("name", models.CharField(max_length=200)), - ("description", models.TextField(blank=True)), - ( - "dashboard_type", - models.CharField( - choices=[ - ("EXECUTIVE", "Executive Dashboard"), - ("CLINICAL", "Clinical Dashboard"), - ("OPERATIONAL", "Operational Dashboard"), - ("FINANCIAL", "Financial Dashboard"), - ("QUALITY", "Quality Dashboard"), - ("PATIENT", "Patient Dashboard"), - ("PROVIDER", "Provider Dashboard"), - ("DEPARTMENT", "Department Dashboard"), - ("CUSTOM", "Custom Dashboard"), - ], - max_length=20, - ), - ), - ( - "layout_config", - models.JSONField( - default=dict, help_text="Dashboard layout configuration" - ), - ), - ( - "refresh_interval", - models.PositiveIntegerField( - default=300, help_text="Refresh interval in seconds" - ), - ), - ("is_public", models.BooleanField(default=False)), - ( - "allowed_roles", - models.JSONField( - default=list, help_text="List of allowed user roles" - ), - ), - ("is_active", models.BooleanField(default=True)), - ("is_default", models.BooleanField(default=False)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "allowed_users", - models.ManyToManyField( - blank=True, - related_name="accessible_dashboards", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "created_by", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "db_table": "analytics_dashboard", - }, - ), - ] diff --git a/analytics/migrations/0002_initial.py b/analytics/migrations/0002_initial.py deleted file mode 100644 index 588591bb..00000000 --- a/analytics/migrations/0002_initial.py +++ /dev/null @@ -1,309 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("analytics", "0001_initial"), - ("core", "0001_initial"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name="dashboard", - name="tenant", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="dashboards", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="dashboardwidget", - name="dashboard", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="widgets", - to="analytics.dashboard", - ), - ), - migrations.AddField( - model_name="datasource", - name="created_by", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="datasource", - name="tenant", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="data_sources", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="dashboardwidget", - name="data_source", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="widgets", - to="analytics.datasource", - ), - ), - migrations.AddField( - model_name="metricdefinition", - name="created_by", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="metricdefinition", - name="data_source", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="metrics", - to="analytics.datasource", - ), - ), - migrations.AddField( - model_name="metricdefinition", - name="tenant", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="metric_definitions", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="metricvalue", - name="metric_definition", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="values", - to="analytics.metricdefinition", - ), - ), - migrations.AddField( - model_name="report", - name="created_by", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="report", - name="data_source", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="reports", - to="analytics.datasource", - ), - ), - migrations.AddField( - model_name="report", - name="tenant", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="reports", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="reportexecution", - name="executed_by", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="reportexecution", - name="report", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="executions", - to="analytics.report", - ), - ), - migrations.AddIndex( - model_name="dashboard", - index=models.Index( - fields=["tenant", "dashboard_type"], - name="analytics_d_tenant__6c4962_idx", - ), - ), - migrations.AddIndex( - model_name="dashboard", - index=models.Index( - fields=["tenant", "is_active"], name="analytics_d_tenant__c68e4a_idx" - ), - ), - migrations.AddIndex( - model_name="dashboard", - index=models.Index( - fields=["tenant", "is_default"], name="analytics_d_tenant__f167b1_idx" - ), - ), - migrations.AlterUniqueTogether( - name="dashboard", - unique_together={("tenant", "name")}, - ), - migrations.AddIndex( - model_name="datasource", - index=models.Index( - fields=["tenant", "source_type"], name="analytics_d_tenant__1f790a_idx" - ), - ), - migrations.AddIndex( - model_name="datasource", - index=models.Index( - fields=["tenant", "is_active"], name="analytics_d_tenant__a566a2_idx" - ), - ), - migrations.AddIndex( - model_name="datasource", - index=models.Index( - fields=["tenant", "is_healthy"], name="analytics_d_tenant__442319_idx" - ), - ), - migrations.AlterUniqueTogether( - name="datasource", - unique_together={("tenant", "name")}, - ), - migrations.AddIndex( - model_name="dashboardwidget", - index=models.Index( - fields=["dashboard", "is_active"], name="analytics_d_dashboa_6a4da0_idx" - ), - ), - migrations.AddIndex( - model_name="dashboardwidget", - index=models.Index( - fields=["dashboard", "position_x", "position_y"], - name="analytics_d_dashboa_4ce236_idx", - ), - ), - migrations.AddIndex( - model_name="metricdefinition", - index=models.Index( - fields=["tenant", "metric_type"], name="analytics_m_tenant__74f857_idx" - ), - ), - migrations.AddIndex( - model_name="metricdefinition", - index=models.Index( - fields=["tenant", "aggregation_period"], - name="analytics_m_tenant__95594d_idx", - ), - ), - migrations.AddIndex( - model_name="metricdefinition", - index=models.Index( - fields=["tenant", "is_active"], name="analytics_m_tenant__fed8ae_idx" - ), - ), - migrations.AlterUniqueTogether( - name="metricdefinition", - unique_together={("tenant", "name")}, - ), - migrations.AddIndex( - model_name="metricvalue", - index=models.Index( - fields=["metric_definition", "period_start"], - name="analytics_m_metric__20f4a3_idx", - ), - ), - migrations.AddIndex( - model_name="metricvalue", - index=models.Index( - fields=["metric_definition", "period_end"], - name="analytics_m_metric__eca5ed_idx", - ), - ), - migrations.AddIndex( - model_name="metricvalue", - index=models.Index( - fields=["period_start", "period_end"], - name="analytics_m_period__286467_idx", - ), - ), - migrations.AddIndex( - model_name="metricvalue", - index=models.Index( - fields=["calculation_timestamp"], name="analytics_m_calcula_c2ca26_idx" - ), - ), - migrations.AlterUniqueTogether( - name="metricvalue", - unique_together={("metric_definition", "period_start", "period_end")}, - ), - migrations.AddIndex( - model_name="report", - index=models.Index( - fields=["tenant", "report_type"], name="analytics_r_tenant__9818e0_idx" - ), - ), - migrations.AddIndex( - model_name="report", - index=models.Index( - fields=["tenant", "schedule_type"], - name="analytics_r_tenant__6d4012_idx", - ), - ), - migrations.AddIndex( - model_name="report", - index=models.Index( - fields=["tenant", "next_execution"], - name="analytics_r_tenant__832dfb_idx", - ), - ), - migrations.AddIndex( - model_name="report", - index=models.Index( - fields=["tenant", "is_active"], name="analytics_r_tenant__88f6f3_idx" - ), - ), - migrations.AlterUniqueTogether( - name="report", - unique_together={("tenant", "name")}, - ), - migrations.AddIndex( - model_name="reportexecution", - index=models.Index( - fields=["report", "status"], name="analytics_r_report__db5768_idx" - ), - ), - migrations.AddIndex( - model_name="reportexecution", - index=models.Index( - fields=["report", "started_at"], name="analytics_r_report__be32b5_idx" - ), - ), - migrations.AddIndex( - model_name="reportexecution", - index=models.Index( - fields=["status", "started_at"], name="analytics_r_status_294e23_idx" - ), - ), - ] diff --git a/analytics/migrations/__pycache__/0001_initial.cpython-312.pyc b/analytics/migrations/__pycache__/0001_initial.cpython-312.pyc deleted file mode 100644 index 9d3fe860..00000000 Binary files a/analytics/migrations/__pycache__/0001_initial.cpython-312.pyc and /dev/null differ diff --git a/analytics/migrations/__pycache__/0002_initial.cpython-312.pyc b/analytics/migrations/__pycache__/0002_initial.cpython-312.pyc deleted file mode 100644 index fe9c3935..00000000 Binary files a/analytics/migrations/__pycache__/0002_initial.cpython-312.pyc and /dev/null differ diff --git a/appointments/migrations/0001_initial.py b/appointments/migrations/0001_initial.py deleted file mode 100644 index 8067daa9..00000000 --- a/appointments/migrations/0001_initial.py +++ /dev/null @@ -1,1568 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.core.validators -import django.db.models.deletion -import uuid -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="AppointmentTemplate", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(help_text="Template name", max_length=200)), - ( - "description", - models.TextField( - blank=True, help_text="Template description", null=True - ), - ), - ( - "appointment_type", - models.CharField( - help_text="Default appointment type", max_length=50 - ), - ), - ( - "specialty", - models.CharField(help_text="Medical specialty", max_length=100), - ), - ( - "duration_minutes", - models.PositiveIntegerField( - help_text="Default duration in minutes" - ), - ), - ( - "advance_booking_days", - models.PositiveIntegerField( - default=30, help_text="Maximum advance booking days" - ), - ), - ( - "minimum_notice_hours", - models.PositiveIntegerField( - default=24, help_text="Minimum notice required in hours" - ), - ), - ( - "insurance_verification_required", - models.BooleanField( - default=False, help_text="Insurance verification required" - ), - ), - ( - "authorization_required", - models.BooleanField( - default=False, help_text="Prior authorization required" - ), - ), - ( - "pre_appointment_instructions", - models.TextField( - blank=True, - help_text="Pre-appointment instructions for patient", - null=True, - ), - ), - ( - "post_appointment_instructions", - models.TextField( - blank=True, - help_text="Post-appointment instructions template", - null=True, - ), - ), - ( - "required_forms", - models.JSONField( - blank=True, - default=list, - help_text="Required forms for this appointment type", - ), - ), - ( - "is_active", - models.BooleanField(default=True, help_text="Template is active"), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Appointment Template", - "verbose_name_plural": "Appointment Templates", - "db_table": "appointments_appointment_template", - "ordering": ["specialty", "name"], - }, - ), - migrations.CreateModel( - name="QueueEntry", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "entry_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique entry identifier", - unique=True, - ), - ), - ( - "queue_position", - models.PositiveIntegerField(help_text="Position in queue"), - ), - ( - "priority_score", - models.FloatField( - default=1.0, help_text="Priority score for queue ordering" - ), - ), - ( - "joined_at", - models.DateTimeField( - auto_now_add=True, help_text="Time patient joined queue" - ), - ), - ( - "estimated_service_time", - models.DateTimeField( - blank=True, help_text="Estimated service time", null=True - ), - ), - ( - "called_at", - models.DateTimeField( - blank=True, help_text="Time patient was called", null=True - ), - ), - ( - "served_at", - models.DateTimeField( - blank=True, help_text="Time patient was served", null=True - ), - ), - ( - "status", - models.CharField( - choices=[ - ("WAITING", "Waiting"), - ("CALLED", "Called"), - ("IN_SERVICE", "In Service"), - ("COMPLETED", "Completed"), - ("LEFT", "Left Queue"), - ("NO_SHOW", "No Show"), - ], - default="WAITING", - help_text="Queue entry status", - max_length=20, - ), - ), - ( - "notification_sent", - models.BooleanField( - default=False, help_text="Notification sent to patient" - ), - ), - ( - "notification_method", - models.CharField( - blank=True, - choices=[ - ("SMS", "SMS"), - ("EMAIL", "Email"), - ("PHONE", "Phone Call"), - ("PAGER", "Pager"), - ("APP", "Mobile App"), - ], - help_text="Notification method used", - max_length=20, - null=True, - ), - ), - ( - "notes", - models.TextField( - blank=True, help_text="Additional notes", null=True - ), - ), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Queue Entry", - "verbose_name_plural": "Queue Entries", - "db_table": "appointments_queue_entry", - "ordering": ["queue", "priority_score", "joined_at"], - }, - ), - migrations.CreateModel( - name="SlotAvailability", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "slot_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique slot identifier", - unique=True, - ), - ), - ("date", models.DateField(help_text="Availability date")), - ("start_time", models.TimeField(help_text="Slot start time")), - ("end_time", models.TimeField(help_text="Slot end time")), - ( - "duration_minutes", - models.PositiveIntegerField(help_text="Slot duration in minutes"), - ), - ( - "availability_type", - models.CharField( - choices=[ - ("REGULAR", "Regular Hours"), - ("EXTENDED", "Extended Hours"), - ("EMERGENCY", "Emergency"), - ("ON_CALL", "On Call"), - ("TELEMEDICINE", "Telemedicine Only"), - ], - default="REGULAR", - help_text="Type of availability", - max_length=20, - ), - ), - ( - "max_appointments", - models.PositiveIntegerField( - default=1, help_text="Maximum appointments for this slot" - ), - ), - ( - "booked_appointments", - models.PositiveIntegerField( - default=0, help_text="Number of booked appointments" - ), - ), - ( - "location", - models.CharField(help_text="Appointment location", max_length=200), - ), - ( - "room_number", - models.CharField( - blank=True, help_text="Room number", max_length=50, null=True - ), - ), - ( - "specialty", - models.CharField( - help_text="Medical specialty for this slot", max_length=100 - ), - ), - ( - "appointment_types", - models.JSONField( - default=list, - help_text="Allowed appointment types for this slot", - ), - ), - ( - "patient_restrictions", - models.JSONField( - blank=True, - default=dict, - help_text="Patient restrictions (age, gender, etc.)", - ), - ), - ( - "insurance_restrictions", - models.JSONField( - blank=True, default=list, help_text="Accepted insurance types" - ), - ), - ( - "supports_telemedicine", - models.BooleanField( - default=False, - help_text="Slot supports telemedicine appointments", - ), - ), - ( - "telemedicine_only", - models.BooleanField( - default=False, help_text="Telemedicine only slot" - ), - ), - ( - "is_active", - models.BooleanField( - default=True, help_text="Slot is active and bookable" - ), - ), - ( - "is_blocked", - models.BooleanField( - default=False, help_text="Slot is temporarily blocked" - ), - ), - ( - "block_reason", - models.CharField( - blank=True, - help_text="Reason for blocking slot", - max_length=200, - null=True, - ), - ), - ( - "is_recurring", - models.BooleanField( - default=False, help_text="Slot is part of recurring pattern" - ), - ), - ( - "recurrence_pattern", - models.JSONField( - blank=True, - default=dict, - help_text="Recurrence pattern configuration", - ), - ), - ( - "recurrence_end_date", - models.DateField( - blank=True, - help_text="End date for recurring pattern", - null=True, - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Slot Availability", - "verbose_name_plural": "Slot Availability", - "db_table": "appointments_slot_availability", - "ordering": ["date", "start_time"], - }, - ), - migrations.CreateModel( - name="TelemedicineSession", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "session_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique session identifier", - unique=True, - ), - ), - ( - "platform", - models.CharField( - choices=[ - ("ZOOM", "Zoom"), - ("TEAMS", "Microsoft Teams"), - ("WEBEX", "Cisco Webex"), - ("DOXY", "Doxy.me"), - ("CUSTOM", "Custom Platform"), - ("OTHER", "Other"), - ], - help_text="Telemedicine platform", - max_length=50, - ), - ), - ("meeting_url", models.URLField(help_text="Meeting URL")), - ( - "meeting_id", - models.CharField( - help_text="Meeting ID or room number", max_length=100 - ), - ), - ( - "meeting_password", - models.CharField( - blank=True, - help_text="Meeting password", - max_length=100, - null=True, - ), - ), - ( - "waiting_room_enabled", - models.BooleanField(default=True, help_text="Waiting room enabled"), - ), - ( - "recording_enabled", - models.BooleanField( - default=False, help_text="Session recording enabled" - ), - ), - ( - "recording_consent", - models.BooleanField( - default=False, help_text="Patient consent for recording" - ), - ), - ( - "encryption_enabled", - models.BooleanField( - default=True, help_text="End-to-end encryption enabled" - ), - ), - ( - "password_required", - models.BooleanField( - default=True, help_text="Password required to join" - ), - ), - ( - "status", - models.CharField( - choices=[ - ("SCHEDULED", "Scheduled"), - ("READY", "Ready to Start"), - ("WAITING", "Waiting"), - ("IN_PROGRESS", "In Progress"), - ("COMPLETED", "Completed"), - ("CANCELLED", "Cancelled"), - ("FAILED", "Failed"), - ], - default="SCHEDULED", - help_text="Session status", - max_length=20, - ), - ), - ( - "scheduled_start", - models.DateTimeField(help_text="Scheduled start time"), - ), - ("scheduled_end", models.DateTimeField(help_text="Scheduled end time")), - ( - "actual_start", - models.DateTimeField( - blank=True, help_text="Actual start time", null=True - ), - ), - ( - "actual_end", - models.DateTimeField( - blank=True, help_text="Actual end time", null=True - ), - ), - ( - "provider_joined_at", - models.DateTimeField( - blank=True, help_text="Provider join time", null=True - ), - ), - ( - "patient_joined_at", - models.DateTimeField( - blank=True, help_text="Patient join time", null=True - ), - ), - ( - "connection_quality", - models.CharField( - blank=True, - choices=[ - ("EXCELLENT", "Excellent"), - ("GOOD", "Good"), - ("FAIR", "Fair"), - ("POOR", "Poor"), - ], - help_text="Connection quality", - max_length=20, - null=True, - ), - ), - ( - "technical_issues", - models.TextField( - blank=True, help_text="Technical issues encountered", null=True - ), - ), - ( - "recording_url", - models.URLField(blank=True, help_text="Recording URL", null=True), - ), - ( - "recording_duration_minutes", - models.PositiveIntegerField( - blank=True, help_text="Recording duration in minutes", null=True - ), - ), - ( - "session_notes", - models.TextField(blank=True, help_text="Session notes", null=True), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Telemedicine Session", - "verbose_name_plural": "Telemedicine Sessions", - "db_table": "appointments_telemedicine_session", - "ordering": ["-scheduled_start"], - }, - ), - migrations.CreateModel( - name="WaitingList", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "waiting_list_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique waiting list entry identifier", - unique=True, - ), - ), - ( - "appointment_type", - models.CharField( - choices=[ - ("CONSULTATION", "Consultation"), - ("FOLLOW_UP", "Follow-up"), - ("PROCEDURE", "Procedure"), - ("SURGERY", "Surgery"), - ("DIAGNOSTIC", "Diagnostic"), - ("THERAPY", "Therapy"), - ("VACCINATION", "Vaccination"), - ("SCREENING", "Screening"), - ("EMERGENCY", "Emergency"), - ("TELEMEDICINE", "Telemedicine"), - ("OTHER", "Other"), - ], - help_text="Type of appointment requested", - max_length=50, - ), - ), - ( - "specialty", - models.CharField( - choices=[ - ("FAMILY_MEDICINE", "Family Medicine"), - ("INTERNAL_MEDICINE", "Internal Medicine"), - ("PEDIATRICS", "Pediatrics"), - ("CARDIOLOGY", "Cardiology"), - ("DERMATOLOGY", "Dermatology"), - ("ENDOCRINOLOGY", "Endocrinology"), - ("GASTROENTEROLOGY", "Gastroenterology"), - ("NEUROLOGY", "Neurology"), - ("ONCOLOGY", "Oncology"), - ("ORTHOPEDICS", "Orthopedics"), - ("PSYCHIATRY", "Psychiatry"), - ("RADIOLOGY", "Radiology"), - ("SURGERY", "Surgery"), - ("UROLOGY", "Urology"), - ("GYNECOLOGY", "Gynecology"), - ("OPHTHALMOLOGY", "Ophthalmology"), - ("ENT", "Ear, Nose & Throat"), - ("EMERGENCY", "Emergency Medicine"), - ("OTHER", "Other"), - ], - help_text="Medical specialty required", - max_length=100, - ), - ), - ( - "priority", - models.CharField( - choices=[ - ("ROUTINE", "Routine"), - ("URGENT", "Urgent"), - ("STAT", "STAT"), - ("EMERGENCY", "Emergency"), - ], - default="ROUTINE", - help_text="Clinical priority level", - max_length=20, - ), - ), - ( - "urgency_score", - models.PositiveIntegerField( - default=1, - help_text="Clinical urgency score (1-10, 10 being most urgent)", - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(10), - ], - ), - ), - ( - "clinical_indication", - models.TextField( - help_text="Clinical reason for appointment request" - ), - ), - ( - "diagnosis_codes", - models.JSONField( - blank=True, default=list, help_text="ICD-10 diagnosis codes" - ), - ), - ( - "preferred_date", - models.DateField( - blank=True, - help_text="Patient preferred appointment date", - null=True, - ), - ), - ( - "preferred_time", - models.TimeField( - blank=True, - help_text="Patient preferred appointment time", - null=True, - ), - ), - ( - "flexible_scheduling", - models.BooleanField( - default=True, - help_text="Patient accepts alternative dates/times", - ), - ), - ( - "earliest_acceptable_date", - models.DateField( - blank=True, - help_text="Earliest acceptable appointment date", - null=True, - ), - ), - ( - "latest_acceptable_date", - models.DateField( - blank=True, - help_text="Latest acceptable appointment date", - null=True, - ), - ), - ( - "acceptable_days", - models.JSONField( - blank=True, - default=list, - help_text="Acceptable days of week (0=Monday, 6=Sunday)", - null=True, - ), - ), - ( - "acceptable_times", - models.JSONField( - blank=True, default=list, help_text="Acceptable time ranges" - ), - ), - ( - "contact_method", - models.CharField( - choices=[ - ("PHONE", "Phone"), - ("EMAIL", "Email"), - ("SMS", "SMS"), - ("PORTAL", "Patient Portal"), - ("MAIL", "Mail"), - ], - default="PHONE", - help_text="Preferred contact method", - max_length=20, - ), - ), - ( - "contact_phone", - models.CharField( - blank=True, - help_text="Contact phone number", - max_length=20, - null=True, - ), - ), - ( - "contact_email", - models.EmailField( - blank=True, - help_text="Contact email address", - max_length=254, - null=True, - ), - ), - ( - "status", - models.CharField( - choices=[ - ("ACTIVE", "Active"), - ("CONTACTED", "Contacted"), - ("OFFERED", "Appointment Offered"), - ("SCHEDULED", "Scheduled"), - ("CANCELLED", "Cancelled"), - ("EXPIRED", "Expired"), - ("TRANSFERRED", "Transferred"), - ], - default="ACTIVE", - help_text="Waiting list status", - max_length=20, - ), - ), - ( - "position", - models.PositiveIntegerField( - blank=True, - help_text="Position in waiting list queue", - null=True, - ), - ), - ( - "estimated_wait_time", - models.PositiveIntegerField( - blank=True, help_text="Estimated wait time in days", null=True - ), - ), - ( - "last_contacted", - models.DateTimeField( - blank=True, - help_text="Last contact attempt date/time", - null=True, - ), - ), - ( - "contact_attempts", - models.PositiveIntegerField( - default=0, help_text="Number of contact attempts made" - ), - ), - ( - "max_contact_attempts", - models.PositiveIntegerField( - default=3, help_text="Maximum contact attempts before expiring" - ), - ), - ( - "appointments_offered", - models.PositiveIntegerField( - default=0, help_text="Number of appointments offered" - ), - ), - ( - "appointments_declined", - models.PositiveIntegerField( - default=0, help_text="Number of appointments declined" - ), - ), - ( - "last_offer_date", - models.DateTimeField( - blank=True, - help_text="Date of last appointment offer", - null=True, - ), - ), - ( - "requires_interpreter", - models.BooleanField( - default=False, help_text="Patient requires interpreter services" - ), - ), - ( - "interpreter_language", - models.CharField( - blank=True, - help_text="Required interpreter language", - max_length=50, - null=True, - ), - ), - ( - "accessibility_requirements", - models.TextField( - blank=True, - help_text="Special accessibility requirements", - null=True, - ), - ), - ( - "transportation_needed", - models.BooleanField( - default=False, - help_text="Patient needs transportation assistance", - ), - ), - ( - "insurance_verified", - models.BooleanField( - default=False, help_text="Insurance coverage verified" - ), - ), - ( - "authorization_required", - models.BooleanField( - default=False, help_text="Prior authorization required" - ), - ), - ( - "authorization_status", - models.CharField( - choices=[ - ("NOT_REQUIRED", "Not Required"), - ("PENDING", "Pending"), - ("APPROVED", "Approved"), - ("DENIED", "Denied"), - ("EXPIRED", "Expired"), - ], - default="NOT_REQUIRED", - help_text="Authorization status", - max_length=20, - ), - ), - ( - "authorization_number", - models.CharField( - blank=True, - help_text="Authorization number", - max_length=100, - null=True, - ), - ), - ( - "referring_provider", - models.CharField( - blank=True, - help_text="Referring provider name", - max_length=200, - null=True, - ), - ), - ( - "referral_date", - models.DateField( - blank=True, help_text="Date of referral", null=True - ), - ), - ( - "referral_urgency", - models.CharField( - choices=[ - ("ROUTINE", "Routine"), - ("URGENT", "Urgent"), - ("STAT", "STAT"), - ], - default="ROUTINE", - help_text="Referral urgency level", - max_length=20, - ), - ), - ( - "removal_reason", - models.CharField( - blank=True, - choices=[ - ("SCHEDULED", "Appointment Scheduled"), - ("PATIENT_CANCELLED", "Patient Cancelled"), - ("PROVIDER_CANCELLED", "Provider Cancelled"), - ("NO_RESPONSE", "No Response to Contact"), - ("INSURANCE_ISSUE", "Insurance Issue"), - ("TRANSFERRED", "Transferred to Another Provider"), - ("EXPIRED", "Entry Expired"), - ("DUPLICATE", "Duplicate Entry"), - ("OTHER", "Other"), - ], - help_text="Reason for removal from waiting list", - max_length=50, - null=True, - ), - ), - ( - "removal_notes", - models.TextField( - blank=True, - help_text="Additional notes about removal", - null=True, - ), - ), - ( - "removed_at", - models.DateTimeField( - blank=True, - help_text="Date/time removed from waiting list", - null=True, - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "notes", - models.TextField( - blank=True, help_text="Additional notes and comments", null=True - ), - ), - ], - options={ - "verbose_name": "Waiting List Entry", - "verbose_name_plural": "Waiting List Entries", - "db_table": "appointments_waiting_list", - "ordering": ["priority", "urgency_score", "created_at"], - }, - ), - migrations.CreateModel( - name="WaitingListContactLog", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "contact_date", - models.DateTimeField( - auto_now_add=True, help_text="Date and time of contact attempt" - ), - ), - ( - "contact_method", - models.CharField( - choices=[ - ("PHONE", "Phone Call"), - ("EMAIL", "Email"), - ("SMS", "SMS"), - ("PORTAL", "Patient Portal Message"), - ("MAIL", "Mail"), - ("IN_PERSON", "In Person"), - ], - help_text="Method of contact used", - max_length=20, - ), - ), - ( - "contact_outcome", - models.CharField( - choices=[ - ("SUCCESSFUL", "Successful Contact"), - ("NO_ANSWER", "No Answer"), - ("BUSY", "Line Busy"), - ("VOICEMAIL", "Left Voicemail"), - ("EMAIL_SENT", "Email Sent"), - ("EMAIL_BOUNCED", "Email Bounced"), - ("SMS_SENT", "SMS Sent"), - ("SMS_FAILED", "SMS Failed"), - ("WRONG_NUMBER", "Wrong Number"), - ("DECLINED", "Patient Declined"), - ], - help_text="Outcome of contact attempt", - max_length=20, - ), - ), - ( - "appointment_offered", - models.BooleanField( - default=False, - help_text="Appointment was offered during contact", - ), - ), - ( - "offered_date", - models.DateField( - blank=True, help_text="Date of offered appointment", null=True - ), - ), - ( - "offered_time", - models.TimeField( - blank=True, help_text="Time of offered appointment", null=True - ), - ), - ( - "patient_response", - models.CharField( - blank=True, - choices=[ - ("ACCEPTED", "Accepted Appointment"), - ("DECLINED", "Declined Appointment"), - ("REQUESTED_DIFFERENT", "Requested Different Time"), - ("WILL_CALL_BACK", "Will Call Back"), - ("NO_LONGER_NEEDED", "No Longer Needed"), - ("INSURANCE_ISSUE", "Insurance Issue"), - ("NO_RESPONSE", "No Response"), - ], - help_text="Patient response to contact", - max_length=20, - null=True, - ), - ), - ( - "notes", - models.TextField( - blank=True, help_text="Notes from contact attempt", null=True - ), - ), - ( - "next_contact_date", - models.DateField( - blank=True, - help_text="Scheduled date for next contact attempt", - null=True, - ), - ), - ], - options={ - "verbose_name": "Waiting List Contact Log", - "verbose_name_plural": "Waiting List Contact Logs", - "db_table": "appointments_waiting_list_contact_log", - "ordering": ["-contact_date"], - }, - ), - migrations.CreateModel( - name="WaitingQueue", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "queue_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique queue identifier", - unique=True, - ), - ), - ("name", models.CharField(help_text="Queue name", max_length=200)), - ( - "description", - models.TextField( - blank=True, help_text="Queue description", null=True - ), - ), - ( - "queue_type", - models.CharField( - choices=[ - ("PROVIDER", "Provider Queue"), - ("SPECIALTY", "Specialty Queue"), - ("LOCATION", "Location Queue"), - ("PROCEDURE", "Procedure Queue"), - ("EMERGENCY", "Emergency Queue"), - ], - help_text="Type of queue", - max_length=20, - ), - ), - ( - "specialty", - models.CharField( - blank=True, - help_text="Medical specialty", - max_length=100, - null=True, - ), - ), - ( - "location", - models.CharField( - blank=True, - help_text="Queue location", - max_length=200, - null=True, - ), - ), - ( - "max_queue_size", - models.PositiveIntegerField( - default=50, help_text="Maximum queue size" - ), - ), - ( - "average_service_time_minutes", - models.PositiveIntegerField( - default=30, help_text="Average service time in minutes" - ), - ), - ( - "priority_weights", - models.JSONField( - default=dict, help_text="Priority weights for queue ordering" - ), - ), - ( - "is_active", - models.BooleanField(default=True, help_text="Queue is active"), - ), - ( - "is_accepting_patients", - models.BooleanField( - default=True, help_text="Queue is accepting new patients" - ), - ), - ( - "operating_hours", - models.JSONField( - default=dict, help_text="Queue operating hours by day" - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Waiting Queue", - "verbose_name_plural": "Waiting Queues", - "db_table": "appointments_waiting_queue", - "ordering": ["name"], - }, - ), - migrations.CreateModel( - name="AppointmentRequest", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "request_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique appointment request identifier", - unique=True, - ), - ), - ( - "appointment_type", - models.CharField( - choices=[ - ("CONSULTATION", "Consultation"), - ("FOLLOW_UP", "Follow-up"), - ("PROCEDURE", "Procedure"), - ("SURGERY", "Surgery"), - ("DIAGNOSTIC", "Diagnostic"), - ("THERAPY", "Therapy"), - ("VACCINATION", "Vaccination"), - ("SCREENING", "Screening"), - ("EMERGENCY", "Emergency"), - ("TELEMEDICINE", "Telemedicine"), - ("OTHER", "Other"), - ], - help_text="Type of appointment", - max_length=50, - ), - ), - ( - "specialty", - models.CharField( - choices=[ - ("FAMILY_MEDICINE", "Family Medicine"), - ("INTERNAL_MEDICINE", "Internal Medicine"), - ("PEDIATRICS", "Pediatrics"), - ("CARDIOLOGY", "Cardiology"), - ("DERMATOLOGY", "Dermatology"), - ("ENDOCRINOLOGY", "Endocrinology"), - ("GASTROENTEROLOGY", "Gastroenterology"), - ("NEUROLOGY", "Neurology"), - ("ONCOLOGY", "Oncology"), - ("ORTHOPEDICS", "Orthopedics"), - ("PSYCHIATRY", "Psychiatry"), - ("RADIOLOGY", "Radiology"), - ("SURGERY", "Surgery"), - ("UROLOGY", "Urology"), - ("GYNECOLOGY", "Gynecology"), - ("OPHTHALMOLOGY", "Ophthalmology"), - ("ENT", "Ear, Nose & Throat"), - ("EMERGENCY", "Emergency Medicine"), - ("OTHER", "Other"), - ], - help_text="Medical specialty", - max_length=100, - ), - ), - ( - "preferred_date", - models.DateField(help_text="Patient preferred appointment date"), - ), - ( - "preferred_time", - models.TimeField( - blank=True, - help_text="Patient preferred appointment time", - null=True, - ), - ), - ( - "duration_minutes", - models.PositiveIntegerField( - default=30, - help_text="Appointment duration in minutes", - validators=[ - django.core.validators.MinValueValidator(15), - django.core.validators.MaxValueValidator(480), - ], - ), - ), - ( - "flexible_scheduling", - models.BooleanField( - default=True, help_text="Patient accepts alternative times" - ), - ), - ( - "earliest_acceptable_date", - models.DateField( - blank=True, - help_text="Earliest acceptable appointment date", - null=True, - ), - ), - ( - "latest_acceptable_date", - models.DateField( - blank=True, - help_text="Latest acceptable appointment date", - null=True, - ), - ), - ( - "acceptable_times", - models.JSONField( - blank=True, - default=list, - help_text="Acceptable time slots (JSON array)", - ), - ), - ( - "priority", - models.CharField( - choices=[ - ("ROUTINE", "Routine"), - ("URGENT", "Urgent"), - ("STAT", "STAT"), - ("EMERGENCY", "Emergency"), - ], - default="ROUTINE", - help_text="Appointment priority", - max_length=20, - ), - ), - ( - "urgency_score", - models.PositiveIntegerField( - default=1, - help_text="Urgency score (1-10, 10 being most urgent)", - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(10), - ], - ), - ), - ( - "chief_complaint", - models.TextField( - help_text="Patient chief complaint or reason for visit" - ), - ), - ( - "clinical_notes", - models.TextField( - blank=True, help_text="Additional clinical notes", null=True - ), - ), - ( - "referring_provider", - models.CharField( - blank=True, - help_text="Referring provider name", - max_length=200, - null=True, - ), - ), - ( - "insurance_verified", - models.BooleanField( - default=False, help_text="Insurance coverage verified" - ), - ), - ( - "authorization_required", - models.BooleanField( - default=False, help_text="Prior authorization required" - ), - ), - ( - "authorization_number", - models.CharField( - blank=True, - help_text="Authorization number", - max_length=100, - null=True, - ), - ), - ( - "status", - models.CharField( - choices=[ - ("PENDING", "Pending"), - ("SCHEDULED", "Scheduled"), - ("CONFIRMED", "Confirmed"), - ("CHECKED_IN", "Checked In"), - ("IN_PROGRESS", "In Progress"), - ("COMPLETED", "Completed"), - ("CANCELLED", "Cancelled"), - ("NO_SHOW", "No Show"), - ("RESCHEDULED", "Rescheduled"), - ], - default="PENDING", - help_text="Appointment status", - max_length=20, - ), - ), - ( - "scheduled_datetime", - models.DateTimeField( - blank=True, - help_text="Scheduled appointment date and time", - null=True, - ), - ), - ( - "scheduled_end_datetime", - models.DateTimeField( - blank=True, - help_text="Scheduled appointment end time", - null=True, - ), - ), - ( - "location", - models.CharField( - blank=True, - help_text="Appointment location", - max_length=200, - null=True, - ), - ), - ( - "room_number", - models.CharField( - blank=True, help_text="Room number", max_length=50, null=True - ), - ), - ( - "is_telemedicine", - models.BooleanField( - default=False, help_text="Telemedicine appointment" - ), - ), - ( - "telemedicine_platform", - models.CharField( - blank=True, - choices=[ - ("ZOOM", "Zoom"), - ("TEAMS", "Microsoft Teams"), - ("WEBEX", "Cisco Webex"), - ("DOXY", "Doxy.me"), - ("CUSTOM", "Custom Platform"), - ("OTHER", "Other"), - ], - help_text="Telemedicine platform", - max_length=50, - null=True, - ), - ), - ( - "meeting_url", - models.URLField( - blank=True, help_text="Telemedicine meeting URL", null=True - ), - ), - ( - "meeting_id", - models.CharField( - blank=True, - help_text="Meeting ID or room number", - max_length=100, - null=True, - ), - ), - ( - "meeting_password", - models.CharField( - blank=True, - help_text="Meeting password", - max_length=100, - null=True, - ), - ), - ( - "checked_in_at", - models.DateTimeField( - blank=True, help_text="Patient check-in time", null=True - ), - ), - ( - "completed_at", - models.DateTimeField( - blank=True, help_text="Appointment completion time", null=True - ), - ), - ( - "actual_duration_minutes", - models.PositiveIntegerField( - blank=True, help_text="Actual appointment duration", null=True - ), - ), - ( - "cancelled_at", - models.DateTimeField( - blank=True, help_text="Cancellation timestamp", null=True - ), - ), - ( - "cancellation_reason", - models.TextField( - blank=True, help_text="Reason for cancellation", null=True - ), - ), - ( - "reminder_preferences", - models.JSONField( - blank=True, - default=dict, - help_text="Reminder preferences (email, SMS, phone)", - ), - ), - ( - "special_requirements", - models.TextField( - blank=True, - help_text="Special requirements or accommodations", - null=True, - ), - ), - ( - "interpreter_needed", - models.BooleanField( - default=False, help_text="Interpreter services needed" - ), - ), - ( - "interpreter_language", - models.CharField( - blank=True, - help_text="Required interpreter language", - max_length=50, - null=True, - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "cancelled_by", - models.ForeignKey( - blank=True, - help_text="User who cancelled appointment", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="cancelled_appointments", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "checked_in_by", - models.ForeignKey( - blank=True, - help_text="Staff member who checked in patient", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="checked_in_appointments", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "created_by", - models.ForeignKey( - blank=True, - help_text="User who created the appointment request", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_appointments", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "verbose_name": "Appointment Request", - "verbose_name_plural": "Appointment Requests", - "db_table": "appointments_appointment_request", - "ordering": ["-created_at"], - }, - ), - ] diff --git a/appointments/migrations/0002_initial.py b/appointments/migrations/0002_initial.py deleted file mode 100644 index 8f7f2d9c..00000000 --- a/appointments/migrations/0002_initial.py +++ /dev/null @@ -1,526 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("appointments", "0001_initial"), - ("core", "0001_initial"), - ("hr", "0001_initial"), - ("patients", "0001_initial"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name="appointmentrequest", - name="patient", - field=models.ForeignKey( - help_text="Patient requesting appointment", - on_delete=django.db.models.deletion.CASCADE, - related_name="appointment_requests", - to="patients.patientprofile", - ), - ), - migrations.AddField( - model_name="appointmentrequest", - name="provider", - field=models.ForeignKey( - help_text="Healthcare provider", - on_delete=django.db.models.deletion.CASCADE, - related_name="provider_appointments", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="appointmentrequest", - name="rescheduled_from", - field=models.ForeignKey( - blank=True, - help_text="Original appointment if rescheduled", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="rescheduled_appointments", - to="appointments.appointmentrequest", - ), - ), - migrations.AddField( - model_name="appointmentrequest", - name="tenant", - field=models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="appointment_requests", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="appointmenttemplate", - name="created_by", - field=models.ForeignKey( - blank=True, - help_text="User who created the template", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_appointment_templates", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="appointmenttemplate", - name="tenant", - field=models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="appointment_templates", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="queueentry", - name="appointment", - field=models.ForeignKey( - help_text="Associated appointment", - on_delete=django.db.models.deletion.CASCADE, - related_name="queue_entries", - to="appointments.appointmentrequest", - ), - ), - migrations.AddField( - model_name="queueentry", - name="assigned_provider", - field=models.ForeignKey( - blank=True, - help_text="Assigned provider", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="assigned_queue_entries", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="queueentry", - name="patient", - field=models.ForeignKey( - help_text="Patient in queue", - on_delete=django.db.models.deletion.CASCADE, - related_name="queue_entries", - to="patients.patientprofile", - ), - ), - migrations.AddField( - model_name="queueentry", - name="updated_by", - field=models.ForeignKey( - blank=True, - help_text="User who last updated entry", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="updated_queue_entries", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="slotavailability", - name="created_by", - field=models.ForeignKey( - blank=True, - help_text="User who created the slot", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_availability_slots", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="slotavailability", - name="provider", - field=models.ForeignKey( - help_text="Healthcare provider", - on_delete=django.db.models.deletion.CASCADE, - related_name="availability_slots", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="slotavailability", - name="tenant", - field=models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="availability_slots", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="telemedicinesession", - name="appointment", - field=models.OneToOneField( - help_text="Associated appointment", - on_delete=django.db.models.deletion.CASCADE, - related_name="telemedicine_session", - to="appointments.appointmentrequest", - ), - ), - migrations.AddField( - model_name="telemedicinesession", - name="created_by", - field=models.ForeignKey( - blank=True, - help_text="User who created the session", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_telemedicine_sessions", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="waitinglist", - name="created_by", - field=models.ForeignKey( - blank=True, - help_text="User who created the waiting list entry", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_waiting_list_entries", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="waitinglist", - name="department", - field=models.ForeignKey( - help_text="Department for appointment", - on_delete=django.db.models.deletion.CASCADE, - related_name="waiting_list_entries", - to="hr.department", - ), - ), - migrations.AddField( - model_name="waitinglist", - name="patient", - field=models.ForeignKey( - help_text="Patient on waiting list", - on_delete=django.db.models.deletion.CASCADE, - related_name="waiting_list_entries", - to="patients.patientprofile", - ), - ), - migrations.AddField( - model_name="waitinglist", - name="provider", - field=models.ForeignKey( - blank=True, - help_text="Preferred healthcare provider", - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="provider_waiting_list", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="waitinglist", - name="removed_by", - field=models.ForeignKey( - blank=True, - help_text="User who removed entry from waiting list", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="removed_waiting_list_entries", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="waitinglist", - name="scheduled_appointment", - field=models.ForeignKey( - blank=True, - help_text="Scheduled appointment from waiting list", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="waiting_list_entry", - to="appointments.appointmentrequest", - ), - ), - migrations.AddField( - model_name="waitinglist", - name="tenant", - field=models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="waiting_list_entries", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="waitinglistcontactlog", - name="contacted_by", - field=models.ForeignKey( - blank=True, - help_text="Staff member who made contact", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="waitinglistcontactlog", - name="waiting_list_entry", - field=models.ForeignKey( - help_text="Associated waiting list entry", - on_delete=django.db.models.deletion.CASCADE, - related_name="contact_logs", - to="appointments.waitinglist", - ), - ), - migrations.AddField( - model_name="waitingqueue", - name="created_by", - field=models.ForeignKey( - blank=True, - help_text="User who created the queue", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_waiting_queues", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="waitingqueue", - name="providers", - field=models.ManyToManyField( - blank=True, - help_text="Providers associated with this queue", - related_name="waiting_queues", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="waitingqueue", - name="tenant", - field=models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="waiting_queues", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="queueentry", - name="queue", - field=models.ForeignKey( - help_text="Waiting queue", - on_delete=django.db.models.deletion.CASCADE, - related_name="queue_entries", - to="appointments.waitingqueue", - ), - ), - migrations.AddIndex( - model_name="appointmentrequest", - index=models.Index( - fields=["tenant", "status"], name="appointment_tenant__979096_idx" - ), - ), - migrations.AddIndex( - model_name="appointmentrequest", - index=models.Index( - fields=["patient", "status"], name="appointment_patient_803ab4_idx" - ), - ), - migrations.AddIndex( - model_name="appointmentrequest", - index=models.Index( - fields=["provider", "scheduled_datetime"], - name="appointment_provide_ef6955_idx", - ), - ), - migrations.AddIndex( - model_name="appointmentrequest", - index=models.Index( - fields=["scheduled_datetime"], name="appointment_schedul_8f6c0e_idx" - ), - ), - migrations.AddIndex( - model_name="appointmentrequest", - index=models.Index( - fields=["priority", "urgency_score"], - name="appointment_priorit_cdad1a_idx", - ), - ), - migrations.AddIndex( - model_name="appointmentrequest", - index=models.Index( - fields=["appointment_type", "specialty"], - name="appointment_appoint_49fcc4_idx", - ), - ), - migrations.AddIndex( - model_name="appointmenttemplate", - index=models.Index( - fields=["tenant", "specialty"], name="appointment_tenant__8f5ab7_idx" - ), - ), - migrations.AddIndex( - model_name="appointmenttemplate", - index=models.Index( - fields=["appointment_type"], name="appointment_appoint_da9846_idx" - ), - ), - migrations.AddIndex( - model_name="appointmenttemplate", - index=models.Index( - fields=["is_active"], name="appointment_is_acti_953e67_idx" - ), - ), - migrations.AddIndex( - model_name="slotavailability", - index=models.Index( - fields=["tenant", "provider", "date"], - name="appointment_tenant__d41564_idx", - ), - ), - migrations.AddIndex( - model_name="slotavailability", - index=models.Index( - fields=["date", "start_time"], name="appointment_date_e6d843_idx" - ), - ), - migrations.AddIndex( - model_name="slotavailability", - index=models.Index( - fields=["specialty"], name="appointment_special_158174_idx" - ), - ), - migrations.AddIndex( - model_name="slotavailability", - index=models.Index( - fields=["is_active", "is_blocked"], - name="appointment_is_acti_4bd0a5_idx", - ), - ), - migrations.AlterUniqueTogether( - name="slotavailability", - unique_together={("provider", "date", "start_time")}, - ), - migrations.AddIndex( - model_name="telemedicinesession", - index=models.Index( - fields=["appointment"], name="appointment_appoint_34f472_idx" - ), - ), - migrations.AddIndex( - model_name="telemedicinesession", - index=models.Index(fields=["status"], name="appointment_status_f49676_idx"), - ), - migrations.AddIndex( - model_name="telemedicinesession", - index=models.Index( - fields=["scheduled_start"], name="appointment_schedul_8a4e8e_idx" - ), - ), - migrations.AddIndex( - model_name="waitinglist", - index=models.Index( - fields=["tenant", "status"], name="appointment_tenant__a558da_idx" - ), - ), - migrations.AddIndex( - model_name="waitinglist", - index=models.Index( - fields=["patient", "status"], name="appointment_patient_73f03d_idx" - ), - ), - migrations.AddIndex( - model_name="waitinglist", - index=models.Index( - fields=["department", "specialty", "status"], - name="appointment_departm_78fd70_idx", - ), - ), - migrations.AddIndex( - model_name="waitinglist", - index=models.Index( - fields=["priority", "urgency_score"], - name="appointment_priorit_30fb90_idx", - ), - ), - migrations.AddIndex( - model_name="waitinglist", - index=models.Index( - fields=["status", "created_at"], name="appointment_status_cfe551_idx" - ), - ), - migrations.AddIndex( - model_name="waitinglist", - index=models.Index( - fields=["provider", "status"], name="appointment_provide_dd6c2b_idx" - ), - ), - migrations.AddIndex( - model_name="waitinglistcontactlog", - index=models.Index( - fields=["waiting_list_entry", "contact_date"], - name="appointment_waiting_50d8ac_idx", - ), - ), - migrations.AddIndex( - model_name="waitinglistcontactlog", - index=models.Index( - fields=["contact_outcome"], name="appointment_contact_ad9c45_idx" - ), - ), - migrations.AddIndex( - model_name="waitinglistcontactlog", - index=models.Index( - fields=["next_contact_date"], name="appointment_next_co_b29984_idx" - ), - ), - migrations.AddIndex( - model_name="waitingqueue", - index=models.Index( - fields=["tenant", "queue_type"], name="appointment_tenant__e21f2a_idx" - ), - ), - migrations.AddIndex( - model_name="waitingqueue", - index=models.Index( - fields=["specialty"], name="appointment_special_b50647_idx" - ), - ), - migrations.AddIndex( - model_name="waitingqueue", - index=models.Index( - fields=["is_active"], name="appointment_is_acti_e2f2a7_idx" - ), - ), - migrations.AddIndex( - model_name="queueentry", - index=models.Index( - fields=["queue", "status"], name="appointment_queue_i_d69f8c_idx" - ), - ), - migrations.AddIndex( - model_name="queueentry", - index=models.Index( - fields=["patient"], name="appointment_patient_beccf1_idx" - ), - ), - migrations.AddIndex( - model_name="queueentry", - index=models.Index( - fields=["priority_score"], name="appointment_priorit_48b785_idx" - ), - ), - migrations.AddIndex( - model_name="queueentry", - index=models.Index( - fields=["joined_at"], name="appointment_joined__709843_idx" - ), - ), - ] diff --git a/appointments/migrations/__pycache__/0001_initial.cpython-312.pyc b/appointments/migrations/__pycache__/0001_initial.cpython-312.pyc deleted file mode 100644 index 0339b5f2..00000000 Binary files a/appointments/migrations/__pycache__/0001_initial.cpython-312.pyc and /dev/null differ diff --git a/appointments/migrations/__pycache__/0002_initial.cpython-312.pyc b/appointments/migrations/__pycache__/0002_initial.cpython-312.pyc deleted file mode 100644 index 2b9f998d..00000000 Binary files a/appointments/migrations/__pycache__/0002_initial.cpython-312.pyc and /dev/null differ diff --git a/billing/migrations/0001_initial.py b/billing/migrations/0001_initial.py deleted file mode 100644 index 4a473009..00000000 --- a/billing/migrations/0001_initial.py +++ /dev/null @@ -1,1173 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import billing.utils -import django.db.models.deletion -import django.utils.timezone -import uuid -from decimal import Decimal -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="BillLineItem", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "line_item_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique line item identifier", - unique=True, - ), - ), - ( - "line_number", - models.PositiveIntegerField(help_text="Line item number"), - ), - ("service_date", models.DateField(help_text="Service date")), - ( - "service_code", - models.CharField( - help_text="Service code (CPT, HCPCS, etc.)", max_length=20 - ), - ), - ( - "service_description", - models.CharField(help_text="Service description", max_length=200), - ), - ( - "service_category", - models.CharField( - choices=[ - ("EVALUATION", "Evaluation & Management"), - ("SURGERY", "Surgery"), - ("RADIOLOGY", "Radiology"), - ("PATHOLOGY", "Pathology & Laboratory"), - ("MEDICINE", "Medicine"), - ("ANESTHESIA", "Anesthesia"), - ("SUPPLIES", "Medical Supplies"), - ("PHARMACY", "Pharmacy"), - ("ROOM_BOARD", "Room & Board"), - ("NURSING", "Nursing Services"), - ("THERAPY", "Therapy Services"), - ("EMERGENCY", "Emergency Services"), - ("AMBULANCE", "Ambulance Services"), - ("DME", "Durable Medical Equipment"), - ("OTHER", "Other Services"), - ], - help_text="Service category", - max_length=30, - ), - ), - ( - "quantity", - models.DecimalField( - decimal_places=2, - default=Decimal("1.00"), - help_text="Quantity of service", - max_digits=10, - ), - ), - ( - "unit_of_measure", - models.CharField( - choices=[ - ("EACH", "Each"), - ("UNIT", "Unit"), - ("HOUR", "Hour"), - ("DAY", "Day"), - ("VISIT", "Visit"), - ("PROCEDURE", "Procedure"), - ("DOSE", "Dose"), - ("MILE", "Mile"), - ("MINUTE", "Minute"), - ], - default="EACH", - help_text="Unit of measure", - max_length=20, - ), - ), - ( - "unit_price", - models.DecimalField( - decimal_places=2, help_text="Unit price", max_digits=10 - ), - ), - ( - "total_price", - models.DecimalField( - decimal_places=2, - help_text="Total price (quantity × unit price)", - max_digits=12, - ), - ), - ( - "modifier_1", - models.CharField( - blank=True, help_text="First modifier", max_length=5, null=True - ), - ), - ( - "modifier_2", - models.CharField( - blank=True, help_text="Second modifier", max_length=5, null=True - ), - ), - ( - "modifier_3", - models.CharField( - blank=True, help_text="Third modifier", max_length=5, null=True - ), - ), - ( - "modifier_4", - models.CharField( - blank=True, help_text="Fourth modifier", max_length=5, null=True - ), - ), - ( - "primary_diagnosis", - models.CharField( - blank=True, - help_text="Primary diagnosis code", - max_length=20, - null=True, - ), - ), - ( - "secondary_diagnoses", - models.JSONField( - default=list, help_text="Secondary diagnosis codes" - ), - ), - ( - "place_of_service", - models.IntegerField( - choices=[ - (11, "Office"), - (12, "Home"), - (21, "Inpatient Hospital"), - (22, "Outpatient Hospital"), - (23, "Emergency Room"), - (24, "Ambulatory Surgical Center"), - (25, "Birthing Center"), - (26, "Military Treatment Facility"), - (31, "Skilled Nursing Facility"), - (32, "Nursing Facility"), - (33, "Custodial Care Facility"), - (34, "Hospice"), - (41, "Ambulance - Land"), - (42, "Ambulance - Air or Water"), - (49, "Independent Clinic"), - (50, "Federally Qualified Health Center"), - (51, "Inpatient Psychiatric Facility"), - (52, "Psychiatric Facility-Partial Hospitalization"), - (53, "Community Mental Health Center"), - (54, "Intermediate Care Facility/Mentally Retarded"), - (55, "Residential Substance Abuse Treatment Facility"), - (56, "Psychiatric Residential Treatment Center"), - (57, "Non-residential Substance Abuse Treatment Facility"), - (60, "Mass Immunization Center"), - (61, "Comprehensive Inpatient Rehabilitation Facility"), - (62, "Comprehensive Outpatient Rehabilitation Facility"), - (65, "End-Stage Renal Disease Treatment Facility"), - (71, "Public Health Clinic"), - (72, "Rural Health Clinic"), - (81, "Independent Laboratory"), - (99, "Other Place of Service"), - ], - default=22, - help_text="Place of service code", - max_length=5, - ), - ), - ( - "revenue_code", - models.CharField( - blank=True, - help_text="Revenue code for facility billing", - max_length=4, - null=True, - ), - ), - ( - "ndc_code", - models.CharField( - blank=True, - help_text="National Drug Code", - max_length=20, - null=True, - ), - ), - ( - "drug_quantity", - models.DecimalField( - blank=True, - decimal_places=3, - help_text="Drug quantity", - max_digits=10, - null=True, - ), - ), - ( - "drug_unit", - models.CharField( - blank=True, - help_text="Drug unit of measure", - max_length=10, - null=True, - ), - ), - ( - "status", - models.CharField( - choices=[ - ("ACTIVE", "Active"), - ("DENIED", "Denied"), - ("ADJUSTED", "Adjusted"), - ("VOIDED", "Voided"), - ], - default="ACTIVE", - help_text="Line item status", - max_length=20, - ), - ), - ( - "notes", - models.TextField( - blank=True, help_text="Line item notes", null=True - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Bill Line Item", - "verbose_name_plural": "Bill Line Items", - "db_table": "billing_bill_line_item", - "ordering": ["line_number"], - }, - ), - migrations.CreateModel( - name="ClaimStatusUpdate", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "update_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique update identifier", - unique=True, - ), - ), - ( - "previous_status", - models.CharField(help_text="Previous claim status", max_length=20), - ), - ( - "new_status", - models.CharField(help_text="New claim status", max_length=20), - ), - ( - "status_date", - models.DateTimeField( - default=django.utils.timezone.now, - help_text="Status change date and time", - ), - ), - ( - "update_source", - models.CharField( - choices=[ - ("MANUAL", "Manual Update"), - ("EDI", "EDI Response"), - ("PHONE", "Phone Call"), - ("PORTAL", "Insurance Portal"), - ("EMAIL", "Email"), - ("FAX", "Fax"), - ("MAIL", "Mail"), - ("SYSTEM", "System Generated"), - ], - help_text="Update source", - max_length=20, - ), - ), - ( - "response_code", - models.CharField( - blank=True, - help_text="Insurance response code", - max_length=20, - null=True, - ), - ), - ( - "response_message", - models.TextField( - blank=True, help_text="Insurance response message", null=True - ), - ), - ( - "allowed_amount", - models.DecimalField( - blank=True, - decimal_places=2, - help_text="Updated allowed amount", - max_digits=12, - null=True, - ), - ), - ( - "paid_amount", - models.DecimalField( - blank=True, - decimal_places=2, - help_text="Updated paid amount", - max_digits=12, - null=True, - ), - ), - ( - "patient_responsibility", - models.DecimalField( - blank=True, - decimal_places=2, - help_text="Updated patient responsibility", - max_digits=12, - null=True, - ), - ), - ( - "notes", - models.TextField( - blank=True, help_text="Update notes and comments", null=True - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ], - options={ - "verbose_name": "Claim Status Update", - "verbose_name_plural": "Claim Status Updates", - "db_table": "billing_claim_status_update", - "ordering": ["-status_date"], - }, - ), - migrations.CreateModel( - name="InsuranceClaim", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "claim_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique claim identifier", - unique=True, - ), - ), - ( - "claim_number", - models.CharField( - help_text="Insurance claim number", max_length=30, unique=True - ), - ), - ( - "claim_type", - models.CharField( - choices=[ - ("PRIMARY", "Primary Claim"), - ("SECONDARY", "Secondary Claim"), - ("TERTIARY", "Tertiary Claim"), - ("CORRECTED", "Corrected Claim"), - ("VOID", "Void Claim"), - ("REPLACEMENT", "Replacement Claim"), - ], - default="PRIMARY", - help_text="Claim type", - max_length=20, - ), - ), - ( - "submission_date", - models.DateField(help_text="Claim submission date"), - ), - ("service_date_from", models.DateField(help_text="Service date from")), - ("service_date_to", models.DateField(help_text="Service date to")), - ( - "billed_amount", - models.DecimalField( - decimal_places=2, help_text="Total billed amount", max_digits=12 - ), - ), - ( - "allowed_amount", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Insurance allowed amount", - max_digits=12, - ), - ), - ( - "paid_amount", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Insurance paid amount", - max_digits=12, - ), - ), - ( - "patient_responsibility", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Patient responsibility amount", - max_digits=12, - ), - ), - ( - "deductible_amount", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Deductible amount", - max_digits=12, - ), - ), - ( - "coinsurance_amount", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Coinsurance amount", - max_digits=12, - ), - ), - ( - "copay_amount", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Copay amount", - max_digits=12, - ), - ), - ( - "status", - models.CharField( - choices=[ - ("DRAFT", "Draft"), - ("SUBMITTED", "Submitted"), - ("PENDING", "Pending"), - ("PROCESSING", "Processing"), - ("PAID", "Paid"), - ("DENIED", "Denied"), - ("REJECTED", "Rejected"), - ("APPEALED", "Appealed"), - ("VOIDED", "Voided"), - ], - default="DRAFT", - help_text="Claim status", - max_length=20, - ), - ), - ( - "clearinghouse", - models.CharField( - blank=True, - help_text="Clearinghouse used for submission", - max_length=100, - null=True, - ), - ), - ( - "batch_number", - models.CharField( - blank=True, help_text="Batch number", max_length=50, null=True - ), - ), - ( - "response_date", - models.DateField( - blank=True, help_text="Insurance response date", null=True - ), - ), - ( - "check_number", - models.CharField( - blank=True, - help_text="Insurance check number", - max_length=50, - null=True, - ), - ), - ( - "check_date", - models.DateField( - blank=True, help_text="Insurance check date", null=True - ), - ), - ( - "denial_reason", - models.CharField( - blank=True, help_text="Denial reason", max_length=200, null=True - ), - ), - ( - "denial_code", - models.CharField( - blank=True, help_text="Denial code", max_length=20, null=True - ), - ), - ( - "prior_auth_number", - models.CharField( - blank=True, - help_text="Prior authorization number", - max_length=50, - null=True, - ), - ), - ( - "notes", - models.TextField( - blank=True, help_text="Claim notes and comments", null=True - ), - ), - ( - "resubmission_count", - models.PositiveIntegerField( - default=0, help_text="Number of resubmissions" - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Insurance Claim", - "verbose_name_plural": "Insurance Claims", - "db_table": "billing_insurance_claim", - "ordering": ["-submission_date"], - }, - ), - migrations.CreateModel( - name="MedicalBill", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "bill_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique bill identifier", - unique=True, - ), - ), - ( - "bill_number", - models.CharField( - help_text="Medical bill number", max_length=20, unique=True - ), - ), - ( - "bill_type", - models.CharField( - choices=[ - ("INPATIENT", "Inpatient"), - ("OUTPATIENT", "Outpatient"), - ("EMERGENCY", "Emergency"), - ("SURGERY", "Surgery"), - ("LABORATORY", "Laboratory"), - ("RADIOLOGY", "Radiology"), - ("PHARMACY", "Pharmacy"), - ("PROFESSIONAL", "Professional Services"), - ("FACILITY", "Facility Charges"), - ("ANCILLARY", "Ancillary Services"), - ], - help_text="Bill type", - max_length=20, - ), - ), - ("service_date_from", models.DateField(help_text="Service date from")), - ("service_date_to", models.DateField(help_text="Service date to")), - ( - "bill_date", - models.DateField( - default=django.utils.timezone.now, help_text="Bill date" - ), - ), - ("due_date", models.DateField(help_text="Payment due date")), - ( - "subtotal", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Subtotal amount", - max_digits=12, - ), - ), - ( - "tax_amount", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Tax amount", - max_digits=12, - ), - ), - ( - "discount_amount", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Discount amount", - max_digits=12, - ), - ), - ( - "adjustment_amount", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Adjustment amount", - max_digits=12, - ), - ), - ( - "total_amount", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Total bill amount", - max_digits=12, - ), - ), - ( - "paid_amount", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Amount paid", - max_digits=12, - ), - ), - ( - "balance_amount", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Balance amount", - max_digits=12, - ), - ), - ( - "status", - models.CharField( - choices=[ - ("DRAFT", "Draft"), - ("PENDING", "Pending"), - ("SUBMITTED", "Submitted"), - ("PARTIAL_PAID", "Partially Paid"), - ("PAID", "Paid"), - ("OVERDUE", "Overdue"), - ("COLLECTIONS", "Collections"), - ("WRITTEN_OFF", "Written Off"), - ("CANCELLED", "Cancelled"), - ], - default="DRAFT", - help_text="Bill status", - max_length=20, - ), - ), - ( - "notes", - models.TextField( - blank=True, help_text="Billing notes and comments", null=True - ), - ), - ( - "payment_terms", - models.CharField( - choices=[ - ("NET_30", "Net 30 Days"), - ("NET_60", "Net 60 Days"), - ("NET_90", "Net 90 Days"), - ("IMMEDIATE", "Immediate"), - ("CUSTOM", "Custom Terms"), - ], - default="NET_30", - help_text="Payment terms", - max_length=20, - ), - ), - ( - "collection_status", - models.CharField( - choices=[ - ("NONE", "None"), - ("FIRST_NOTICE", "First Notice"), - ("SECOND_NOTICE", "Second Notice"), - ("FINAL_NOTICE", "Final Notice"), - ("COLLECTIONS", "Collections"), - ("LEGAL", "Legal Action"), - ], - default="NONE", - help_text="Collection status", - max_length=20, - ), - ), - ( - "last_statement_date", - models.DateField( - blank=True, help_text="Last statement date", null=True - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Medical Bill", - "verbose_name_plural": "Medical Bills", - "db_table": "billing_medical_bill", - "ordering": ["-bill_date"], - }, - ), - migrations.CreateModel( - name="Payment", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "payment_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique payment identifier", - unique=True, - ), - ), - ( - "payment_number", - models.CharField( - help_text="Payment number", max_length=20, unique=True - ), - ), - ("payment_date", models.DateField(help_text="Payment date")), - ( - "payment_amount", - models.DecimalField( - decimal_places=2, help_text="Payment amount", max_digits=12 - ), - ), - ( - "payment_method", - models.CharField( - choices=[ - ("CASH", "Cash"), - ("CHECK", "Check"), - ("CREDIT_CARD", "Credit Card"), - ("DEBIT_CARD", "Debit Card"), - ("BANK_TRANSFER", "Bank Transfer"), - ("ACH", "ACH Transfer"), - ("WIRE", "Wire Transfer"), - ("MONEY_ORDER", "Money Order"), - ("INSURANCE", "Insurance Payment"), - ("ADJUSTMENT", "Adjustment"), - ("WRITE_OFF", "Write Off"), - ("OTHER", "Other"), - ], - help_text="Payment method", - max_length=20, - ), - ), - ( - "payment_source", - models.CharField( - choices=[ - ("PATIENT", "Patient"), - ("INSURANCE", "Insurance"), - ("GUARANTOR", "Guarantor"), - ("GOVERNMENT", "Government"), - ("CHARITY", "Charity"), - ("OTHER", "Other"), - ], - help_text="Payment source", - max_length=20, - ), - ), - ( - "check_number", - models.CharField( - blank=True, help_text="Check number", max_length=50, null=True - ), - ), - ( - "bank_name", - models.CharField( - blank=True, help_text="Bank name", max_length=100, null=True - ), - ), - ( - "routing_number", - models.CharField( - blank=True, - help_text="Bank routing number", - max_length=20, - null=True, - ), - ), - ( - "card_type", - models.CharField( - blank=True, - choices=[ - ("VISA", "Visa"), - ("MASTERCARD", "MasterCard"), - ("AMEX", "American Express"), - ("DISCOVER", "Discover"), - ("OTHER", "Other"), - ], - help_text="Credit card type", - max_length=20, - null=True, - ), - ), - ( - "card_last_four", - models.CharField( - blank=True, - help_text="Last four digits of card", - max_length=4, - null=True, - ), - ), - ( - "authorization_code", - models.CharField( - blank=True, - help_text="Authorization code", - max_length=20, - null=True, - ), - ), - ( - "transaction_id", - models.CharField( - blank=True, help_text="Transaction ID", max_length=50, null=True - ), - ), - ( - "eob_number", - models.CharField( - blank=True, - help_text="Explanation of Benefits number", - max_length=50, - null=True, - ), - ), - ( - "status", - models.CharField( - choices=[ - ("PENDING", "Pending"), - ("PROCESSED", "Processed"), - ("CLEARED", "Cleared"), - ("BOUNCED", "Bounced"), - ("REVERSED", "Reversed"), - ("REFUNDED", "Refunded"), - ], - default="PENDING", - help_text="Payment status", - max_length=20, - ), - ), - ( - "deposit_date", - models.DateField(blank=True, help_text="Deposit date", null=True), - ), - ( - "deposit_slip", - models.CharField( - blank=True, - help_text="Deposit slip number", - max_length=50, - null=True, - ), - ), - ( - "notes", - models.TextField( - blank=True, help_text="Payment notes and comments", null=True - ), - ), - ( - "refund_amount", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Refund amount", - max_digits=12, - ), - ), - ( - "refund_date", - models.DateField(blank=True, help_text="Refund date", null=True), - ), - ( - "refund_reason", - models.CharField( - blank=True, help_text="Refund reason", max_length=200, null=True - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Payment", - "verbose_name_plural": "Payments", - "db_table": "billing_payment", - "ordering": ["-payment_date"], - }, - ), - migrations.CreateModel( - name="BillingConfiguration", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "config_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique configuration identifier", - unique=True, - ), - ), - ( - "default_payment_terms", - models.CharField( - choices=[ - ("NET_30", "Net 30 Days"), - ("NET_60", "Net 60 Days"), - ("NET_90", "Net 90 Days"), - ("IMMEDIATE", "Immediate"), - ], - default="NET_30", - help_text="Default payment terms", - max_length=20, - ), - ), - ( - "tax_rate", - models.DecimalField( - decimal_places=4, - default=Decimal("0.0000"), - help_text="Default tax rate (as decimal, e.g., 0.0825 for 8.25%)", - max_digits=5, - ), - ), - ( - "tax_exempt", - models.BooleanField( - default=True, help_text="Organization is tax exempt" - ), - ), - ( - "statement_frequency", - models.CharField( - choices=[ - ("MONTHLY", "Monthly"), - ("QUARTERLY", "Quarterly"), - ("ON_DEMAND", "On Demand"), - ], - default="MONTHLY", - help_text="Statement frequency", - max_length=20, - ), - ), - ( - "statement_message", - models.TextField( - blank=True, help_text="Default statement message", null=True - ), - ), - ( - "first_notice_days", - models.PositiveIntegerField( - default=30, help_text="Days after due date for first notice" - ), - ), - ( - "second_notice_days", - models.PositiveIntegerField( - default=60, help_text="Days after due date for second notice" - ), - ), - ( - "final_notice_days", - models.PositiveIntegerField( - default=90, help_text="Days after due date for final notice" - ), - ), - ( - "collections_days", - models.PositiveIntegerField( - default=120, - help_text="Days after due date to send to collections", - ), - ), - ( - "apply_interest", - models.BooleanField( - default=False, help_text="Apply interest to overdue accounts" - ), - ), - ( - "interest_rate", - models.DecimalField( - decimal_places=4, - default=Decimal("0.0000"), - help_text="Monthly interest rate (as decimal)", - max_digits=5, - ), - ), - ( - "accept_credit_cards", - models.BooleanField( - default=True, help_text="Accept credit card payments" - ), - ), - ( - "accept_ach", - models.BooleanField(default=True, help_text="Accept ACH payments"), - ), - ( - "payment_portal_enabled", - models.BooleanField( - default=True, help_text="Enable online payment portal" - ), - ), - ( - "auto_submit_claims", - models.BooleanField( - default=False, help_text="Automatically submit claims" - ), - ), - ( - "claim_submission_frequency", - models.CharField( - choices=[ - ("DAILY", "Daily"), - ("WEEKLY", "Weekly"), - ("MANUAL", "Manual"), - ], - default="DAILY", - help_text="Claim submission frequency", - max_length=20, - ), - ), - ( - "primary_clearinghouse", - models.CharField( - blank=True, - help_text="Primary clearinghouse", - max_length=100, - null=True, - ), - ), - ( - "secondary_clearinghouse", - models.CharField( - blank=True, - help_text="Secondary clearinghouse", - max_length=100, - null=True, - ), - ), - ( - "aging_buckets", - models.JSONField( - default=billing.utils.default_aging_buckets, - help_text="Aging report buckets in days", - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "created_by", - models.ForeignKey( - blank=True, - help_text="User who created the configuration", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_billing_configurations", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "verbose_name": "Billing Configuration", - "verbose_name_plural": "Billing Configurations", - "db_table": "billing_configuration", - }, - ), - ] diff --git a/billing/migrations/0002_initial.py b/billing/migrations/0002_initial.py deleted file mode 100644 index 2ba64dc9..00000000 --- a/billing/migrations/0002_initial.py +++ /dev/null @@ -1,110 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("billing", "0001_initial"), - ("core", "0001_initial"), - ("patients", "0001_initial"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name="billingconfiguration", - name="tenant", - field=models.OneToOneField( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="billing_configuration", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="billlineitem", - name="rendering_provider", - field=models.ForeignKey( - blank=True, - help_text="Rendering provider", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="rendered_line_items", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="billlineitem", - name="supervising_provider", - field=models.ForeignKey( - blank=True, - help_text="Supervising provider", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="supervised_line_items", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="claimstatusupdate", - name="updated_by", - field=models.ForeignKey( - blank=True, - help_text="Staff member who made the update", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="claim_status_updates", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="insuranceclaim", - name="created_by", - field=models.ForeignKey( - blank=True, - help_text="User who created the claim", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_insurance_claims", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="insuranceclaim", - name="insurance_info", - field=models.ForeignKey( - help_text="Insurance information", - on_delete=django.db.models.deletion.CASCADE, - related_name="insurance_claims", - to="patients.insuranceinfo", - ), - ), - migrations.AddField( - model_name="insuranceclaim", - name="original_claim", - field=models.ForeignKey( - blank=True, - help_text="Original claim if this is a resubmission", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="resubmissions", - to="billing.insuranceclaim", - ), - ), - migrations.AddField( - model_name="claimstatusupdate", - name="insurance_claim", - field=models.ForeignKey( - help_text="Related insurance claim", - on_delete=django.db.models.deletion.CASCADE, - related_name="status_updates", - to="billing.insuranceclaim", - ), - ), - ] diff --git a/billing/migrations/0003_initial.py b/billing/migrations/0003_initial.py deleted file mode 100644 index a0700f1f..00000000 --- a/billing/migrations/0003_initial.py +++ /dev/null @@ -1,328 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("billing", "0002_initial"), - ("core", "0001_initial"), - ("emr", "0001_initial"), - ("inpatients", "0001_initial"), - ("patients", "0001_initial"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name="medicalbill", - name="admission", - field=models.ForeignKey( - blank=True, - help_text="Related admission", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="medical_bills", - to="inpatients.admission", - ), - ), - migrations.AddField( - model_name="medicalbill", - name="attending_provider", - field=models.ForeignKey( - blank=True, - help_text="Attending provider", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="attending_bills", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="medicalbill", - name="billing_provider", - field=models.ForeignKey( - blank=True, - help_text="Billing provider", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="billing_provider_bills", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="medicalbill", - name="created_by", - field=models.ForeignKey( - blank=True, - help_text="User who created the bill", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_medical_bills", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="medicalbill", - name="encounter", - field=models.ForeignKey( - blank=True, - help_text="Related encounter", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="medical_bills", - to="emr.encounter", - ), - ), - migrations.AddField( - model_name="medicalbill", - name="patient", - field=models.ForeignKey( - help_text="Patient", - on_delete=django.db.models.deletion.CASCADE, - related_name="medical_bills", - to="patients.patientprofile", - ), - ), - migrations.AddField( - model_name="medicalbill", - name="primary_insurance", - field=models.ForeignKey( - blank=True, - help_text="Primary insurance", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="primary_bills", - to="patients.insuranceinfo", - ), - ), - migrations.AddField( - model_name="medicalbill", - name="secondary_insurance", - field=models.ForeignKey( - blank=True, - help_text="Secondary insurance", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="secondary_bills", - to="patients.insuranceinfo", - ), - ), - migrations.AddField( - model_name="medicalbill", - name="tenant", - field=models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="medical_bills", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="insuranceclaim", - name="medical_bill", - field=models.ForeignKey( - help_text="Related medical bill", - on_delete=django.db.models.deletion.CASCADE, - related_name="insurance_claims", - to="billing.medicalbill", - ), - ), - migrations.AddField( - model_name="billlineitem", - name="medical_bill", - field=models.ForeignKey( - help_text="Medical bill", - on_delete=django.db.models.deletion.CASCADE, - related_name="line_items", - to="billing.medicalbill", - ), - ), - migrations.AddField( - model_name="payment", - name="insurance_claim", - field=models.ForeignKey( - blank=True, - help_text="Related insurance claim", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="payments", - to="billing.insuranceclaim", - ), - ), - migrations.AddField( - model_name="payment", - name="medical_bill", - field=models.ForeignKey( - help_text="Related medical bill", - on_delete=django.db.models.deletion.CASCADE, - related_name="payments", - to="billing.medicalbill", - ), - ), - migrations.AddField( - model_name="payment", - name="processed_by", - field=models.ForeignKey( - blank=True, - help_text="Staff member who processed payment", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="processed_payments", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="payment", - name="received_by", - field=models.ForeignKey( - blank=True, - help_text="Staff member who received payment", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="received_payments", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddIndex( - model_name="claimstatusupdate", - index=models.Index( - fields=["insurance_claim"], name="billing_cla_insuran_7c9e49_idx" - ), - ), - migrations.AddIndex( - model_name="claimstatusupdate", - index=models.Index( - fields=["status_date"], name="billing_cla_status__49d81f_idx" - ), - ), - migrations.AddIndex( - model_name="claimstatusupdate", - index=models.Index( - fields=["new_status"], name="billing_cla_new_sta_9c28d3_idx" - ), - ), - migrations.AddIndex( - model_name="medicalbill", - index=models.Index( - fields=["tenant", "status"], name="billing_med_tenant__fe8d14_idx" - ), - ), - migrations.AddIndex( - model_name="medicalbill", - index=models.Index( - fields=["patient", "bill_date"], name="billing_med_patient_8f1a85_idx" - ), - ), - migrations.AddIndex( - model_name="medicalbill", - index=models.Index( - fields=["bill_number"], name="billing_med_bill_nu_f01dfa_idx" - ), - ), - migrations.AddIndex( - model_name="medicalbill", - index=models.Index( - fields=["status", "due_date"], name="billing_med_status_cde77f_idx" - ), - ), - migrations.AddIndex( - model_name="medicalbill", - index=models.Index( - fields=["collection_status"], name="billing_med_collect_6d0faf_idx" - ), - ), - migrations.AddIndex( - model_name="insuranceclaim", - index=models.Index( - fields=["medical_bill"], name="billing_ins_medical_1fec52_idx" - ), - ), - migrations.AddIndex( - model_name="insuranceclaim", - index=models.Index( - fields=["insurance_info"], name="billing_ins_insuran_e54611_idx" - ), - ), - migrations.AddIndex( - model_name="insuranceclaim", - index=models.Index( - fields=["claim_number"], name="billing_ins_claim_n_becaa3_idx" - ), - ), - migrations.AddIndex( - model_name="insuranceclaim", - index=models.Index( - fields=["status", "submission_date"], - name="billing_ins_status_921ea7_idx", - ), - ), - migrations.AddIndex( - model_name="insuranceclaim", - index=models.Index( - fields=["response_date"], name="billing_ins_respons_fc4f3d_idx" - ), - ), - migrations.AddIndex( - model_name="billlineitem", - index=models.Index( - fields=["medical_bill", "line_number"], - name="billing_bil_medical_37a377_idx", - ), - ), - migrations.AddIndex( - model_name="billlineitem", - index=models.Index( - fields=["service_code"], name="billing_bil_service_b88f5b_idx" - ), - ), - migrations.AddIndex( - model_name="billlineitem", - index=models.Index( - fields=["service_date"], name="billing_bil_service_658c36_idx" - ), - ), - migrations.AddIndex( - model_name="billlineitem", - index=models.Index( - fields=["rendering_provider"], name="billing_bil_renderi_2740ad_idx" - ), - ), - migrations.AlterUniqueTogether( - name="billlineitem", - unique_together={("medical_bill", "line_number")}, - ), - migrations.AddIndex( - model_name="payment", - index=models.Index( - fields=["medical_bill"], name="billing_pay_medical_e4e348_idx" - ), - ), - migrations.AddIndex( - model_name="payment", - index=models.Index( - fields=["payment_number"], name="billing_pay_payment_0825d6_idx" - ), - ), - migrations.AddIndex( - model_name="payment", - index=models.Index( - fields=["payment_date"], name="billing_pay_payment_bed741_idx" - ), - ), - migrations.AddIndex( - model_name="payment", - index=models.Index( - fields=["payment_method"], name="billing_pay_payment_715e62_idx" - ), - ), - migrations.AddIndex( - model_name="payment", - index=models.Index(fields=["status"], name="billing_pay_status_b7739e_idx"), - ), - ] diff --git a/billing/migrations/0004_alter_billlineitem_place_of_service.py b/billing/migrations/0004_alter_billlineitem_place_of_service.py deleted file mode 100644 index 58f92372..00000000 --- a/billing/migrations/0004_alter_billlineitem_place_of_service.py +++ /dev/null @@ -1,54 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-28 13:08 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("billing", "0003_initial"), - ] - - operations = [ - migrations.AlterField( - model_name="billlineitem", - name="place_of_service", - field=models.IntegerField( - choices=[ - (11, "Office"), - (12, "Home"), - (21, "Inpatient Hospital"), - (22, "Outpatient Hospital"), - (23, "Emergency Room"), - (24, "Ambulatory Surgical Center"), - (25, "Birthing Center"), - (26, "Military Treatment Facility"), - (31, "Skilled Nursing Facility"), - (32, "Nursing Facility"), - (33, "Custodial Care Facility"), - (34, "Hospice"), - (41, "Ambulance - Land"), - (42, "Ambulance - Air or Water"), - (49, "Independent Clinic"), - (50, "Federally Qualified Health Center"), - (51, "Inpatient Psychiatric Facility"), - (52, "Psychiatric Facility-Partial Hospitalization"), - (53, "Community Mental Health Center"), - (54, "Intermediate Care Facility/Mentally Retarded"), - (55, "Residential Substance Abuse Treatment Facility"), - (56, "Psychiatric Residential Treatment Center"), - (57, "Non-residential Substance Abuse Treatment Facility"), - (60, "Mass Immunization Center"), - (61, "Comprehensive Inpatient Rehabilitation Facility"), - (62, "Comprehensive Outpatient Rehabilitation Facility"), - (65, "End-Stage Renal Disease Treatment Facility"), - (71, "Public Health Clinic"), - (72, "Rural Health Clinic"), - (81, "Independent Laboratory"), - (99, "Other Place of Service"), - ], - default=22, - help_text="Place of service code", - ), - ), - ] diff --git a/billing/migrations/__pycache__/0001_initial.cpython-312.pyc b/billing/migrations/__pycache__/0001_initial.cpython-312.pyc deleted file mode 100644 index 7ea752a9..00000000 Binary files a/billing/migrations/__pycache__/0001_initial.cpython-312.pyc and /dev/null differ diff --git a/billing/migrations/__pycache__/0002_initial.cpython-312.pyc b/billing/migrations/__pycache__/0002_initial.cpython-312.pyc deleted file mode 100644 index 35c090f1..00000000 Binary files a/billing/migrations/__pycache__/0002_initial.cpython-312.pyc and /dev/null differ diff --git a/billing/migrations/__pycache__/0003_initial.cpython-312.pyc b/billing/migrations/__pycache__/0003_initial.cpython-312.pyc deleted file mode 100644 index 9e112838..00000000 Binary files a/billing/migrations/__pycache__/0003_initial.cpython-312.pyc and /dev/null differ diff --git a/billing/migrations/__pycache__/0004_alter_billlineitem_place_of_service.cpython-312.pyc b/billing/migrations/__pycache__/0004_alter_billlineitem_place_of_service.cpython-312.pyc deleted file mode 100644 index 54ca0326..00000000 Binary files a/billing/migrations/__pycache__/0004_alter_billlineitem_place_of_service.cpython-312.pyc and /dev/null differ diff --git a/blood_bank/migrations/0001_initial.py b/blood_bank/migrations/0001_initial.py deleted file mode 100644 index 04dbaf01..00000000 --- a/blood_bank/migrations/0001_initial.py +++ /dev/null @@ -1,673 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.core.validators -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="BloodComponent", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "name", - models.CharField( - choices=[ - ("WHOLE_BLOOD", "Whole Blood"), - ("PACKED_RBC", "Packed Red Blood Cells"), - ("FRESH_FROZEN_PLASMA", "Fresh Frozen Plasma"), - ("PLATELETS", "Platelets"), - ("CRYOPRECIPITATE", "Cryoprecipitate"), - ("GRANULOCYTES", "Granulocytes"), - ], - max_length=50, - unique=True, - ), - ), - ("description", models.TextField()), - ("shelf_life_days", models.PositiveIntegerField()), - ("storage_temperature", models.CharField(max_length=50)), - ("volume_ml", models.PositiveIntegerField()), - ("is_active", models.BooleanField(default=True)), - ], - options={ - "ordering": ["name"], - }, - ), - migrations.CreateModel( - name="BloodTest", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "test_type", - models.CharField( - choices=[ - ("ABO_RH", "ABO/Rh Typing"), - ("ANTIBODY_SCREEN", "Antibody Screening"), - ("HIV", "HIV"), - ("HBV", "Hepatitis B"), - ("HCV", "Hepatitis C"), - ("SYPHILIS", "Syphilis"), - ("HTLV", "HTLV"), - ("CMV", "CMV"), - ("MALARIA", "Malaria"), - ], - max_length=20, - ), - ), - ( - "result", - models.CharField( - choices=[ - ("POSITIVE", "Positive"), - ("NEGATIVE", "Negative"), - ("INDETERMINATE", "Indeterminate"), - ("PENDING", "Pending"), - ], - default="PENDING", - max_length=15, - ), - ), - ("test_date", models.DateTimeField()), - ("equipment_used", models.CharField(blank=True, max_length=100)), - ("lot_number", models.CharField(blank=True, max_length=50)), - ("notes", models.TextField(blank=True)), - ("verified_at", models.DateTimeField(blank=True, null=True)), - ], - options={ - "ordering": ["-test_date"], - }, - ), - migrations.CreateModel( - name="BloodUnit", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("unit_number", models.CharField(max_length=20, unique=True)), - ("collection_date", models.DateTimeField()), - ("expiry_date", models.DateTimeField()), - ("volume_ml", models.PositiveIntegerField()), - ( - "status", - models.CharField( - choices=[ - ("COLLECTED", "Collected"), - ("TESTING", "Testing"), - ("QUARANTINE", "Quarantine"), - ("AVAILABLE", "Available"), - ("RESERVED", "Reserved"), - ("ISSUED", "Issued"), - ("TRANSFUSED", "Transfused"), - ("EXPIRED", "Expired"), - ("DISCARDED", "Discarded"), - ], - default="COLLECTED", - max_length=20, - ), - ), - ("location", models.CharField(max_length=100)), - ("bag_type", models.CharField(max_length=50)), - ("anticoagulant", models.CharField(default="CPDA-1", max_length=50)), - ("collection_site", models.CharField(max_length=100)), - ("notes", models.TextField(blank=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "ordering": ["-collection_date"], - }, - ), - migrations.CreateModel( - name="CrossMatch", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "test_type", - models.CharField( - choices=[ - ("MAJOR", "Major Crossmatch"), - ("MINOR", "Minor Crossmatch"), - ("IMMEDIATE_SPIN", "Immediate Spin"), - ("ANTIGLOBULIN", "Antiglobulin Test"), - ], - max_length=20, - ), - ), - ( - "compatibility", - models.CharField( - choices=[ - ("COMPATIBLE", "Compatible"), - ("INCOMPATIBLE", "Incompatible"), - ("PENDING", "Pending"), - ], - default="PENDING", - max_length=15, - ), - ), - ("test_date", models.DateTimeField()), - ("temperature", models.CharField(default="37°C", max_length=20)), - ("incubation_time", models.PositiveIntegerField(default=15)), - ("notes", models.TextField(blank=True)), - ("verified_at", models.DateTimeField(blank=True, null=True)), - ], - options={ - "ordering": ["-test_date"], - }, - ), - migrations.CreateModel( - name="Donor", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("donor_id", models.CharField(max_length=20, unique=True)), - ("first_name", models.CharField(max_length=100)), - ("last_name", models.CharField(max_length=100)), - ("date_of_birth", models.DateField()), - ( - "gender", - models.CharField( - choices=[ - ("MALE", "Male"), - ("FEMALE", "Female"), - ("OTHER", "Other"), - ], - max_length=10, - ), - ), - ("national_id", models.CharField(max_length=10, unique=True)), - ("phone", models.CharField(max_length=20)), - ("email", models.EmailField(blank=True, max_length=254)), - ("address", models.TextField()), - ("emergency_contact_name", models.CharField(max_length=100)), - ("emergency_contact_phone", models.CharField(max_length=20)), - ( - "donor_type", - models.CharField( - choices=[ - ("VOLUNTARY", "Voluntary"), - ("REPLACEMENT", "Replacement"), - ("AUTOLOGOUS", "Autologous"), - ("DIRECTED", "Directed"), - ], - default="VOLUNTARY", - max_length=20, - ), - ), - ( - "status", - models.CharField( - choices=[ - ("ACTIVE", "Active"), - ("DEFERRED", "Deferred"), - ("PERMANENTLY_DEFERRED", "Permanently Deferred"), - ("INACTIVE", "Inactive"), - ], - default="ACTIVE", - max_length=20, - ), - ), - ("registration_date", models.DateTimeField(auto_now_add=True)), - ("last_donation_date", models.DateTimeField(blank=True, null=True)), - ("total_donations", models.PositiveIntegerField(default=0)), - ( - "weight", - models.FloatField( - validators=[django.core.validators.MinValueValidator(45.0)] - ), - ), - ( - "height", - models.FloatField( - validators=[django.core.validators.MinValueValidator(140.0)] - ), - ), - ("notes", models.TextField(blank=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "ordering": ["-registration_date"], - }, - ), - migrations.CreateModel( - name="InventoryLocation", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=100, unique=True)), - ( - "location_type", - models.CharField( - choices=[ - ("REFRIGERATOR", "Refrigerator"), - ("FREEZER", "Freezer"), - ("PLATELET_AGITATOR", "Platelet Agitator"), - ("QUARANTINE", "Quarantine"), - ("TESTING", "Testing Area"), - ], - max_length=20, - ), - ), - ("temperature_range", models.CharField(max_length=50)), - ("temperature", models.FloatField(blank=True, null=True)), - ("capacity", models.PositiveIntegerField()), - ("current_stock", models.PositiveIntegerField(default=0)), - ("is_active", models.BooleanField(default=True)), - ("notes", models.TextField(blank=True)), - ], - options={ - "ordering": ["name"], - }, - ), - migrations.CreateModel( - name="QualityControl", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "test_type", - models.CharField( - choices=[ - ("TEMPERATURE_MONITORING", "Temperature Monitoring"), - ("EQUIPMENT_CALIBRATION", "Equipment Calibration"), - ("REAGENT_TESTING", "Reagent Testing"), - ("PROFICIENCY_TESTING", "Proficiency Testing"), - ("PROCESS_VALIDATION", "Process Validation"), - ], - max_length=30, - ), - ), - ("test_date", models.DateTimeField()), - ("equipment_tested", models.CharField(blank=True, max_length=100)), - ("parameters_tested", models.TextField()), - ("expected_results", models.TextField()), - ("actual_results", models.TextField()), - ( - "status", - models.CharField( - choices=[ - ("PASS", "Pass"), - ("FAIL", "Fail"), - ("PENDING", "Pending"), - ], - max_length=10, - ), - ), - ("review_date", models.DateTimeField(blank=True, null=True)), - ("review_notes", models.TextField(blank=True)), - ("corrective_action", models.TextField(blank=True)), - ("next_test_date", models.DateTimeField(blank=True, null=True)), - ("capa_initiated", models.BooleanField(default=False)), - ("capa_number", models.CharField(blank=True, max_length=50)), - ( - "capa_priority", - models.CharField( - blank=True, - choices=[ - ("LOW", "Low"), - ("MEDIUM", "Medium"), - ("HIGH", "High"), - ], - max_length=10, - ), - ), - ("capa_date", models.DateTimeField(blank=True, null=True)), - ("capa_assessment", models.TextField(blank=True)), - ( - "capa_status", - models.CharField( - blank=True, - choices=[ - ("OPEN", "Open"), - ("IN_PROGRESS", "In Progress"), - ("CLOSED", "Closed"), - ], - max_length=20, - ), - ), - ], - options={ - "ordering": ["-test_date"], - }, - ), - migrations.CreateModel( - name="Transfusion", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("start_time", models.DateTimeField()), - ("end_time", models.DateTimeField(blank=True, null=True)), - ( - "status", - models.CharField( - choices=[ - ("STARTED", "Started"), - ("IN_PROGRESS", "In Progress"), - ("COMPLETED", "Completed"), - ("STOPPED", "Stopped"), - ("ADVERSE_REACTION", "Adverse Reaction"), - ], - default="STARTED", - max_length=20, - ), - ), - ( - "volume_transfused", - models.PositiveIntegerField(blank=True, null=True), - ), - ("transfusion_rate", models.CharField(blank=True, max_length=50)), - ("pre_transfusion_vitals", models.JSONField(default=dict)), - ("post_transfusion_vitals", models.JSONField(default=dict)), - ("vital_signs_history", models.JSONField(default=list)), - ("current_blood_pressure", models.CharField(blank=True, max_length=20)), - ("current_heart_rate", models.IntegerField(blank=True, null=True)), - ("current_temperature", models.FloatField(blank=True, null=True)), - ( - "current_respiratory_rate", - models.IntegerField(blank=True, null=True), - ), - ( - "current_oxygen_saturation", - models.IntegerField(blank=True, null=True), - ), - ("last_vitals_check", models.DateTimeField(blank=True, null=True)), - ("patient_consent", models.BooleanField(default=False)), - ("consent_date", models.DateTimeField(blank=True, null=True)), - ("notes", models.TextField(blank=True)), - ("stop_reason", models.TextField(blank=True)), - ("completion_notes", models.TextField(blank=True)), - ], - options={ - "ordering": ["-start_time"], - }, - ), - migrations.CreateModel( - name="AdverseReaction", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "reaction_type", - models.CharField( - choices=[ - ("FEBRILE", "Febrile Non-Hemolytic"), - ("ALLERGIC", "Allergic"), - ("HEMOLYTIC_ACUTE", "Acute Hemolytic"), - ("HEMOLYTIC_DELAYED", "Delayed Hemolytic"), - ("ANAPHYLACTIC", "Anaphylactic"), - ("SEPTIC", "Septic"), - ("CIRCULATORY_OVERLOAD", "Circulatory Overload"), - ("LUNG_INJURY", "Transfusion-Related Acute Lung Injury"), - ("OTHER", "Other"), - ], - max_length=30, - ), - ), - ( - "severity", - models.CharField( - choices=[ - ("MILD", "Mild"), - ("MODERATE", "Moderate"), - ("SEVERE", "Severe"), - ("LIFE_THREATENING", "Life Threatening"), - ], - max_length=20, - ), - ), - ("onset_time", models.DateTimeField()), - ("symptoms", models.TextField()), - ("treatment_given", models.TextField()), - ("outcome", models.TextField()), - ("investigation_notes", models.TextField(blank=True)), - ("regulatory_reported", models.BooleanField(default=False)), - ("report_date", models.DateTimeField(blank=True, null=True)), - ( - "investigated_by", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="investigated_reactions", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "reported_by", - models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="reported_reactions", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "ordering": ["-onset_time"], - }, - ), - migrations.CreateModel( - name="BloodGroup", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "abo_type", - models.CharField( - choices=[("A", "A"), ("B", "B"), ("AB", "AB"), ("O", "O")], - max_length=2, - ), - ), - ( - "rh_factor", - models.CharField( - choices=[("POS", "Positive"), ("NEG", "Negative")], max_length=8 - ), - ), - ], - options={ - "ordering": ["abo_type", "rh_factor"], - "unique_together": {("abo_type", "rh_factor")}, - }, - ), - migrations.CreateModel( - name="BloodIssue", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("issue_date", models.DateTimeField(auto_now_add=True)), - ("expiry_time", models.DateTimeField()), - ("returned", models.BooleanField(default=False)), - ("return_date", models.DateTimeField(blank=True, null=True)), - ("return_reason", models.TextField(blank=True)), - ("notes", models.TextField(blank=True)), - ( - "issued_by", - models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="issued_units", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "issued_to", - models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="received_units", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "ordering": ["-issue_date"], - }, - ), - migrations.CreateModel( - name="BloodRequest", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("request_number", models.CharField(max_length=20, unique=True)), - ( - "units_requested", - models.PositiveIntegerField( - validators=[django.core.validators.MinValueValidator(1)] - ), - ), - ( - "urgency", - models.CharField( - choices=[ - ("ROUTINE", "Routine"), - ("URGENT", "Urgent"), - ("EMERGENCY", "Emergency"), - ], - default="ROUTINE", - max_length=10, - ), - ), - ("indication", models.TextField()), - ("special_requirements", models.TextField(blank=True)), - ("hemoglobin_level", models.FloatField(blank=True, null=True)), - ("platelet_count", models.IntegerField(blank=True, null=True)), - ( - "status", - models.CharField( - choices=[ - ("PENDING", "Pending"), - ("PROCESSING", "Processing"), - ("READY", "Ready"), - ("ISSUED", "Issued"), - ("COMPLETED", "Completed"), - ("CANCELLED", "Cancelled"), - ], - default="PENDING", - max_length=15, - ), - ), - ("request_date", models.DateTimeField(auto_now_add=True)), - ("required_by", models.DateTimeField()), - ("processed_at", models.DateTimeField(blank=True, null=True)), - ("notes", models.TextField(blank=True)), - ("cancellation_reason", models.TextField(blank=True)), - ("cancellation_date", models.DateTimeField(blank=True, null=True)), - ( - "cancelled_by", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="cancelled_requests", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "component_requested", - models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - to="blood_bank.bloodcomponent", - ), - ), - ], - options={ - "ordering": ["-request_date"], - }, - ), - ] diff --git a/blood_bank/migrations/0002_initial.py b/blood_bank/migrations/0002_initial.py deleted file mode 100644 index f730281a..00000000 --- a/blood_bank/migrations/0002_initial.py +++ /dev/null @@ -1,297 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("blood_bank", "0001_initial"), - ("hr", "0001_initial"), - ("patients", "0001_initial"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name="bloodrequest", - name="patient", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="blood_requests", - to="patients.patientprofile", - ), - ), - migrations.AddField( - model_name="bloodrequest", - name="patient_blood_group", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, to="blood_bank.bloodgroup" - ), - ), - migrations.AddField( - model_name="bloodrequest", - name="processed_by", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="processed_requests", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="bloodrequest", - name="requesting_department", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, to="hr.department" - ), - ), - migrations.AddField( - model_name="bloodrequest", - name="requesting_physician", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="blood_requests", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="bloodissue", - name="blood_request", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="issues", - to="blood_bank.bloodrequest", - ), - ), - migrations.AddField( - model_name="bloodtest", - name="tested_by", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL - ), - ), - migrations.AddField( - model_name="bloodtest", - name="verified_by", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="verified_tests", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="bloodunit", - name="blood_group", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, to="blood_bank.bloodgroup" - ), - ), - migrations.AddField( - model_name="bloodunit", - name="collected_by", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="collected_units", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="bloodunit", - name="component", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - to="blood_bank.bloodcomponent", - ), - ), - migrations.AddField( - model_name="bloodtest", - name="blood_unit", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="tests", - to="blood_bank.bloodunit", - ), - ), - migrations.AddField( - model_name="bloodissue", - name="blood_unit", - field=models.OneToOneField( - on_delete=django.db.models.deletion.PROTECT, - related_name="issue", - to="blood_bank.bloodunit", - ), - ), - migrations.AddField( - model_name="crossmatch", - name="blood_unit", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="crossmatches", - to="blood_bank.bloodunit", - ), - ), - migrations.AddField( - model_name="crossmatch", - name="recipient", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - to="patients.patientprofile", - ), - ), - migrations.AddField( - model_name="crossmatch", - name="tested_by", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL - ), - ), - migrations.AddField( - model_name="crossmatch", - name="verified_by", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="verified_crossmatches", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="bloodissue", - name="crossmatch", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - to="blood_bank.crossmatch", - ), - ), - migrations.AddField( - model_name="donor", - name="blood_group", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, to="blood_bank.bloodgroup" - ), - ), - migrations.AddField( - model_name="donor", - name="created_by", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="created_donors", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="bloodunit", - name="donor", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="blood_units", - to="blood_bank.donor", - ), - ), - migrations.AddField( - model_name="qualitycontrol", - name="capa_initiated_by", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="initiated_capas", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="qualitycontrol", - name="performed_by", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="qc_tests", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="qualitycontrol", - name="reviewed_by", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="reviewed_qc_tests", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="transfusion", - name="administered_by", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="administered_transfusions", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="transfusion", - name="blood_issue", - field=models.OneToOneField( - on_delete=django.db.models.deletion.PROTECT, - related_name="transfusion", - to="blood_bank.bloodissue", - ), - ), - migrations.AddField( - model_name="transfusion", - name="completed_by", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="completed_transfusions", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="transfusion", - name="stopped_by", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="stopped_transfusions", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="transfusion", - name="witnessed_by", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="witnessed_transfusions", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="adversereaction", - name="transfusion", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="adverse_reactions", - to="blood_bank.transfusion", - ), - ), - migrations.AlterUniqueTogether( - name="bloodtest", - unique_together={("blood_unit", "test_type")}, - ), - ] diff --git a/blood_bank/migrations/__pycache__/0001_initial.cpython-312.pyc b/blood_bank/migrations/__pycache__/0001_initial.cpython-312.pyc deleted file mode 100644 index 05663b9d..00000000 Binary files a/blood_bank/migrations/__pycache__/0001_initial.cpython-312.pyc and /dev/null differ diff --git a/blood_bank/migrations/__pycache__/0002_initial.cpython-312.pyc b/blood_bank/migrations/__pycache__/0002_initial.cpython-312.pyc deleted file mode 100644 index 137b9b48..00000000 Binary files a/blood_bank/migrations/__pycache__/0002_initial.cpython-312.pyc and /dev/null differ diff --git a/communications/migrations/0001_initial.py b/communications/migrations/0001_initial.py deleted file mode 100644 index 005d7daf..00000000 --- a/communications/migrations/0001_initial.py +++ /dev/null @@ -1,968 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.db.models.deletion -import uuid -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="CommunicationChannel", - fields=[ - ( - "channel_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique identifier for the channel", - primary_key=True, - serialize=False, - ), - ), - ("name", models.CharField(help_text="Channel name", max_length=255)), - ( - "description", - models.TextField( - blank=True, help_text="Channel description", null=True - ), - ), - ( - "channel_type", - models.CharField( - choices=[ - ("EMAIL", "Email"), - ("SMS", "SMS"), - ("PUSH", "Push Notification"), - ("SLACK", "Slack"), - ("TEAMS", "Microsoft Teams"), - ("WEBHOOK", "Webhook"), - ("PHONE", "Phone Call"), - ("FAX", "Fax"), - ("PAGER", "Pager"), - ], - help_text="Type of communication channel", - max_length=20, - ), - ), - ( - "provider_type", - models.CharField( - choices=[ - ("SMTP", "SMTP Email"), - ("SENDGRID", "SendGrid"), - ("MAILGUN", "Mailgun"), - ("TWILIO", "Twilio SMS"), - ("AWS_SNS", "AWS SNS"), - ("FIREBASE", "Firebase"), - ("SLACK_API", "Slack API"), - ("TEAMS_API", "Teams API"), - ("WEBHOOK", "Webhook"), - ("CUSTOM", "Custom Provider"), - ], - help_text="Provider type", - max_length=20, - ), - ), - ( - "configuration", - models.JSONField( - default=dict, help_text="Channel configuration settings" - ), - ), - ( - "authentication_config", - models.JSONField( - default=dict, help_text="Authentication configuration" - ), - ), - ( - "rate_limits", - models.JSONField( - default=dict, help_text="Rate limiting configuration" - ), - ), - ( - "is_active", - models.BooleanField(default=True, help_text="Channel is active"), - ), - ( - "is_healthy", - models.BooleanField( - default=True, help_text="Channel health status" - ), - ), - ( - "last_health_check", - models.DateTimeField( - blank=True, help_text="Last health check timestamp", null=True - ), - ), - ( - "health_check_interval", - models.PositiveIntegerField( - default=300, help_text="Health check interval in seconds" - ), - ), - ( - "message_count", - models.PositiveIntegerField( - default=0, help_text="Total messages sent through channel" - ), - ), - ( - "success_count", - models.PositiveIntegerField( - default=0, help_text="Successful message deliveries" - ), - ), - ( - "failure_count", - models.PositiveIntegerField( - default=0, help_text="Failed message deliveries" - ), - ), - ( - "last_used_at", - models.DateTimeField( - blank=True, help_text="Last usage timestamp", null=True - ), - ), - ( - "created_at", - models.DateTimeField( - auto_now_add=True, help_text="Channel creation timestamp" - ), - ), - ( - "updated_at", - models.DateTimeField( - auto_now=True, help_text="Last update timestamp" - ), - ), - ], - options={ - "db_table": "communications_communication_channel", - }, - ), - migrations.CreateModel( - name="DeliveryLog", - fields=[ - ( - "log_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique identifier for the delivery log", - primary_key=True, - serialize=False, - ), - ), - ( - "status", - models.CharField( - choices=[ - ("PENDING", "Pending"), - ("PROCESSING", "Processing"), - ("SENT", "Sent"), - ("DELIVERED", "Delivered"), - ("FAILED", "Failed"), - ("BOUNCED", "Bounced"), - ("REJECTED", "Rejected"), - ("TIMEOUT", "Timeout"), - ], - default="PENDING", - help_text="Delivery status", - max_length=20, - ), - ), - ( - "attempt_number", - models.PositiveIntegerField( - default=1, help_text="Delivery attempt number" - ), - ), - ( - "started_at", - models.DateTimeField( - auto_now_add=True, help_text="Delivery start timestamp" - ), - ), - ( - "completed_at", - models.DateTimeField( - blank=True, help_text="Delivery completion timestamp", null=True - ), - ), - ( - "external_id", - models.CharField( - blank=True, - help_text="External delivery ID", - max_length=255, - null=True, - ), - ), - ( - "response_code", - models.CharField( - blank=True, - help_text="Response code from provider", - max_length=50, - null=True, - ), - ), - ( - "response_message", - models.TextField( - blank=True, - help_text="Response message from provider", - null=True, - ), - ), - ( - "error_details", - models.JSONField( - default=dict, help_text="Detailed error information" - ), - ), - ( - "processing_time_ms", - models.PositiveIntegerField( - blank=True, - help_text="Processing time in milliseconds", - null=True, - ), - ), - ( - "payload_size_bytes", - models.PositiveIntegerField( - blank=True, help_text="Payload size in bytes", null=True - ), - ), - ( - "metadata", - models.JSONField( - default=dict, help_text="Additional delivery metadata" - ), - ), - ], - options={ - "db_table": "communications_delivery_log", - "ordering": ["-started_at"], - }, - ), - migrations.CreateModel( - name="Message", - fields=[ - ( - "message_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique identifier for the message", - primary_key=True, - serialize=False, - ), - ), - ( - "subject", - models.CharField(help_text="Message subject line", max_length=255), - ), - ("content", models.TextField(help_text="Message content/body")), - ( - "message_type", - models.CharField( - choices=[ - ("INTERNAL", "Internal Message"), - ("EMAIL", "Email"), - ("SMS", "SMS"), - ("PUSH", "Push Notification"), - ("SLACK", "Slack Message"), - ("TEAMS", "Microsoft Teams"), - ("WEBHOOK", "Webhook"), - ("SYSTEM", "System Message"), - ("ALERT", "Alert Message"), - ], - default="INTERNAL", - help_text="Type of message", - max_length=20, - ), - ), - ( - "priority", - models.CharField( - choices=[ - ("LOW", "Low"), - ("NORMAL", "Normal"), - ("HIGH", "High"), - ("URGENT", "Urgent"), - ("CRITICAL", "Critical"), - ], - default="NORMAL", - help_text="Message priority level", - max_length=20, - ), - ), - ( - "status", - models.CharField( - choices=[ - ("DRAFT", "Draft"), - ("PENDING", "Pending"), - ("SENDING", "Sending"), - ("SENT", "Sent"), - ("DELIVERED", "Delivered"), - ("READ", "Read"), - ("FAILED", "Failed"), - ("CANCELLED", "Cancelled"), - ], - default="DRAFT", - help_text="Message status", - max_length=20, - ), - ), - ( - "created_at", - models.DateTimeField( - auto_now_add=True, help_text="Message creation timestamp" - ), - ), - ( - "scheduled_at", - models.DateTimeField( - blank=True, help_text="Scheduled send time", null=True - ), - ), - ( - "sent_at", - models.DateTimeField( - blank=True, help_text="Actual send timestamp", null=True - ), - ), - ( - "expires_at", - models.DateTimeField( - blank=True, help_text="Message expiration time", null=True - ), - ), - ( - "is_urgent", - models.BooleanField(default=False, help_text="Urgent message flag"), - ), - ( - "requires_acknowledgment", - models.BooleanField( - default=False, help_text="Requires recipient acknowledgment" - ), - ), - ( - "is_confidential", - models.BooleanField( - default=False, help_text="Confidential message flag" - ), - ), - ( - "delivery_attempts", - models.PositiveIntegerField( - default=0, help_text="Number of delivery attempts" - ), - ), - ( - "max_delivery_attempts", - models.PositiveIntegerField( - default=3, help_text="Maximum delivery attempts" - ), - ), - ( - "message_thread_id", - models.UUIDField( - blank=True, - help_text="Thread ID for message grouping", - null=True, - ), - ), - ( - "external_message_id", - models.CharField( - blank=True, - help_text="External system message ID", - max_length=255, - null=True, - ), - ), - ( - "has_attachments", - models.BooleanField( - default=False, help_text="Message has attachments" - ), - ), - ( - "content_type", - models.CharField( - default="text/plain", - help_text="Content MIME type", - max_length=50, - ), - ), - ], - options={ - "db_table": "communications_message", - "ordering": ["-created_at"], - }, - ), - migrations.CreateModel( - name="MessageRecipient", - fields=[ - ( - "recipient_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique identifier for the recipient", - primary_key=True, - serialize=False, - ), - ), - ( - "recipient_type", - models.CharField( - choices=[ - ("USER", "User"), - ("EMAIL", "Email Address"), - ("PHONE", "Phone Number"), - ("ROLE", "User Role"), - ("DEPARTMENT", "Department"), - ("GROUP", "User Group"), - ], - help_text="Type of recipient", - max_length=20, - ), - ), - ( - "email_address", - models.EmailField( - blank=True, - help_text="Email address recipient", - max_length=254, - null=True, - ), - ), - ( - "phone_number", - models.CharField( - blank=True, - help_text="Phone number recipient", - max_length=20, - null=True, - ), - ), - ( - "role_name", - models.CharField( - blank=True, - help_text="Role name for role-based recipients", - max_length=100, - null=True, - ), - ), - ( - "status", - models.CharField( - choices=[ - ("PENDING", "Pending"), - ("SENT", "Sent"), - ("DELIVERED", "Delivered"), - ("READ", "Read"), - ("ACKNOWLEDGED", "Acknowledged"), - ("FAILED", "Failed"), - ("BOUNCED", "Bounced"), - ("UNSUBSCRIBED", "Unsubscribed"), - ], - default="PENDING", - help_text="Delivery status", - max_length=20, - ), - ), - ( - "sent_at", - models.DateTimeField( - blank=True, help_text="Sent timestamp", null=True - ), - ), - ( - "delivered_at", - models.DateTimeField( - blank=True, help_text="Delivered timestamp", null=True - ), - ), - ( - "read_at", - models.DateTimeField( - blank=True, help_text="Read timestamp", null=True - ), - ), - ( - "acknowledged_at", - models.DateTimeField( - blank=True, help_text="Acknowledged timestamp", null=True - ), - ), - ( - "delivery_attempts", - models.PositiveIntegerField( - default=0, help_text="Number of delivery attempts" - ), - ), - ( - "last_attempt_at", - models.DateTimeField( - blank=True, - help_text="Last delivery attempt timestamp", - null=True, - ), - ), - ( - "error_message", - models.TextField( - blank=True, help_text="Last delivery error message", null=True - ), - ), - ( - "external_delivery_id", - models.CharField( - blank=True, - help_text="External delivery tracking ID", - max_length=255, - null=True, - ), - ), - ], - options={ - "db_table": "communications_message_recipient", - }, - ), - migrations.CreateModel( - name="NotificationTemplate", - fields=[ - ( - "template_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique identifier for the template", - primary_key=True, - serialize=False, - ), - ), - ("name", models.CharField(help_text="Template name", max_length=255)), - ( - "description", - models.TextField( - blank=True, help_text="Template description", null=True - ), - ), - ( - "template_type", - models.CharField( - choices=[ - ("EMAIL", "Email Template"), - ("SMS", "SMS Template"), - ("PUSH", "Push Notification Template"), - ("SLACK", "Slack Template"), - ("TEAMS", "Teams Template"), - ("WEBHOOK", "Webhook Template"), - ("SYSTEM", "System Notification Template"), - ], - help_text="Type of template", - max_length=20, - ), - ), - ( - "category", - models.CharField( - choices=[ - ("APPOINTMENT", "Appointment Notifications"), - ("MEDICATION", "Medication Reminders"), - ("LAB_RESULTS", "Lab Results"), - ("BILLING", "Billing Notifications"), - ("EMERGENCY", "Emergency Alerts"), - ("SYSTEM", "System Notifications"), - ("MARKETING", "Marketing Communications"), - ("CLINICAL", "Clinical Notifications"), - ("ADMINISTRATIVE", "Administrative Messages"), - ("QUALITY", "Quality Alerts"), - ], - help_text="Template category", - max_length=30, - ), - ), - ( - "subject_template", - models.CharField( - blank=True, - help_text="Subject line template", - max_length=255, - null=True, - ), - ), - ( - "content_template", - models.TextField(help_text="Message content template"), - ), - ( - "variables", - models.JSONField( - default=dict, help_text="Available template variables" - ), - ), - ( - "default_values", - models.JSONField(default=dict, help_text="Default variable values"), - ), - ( - "formatting_rules", - models.JSONField( - default=dict, help_text="Content formatting rules" - ), - ), - ( - "is_active", - models.BooleanField(default=True, help_text="Template is active"), - ), - ( - "is_system_template", - models.BooleanField( - default=False, help_text="System-defined template" - ), - ), - ( - "requires_approval", - models.BooleanField( - default=False, help_text="Requires approval before use" - ), - ), - ( - "usage_count", - models.PositiveIntegerField( - default=0, help_text="Number of times template has been used" - ), - ), - ( - "last_used_at", - models.DateTimeField( - blank=True, help_text="Last usage timestamp", null=True - ), - ), - ( - "created_at", - models.DateTimeField( - auto_now_add=True, help_text="Template creation timestamp" - ), - ), - ( - "updated_at", - models.DateTimeField( - auto_now=True, help_text="Last update timestamp" - ), - ), - ], - options={ - "db_table": "communications_notification_template", - }, - ), - migrations.CreateModel( - name="AlertInstance", - fields=[ - ( - "alert_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique identifier for the alert instance", - primary_key=True, - serialize=False, - ), - ), - ("title", models.CharField(help_text="Alert title", max_length=255)), - ("description", models.TextField(help_text="Alert description")), - ( - "severity", - models.CharField( - choices=[ - ("INFO", "Information"), - ("WARNING", "Warning"), - ("ERROR", "Error"), - ("CRITICAL", "Critical"), - ("EMERGENCY", "Emergency"), - ], - help_text="Alert severity level", - max_length=20, - ), - ), - ( - "trigger_data", - models.JSONField( - default=dict, help_text="Data that triggered the alert" - ), - ), - ( - "context_data", - models.JSONField(default=dict, help_text="Additional context data"), - ), - ( - "status", - models.CharField( - choices=[ - ("ACTIVE", "Active"), - ("ACKNOWLEDGED", "Acknowledged"), - ("RESOLVED", "Resolved"), - ("SUPPRESSED", "Suppressed"), - ("ESCALATED", "Escalated"), - ("EXPIRED", "Expired"), - ], - default="ACTIVE", - help_text="Alert status", - max_length=20, - ), - ), - ( - "triggered_at", - models.DateTimeField( - auto_now_add=True, help_text="Alert trigger timestamp" - ), - ), - ( - "acknowledged_at", - models.DateTimeField( - blank=True, help_text="Acknowledgment timestamp", null=True - ), - ), - ( - "resolved_at", - models.DateTimeField( - blank=True, help_text="Resolution timestamp", null=True - ), - ), - ( - "expires_at", - models.DateTimeField( - blank=True, help_text="Alert expiration time", null=True - ), - ), - ( - "resolution_notes", - models.TextField( - blank=True, help_text="Resolution notes", null=True - ), - ), - ( - "escalation_level", - models.PositiveIntegerField( - default=0, help_text="Current escalation level" - ), - ), - ( - "escalated_at", - models.DateTimeField( - blank=True, help_text="Last escalation timestamp", null=True - ), - ), - ( - "notifications_sent", - models.PositiveIntegerField( - default=0, help_text="Number of notifications sent" - ), - ), - ( - "last_notification_at", - models.DateTimeField( - blank=True, help_text="Last notification timestamp", null=True - ), - ), - ( - "acknowledged_by", - models.ForeignKey( - blank=True, - help_text="User who acknowledged the alert", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="acknowledged_alerts", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "resolved_by", - models.ForeignKey( - blank=True, - help_text="User who resolved the alert", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="resolved_alerts", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "db_table": "communications_alert_instance", - "ordering": ["-triggered_at"], - }, - ), - migrations.CreateModel( - name="AlertRule", - fields=[ - ( - "rule_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique identifier for the alert rule", - primary_key=True, - serialize=False, - ), - ), - ("name", models.CharField(help_text="Alert rule name", max_length=255)), - ( - "description", - models.TextField( - blank=True, help_text="Alert rule description", null=True - ), - ), - ( - "trigger_type", - models.CharField( - choices=[ - ("THRESHOLD", "Threshold Alert"), - ("PATTERN", "Pattern Alert"), - ("SCHEDULE", "Scheduled Alert"), - ("EVENT", "Event-based Alert"), - ("ANOMALY", "Anomaly Detection"), - ("SYSTEM", "System Alert"), - ("CLINICAL", "Clinical Alert"), - ("OPERATIONAL", "Operational Alert"), - ], - help_text="Type of alert trigger", - max_length=20, - ), - ), - ( - "severity", - models.CharField( - choices=[ - ("INFO", "Information"), - ("WARNING", "Warning"), - ("ERROR", "Error"), - ("CRITICAL", "Critical"), - ("EMERGENCY", "Emergency"), - ], - default="WARNING", - help_text="Alert severity level", - max_length=20, - ), - ), - ( - "trigger_conditions", - models.JSONField( - default=dict, help_text="Conditions that trigger the alert" - ), - ), - ( - "evaluation_frequency", - models.PositiveIntegerField( - default=300, help_text="Evaluation frequency in seconds" - ), - ), - ( - "cooldown_period", - models.PositiveIntegerField( - default=3600, - help_text="Cooldown period between alerts in seconds", - ), - ), - ( - "notification_channels", - models.JSONField( - default=list, help_text="Notification channels to use" - ), - ), - ( - "escalation_rules", - models.JSONField( - default=dict, help_text="Escalation configuration" - ), - ), - ( - "recipient_roles", - models.JSONField(default=list, help_text="Recipient roles"), - ), - ( - "is_active", - models.BooleanField(default=True, help_text="Alert rule is active"), - ), - ( - "is_system_rule", - models.BooleanField(default=False, help_text="System-defined rule"), - ), - ( - "trigger_count", - models.PositiveIntegerField( - default=0, help_text="Number of times rule has triggered" - ), - ), - ( - "last_triggered_at", - models.DateTimeField( - blank=True, help_text="Last trigger timestamp", null=True - ), - ), - ( - "last_evaluated_at", - models.DateTimeField( - blank=True, help_text="Last evaluation timestamp", null=True - ), - ), - ( - "created_at", - models.DateTimeField( - auto_now_add=True, help_text="Rule creation timestamp" - ), - ), - ( - "updated_at", - models.DateTimeField( - auto_now=True, help_text="Last update timestamp" - ), - ), - ( - "created_by", - models.ForeignKey( - blank=True, - help_text="Rule creator", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_alert_rules", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "default_recipients", - models.ManyToManyField( - blank=True, - help_text="Default alert recipients", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "db_table": "communications_alert_rule", - }, - ), - ] diff --git a/communications/migrations/0002_initial.py b/communications/migrations/0002_initial.py deleted file mode 100644 index 896eb9cf..00000000 --- a/communications/migrations/0002_initial.py +++ /dev/null @@ -1,375 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("communications", "0001_initial"), - ("core", "0001_initial"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name="alertrule", - name="tenant", - field=models.ForeignKey( - help_text="Tenant organization", - on_delete=django.db.models.deletion.CASCADE, - to="core.tenant", - ), - ), - migrations.AddField( - model_name="alertinstance", - name="alert_rule", - field=models.ForeignKey( - help_text="Associated alert rule", - on_delete=django.db.models.deletion.CASCADE, - related_name="instances", - to="communications.alertrule", - ), - ), - migrations.AddField( - model_name="communicationchannel", - name="created_by", - field=models.ForeignKey( - blank=True, - help_text="Channel creator", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="communicationchannel", - name="tenant", - field=models.ForeignKey( - help_text="Tenant organization", - on_delete=django.db.models.deletion.CASCADE, - to="core.tenant", - ), - ), - migrations.AddField( - model_name="deliverylog", - name="channel", - field=models.ForeignKey( - help_text="Communication channel used", - on_delete=django.db.models.deletion.CASCADE, - related_name="delivery_logs", - to="communications.communicationchannel", - ), - ), - migrations.AddField( - model_name="message", - name="reply_to_message", - field=models.ForeignKey( - blank=True, - help_text="Original message if this is a reply", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="communications.message", - ), - ), - migrations.AddField( - model_name="message", - name="sender", - field=models.ForeignKey( - help_text="Message sender", - on_delete=django.db.models.deletion.CASCADE, - related_name="sent_messages", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="message", - name="tenant", - field=models.ForeignKey( - help_text="Tenant organization", - on_delete=django.db.models.deletion.CASCADE, - to="core.tenant", - ), - ), - migrations.AddField( - model_name="deliverylog", - name="message", - field=models.ForeignKey( - help_text="Associated message", - on_delete=django.db.models.deletion.CASCADE, - related_name="delivery_logs", - to="communications.message", - ), - ), - migrations.AddField( - model_name="messagerecipient", - name="message", - field=models.ForeignKey( - help_text="Associated message", - on_delete=django.db.models.deletion.CASCADE, - related_name="recipients", - to="communications.message", - ), - ), - migrations.AddField( - model_name="messagerecipient", - name="user", - field=models.ForeignKey( - blank=True, - help_text="User recipient", - null=True, - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="deliverylog", - name="recipient", - field=models.ForeignKey( - help_text="Associated recipient", - on_delete=django.db.models.deletion.CASCADE, - related_name="delivery_logs", - to="communications.messagerecipient", - ), - ), - migrations.AddField( - model_name="notificationtemplate", - name="created_by", - field=models.ForeignKey( - blank=True, - help_text="Template creator", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="notificationtemplate", - name="tenant", - field=models.ForeignKey( - help_text="Tenant organization", - on_delete=django.db.models.deletion.CASCADE, - to="core.tenant", - ), - ), - migrations.AddField( - model_name="alertrule", - name="notification_template", - field=models.ForeignKey( - blank=True, - help_text="Notification template to use", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="communications.notificationtemplate", - ), - ), - migrations.AddIndex( - model_name="alertinstance", - index=models.Index( - fields=["alert_rule", "status"], name="communicati_alert_r_69aa1e_idx" - ), - ), - migrations.AddIndex( - model_name="alertinstance", - index=models.Index( - fields=["severity", "triggered_at"], - name="communicati_severit_65b0c9_idx", - ), - ), - migrations.AddIndex( - model_name="alertinstance", - index=models.Index( - fields=["status", "triggered_at"], name="communicati_status_402adb_idx" - ), - ), - migrations.AddIndex( - model_name="alertinstance", - index=models.Index( - fields=["expires_at"], name="communicati_expires_2c10ee_idx" - ), - ), - migrations.AddIndex( - model_name="communicationchannel", - index=models.Index( - fields=["tenant", "channel_type"], name="communicati_tenant__0326d1_idx" - ), - ), - migrations.AddIndex( - model_name="communicationchannel", - index=models.Index( - fields=["is_active", "is_healthy"], - name="communicati_is_acti_77d48f_idx", - ), - ), - migrations.AddIndex( - model_name="communicationchannel", - index=models.Index( - fields=["last_health_check"], name="communicati_last_he_71110c_idx" - ), - ), - migrations.AddIndex( - model_name="communicationchannel", - index=models.Index( - fields=["provider_type"], name="communicati_provide_583ead_idx" - ), - ), - migrations.AlterUniqueTogether( - name="communicationchannel", - unique_together={("tenant", "name")}, - ), - migrations.AddIndex( - model_name="message", - index=models.Index( - fields=["tenant", "status"], name="communicati_tenant__d41606_idx" - ), - ), - migrations.AddIndex( - model_name="message", - index=models.Index( - fields=["sender", "created_at"], name="communicati_sender__7da57d_idx" - ), - ), - migrations.AddIndex( - model_name="message", - index=models.Index( - fields=["message_type", "priority"], - name="communicati_message_b3baa0_idx", - ), - ), - migrations.AddIndex( - model_name="message", - index=models.Index( - fields=["scheduled_at"], name="communicati_schedul_59afe2_idx" - ), - ), - migrations.AddIndex( - model_name="message", - index=models.Index( - fields=["message_thread_id"], name="communicati_message_990a68_idx" - ), - ), - migrations.AddIndex( - model_name="messagerecipient", - index=models.Index( - fields=["message", "status"], name="communicati_message_6c5b5b_idx" - ), - ), - migrations.AddIndex( - model_name="messagerecipient", - index=models.Index( - fields=["user", "status"], name="communicati_user_id_2e3702_idx" - ), - ), - migrations.AddIndex( - model_name="messagerecipient", - index=models.Index( - fields=["recipient_type"], name="communicati_recipie_b45f4d_idx" - ), - ), - migrations.AddIndex( - model_name="messagerecipient", - index=models.Index( - fields=["sent_at"], name="communicati_sent_at_10fda1_idx" - ), - ), - migrations.AlterUniqueTogether( - name="messagerecipient", - unique_together={ - ("message", "email_address"), - ("message", "phone_number"), - ("message", "user"), - }, - ), - migrations.AddIndex( - model_name="deliverylog", - index=models.Index( - fields=["message", "status"], name="communicati_message_fdf561_idx" - ), - ), - migrations.AddIndex( - model_name="deliverylog", - index=models.Index( - fields=["recipient", "status"], name="communicati_recipie_8a8767_idx" - ), - ), - migrations.AddIndex( - model_name="deliverylog", - index=models.Index( - fields=["channel", "started_at"], name="communicati_channel_03e902_idx" - ), - ), - migrations.AddIndex( - model_name="deliverylog", - index=models.Index( - fields=["status", "started_at"], name="communicati_status_eb46bd_idx" - ), - ), - migrations.AddIndex( - model_name="deliverylog", - index=models.Index( - fields=["external_id"], name="communicati_externa_64021f_idx" - ), - ), - migrations.AddIndex( - model_name="notificationtemplate", - index=models.Index( - fields=["tenant", "template_type"], - name="communicati_tenant__c0ae05_idx", - ), - ), - migrations.AddIndex( - model_name="notificationtemplate", - index=models.Index( - fields=["category", "is_active"], name="communicati_categor_2c7900_idx" - ), - ), - migrations.AddIndex( - model_name="notificationtemplate", - index=models.Index( - fields=["is_system_template"], name="communicati_is_syst_dae5b7_idx" - ), - ), - migrations.AddIndex( - model_name="notificationtemplate", - index=models.Index( - fields=["usage_count"], name="communicati_usage_c_d78c30_idx" - ), - ), - migrations.AlterUniqueTogether( - name="notificationtemplate", - unique_together={("tenant", "name", "template_type")}, - ), - migrations.AddIndex( - model_name="alertrule", - index=models.Index( - fields=["tenant", "is_active"], name="communicati_tenant__6d58f7_idx" - ), - ), - migrations.AddIndex( - model_name="alertrule", - index=models.Index( - fields=["trigger_type", "severity"], - name="communicati_trigger_0ab274_idx", - ), - ), - migrations.AddIndex( - model_name="alertrule", - index=models.Index( - fields=["last_evaluated_at"], name="communicati_last_ev_3529e2_idx" - ), - ), - migrations.AddIndex( - model_name="alertrule", - index=models.Index( - fields=["is_system_rule"], name="communicati_is_syst_52420d_idx" - ), - ), - migrations.AlterUniqueTogether( - name="alertrule", - unique_together={("tenant", "name")}, - ), - ] diff --git a/communications/migrations/__pycache__/0001_initial.cpython-312.pyc b/communications/migrations/__pycache__/0001_initial.cpython-312.pyc deleted file mode 100644 index 4061a4d8..00000000 Binary files a/communications/migrations/__pycache__/0001_initial.cpython-312.pyc and /dev/null differ diff --git a/communications/migrations/__pycache__/0002_initial.cpython-312.pyc b/communications/migrations/__pycache__/0002_initial.cpython-312.pyc deleted file mode 100644 index 65963b1a..00000000 Binary files a/communications/migrations/__pycache__/0002_initial.cpython-312.pyc and /dev/null differ diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py deleted file mode 100644 index 01651b98..00000000 --- a/core/migrations/0001_initial.py +++ /dev/null @@ -1,921 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.core.validators -import django.db.models.deletion -import django.utils.timezone -import uuid -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("contenttypes", "0002_remove_content_type_name"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="Tenant", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "tenant_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique tenant identifier", - unique=True, - ), - ), - ( - "name", - models.CharField(help_text="Organization name", max_length=200), - ), - ( - "display_name", - models.CharField( - help_text="Display name for the organization", max_length=200 - ), - ), - ( - "description", - models.TextField( - blank=True, help_text="Organization description", null=True - ), - ), - ( - "organization_type", - models.CharField( - choices=[ - ("HOSPITAL", "Hospital"), - ("CLINIC", "Clinic"), - ("HEALTH_SYSTEM", "Health System"), - ("AMBULATORY", "Ambulatory Care"), - ("SPECIALTY", "Specialty Practice"), - ("URGENT_CARE", "Urgent Care"), - ("REHABILITATION", "Rehabilitation Center"), - ("LONG_TERM_CARE", "Long-term Care"), - ], - default="HOSPITAL", - max_length=50, - ), - ), - ( - "address_line1", - models.CharField(help_text="Address line 1", max_length=200), - ), - ( - "address_line2", - models.CharField( - blank=True, - help_text="Address line 2", - max_length=200, - null=True, - ), - ), - ("city", models.CharField(help_text="City", max_length=100)), - ( - "state", - models.CharField(help_text="State or province", max_length=100), - ), - ( - "postal_code", - models.CharField(help_text="Postal code", max_length=20), - ), - ( - "country", - models.CharField( - default="Saudi Arabia", help_text="Country", max_length=100 - ), - ), - ( - "phone_number", - models.CharField( - help_text="Primary phone number", - max_length=20, - validators=[ - django.core.validators.RegexValidator( - message='Phone number must be entered in the format: "+999999999". Up to 15 digits allowed.', - regex="^\\+?1?\\d{9,15}$", - ) - ], - ), - ), - ( - "email", - models.EmailField( - help_text="Primary email address", max_length=254 - ), - ), - ( - "website", - models.URLField( - blank=True, help_text="Organization website", null=True - ), - ), - ( - "license_number", - models.CharField( - blank=True, - help_text="Healthcare license number", - max_length=100, - null=True, - ), - ), - ( - "accreditation_body", - models.CharField( - blank=True, - help_text="Accreditation body (e.g., Joint Commission)", - max_length=100, - null=True, - ), - ), - ( - "accreditation_number", - models.CharField( - blank=True, - help_text="Accreditation number", - max_length=100, - null=True, - ), - ), - ( - "accreditation_expiry", - models.DateField( - blank=True, help_text="Accreditation expiry date", null=True - ), - ), - ( - "timezone", - models.CharField( - default="UTC", help_text="Organization timezone", max_length=50 - ), - ), - ( - "locale", - models.CharField( - default="en-US", help_text="Organization locale", max_length=10 - ), - ), - ( - "currency", - models.CharField( - default="SAR", - help_text="Organization currency code", - max_length=3, - ), - ), - ( - "subscription_plan", - models.CharField( - choices=[ - ("BASIC", "Basic"), - ("STANDARD", "Standard"), - ("PREMIUM", "Premium"), - ("ENTERPRISE", "Enterprise"), - ], - default="BASIC", - max_length=50, - ), - ), - ( - "max_users", - models.PositiveIntegerField( - default=50, help_text="Maximum number of users allowed" - ), - ), - ( - "max_patients", - models.PositiveIntegerField( - default=1000, help_text="Maximum number of patients allowed" - ), - ), - ( - "is_active", - models.BooleanField(default=True, help_text="Tenant is active"), - ), - ( - "is_trial", - models.BooleanField(default=False, help_text="Tenant is on trial"), - ), - ( - "trial_expires_at", - models.DateTimeField( - blank=True, help_text="Trial expiration date", null=True - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Tenant", - "verbose_name_plural": "Tenants", - "db_table": "core_tenant", - "ordering": ["name"], - }, - ), - migrations.CreateModel( - name="SystemNotification", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "notification_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique notification identifier", - unique=True, - ), - ), - ( - "title", - models.CharField(help_text="Notification title", max_length=200), - ), - ("message", models.TextField(help_text="Notification message")), - ( - "notification_type", - models.CharField( - choices=[ - ("INFO", "Information"), - ("WARNING", "Warning"), - ("ERROR", "Error"), - ("SUCCESS", "Success"), - ("MAINTENANCE", "Maintenance"), - ("SECURITY", "Security Alert"), - ("FEATURE", "New Feature"), - ("UPDATE", "System Update"), - ], - max_length=30, - ), - ), - ( - "priority", - models.CharField( - choices=[ - ("LOW", "Low"), - ("MEDIUM", "Medium"), - ("HIGH", "High"), - ("URGENT", "Urgent"), - ], - default="MEDIUM", - max_length=20, - ), - ), - ( - "target_audience", - models.CharField( - choices=[ - ("ALL_USERS", "All Users"), - ("ADMINISTRATORS", "Administrators"), - ("CLINICAL_STAFF", "Clinical Staff"), - ("SUPPORT_STAFF", "Support Staff"), - ("SPECIFIC_ROLES", "Specific Roles"), - ("SPECIFIC_USERS", "Specific Users"), - ], - default="ALL_USERS", - max_length=30, - ), - ), - ( - "target_roles", - models.JSONField( - default=list, - help_text="Target user roles (if target_audience is SPECIFIC_ROLES)", - ), - ), - ( - "is_dismissible", - models.BooleanField( - default=True, help_text="Users can dismiss this notification" - ), - ), - ( - "auto_dismiss_after", - models.PositiveIntegerField( - blank=True, help_text="Auto-dismiss after X seconds", null=True - ), - ), - ( - "show_on_login", - models.BooleanField( - default=False, help_text="Show notification on user login" - ), - ), - ( - "start_date", - models.DateTimeField( - default=django.utils.timezone.now, - help_text="Notification start date", - ), - ), - ( - "end_date", - models.DateTimeField( - blank=True, help_text="Notification end date", null=True - ), - ), - ( - "action_url", - models.URLField( - blank=True, - help_text="Action URL for the notification", - null=True, - ), - ), - ( - "action_text", - models.CharField( - blank=True, - help_text="Action button text", - max_length=100, - null=True, - ), - ), - ( - "is_active", - models.BooleanField( - default=True, help_text="Notification is active" - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ( - "created_by", - models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_notifications", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "target_users", - models.ManyToManyField( - blank=True, - related_name="targeted_notifications", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "tenant", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="notifications", - to="core.tenant", - ), - ), - ], - options={ - "verbose_name": "System Notification", - "verbose_name_plural": "System Notifications", - "db_table": "core_system_notification", - "ordering": ["-priority", "-created_at"], - }, - ), - migrations.CreateModel( - name="SystemConfiguration", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "key", - models.CharField(help_text="Configuration key", max_length=200), - ), - ("value", models.TextField(help_text="Configuration value")), - ( - "data_type", - models.CharField( - choices=[ - ("STRING", "String"), - ("INTEGER", "Integer"), - ("FLOAT", "Float"), - ("BOOLEAN", "Boolean"), - ("JSON", "JSON"), - ("DATE", "Date"), - ("DATETIME", "DateTime"), - ], - default="STRING", - max_length=20, - ), - ), - ( - "category", - models.CharField( - help_text="Configuration category", max_length=100 - ), - ), - ( - "description", - models.TextField( - blank=True, help_text="Configuration description", null=True - ), - ), - ( - "validation_rules", - models.JSONField( - default=dict, - help_text="Validation rules for the configuration value", - ), - ), - ( - "default_value", - models.TextField(blank=True, help_text="Default value", null=True), - ), - ( - "is_sensitive", - models.BooleanField( - default=False, help_text="Configuration contains sensitive data" - ), - ), - ( - "is_encrypted", - models.BooleanField( - default=False, help_text="Configuration value is encrypted" - ), - ), - ( - "required_permission", - models.CharField( - blank=True, - help_text="Permission required to modify this configuration", - max_length=100, - null=True, - ), - ), - ( - "is_active", - models.BooleanField( - default=True, help_text="Configuration is active" - ), - ), - ( - "is_readonly", - models.BooleanField( - default=False, help_text="Configuration is read-only" - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "updated_by", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="updated_configurations", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "tenant", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="configurations", - to="core.tenant", - ), - ), - ], - options={ - "verbose_name": "System Configuration", - "verbose_name_plural": "System Configurations", - "db_table": "core_system_configuration", - "ordering": ["category", "key"], - "unique_together": {("tenant", "key")}, - }, - ), - migrations.CreateModel( - name="IntegrationLog", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "log_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique log identifier", - unique=True, - ), - ), - ( - "integration_type", - models.CharField( - choices=[ - ("HL7", "HL7 Message"), - ("DICOM", "DICOM Communication"), - ("API", "API Call"), - ("DATABASE", "Database Sync"), - ("FILE_TRANSFER", "File Transfer"), - ("WEBHOOK", "Webhook"), - ("EMAIL", "Email"), - ("SMS", "SMS"), - ], - max_length=30, - ), - ), - ( - "direction", - models.CharField( - choices=[("INBOUND", "Inbound"), ("OUTBOUND", "Outbound")], - max_length=10, - ), - ), - ( - "external_system", - models.CharField(help_text="External system name", max_length=200), - ), - ( - "endpoint", - models.CharField( - blank=True, - help_text="Integration endpoint", - max_length=500, - null=True, - ), - ), - ( - "message_type", - models.CharField( - blank=True, - help_text="Message type (e.g., HL7 message type)", - max_length=100, - null=True, - ), - ), - ( - "message_id", - models.CharField( - blank=True, - help_text="Message identifier", - max_length=200, - null=True, - ), - ), - ( - "correlation_id", - models.UUIDField( - blank=True, - help_text="Correlation ID for tracking related messages", - null=True, - ), - ), - ( - "request_data", - models.TextField( - blank=True, help_text="Request data sent", null=True - ), - ), - ( - "response_data", - models.TextField( - blank=True, help_text="Response data received", null=True - ), - ), - ( - "status", - models.CharField( - choices=[ - ("SUCCESS", "Success"), - ("FAILED", "Failed"), - ("PENDING", "Pending"), - ("TIMEOUT", "Timeout"), - ("RETRY", "Retry"), - ], - max_length=20, - ), - ), - ( - "error_code", - models.CharField( - blank=True, help_text="Error code", max_length=50, null=True - ), - ), - ( - "error_message", - models.TextField(blank=True, help_text="Error message", null=True), - ), - ( - "processing_time_ms", - models.PositiveIntegerField( - blank=True, - help_text="Processing time in milliseconds", - null=True, - ), - ), - ( - "timestamp", - models.DateTimeField( - default=django.utils.timezone.now, help_text="Log timestamp" - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ( - "tenant", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="integration_logs", - to="core.tenant", - ), - ), - ], - options={ - "verbose_name": "Integration Log", - "verbose_name_plural": "Integration Logs", - "db_table": "core_integration_log", - "ordering": ["-timestamp"], - "indexes": [ - models.Index( - fields=["tenant", "integration_type", "timestamp"], - name="core_integr_tenant__b44419_idx", - ), - models.Index( - fields=["external_system", "status"], - name="core_integr_externa_11a6db_idx", - ), - models.Index( - fields=["correlation_id"], name="core_integr_correla_d72107_idx" - ), - ], - }, - ), - migrations.CreateModel( - name="AuditLogEntry", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "log_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique log identifier", - unique=True, - ), - ), - ( - "event_type", - models.CharField( - choices=[ - ("CREATE", "Create"), - ("READ", "Read"), - ("UPDATE", "Update"), - ("DELETE", "Delete"), - ("LOGIN", "Login"), - ("LOGOUT", "Logout"), - ("ACCESS", "Access"), - ("EXPORT", "Export"), - ("PRINT", "Print"), - ("SHARE", "Share"), - ("SYSTEM", "System Event"), - ("ERROR", "Error"), - ("SECURITY", "Security Event"), - ], - max_length=50, - ), - ), - ( - "event_category", - models.CharField( - choices=[ - ("AUTHENTICATION", "Authentication"), - ("AUTHORIZATION", "Authorization"), - ("DATA_ACCESS", "Data Access"), - ("DATA_MODIFICATION", "Data Modification"), - ("SYSTEM_ADMINISTRATION", "System Administration"), - ("PATIENT_DATA", "Patient Data"), - ("CLINICAL_DATA", "Clinical Data"), - ("FINANCIAL_DATA", "Financial Data"), - ("SECURITY", "Security"), - ("INTEGRATION", "Integration"), - ("REPORTING", "Reporting"), - ], - max_length=50, - ), - ), - ( - "user_email", - models.EmailField( - blank=True, - help_text="User email at time of event", - max_length=254, - null=True, - ), - ), - ( - "user_role", - models.CharField( - blank=True, - help_text="User role at time of event", - max_length=50, - null=True, - ), - ), - ( - "session_key", - models.CharField( - blank=True, help_text="Session key", max_length=40, null=True - ), - ), - ( - "ip_address", - models.GenericIPAddressField( - blank=True, help_text="IP address", null=True - ), - ), - ( - "user_agent", - models.TextField( - blank=True, help_text="User agent string", null=True - ), - ), - ("object_id", models.PositiveIntegerField(blank=True, null=True)), - ( - "object_repr", - models.CharField( - blank=True, - help_text="String representation of the object", - max_length=200, - null=True, - ), - ), - ( - "action", - models.CharField(help_text="Action performed", max_length=200), - ), - ( - "description", - models.TextField(help_text="Detailed description of the event"), - ), - ( - "changes", - models.JSONField( - default=dict, help_text="Field changes (before/after values)" - ), - ), - ( - "additional_data", - models.JSONField(default=dict, help_text="Additional event data"), - ), - ( - "patient_id", - models.CharField( - blank=True, - help_text="Patient identifier if applicable", - max_length=50, - null=True, - ), - ), - ( - "patient_mrn", - models.CharField( - blank=True, - help_text="Patient MRN if applicable", - max_length=50, - null=True, - ), - ), - ( - "risk_level", - models.CharField( - choices=[ - ("LOW", "Low"), - ("MEDIUM", "Medium"), - ("HIGH", "High"), - ("CRITICAL", "Critical"), - ], - default="LOW", - max_length=20, - ), - ), - ( - "hipaa_relevant", - models.BooleanField( - default=False, help_text="Event is HIPAA relevant" - ), - ), - ( - "gdpr_relevant", - models.BooleanField( - default=False, help_text="Event is GDPR relevant" - ), - ), - ( - "is_successful", - models.BooleanField(default=True, help_text="Event was successful"), - ), - ( - "error_message", - models.TextField( - blank=True, help_text="Error message if event failed", null=True - ), - ), - ( - "timestamp", - models.DateTimeField( - default=django.utils.timezone.now, help_text="Event timestamp" - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ( - "content_type", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="contenttypes.contenttype", - ), - ), - ( - "user", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="audit_logs", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "tenant", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="audit_logs", - to="core.tenant", - ), - ), - ], - options={ - "verbose_name": "Audit Log Entry", - "verbose_name_plural": "Audit Log Entries", - "db_table": "core_audit_log_entry", - "ordering": ["-timestamp"], - "indexes": [ - models.Index( - fields=["tenant", "event_type", "timestamp"], - name="core_audit__tenant__96449c_idx", - ), - models.Index( - fields=["user", "timestamp"], - name="core_audit__user_id_4190d3_idx", - ), - models.Index( - fields=["patient_mrn", "timestamp"], - name="core_audit__patient_9afecd_idx", - ), - models.Index( - fields=["content_type", "object_id"], - name="core_audit__content_4866d0_idx", - ), - models.Index( - fields=["risk_level", "timestamp"], - name="core_audit__risk_le_b2cf34_idx", - ), - ], - }, - ), - ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-312.pyc b/core/migrations/__pycache__/0001_initial.cpython-312.pyc deleted file mode 100644 index 87e55244..00000000 Binary files a/core/migrations/__pycache__/0001_initial.cpython-312.pyc and /dev/null differ diff --git a/create_insurance_tables.py b/create_insurance_tables.py new file mode 100644 index 00000000..1845ec95 --- /dev/null +++ b/create_insurance_tables.py @@ -0,0 +1,213 @@ +""" +Directly create insurance_approvals tables using SQL. +""" +import sqlite3 +import os + +db_path = 'db.sqlite3' +conn = sqlite3.connect(db_path) +cursor = conn.cursor() + +print("Creating insurance_approvals tables directly...") + +# Disable foreign key checks +cursor.execute("PRAGMA foreign_keys = OFF;") + +# Create tables from migration SQL +tables_sql = [ + # InsuranceApprovalRequest table + """ + CREATE TABLE IF NOT EXISTS "insurance_approvals_request" ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "approval_id" char(32) NOT NULL UNIQUE, + "approval_number" varchar(30) NOT NULL UNIQUE, + "request_type" varchar(20) NOT NULL, + "service_description" varchar(500) NOT NULL, + "procedure_codes" text NOT NULL, + "diagnosis_codes" text NOT NULL, + "clinical_justification" text NOT NULL, + "medical_necessity" text NULL, + "alternative_treatments_tried" text NULL, + "requested_quantity" integer unsigned NOT NULL CHECK ("requested_quantity" >= 0), + "requested_visits" integer unsigned NULL CHECK ("requested_visits" >= 0), + "requested_units" integer unsigned NULL CHECK ("requested_units" >= 0), + "service_start_date" date NOT NULL, + "service_end_date" date NULL, + "status" varchar(30) NOT NULL, + "priority" varchar(20) NOT NULL, + "submission_method" varchar(20) NULL, + "submitted_date" datetime NULL, + "decision_date" datetime NULL, + "authorization_number" varchar(100) NULL, + "reference_number" varchar(100) NULL, + "approved_quantity" integer unsigned NULL CHECK ("approved_quantity" >= 0), + "approved_visits" integer unsigned NULL CHECK ("approved_visits" >= 0), + "approved_units" integer unsigned NULL CHECK ("approved_units" >= 0), + "approved_amount" decimal NULL, + "effective_date" date NULL, + "expiration_date" date NULL, + "denial_reason" text NULL, + "denial_code" varchar(50) NULL, + "appeal_date" datetime NULL, + "appeal_reason" text NULL, + "appeal_deadline" date NULL, + "last_contact_date" datetime NULL, + "last_contact_method" varchar(20) NULL, + "last_contact_notes" text NULL, + "is_urgent" bool NOT NULL, + "is_expedited" bool NOT NULL, + "requires_peer_review" bool NOT NULL, + "internal_notes" text NULL, + "insurance_notes" text NULL, + "created_at" datetime NOT NULL, + "updated_at" datetime NOT NULL, + "tenant_id" bigint NOT NULL REFERENCES "core_tenant" ("id") DEFERRABLE INITIALLY DEFERRED, + "patient_id" bigint NOT NULL REFERENCES "patients_patient_profile" ("id") DEFERRABLE INITIALLY DEFERRED, + "insurance_info_id" bigint NOT NULL REFERENCES "patients_insurance_info" ("id") DEFERRABLE INITIALLY DEFERRED, + "content_type_id" integer NOT NULL REFERENCES "django_content_type" ("id") DEFERRABLE INITIALLY DEFERRED, + "object_id" integer unsigned NOT NULL CHECK ("object_id" >= 0), + "submitted_by_id" integer NULL REFERENCES "accounts_user" ("id") DEFERRABLE INITIALLY DEFERRED, + "assigned_to_id" integer NULL REFERENCES "accounts_user" ("id") DEFERRABLE INITIALLY DEFERRED, + "requesting_provider_id" integer NOT NULL REFERENCES "accounts_user" ("id") DEFERRABLE INITIALLY DEFERRED, + "created_by_id" integer NULL REFERENCES "accounts_user" ("id") DEFERRABLE INITIALLY DEFERRED + ); + """, + + # ApprovalDocument table + """ + CREATE TABLE IF NOT EXISTS "insurance_approvals_document" ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "document_id" char(32) NOT NULL UNIQUE, + "document_type" varchar(30) NOT NULL, + "title" varchar(200) NOT NULL, + "description" text NULL, + "file" varchar(100) NOT NULL, + "file_size" integer unsigned NOT NULL CHECK ("file_size" >= 0), + "mime_type" varchar(100) NOT NULL, + "uploaded_at" datetime NOT NULL, + "approval_request_id" bigint NOT NULL REFERENCES "insurance_approvals_request" ("id") DEFERRABLE INITIALLY DEFERRED, + "uploaded_by_id" integer NULL REFERENCES "accounts_user" ("id") DEFERRABLE INITIALLY DEFERRED + ); + """, + + # ApprovalStatusHistory table + """ + CREATE TABLE IF NOT EXISTS "insurance_approvals_status_history" ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "from_status" varchar(30) NULL, + "to_status" varchar(30) NOT NULL, + "reason" text NULL, + "notes" text NULL, + "changed_at" datetime NOT NULL, + "approval_request_id" bigint NOT NULL REFERENCES "insurance_approvals_request" ("id") DEFERRABLE INITIALLY DEFERRED, + "changed_by_id" integer NULL REFERENCES "accounts_user" ("id") DEFERRABLE INITIALLY DEFERRED + ); + """, + + # ApprovalCommunicationLog table + """ + CREATE TABLE IF NOT EXISTS "insurance_approvals_communication_log" ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "communication_id" char(32) NOT NULL UNIQUE, + "communication_type" varchar(20) NOT NULL, + "contact_person" varchar(200) NULL, + "contact_number" varchar(50) NULL, + "subject" varchar(200) NOT NULL, + "message" text NOT NULL, + "response" text NULL, + "outcome" varchar(200) NULL, + "follow_up_required" bool NOT NULL, + "follow_up_date" date NULL, + "communicated_at" datetime NOT NULL, + "approval_request_id" bigint NOT NULL REFERENCES "insurance_approvals_request" ("id") DEFERRABLE INITIALLY DEFERRED, + "communicated_by_id" integer NULL REFERENCES "accounts_user" ("id") DEFERRABLE INITIALLY DEFERRED + ); + """, + + # ApprovalTemplate table + """ + CREATE TABLE IF NOT EXISTS "insurance_approvals_template" ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "template_id" char(32) NOT NULL UNIQUE, + "name" varchar(200) NOT NULL, + "description" text NULL, + "request_type" varchar(20) NOT NULL, + "insurance_company" varchar(200) NULL, + "clinical_justification_template" text NOT NULL, + "medical_necessity_template" text NULL, + "required_documents" text NOT NULL, + "required_codes" text NOT NULL, + "is_active" bool NOT NULL, + "usage_count" integer unsigned NOT NULL CHECK ("usage_count" >= 0), + "created_at" datetime NOT NULL, + "updated_at" datetime NOT NULL, + "tenant_id" bigint NOT NULL REFERENCES "core_tenant" ("id") DEFERRABLE INITIALLY DEFERRED, + "created_by_id" integer NULL REFERENCES "accounts_user" ("id") DEFERRABLE INITIALLY DEFERRED, + CONSTRAINT "insurance_approvals_template_tenant_id_name_uniq" UNIQUE ("tenant_id", "name") + ); + """ +] + +# Create tables +for i, sql in enumerate(tables_sql, 1): + try: + cursor.execute(sql) + print(f"✓ Created table {i}/5") + except sqlite3.OperationalError as e: + if "already exists" in str(e): + print(f"✓ Table {i}/5 already exists") + else: + print(f"✗ Error creating table {i}/5: {e}") + +# Create indexes +indexes_sql = [ + 'CREATE INDEX IF NOT EXISTS "insurance_a_tenant__d6763e_idx" ON "insurance_approvals_request" ("tenant_id", "status");', + 'CREATE INDEX IF NOT EXISTS "insurance_a_patient_00ddbd_idx" ON "insurance_approvals_request" ("patient_id", "status");', + 'CREATE INDEX IF NOT EXISTS "insurance_a_insuran_438196_idx" ON "insurance_approvals_request" ("insurance_info_id", "status");', + 'CREATE INDEX IF NOT EXISTS "insurance_a_approva_cefd6a_idx" ON "insurance_approvals_request" ("approval_number");', + 'CREATE INDEX IF NOT EXISTS "insurance_a_authori_f05618_idx" ON "insurance_approvals_request" ("authorization_number");', + 'CREATE INDEX IF NOT EXISTS "insurance_a_expirat_ef068e_idx" ON "insurance_approvals_request" ("expiration_date");', + 'CREATE INDEX IF NOT EXISTS "insurance_a_assigne_19b007_idx" ON "insurance_approvals_request" ("assigned_to_id", "status");', + 'CREATE INDEX IF NOT EXISTS "insurance_a_content_de7404_idx" ON "insurance_approvals_request" ("content_type_id", "object_id");', + 'CREATE INDEX IF NOT EXISTS "insurance_a_priorit_0abcbd_idx" ON "insurance_approvals_request" ("priority", "status");', + 'CREATE INDEX IF NOT EXISTS "insurance_a_approva_a5298b_idx" ON "insurance_approvals_status_history" ("approval_request_id", "changed_at");', + 'CREATE INDEX IF NOT EXISTS "insurance_a_approva_eae9e0_idx" ON "insurance_approvals_document" ("approval_request_id", "document_type");', + 'CREATE INDEX IF NOT EXISTS "insurance_a_approva_fa779d_idx" ON "insurance_approvals_communication_log" ("approval_request_id", "communicated_at");', + 'CREATE INDEX IF NOT EXISTS "insurance_a_follow__2a3b0a_idx" ON "insurance_approvals_communication_log" ("follow_up_required", "follow_up_date");', + 'CREATE INDEX IF NOT EXISTS "insurance_a_tenant__8f9c3a_idx" ON "insurance_approvals_template" ("tenant_id", "is_active");', + 'CREATE INDEX IF NOT EXISTS "insurance_a_request_4b3e2f_idx" ON "insurance_approvals_template" ("request_type");', +] + +print("\nCreating indexes...") +for i, sql in enumerate(indexes_sql, 1): + try: + cursor.execute(sql) + except sqlite3.OperationalError as e: + print(f" Index {i}: {e}") + +print(f"✓ Created {len(indexes_sql)} indexes") + +# Mark migration as applied +cursor.execute(""" + INSERT OR IGNORE INTO django_migrations (app, name, applied) + VALUES ('insurance_approvals', '0001_initial', datetime('now')) +""") + +conn.commit() + +# Re-enable foreign key checks +cursor.execute("PRAGMA foreign_keys = ON;") + +conn.close() + +print("\n" + "="*60) +print("✓ Insurance Approvals tables created successfully!") +print("="*60) +print("\nTables created:") +print(" 1. insurance_approvals_request") +print(" 2. insurance_approvals_document") +print(" 3. insurance_approvals_status_history") +print(" 4. insurance_approvals_communication_log") +print(" 5. insurance_approvals_template") +print("\nModule is ready to use!") +print("Access admin at: http://127.0.0.1:8000/admin/insurance_approvals/") diff --git a/db.sqlite3 b/db.sqlite3 deleted file mode 100644 index e9d90dfb..00000000 Binary files a/db.sqlite3 and /dev/null differ diff --git a/emr/__pycache__/urls.cpython-312.pyc b/emr/__pycache__/urls.cpython-312.pyc index c3ba37db..a271fe07 100644 Binary files a/emr/__pycache__/urls.cpython-312.pyc and b/emr/__pycache__/urls.cpython-312.pyc differ diff --git a/emr/__pycache__/views.cpython-312.pyc b/emr/__pycache__/views.cpython-312.pyc index 238bc9d0..36208d2e 100644 Binary files a/emr/__pycache__/views.cpython-312.pyc and b/emr/__pycache__/views.cpython-312.pyc differ diff --git a/emr/migrations/0001_initial.py b/emr/migrations/0001_initial.py deleted file mode 100644 index 31da0879..00000000 --- a/emr/migrations/0001_initial.py +++ /dev/null @@ -1,2128 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.core.validators -import django.db.models.deletion -import django.utils.timezone -import uuid -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("core", "0001_initial"), - ("patients", "0001_initial"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="Encounter", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "encounter_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique encounter identifier", - unique=True, - ), - ), - ( - "encounter_type", - models.CharField( - choices=[ - ("INPATIENT", "Inpatient"), - ("OUTPATIENT", "Outpatient"), - ("EMERGENCY", "Emergency"), - ("URGENT_CARE", "Urgent Care"), - ("OBSERVATION", "Observation"), - ("TELEMEDICINE", "Telemedicine"), - ("HOME_VISIT", "Home Visit"), - ("CONSULTATION", "Consultation"), - ("FOLLOW_UP", "Follow-up"), - ("PROCEDURE", "Procedure"), - ("SURGERY", "Surgery"), - ("DIAGNOSTIC", "Diagnostic"), - ("PREVENTIVE", "Preventive Care"), - ], - help_text="Type of encounter", - max_length=30, - ), - ), - ( - "encounter_class", - models.CharField( - choices=[ - ("AMB", "Ambulatory"), - ("EMER", "Emergency"), - ("FLD", "Field"), - ("HH", "Home Health"), - ("IMP", "Inpatient"), - ("ACUTE", "Inpatient Acute"), - ("NONAC", "Inpatient Non-Acute"), - ("OBSENC", "Observation Encounter"), - ("PRENC", "Pre-Admission"), - ("SS", "Short Stay"), - ("VR", "Virtual"), - ], - help_text="Encounter class (HL7 standard)", - max_length=20, - ), - ), - ( - "start_datetime", - models.DateTimeField(help_text="Encounter start date and time"), - ), - ( - "end_datetime", - models.DateTimeField( - blank=True, help_text="Encounter end date and time", null=True - ), - ), - ( - "status", - models.CharField( - choices=[ - ("PLANNED", "Planned"), - ("ARRIVED", "Arrived"), - ("TRIAGED", "Triaged"), - ("IN_PROGRESS", "In Progress"), - ("ON_HOLD", "On Hold"), - ("FINISHED", "Finished"), - ("CANCELLED", "Cancelled"), - ("ENTERED_IN_ERROR", "Entered in Error"), - ("UNKNOWN", "Unknown"), - ], - default="PLANNED", - help_text="Current encounter status", - max_length=20, - ), - ), - ( - "location", - models.CharField( - blank=True, - help_text="Encounter location", - max_length=100, - null=True, - ), - ), - ( - "room_number", - models.CharField( - blank=True, help_text="Room number", max_length=20, null=True - ), - ), - ( - "chief_complaint", - models.TextField( - blank=True, help_text="Chief complaint", null=True - ), - ), - ( - "reason_for_visit", - models.TextField( - blank=True, help_text="Reason for visit", null=True - ), - ), - ( - "priority", - models.CharField( - choices=[ - ("ROUTINE", "Routine"), - ("URGENT", "Urgent"), - ("STAT", "STAT"), - ("EMERGENCY", "Emergency"), - ], - default="ROUTINE", - help_text="Encounter priority", - max_length=20, - ), - ), - ( - "acuity_level", - models.PositiveIntegerField( - blank=True, - help_text="Patient acuity level (1-5, 5 being highest)", - null=True, - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(5), - ], - ), - ), - ( - "documentation_complete", - models.BooleanField( - default=False, help_text="Documentation is complete" - ), - ), - ( - "signed_off", - models.BooleanField( - default=False, help_text="Encounter has been signed off" - ), - ), - ( - "signed_datetime", - models.DateTimeField( - blank=True, help_text="Date and time of sign-off", null=True - ), - ), - ( - "billable", - models.BooleanField( - default=True, help_text="Encounter is billable" - ), - ), - ( - "billing_codes", - models.JSONField( - blank=True, default=list, help_text="Associated billing codes" - ), - ), - ( - "quality_measures", - models.JSONField( - blank=True, default=dict, help_text="Quality measure data" - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Encounter", - "verbose_name_plural": "Encounters", - "db_table": "emr_encounter", - "ordering": ["-start_datetime"], - }, - ), - migrations.CreateModel( - name="Icd10", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("code", models.CharField(db_index=True, max_length=10, unique=True)), - ("description", models.TextField(blank=True, null=True)), - ( - "chapter_name", - models.CharField(blank=True, max_length=255, null=True), - ), - ( - "section_name", - models.CharField(blank=True, max_length=255, null=True), - ), - ("is_header", models.BooleanField(default=False)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "ICD-10 Code", - "verbose_name_plural": "ICD-10 Codes", - "db_table": "emr_icd10", - "ordering": ["code"], - }, - ), - migrations.CreateModel( - name="NoteTemplate", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "template_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique template identifier", - unique=True, - ), - ), - ("name", models.CharField(help_text="Template name", max_length=200)), - ( - "description", - models.TextField( - blank=True, help_text="Template description", null=True - ), - ), - ( - "note_type", - models.CharField( - choices=[ - ("PROGRESS", "Progress Note"), - ("ADMISSION", "Admission Note"), - ("DISCHARGE", "Discharge Note"), - ("CONSULTATION", "Consultation Note"), - ("PROCEDURE", "Procedure Note"), - ("OPERATIVE", "Operative Note"), - ("NURSING", "Nursing Note"), - ("THERAPY", "Therapy Note"), - ("SOCIAL_WORK", "Social Work Note"), - ("PSYCHOLOGY", "Psychology Note"), - ("NUTRITION", "Nutrition Note"), - ("PHARMACY", "Pharmacy Note"), - ("CASE_MANAGEMENT", "Case Management Note"), - ("EDUCATION", "Patient Education Note"), - ("TELEPHONE", "Telephone Note"), - ("OTHER", "Other"), - ], - help_text="Type of note this template is for", - max_length=30, - ), - ), - ( - "specialty", - models.CharField( - blank=True, - choices=[ - ("GENERAL_MEDICINE", "General Medicine"), - ("SURGERY", "Surgery"), - ("CARDIOLOGY", "Cardiology"), - ("NEUROLOGY", "Neurology"), - ("ONCOLOGY", "Oncology"), - ("PEDIATRICS", "Pediatrics"), - ("OBSTETRICS", "Obstetrics"), - ("GYNECOLOGY", "Gynecology"), - ("ORTHOPEDICS", "Orthopedics"), - ("PSYCHIATRY", "Psychiatry"), - ("EMERGENCY", "Emergency Medicine"), - ("CRITICAL_CARE", "Critical Care"), - ("REHABILITATION", "Rehabilitation"), - ("NURSING", "Nursing"), - ("THERAPY", "Therapy"), - ("SOCIAL_WORK", "Social Work"), - ("NUTRITION", "Nutrition"), - ("PHARMACY", "Pharmacy"), - ("OTHER", "Other"), - ], - help_text="Medical specialty", - max_length=100, - null=True, - ), - ), - ( - "template_content", - models.TextField(help_text="Template content with placeholders"), - ), - ( - "structured_fields", - models.JSONField( - default=list, help_text="Structured fields definition" - ), - ), - ( - "is_active", - models.BooleanField( - default=True, - help_text="Template is active and available for use", - ), - ), - ( - "is_default", - models.BooleanField( - default=False, help_text="Default template for this note type" - ), - ), - ( - "usage_count", - models.PositiveIntegerField( - default=0, help_text="Number of times template has been used" - ), - ), - ( - "version", - models.CharField( - default="1.0", help_text="Template version", max_length=20 - ), - ), - ( - "quality_indicators", - models.JSONField( - default=list, help_text="Quality indicators to track" - ), - ), - ( - "compliance_requirements", - models.JSONField(default=list, help_text="Compliance requirements"), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Note Template", - "verbose_name_plural": "Note Templates", - "db_table": "emr_note_template", - "ordering": ["note_type", "name"], - }, - ), - migrations.CreateModel( - name="ProblemList", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "problem_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique problem identifier", - unique=True, - ), - ), - ( - "problem_name", - models.CharField( - help_text="Problem name or description", max_length=200 - ), - ), - ( - "problem_code", - models.CharField( - blank=True, - help_text="ICD-10 or SNOMED code", - max_length=20, - null=True, - ), - ), - ( - "coding_system", - models.CharField( - blank=True, - choices=[ - ("ICD10", "ICD-10"), - ("ICD9", "ICD-9"), - ("SNOMED", "SNOMED CT"), - ("CPT", "CPT"), - ("LOINC", "LOINC"), - ("OTHER", "Other"), - ], - help_text="Coding system used", - max_length=20, - null=True, - ), - ), - ( - "problem_type", - models.CharField( - choices=[ - ("DIAGNOSIS", "Diagnosis"), - ("SYMPTOM", "Symptom"), - ("FINDING", "Finding"), - ("COMPLAINT", "Complaint"), - ("CONDITION", "Condition"), - ("DISORDER", "Disorder"), - ("SYNDROME", "Syndrome"), - ("INJURY", "Injury"), - ("ALLERGY", "Allergy"), - ("INTOLERANCE", "Intolerance"), - ("RISK_FACTOR", "Risk Factor"), - ("OTHER", "Other"), - ], - help_text="Type of problem", - max_length=30, - ), - ), - ( - "onset_date", - models.DateField(blank=True, help_text="Date of onset", null=True), - ), - ( - "onset_description", - models.CharField( - blank=True, - help_text="Description of onset", - max_length=100, - null=True, - ), - ), - ( - "severity", - models.CharField( - blank=True, - choices=[ - ("MILD", "Mild"), - ("MODERATE", "Moderate"), - ("SEVERE", "Severe"), - ("CRITICAL", "Critical"), - ("UNKNOWN", "Unknown"), - ], - help_text="Problem severity", - max_length=20, - null=True, - ), - ), - ( - "priority", - models.CharField( - choices=[ - ("LOW", "Low"), - ("MEDIUM", "Medium"), - ("HIGH", "High"), - ("URGENT", "Urgent"), - ], - default="MEDIUM", - help_text="Problem priority", - max_length=20, - ), - ), - ( - "status", - models.CharField( - choices=[ - ("ACTIVE", "Active"), - ("INACTIVE", "Inactive"), - ("RESOLVED", "Resolved"), - ("REMISSION", "In Remission"), - ("RECURRENCE", "Recurrence"), - ("RELAPSE", "Relapse"), - ("UNKNOWN", "Unknown"), - ("OTHER", "Other"), - ], - default="ACTIVE", - help_text="Current problem status", - max_length=20, - ), - ), - ( - "resolution_date", - models.DateField( - blank=True, help_text="Date problem was resolved", null=True - ), - ), - ( - "resolution_notes", - models.TextField( - blank=True, - help_text="Notes about problem resolution", - null=True, - ), - ), - ( - "body_site", - models.CharField( - blank=True, - help_text="Body site affected", - max_length=100, - null=True, - ), - ), - ( - "laterality", - models.CharField( - blank=True, - choices=[ - ("LEFT", "Left"), - ("RIGHT", "Right"), - ("BILATERAL", "Bilateral"), - ("UNILATERAL", "Unilateral"), - ("NOT_APPLICABLE", "Not Applicable"), - ], - help_text="Laterality", - max_length=20, - null=True, - ), - ), - ( - "clinical_notes", - models.TextField( - blank=True, - help_text="Clinical notes about the problem", - null=True, - ), - ), - ( - "patient_concerns", - models.TextField( - blank=True, help_text="Patient concerns and comments", null=True - ), - ), - ( - "treatment_goals", - models.JSONField( - blank=True, - default=list, - help_text="Treatment goals for this problem", - ), - ), - ( - "outcome_measures", - models.JSONField( - blank=True, - default=list, - help_text="Outcome measures being tracked", - ), - ), - ( - "verified", - models.BooleanField( - default=False, help_text="Problem has been verified" - ), - ), - ( - "verified_date", - models.DateTimeField( - blank=True, help_text="Date problem was verified", null=True - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Problem", - "verbose_name_plural": "Problem List", - "db_table": "emr_problem_list", - "ordering": ["-created_at"], - }, - ), - migrations.CreateModel( - name="TreatmentProtocol", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "protocol_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique protocol identifier", - unique=True, - ), - ), - ("name", models.CharField(help_text="Protocol name", max_length=200)), - ("description", models.TextField(help_text="Protocol description")), - ( - "indication", - models.TextField(help_text="Clinical indications for use"), - ), - ("goals", models.JSONField(default=list, help_text="Treatment goals")), - ( - "interventions", - models.JSONField(default=list, help_text="Required interventions"), - ), - ( - "monitoring_parameters", - models.JSONField(default=list, help_text="Parameters to monitor"), - ), - ( - "success_rate", - models.DecimalField( - decimal_places=2, - help_text="Protocol success rate (%)", - max_digits=5, - ), - ), - ( - "average_duration", - models.PositiveIntegerField( - help_text="Average treatment duration in days" - ), - ), - ( - "is_active", - models.BooleanField( - default=True, help_text="Protocol is active and available" - ), - ), - ( - "usage_count", - models.PositiveIntegerField( - default=0, help_text="Number of times protocol has been used" - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Treatment Protocol", - "verbose_name_plural": "Treatment Protocols", - "db_table": "emr_treatment_protocol", - "ordering": ["-success_rate", "name"], - }, - ), - migrations.CreateModel( - name="VitalSigns", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "measurement_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique measurement identifier", - unique=True, - ), - ), - ( - "measured_datetime", - models.DateTimeField( - default=django.utils.timezone.now, - help_text="Date and time of measurement", - ), - ), - ( - "temperature", - models.DecimalField( - blank=True, - decimal_places=1, - help_text="Temperature in Celsius", - max_digits=4, - null=True, - ), - ), - ( - "temperature_method", - models.CharField( - blank=True, - choices=[ - ("ORAL", "Oral"), - ("RECTAL", "Rectal"), - ("AXILLARY", "Axillary"), - ("TYMPANIC", "Tympanic"), - ("TEMPORAL", "Temporal"), - ("CORE", "Core"), - ], - help_text="Temperature measurement method", - max_length=20, - null=True, - ), - ), - ( - "systolic_bp", - models.PositiveIntegerField( - blank=True, - help_text="Systolic blood pressure (mmHg)", - null=True, - validators=[ - django.core.validators.MinValueValidator(50), - django.core.validators.MaxValueValidator(300), - ], - ), - ), - ( - "diastolic_bp", - models.PositiveIntegerField( - blank=True, - help_text="Diastolic blood pressure (mmHg)", - null=True, - validators=[ - django.core.validators.MinValueValidator(30), - django.core.validators.MaxValueValidator(200), - ], - ), - ), - ( - "bp_position", - models.CharField( - blank=True, - choices=[ - ("SITTING", "Sitting"), - ("STANDING", "Standing"), - ("LYING", "Lying"), - ("SUPINE", "Supine"), - ], - help_text="Patient position during BP measurement", - max_length=20, - null=True, - ), - ), - ( - "bp_cuff_size", - models.CharField( - blank=True, - choices=[ - ("SMALL", "Small"), - ("REGULAR", "Regular"), - ("LARGE", "Large"), - ("EXTRA_LARGE", "Extra Large"), - ("PEDIATRIC", "Pediatric"), - ], - help_text="Blood pressure cuff size", - max_length=20, - null=True, - ), - ), - ( - "heart_rate", - models.PositiveIntegerField( - blank=True, - help_text="Heart rate (beats per minute)", - null=True, - validators=[ - django.core.validators.MinValueValidator(20), - django.core.validators.MaxValueValidator(300), - ], - ), - ), - ( - "heart_rhythm", - models.CharField( - blank=True, - choices=[ - ("REGULAR", "Regular"), - ("REGULARLY_IRREGULAR", "Regularly irregular"), - ("IRREGULARLY_IRREGULAR", "Irregularly irregular"), - ("IRREGULAR_UNSPECIFIED", "Irregular (unspecified)"), - ], - help_text="Heart rhythm", - max_length=25, - null=True, - ), - ), - ( - "respiratory_rate", - models.PositiveIntegerField( - blank=True, - help_text="Respiratory rate (breaths per minute)", - null=True, - validators=[ - django.core.validators.MinValueValidator(5), - django.core.validators.MaxValueValidator(60), - ], - ), - ), - ( - "oxygen_saturation", - models.PositiveIntegerField( - blank=True, - help_text="Oxygen saturation (%)", - null=True, - validators=[ - django.core.validators.MinValueValidator(50), - django.core.validators.MaxValueValidator(100), - ], - ), - ), - ( - "oxygen_delivery", - models.CharField( - choices=[ - ("ROOM_AIR", "Room Air"), - ("NASAL_CANNULA", "Nasal Cannula"), - ("SIMPLE_MASK", "Simple Mask"), - ("NON_REBREATHER", "Non-Rebreather Mask"), - ("VENTURI_MASK", "Venturi Mask"), - ("CPAP", "CPAP"), - ("BIPAP", "BiPAP"), - ("MECHANICAL_VENTILATION", "Mechanical Ventilation"), - ("OTHER", "Other"), - ], - default="ROOM_AIR", - help_text="Oxygen delivery method", - max_length=30, - ), - ), - ( - "oxygen_flow_rate", - models.DecimalField( - blank=True, - decimal_places=1, - help_text="Oxygen flow rate (L/min)", - max_digits=4, - null=True, - ), - ), - ( - "pain_scale", - models.PositiveIntegerField( - blank=True, - help_text="Pain scale (0-10)", - null=True, - validators=[ - django.core.validators.MinValueValidator(0), - django.core.validators.MaxValueValidator(10), - ], - ), - ), - ( - "pain_location", - models.CharField( - blank=True, help_text="Pain location", max_length=100, null=True - ), - ), - ( - "pain_quality", - models.CharField( - blank=True, - help_text="Pain quality description", - max_length=50, - null=True, - ), - ), - ( - "weight", - models.DecimalField( - blank=True, - decimal_places=1, - help_text="Weight in pounds", - max_digits=5, - null=True, - ), - ), - ( - "height", - models.DecimalField( - blank=True, - decimal_places=1, - help_text="Height in inches", - max_digits=5, - null=True, - ), - ), - ( - "bmi", - models.DecimalField( - blank=True, - decimal_places=1, - help_text="Body Mass Index", - max_digits=4, - null=True, - ), - ), - ( - "head_circumference", - models.DecimalField( - blank=True, - decimal_places=1, - help_text="Head circumference in cm (pediatric)", - max_digits=4, - null=True, - ), - ), - ( - "device_used", - models.CharField( - blank=True, - help_text="Device used for measurements", - max_length=100, - null=True, - ), - ), - ( - "device_calibrated", - models.BooleanField( - default=True, help_text="Device was calibrated" - ), - ), - ( - "critical_values", - models.JSONField( - blank=True, default=list, help_text="Critical values identified" - ), - ), - ( - "alerts_generated", - models.JSONField( - blank=True, - default=list, - help_text="Alerts generated from measurements", - ), - ), - ( - "notes", - models.TextField( - blank=True, - help_text="Additional notes about measurements", - null=True, - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - options={ - "verbose_name": "Vital Signs", - "verbose_name_plural": "Vital Signs", - "db_table": "emr_vital_signs", - "ordering": ["-measured_datetime"], - }, - ), - migrations.CreateModel( - name="AllergyAlert", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "alert_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique alert identifier", - unique=True, - ), - ), - ( - "allergen", - models.CharField(help_text="Allergen name", max_length=100), - ), - ( - "reaction_type", - models.CharField( - help_text="Type of allergic reaction", max_length=100 - ), - ), - ( - "severity", - models.CharField( - choices=[ - ("MILD", "Mild"), - ("MODERATE", "Moderate"), - ("SEVERE", "Severe"), - ("LIFE_THREATENING", "Life-threatening"), - ], - help_text="Alert severity", - max_length=20, - ), - ), - ( - "symptoms", - models.TextField( - blank=True, help_text="Allergic reaction symptoms", null=True - ), - ), - ( - "onset", - models.CharField( - blank=True, - help_text="Reaction onset timing", - max_length=50, - null=True, - ), - ), - ( - "resolved", - models.BooleanField( - default=False, help_text="Alert has been resolved" - ), - ), - ( - "resolved_at", - models.DateTimeField( - blank=True, help_text="Date alert was resolved", null=True - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "detected_at", - models.DateTimeField( - default=django.utils.timezone.now, - help_text="When alert was detected", - ), - ), - ( - "patient", - models.ForeignKey( - help_text="Patient", - on_delete=django.db.models.deletion.CASCADE, - related_name="allergy_alerts", - to="patients.patientprofile", - ), - ), - ( - "resolved_by", - models.ForeignKey( - blank=True, - help_text="Provider who resolved the alert", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="resolved_allergy_alerts", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "tenant", - models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="allergy_alerts", - to="core.tenant", - ), - ), - ], - options={ - "verbose_name": "Allergy Alert", - "verbose_name_plural": "Allergy Alerts", - "db_table": "emr_allergy_alert", - "ordering": ["-detected_at"], - }, - ), - migrations.CreateModel( - name="CarePlan", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "care_plan_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique care plan identifier", - unique=True, - ), - ), - ( - "title", - models.CharField(help_text="Care plan title", max_length=200), - ), - ("description", models.TextField(help_text="Care plan description")), - ( - "plan_type", - models.CharField( - choices=[ - ("COMPREHENSIVE", "Comprehensive Care Plan"), - ("DISEASE_SPECIFIC", "Disease-Specific Plan"), - ("PREVENTIVE", "Preventive Care Plan"), - ("CHRONIC_CARE", "Chronic Care Management"), - ("ACUTE_CARE", "Acute Care Plan"), - ("DISCHARGE", "Discharge Planning"), - ("REHABILITATION", "Rehabilitation Plan"), - ("PALLIATIVE", "Palliative Care Plan"), - ("MENTAL_HEALTH", "Mental Health Plan"), - ("MEDICATION", "Medication Management"), - ("NUTRITION", "Nutrition Plan"), - ("EXERCISE", "Exercise Plan"), - ("OTHER", "Other"), - ], - help_text="Type of care plan", - max_length=30, - ), - ), - ( - "category", - models.CharField( - choices=[ - ("ASSESSMENT", "Assessment and Monitoring"), - ("TREATMENT", "Treatment"), - ("EDUCATION", "Patient Education"), - ("COORDINATION", "Care Coordination"), - ("PREVENTION", "Prevention"), - ("LIFESTYLE", "Lifestyle Modification"), - ("MEDICATION", "Medication Management"), - ("FOLLOW_UP", "Follow-up Care"), - ("EMERGENCY", "Emergency Planning"), - ("SUPPORT", "Support Services"), - ], - help_text="Care plan category", - max_length=50, - ), - ), - ("start_date", models.DateField(help_text="Care plan start date")), - ( - "end_date", - models.DateField( - blank=True, help_text="Care plan end date", null=True - ), - ), - ( - "target_completion_date", - models.DateField( - blank=True, help_text="Target completion date", null=True - ), - ), - ( - "status", - models.CharField( - choices=[ - ("DRAFT", "Draft"), - ("ACTIVE", "Active"), - ("ON_HOLD", "On Hold"), - ("COMPLETED", "Completed"), - ("CANCELLED", "Cancelled"), - ("ERROR", "Entered in Error"), - ("UNKNOWN", "Unknown"), - ], - default="DRAFT", - help_text="Care plan status", - max_length=20, - ), - ), - ( - "priority", - models.CharField( - choices=[ - ("LOW", "Low"), - ("ROUTINE", "Routine"), - ("URGENT", "Urgent"), - ("STAT", "STAT"), - ], - default="ROUTINE", - help_text="Care plan priority", - max_length=20, - ), - ), - ("goals", models.JSONField(default=list, help_text="Care plan goals")), - ( - "objectives", - models.JSONField( - default=list, help_text="Specific objectives and targets" - ), - ), - ( - "interventions", - models.JSONField(default=list, help_text="Planned interventions"), - ), - ( - "activities", - models.JSONField( - default=list, help_text="Specific activities and tasks" - ), - ), - ( - "monitoring_parameters", - models.JSONField(default=list, help_text="Parameters to monitor"), - ), - ( - "evaluation_criteria", - models.JSONField( - default=list, help_text="Criteria for evaluating progress" - ), - ), - ( - "patient_goals", - models.TextField( - blank=True, help_text="Patient-identified goals", null=True - ), - ), - ( - "patient_preferences", - models.TextField( - blank=True, - help_text="Patient preferences and concerns", - null=True, - ), - ), - ( - "patient_barriers", - models.TextField( - blank=True, help_text="Identified barriers to care", null=True - ), - ), - ( - "resources_needed", - models.JSONField( - default=list, - help_text="Resources needed for plan implementation", - ), - ), - ( - "support_systems", - models.JSONField( - default=list, help_text="Available support systems" - ), - ), - ( - "progress_notes", - models.TextField(blank=True, help_text="Progress notes", null=True), - ), - ( - "last_reviewed", - models.DateField( - blank=True, help_text="Date of last review", null=True - ), - ), - ( - "next_review_date", - models.DateField( - blank=True, help_text="Next scheduled review date", null=True - ), - ), - ( - "outcomes_achieved", - models.JSONField(default=list, help_text="Outcomes achieved"), - ), - ( - "completion_percentage", - models.PositiveIntegerField( - default=0, - help_text="Completion percentage", - validators=[ - django.core.validators.MinValueValidator(0), - django.core.validators.MaxValueValidator(100), - ], - ), - ), - ( - "approved", - models.BooleanField( - default=False, help_text="Care plan has been approved" - ), - ), - ( - "approved_date", - models.DateTimeField( - blank=True, help_text="Date of approval", null=True - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "approved_by", - models.ForeignKey( - blank=True, - help_text="Provider who approved the plan", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="approved_care_plans", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "care_team", - models.ManyToManyField( - blank=True, - help_text="Care team members", - related_name="care_team_plans", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "created_by", - models.ForeignKey( - blank=True, - help_text="User who created the care plan", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_care_plans", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "patient", - models.ForeignKey( - help_text="Patient", - on_delete=django.db.models.deletion.CASCADE, - related_name="care_plans", - to="patients.patientprofile", - ), - ), - ( - "primary_provider", - models.ForeignKey( - help_text="Primary provider responsible for care plan", - on_delete=django.db.models.deletion.CASCADE, - related_name="primary_care_plans", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "tenant", - models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="care_plans", - to="core.tenant", - ), - ), - ], - options={ - "verbose_name": "Care Plan", - "verbose_name_plural": "Care Plans", - "db_table": "emr_care_plan", - "ordering": ["-created_at"], - }, - ), - migrations.CreateModel( - name="ClinicalGuideline", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "guideline_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique guideline identifier", - unique=True, - ), - ), - ( - "title", - models.CharField(help_text="Guideline title", max_length=300), - ), - ( - "organization", - models.CharField( - help_text="Publishing organization", max_length=100 - ), - ), - ("summary", models.TextField(help_text="Guideline summary")), - ( - "url", - models.URLField( - blank=True, help_text="Link to full guideline", null=True - ), - ), - ( - "publication_date", - models.DateField(help_text="Guideline publication date"), - ), - ( - "last_updated", - models.DateField(auto_now=True, help_text="Last updated date"), - ), - ( - "version", - models.CharField( - blank=True, - help_text="Guideline version", - max_length=20, - null=True, - ), - ), - ( - "is_active", - models.BooleanField( - default=True, help_text="Guideline is current and active" - ), - ), - ( - "keywords", - models.JSONField(default=list, help_text="Keywords for searching"), - ), - ( - "specialties", - models.JSONField( - default=list, help_text="Relevant medical specialties" - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "tenant", - models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="clinical_guidelines", - to="core.tenant", - ), - ), - ], - options={ - "verbose_name": "Clinical Guideline", - "verbose_name_plural": "Clinical Guidelines", - "db_table": "emr_clinical_guideline", - "ordering": ["-last_updated", "title"], - }, - ), - migrations.CreateModel( - name="ClinicalNote", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "note_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique note identifier", - unique=True, - ), - ), - ( - "note_type", - models.CharField( - choices=[ - ("PROGRESS", "Progress"), - ("ADMISSION", "Admission Note"), - ("DISCHARGE", "Discharge Note"), - ("CONSULTATION", "Consultation Note"), - ("PROCEDURE", "Procedure Note"), - ("OPERATIVE", "Operative Note"), - ("NURSING", "Nursing Note"), - ("THERAPY", "Therapy Note"), - ("SOCIAL_WORK", "Social Work Note"), - ("PSYCHOLOGY", "Psychology Note"), - ("NUTRITION", "Nutrition Note"), - ("PHARMACY", "Pharmacy Note"), - ("CASE_MANAGEMENT", "Case Management Note"), - ("EDUCATION", "Patient Education Note"), - ("TELEPHONE", "Telephone Note"), - ("ADDENDUM", "Addendum"), - ("CORRECTION", "Correction"), - ("OTHER", "Other"), - ], - help_text="Type of clinical note", - max_length=30, - ), - ), - ("title", models.CharField(help_text="Note title", max_length=200)), - ("content", models.TextField(help_text="Note content")), - ( - "structured_data", - models.JSONField( - blank=True, - default=dict, - help_text="Structured data from template", - ), - ), - ( - "status", - models.CharField( - choices=[ - ("DRAFT", "Draft"), - ("IN_PROGRESS", "In Progress"), - ("COMPLETED", "Completed"), - ("SIGNED", "Signed"), - ("AMENDED", "Amended"), - ("CORRECTED", "Corrected"), - ("CANCELLED", "Cancelled"), - ("ERROR", "Entered in Error"), - ("UNKNOWN", "Unknown"), - ], - default="DRAFT", - help_text="Note status", - max_length=20, - ), - ), - ( - "electronically_signed", - models.BooleanField( - default=False, help_text="Note has been electronically signed" - ), - ), - ( - "signed_datetime", - models.DateTimeField( - blank=True, help_text="Date and time of signature", null=True - ), - ), - ( - "signature_method", - models.CharField( - blank=True, - choices=[ - ("ELECTRONIC", "Electronic"), - ("DIGITAL", "Digital Signature"), - ("BIOMETRIC", "Biometric Signature"), - ("PASSWORD", "Password"), - ("TOKEN", "Token Authentication"), - ("OTHER", "Other"), - ], - help_text="Method of signature", - max_length=20, - null=True, - ), - ), - ( - "amendment_reason", - models.TextField( - blank=True, help_text="Reason for amendment", null=True - ), - ), - ( - "quality_score", - models.PositiveIntegerField( - blank=True, - help_text="Documentation quality score", - null=True, - validators=[ - django.core.validators.MinValueValidator(0), - django.core.validators.MaxValueValidator(100), - ], - ), - ), - ( - "compliance_flags", - models.JSONField( - blank=True, - default=list, - help_text="Compliance flags and issues", - ), - ), - ( - "note_datetime", - models.DateTimeField( - default=django.utils.timezone.now, - help_text="Date and time note was written", - ), - ), - ( - "confidential", - models.BooleanField( - default=False, - help_text="Note contains confidential information", - ), - ), - ( - "restricted_access", - models.BooleanField( - default=False, help_text="Access to note is restricted" - ), - ), - ( - "access_restrictions", - models.JSONField( - blank=True, - default=list, - help_text="Specific access restrictions", - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "amended_note", - models.ForeignKey( - blank=True, - help_text="Original note if this is an amendment", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="amendments", - to="emr.clinicalnote", - ), - ), - ( - "author", - models.ForeignKey( - help_text="Note author", - on_delete=django.db.models.deletion.CASCADE, - related_name="authored_notes", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "co_signers", - models.ManyToManyField( - blank=True, - help_text="Co-signers for this note", - related_name="co_signed_notes", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "patient", - models.ForeignKey( - help_text="Patient", - on_delete=django.db.models.deletion.CASCADE, - related_name="clinical_notes", - to="patients.patientprofile", - ), - ), - ( - "related_care_plans", - models.ManyToManyField( - blank=True, - help_text="Related care plans", - related_name="clinical_notes", - to="emr.careplan", - ), - ), - ], - options={ - "verbose_name": "Clinical Note", - "verbose_name_plural": "Clinical Notes", - "db_table": "emr_clinical_note", - "ordering": ["-note_datetime"], - }, - ), - migrations.CreateModel( - name="ClinicalRecommendation", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "recommendation_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique recommendation identifier", - unique=True, - ), - ), - ( - "title", - models.CharField(help_text="Recommendation title", max_length=200), - ), - ( - "description", - models.TextField(help_text="Detailed recommendation description"), - ), - ( - "category", - models.CharField( - choices=[ - ("PREVENTIVE", "Preventive Care"), - ("DIAGNOSTIC", "Diagnostic"), - ("TREATMENT", "Treatment"), - ("MONITORING", "Monitoring"), - ("LIFESTYLE", "Lifestyle"), - ("MEDICATION", "Medication"), - ("FOLLOW_UP", "Follow-up"), - ("REFERRAL", "Referral"), - ("EDUCATION", "Patient Education"), - ("OTHER", "Other"), - ], - help_text="Recommendation category", - max_length=20, - ), - ), - ( - "priority", - models.CharField( - choices=[ - ("LOW", "Low"), - ("MEDIUM", "Medium"), - ("HIGH", "High"), - ("URGENT", "Urgent"), - ("CRITICAL", "Critical"), - ], - default="MEDIUM", - help_text="Recommendation priority", - max_length=20, - ), - ), - ( - "evidence_level", - models.CharField( - help_text="Level of evidence (1A, 1B, 2A, etc.)", max_length=10 - ), - ), - ( - "source", - models.CharField( - help_text="Source of recommendation (guideline, study, etc.)", - max_length=100, - ), - ), - ( - "rationale", - models.TextField( - blank=True, - help_text="Clinical rationale for recommendation", - null=True, - ), - ), - ( - "status", - models.CharField( - choices=[ - ("PENDING", "Pending"), - ("ACTIVE", "Active"), - ("ACCEPTED", "Accepted"), - ("DEFERRED", "Deferred"), - ("DISMISSED", "Dismissed"), - ("COMPLETED", "Completed"), - ("EXPIRED", "Expired"), - ], - default="PENDING", - help_text="Current recommendation status", - max_length=20, - ), - ), - ( - "accepted_at", - models.DateTimeField( - blank=True, - help_text="Date and time recommendation was accepted", - null=True, - ), - ), - ( - "deferred_at", - models.DateTimeField( - blank=True, - help_text="Date and time recommendation was deferred", - null=True, - ), - ), - ( - "dismissed_at", - models.DateTimeField( - blank=True, - help_text="Date and time recommendation was dismissed", - null=True, - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "expires_at", - models.DateTimeField( - blank=True, - help_text="Recommendation expiration date", - null=True, - ), - ), - ( - "accepted_by", - models.ForeignKey( - blank=True, - help_text="Provider who accepted the recommendation", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="accepted_recommendations", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "created_by", - models.ForeignKey( - blank=True, - help_text="User who created the recommendation", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_recommendations", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "deferred_by", - models.ForeignKey( - blank=True, - help_text="Provider who deferred the recommendation", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="deferred_recommendations", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "dismissed_by", - models.ForeignKey( - blank=True, - help_text="Provider who dismissed the recommendation", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="dismissed_recommendations", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "patient", - models.ForeignKey( - help_text="Patient", - on_delete=django.db.models.deletion.CASCADE, - related_name="clinical_recommendations", - to="patients.patientprofile", - ), - ), - ( - "tenant", - models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="clinical_recommendations", - to="core.tenant", - ), - ), - ], - options={ - "verbose_name": "Clinical Recommendation", - "verbose_name_plural": "Clinical Recommendations", - "db_table": "emr_clinical_recommendation", - "ordering": ["-created_at"], - }, - ), - migrations.CreateModel( - name="CriticalAlert", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "alert_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique alert identifier", - unique=True, - ), - ), - ("title", models.CharField(help_text="Alert title", max_length=200)), - ("description", models.TextField(help_text="Alert description")), - ( - "priority", - models.CharField( - choices=[ - ("HIGH", "High"), - ("URGENT", "Urgent"), - ("CRITICAL", "Critical"), - ], - default="HIGH", - help_text="Alert priority level", - max_length=20, - ), - ), - ( - "recommendation", - models.TextField( - blank=True, help_text="Recommended actions", null=True - ), - ), - ( - "acknowledged", - models.BooleanField( - default=False, help_text="Alert has been acknowledged" - ), - ), - ( - "acknowledged_at", - models.DateTimeField( - blank=True, - help_text="Date and time alert was acknowledged", - null=True, - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "expires_at", - models.DateTimeField( - blank=True, help_text="Alert expiration date", null=True - ), - ), - ( - "acknowledged_by", - models.ForeignKey( - blank=True, - help_text="Provider who acknowledged the alert", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="emr_acknowledged_alerts", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "created_by", - models.ForeignKey( - blank=True, - help_text="User who created the alert", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_alerts", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "patient", - models.ForeignKey( - help_text="Patient", - on_delete=django.db.models.deletion.CASCADE, - related_name="critical_alerts", - to="patients.patientprofile", - ), - ), - ( - "tenant", - models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="critical_alerts", - to="core.tenant", - ), - ), - ], - options={ - "verbose_name": "Critical Alert", - "verbose_name_plural": "Critical Alerts", - "db_table": "emr_critical_alert", - "ordering": ["-created_at"], - }, - ), - migrations.CreateModel( - name="DiagnosticSuggestion", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "suggestion_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique suggestion identifier", - unique=True, - ), - ), - ( - "test_name", - models.CharField(help_text="Suggested test name", max_length=200), - ), - ( - "test_code", - models.CharField( - help_text="Test code or identifier", max_length=20 - ), - ), - ( - "indication", - models.TextField(help_text="Clinical indication for the test"), - ), - ( - "confidence", - models.DecimalField( - decimal_places=2, - help_text="AI confidence score (%)", - max_digits=5, - validators=[ - django.core.validators.MinValueValidator(0), - django.core.validators.MaxValueValidator(100), - ], - ), - ), - ( - "status", - models.CharField( - choices=[ - ("PENDING", "Pending"), - ("ORDERED", "Ordered"), - ("COMPLETED", "Completed"), - ("CANCELLED", "Cancelled"), - ], - default="ORDERED", - help_text="Suggestion status", - max_length=20, - ), - ), - ( - "ordered_at", - models.DateTimeField( - blank=True, - help_text="Date and time test was ordered", - null=True, - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "created_by", - models.ForeignKey( - blank=True, - help_text="User who created the suggestion", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_suggestions", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "ordered_by", - models.ForeignKey( - blank=True, - help_text="Provider who ordered the test", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="ordered_suggestions", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "patient", - models.ForeignKey( - help_text="Patient", - on_delete=django.db.models.deletion.CASCADE, - related_name="diagnostic_suggestions", - to="patients.patientprofile", - ), - ), - ( - "tenant", - models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="diagnostic_suggestions", - to="core.tenant", - ), - ), - ], - options={ - "verbose_name": "Diagnostic Suggestion", - "verbose_name_plural": "Diagnostic Suggestions", - "db_table": "emr_diagnostic_suggestion", - "ordering": ["-confidence", "-created_at"], - }, - ), - ] diff --git a/emr/migrations/0002_initial.py b/emr/migrations/0002_initial.py deleted file mode 100644 index c0076aed..00000000 --- a/emr/migrations/0002_initial.py +++ /dev/null @@ -1,657 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("appointments", "0002_initial"), - ("core", "0001_initial"), - ("emr", "0001_initial"), - ("inpatients", "0001_initial"), - ("patients", "0001_initial"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name="encounter", - name="admission", - field=models.ForeignKey( - blank=True, - help_text="Related admission", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="encounters", - to="inpatients.admission", - ), - ), - migrations.AddField( - model_name="encounter", - name="appointment", - field=models.ForeignKey( - blank=True, - help_text="Related appointment", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="encounters", - to="appointments.appointmentrequest", - ), - ), - migrations.AddField( - model_name="encounter", - name="created_by", - field=models.ForeignKey( - blank=True, - help_text="User who created the encounter", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_encounters", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="encounter", - name="patient", - field=models.ForeignKey( - help_text="Patient for this encounter", - on_delete=django.db.models.deletion.CASCADE, - related_name="encounters", - to="patients.patientprofile", - ), - ), - migrations.AddField( - model_name="encounter", - name="provider", - field=models.ForeignKey( - help_text="Primary provider for this encounter", - on_delete=django.db.models.deletion.CASCADE, - related_name="encounters", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="encounter", - name="signed_by", - field=models.ForeignKey( - blank=True, - help_text="Provider who signed off", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="signed_encounters", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="encounter", - name="tenant", - field=models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="encounters", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="criticalalert", - name="related_encounter", - field=models.ForeignKey( - blank=True, - help_text="Related encounter", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="critical_alerts", - to="emr.encounter", - ), - ), - migrations.AddField( - model_name="clinicalrecommendation", - name="related_encounter", - field=models.ForeignKey( - blank=True, - help_text="Related encounter", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="recommendations", - to="emr.encounter", - ), - ), - migrations.AddField( - model_name="clinicalnote", - name="encounter", - field=models.ForeignKey( - help_text="Associated encounter", - on_delete=django.db.models.deletion.CASCADE, - related_name="clinical_notes", - to="emr.encounter", - ), - ), - migrations.AddField( - model_name="icd10", - name="parent", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="children", - to="emr.icd10", - ), - ), - migrations.AddField( - model_name="notetemplate", - name="created_by", - field=models.ForeignKey( - blank=True, - help_text="User who created the template", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_note_templates", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="notetemplate", - name="previous_version", - field=models.ForeignKey( - blank=True, - help_text="Previous version of this template", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="newer_versions", - to="emr.notetemplate", - ), - ), - migrations.AddField( - model_name="notetemplate", - name="tenant", - field=models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="note_templates", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="clinicalnote", - name="template", - field=models.ForeignKey( - blank=True, - help_text="Template used for this note", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="notes", - to="emr.notetemplate", - ), - ), - migrations.AddField( - model_name="problemlist", - name="created_by", - field=models.ForeignKey( - blank=True, - help_text="User who created the problem", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_problems", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="problemlist", - name="diagnosing_provider", - field=models.ForeignKey( - help_text="Provider who diagnosed the problem", - on_delete=django.db.models.deletion.CASCADE, - related_name="diagnosed_problems", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="problemlist", - name="managing_provider", - field=models.ForeignKey( - blank=True, - help_text="Provider managing the problem", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="managed_problems", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="problemlist", - name="patient", - field=models.ForeignKey( - help_text="Patient", - on_delete=django.db.models.deletion.CASCADE, - related_name="problems", - to="patients.patientprofile", - ), - ), - migrations.AddField( - model_name="problemlist", - name="related_encounter", - field=models.ForeignKey( - blank=True, - help_text="Encounter where problem was identified", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="problems_identified", - to="emr.encounter", - ), - ), - migrations.AddField( - model_name="problemlist", - name="tenant", - field=models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="problem_lists", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="problemlist", - name="verified_by", - field=models.ForeignKey( - blank=True, - help_text="Provider who verified the problem", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="verified_problems", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="clinicalrecommendation", - name="related_problems", - field=models.ManyToManyField( - blank=True, - help_text="Related problems", - related_name="recommendations", - to="emr.problemlist", - ), - ), - migrations.AddField( - model_name="clinicalnote", - name="related_problems", - field=models.ManyToManyField( - blank=True, - help_text="Related problems", - related_name="related_clinical_notes", - to="emr.problemlist", - ), - ), - migrations.AddField( - model_name="careplan", - name="related_problems", - field=models.ManyToManyField( - blank=True, - help_text="Related problems addressed by this plan", - related_name="care_plans", - to="emr.problemlist", - ), - ), - migrations.AddField( - model_name="treatmentprotocol", - name="created_by", - field=models.ForeignKey( - blank=True, - help_text="User who created the protocol", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_protocols", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="treatmentprotocol", - name="tenant", - field=models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="treatment_protocols", - to="core.tenant", - ), - ), - migrations.AddField( - model_name="vitalsigns", - name="encounter", - field=models.ForeignKey( - help_text="Associated encounter", - on_delete=django.db.models.deletion.CASCADE, - related_name="vital_signs", - to="emr.encounter", - ), - ), - migrations.AddField( - model_name="vitalsigns", - name="measured_by", - field=models.ForeignKey( - help_text="Staff member who took measurements", - on_delete=django.db.models.deletion.CASCADE, - related_name="vital_signs_measurements", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="vitalsigns", - name="patient", - field=models.ForeignKey( - help_text="Patient", - on_delete=django.db.models.deletion.CASCADE, - related_name="vital_signs", - to="patients.patientprofile", - ), - ), - migrations.AddField( - model_name="vitalsigns", - name="verified_by", - field=models.ForeignKey( - blank=True, - help_text="Staff member who verified measurements", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="verified_vital_signs", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddIndex( - model_name="allergyalert", - index=models.Index( - fields=["tenant", "resolved"], name="emr_allergy_tenant__da9219_idx" - ), - ), - migrations.AddIndex( - model_name="allergyalert", - index=models.Index( - fields=["patient", "resolved"], name="emr_allergy_patient_674c53_idx" - ), - ), - migrations.AddIndex( - model_name="allergyalert", - index=models.Index( - fields=["severity"], name="emr_allergy_severit_38d8dd_idx" - ), - ), - migrations.AddIndex( - model_name="allergyalert", - index=models.Index( - fields=["detected_at"], name="emr_allergy_detecte_97c184_idx" - ), - ), - migrations.AddIndex( - model_name="clinicalguideline", - index=models.Index( - fields=["tenant", "is_active"], name="emr_clinica_tenant__08a1f7_idx" - ), - ), - migrations.AddIndex( - model_name="clinicalguideline", - index=models.Index( - fields=["organization"], name="emr_clinica_organiz_107f8d_idx" - ), - ), - migrations.AddIndex( - model_name="clinicalguideline", - index=models.Index( - fields=["publication_date"], name="emr_clinica_publica_4e35d0_idx" - ), - ), - migrations.AddIndex( - model_name="diagnosticsuggestion", - index=models.Index( - fields=["tenant", "status"], name="emr_diagnos_tenant__77e6f8_idx" - ), - ), - migrations.AddIndex( - model_name="diagnosticsuggestion", - index=models.Index( - fields=["patient", "status"], name="emr_diagnos_patient_8c5470_idx" - ), - ), - migrations.AddIndex( - model_name="diagnosticsuggestion", - index=models.Index( - fields=["confidence"], name="emr_diagnos_confide_f7447d_idx" - ), - ), - migrations.AddIndex( - model_name="diagnosticsuggestion", - index=models.Index( - fields=["created_at"], name="emr_diagnos_created_34cc79_idx" - ), - ), - migrations.AddIndex( - model_name="encounter", - index=models.Index( - fields=["tenant", "status"], name="emr_encount_tenant__6734f5_idx" - ), - ), - migrations.AddIndex( - model_name="encounter", - index=models.Index( - fields=["patient", "start_datetime"], - name="emr_encount_patient_c0f443_idx", - ), - ), - migrations.AddIndex( - model_name="encounter", - index=models.Index( - fields=["provider"], name="emr_encount_provide_5e06b3_idx" - ), - ), - migrations.AddIndex( - model_name="encounter", - index=models.Index( - fields=["encounter_type"], name="emr_encount_encount_019f80_idx" - ), - ), - migrations.AddIndex( - model_name="encounter", - index=models.Index( - fields=["start_datetime"], name="emr_encount_start_d_a01018_idx" - ), - ), - migrations.AddIndex( - model_name="criticalalert", - index=models.Index( - fields=["tenant", "acknowledged"], name="emr_critica_tenant__a7de09_idx" - ), - ), - migrations.AddIndex( - model_name="criticalalert", - index=models.Index( - fields=["patient", "acknowledged"], - name="emr_critica_patient_3f3d88_idx", - ), - ), - migrations.AddIndex( - model_name="criticalalert", - index=models.Index( - fields=["priority"], name="emr_critica_priorit_06ad08_idx" - ), - ), - migrations.AddIndex( - model_name="criticalalert", - index=models.Index( - fields=["created_at"], name="emr_critica_created_3acbe1_idx" - ), - ), - migrations.AddIndex( - model_name="notetemplate", - index=models.Index( - fields=["tenant", "is_active"], name="emr_note_te_tenant__caa5d3_idx" - ), - ), - migrations.AddIndex( - model_name="notetemplate", - index=models.Index( - fields=["note_type", "specialty"], name="emr_note_te_note_ty_d18594_idx" - ), - ), - migrations.AddIndex( - model_name="notetemplate", - index=models.Index( - fields=["is_default"], name="emr_note_te_is_defa_7b5223_idx" - ), - ), - migrations.AlterUniqueTogether( - name="notetemplate", - unique_together={("tenant", "note_type", "specialty", "is_default")}, - ), - migrations.AddIndex( - model_name="problemlist", - index=models.Index( - fields=["tenant", "status"], name="emr_problem_tenant__bb0abf_idx" - ), - ), - migrations.AddIndex( - model_name="problemlist", - index=models.Index( - fields=["patient", "status"], name="emr_problem_patient_d732d2_idx" - ), - ), - migrations.AddIndex( - model_name="problemlist", - index=models.Index( - fields=["problem_type"], name="emr_problem_problem_90c9f8_idx" - ), - ), - migrations.AddIndex( - model_name="problemlist", - index=models.Index( - fields=["priority"], name="emr_problem_priorit_327dd3_idx" - ), - ), - migrations.AddIndex( - model_name="problemlist", - index=models.Index( - fields=["onset_date"], name="emr_problem_onset_d_de94bd_idx" - ), - ), - migrations.AddIndex( - model_name="clinicalrecommendation", - index=models.Index( - fields=["tenant", "status"], name="emr_clinica_tenant__9ac4a3_idx" - ), - ), - migrations.AddIndex( - model_name="clinicalrecommendation", - index=models.Index( - fields=["patient", "status"], name="emr_clinica_patient_6a41b7_idx" - ), - ), - migrations.AddIndex( - model_name="clinicalrecommendation", - index=models.Index( - fields=["category"], name="emr_clinica_categor_44bb6e_idx" - ), - ), - migrations.AddIndex( - model_name="clinicalrecommendation", - index=models.Index( - fields=["priority"], name="emr_clinica_priorit_d52001_idx" - ), - ), - migrations.AddIndex( - model_name="clinicalrecommendation", - index=models.Index( - fields=["created_at"], name="emr_clinica_created_d816a2_idx" - ), - ), - migrations.AddIndex( - model_name="clinicalnote", - index=models.Index( - fields=["patient", "note_datetime"], - name="emr_clinica_patient_442718_idx", - ), - ), - migrations.AddIndex( - model_name="clinicalnote", - index=models.Index( - fields=["encounter"], name="emr_clinica_encount_751749_idx" - ), - ), - migrations.AddIndex( - model_name="clinicalnote", - index=models.Index( - fields=["author"], name="emr_clinica_author__85ec13_idx" - ), - ), - migrations.AddIndex( - model_name="clinicalnote", - index=models.Index( - fields=["note_type"], name="emr_clinica_note_ty_e6c13c_idx" - ), - ), - migrations.AddIndex( - model_name="clinicalnote", - index=models.Index(fields=["status"], name="emr_clinica_status_0ba513_idx"), - ), - migrations.AddIndex( - model_name="careplan", - index=models.Index( - fields=["tenant", "status"], name="emr_care_pl_tenant__46659b_idx" - ), - ), - migrations.AddIndex( - model_name="careplan", - index=models.Index( - fields=["patient", "status"], name="emr_care_pl_patient_a85a8d_idx" - ), - ), - migrations.AddIndex( - model_name="careplan", - index=models.Index( - fields=["primary_provider"], name="emr_care_pl_primary_7b0b7d_idx" - ), - ), - migrations.AddIndex( - model_name="careplan", - index=models.Index( - fields=["start_date", "end_date"], name="emr_care_pl_start_d_1183e0_idx" - ), - ), - migrations.AddIndex( - model_name="careplan", - index=models.Index( - fields=["priority"], name="emr_care_pl_priorit_0a41d3_idx" - ), - ), - migrations.AddIndex( - model_name="treatmentprotocol", - index=models.Index( - fields=["tenant", "is_active"], name="emr_treatme_tenant__1f3aaa_idx" - ), - ), - migrations.AddIndex( - model_name="treatmentprotocol", - index=models.Index( - fields=["success_rate"], name="emr_treatme_success_d8024a_idx" - ), - ), - migrations.AddIndex( - model_name="vitalsigns", - index=models.Index( - fields=["patient", "measured_datetime"], - name="emr_vital_s_patient_0fc206_idx", - ), - ), - migrations.AddIndex( - model_name="vitalsigns", - index=models.Index( - fields=["encounter"], name="emr_vital_s_encount_6b9829_idx" - ), - ), - migrations.AddIndex( - model_name="vitalsigns", - index=models.Index( - fields=["measured_datetime"], name="emr_vital_s_measure_8badac_idx" - ), - ), - ] diff --git a/emr/migrations/0003_icd10_tenant_alter_icd10_code_and_more.py b/emr/migrations/0003_icd10_tenant_alter_icd10_code_and_more.py deleted file mode 100644 index f907302f..00000000 --- a/emr/migrations/0003_icd10_tenant_alter_icd10_code_and_more.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-28 13:55 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("core", "0001_initial"), - ("emr", "0002_initial"), - ] - - operations = [ - migrations.AddField( - model_name="icd10", - name="tenant", - field=models.ForeignKey( - default=1, - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="icd10_codes", - to="core.tenant", - ), - preserve_default=False, - ), - migrations.AlterField( - model_name="icd10", - name="code", - field=models.CharField(db_index=True, max_length=10), - ), - migrations.AlterUniqueTogether( - name="icd10", - unique_together={("tenant", "code")}, - ), - ] diff --git a/emr/migrations/0004_alter_encounter_status.py b/emr/migrations/0004_alter_encounter_status.py deleted file mode 100644 index 40381aba..00000000 --- a/emr/migrations/0004_alter_encounter_status.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-29 13:23 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("emr", "0003_icd10_tenant_alter_icd10_code_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="encounter", - name="status", - field=models.CharField( - choices=[ - ("PLANNED", "Planned"), - ("ARRIVED", "Arrived"), - ("TRIAGED", "Triaged"), - ("IN_PROGRESS", "In Progress"), - ("ON_HOLD", "On Hold"), - ("COMPLETED", "Completed"), - ("CANCELLED", "Cancelled"), - ("ENTERED_IN_ERROR", "Entered in Error"), - ("UNKNOWN", "Unknown"), - ], - default="PLANNED", - help_text="Current encounter status", - max_length=20, - ), - ), - ] diff --git a/emr/migrations/__pycache__/0001_initial.cpython-312.pyc b/emr/migrations/__pycache__/0001_initial.cpython-312.pyc deleted file mode 100644 index 4bbea66d..00000000 Binary files a/emr/migrations/__pycache__/0001_initial.cpython-312.pyc and /dev/null differ diff --git a/emr/migrations/__pycache__/0002_initial.cpython-312.pyc b/emr/migrations/__pycache__/0002_initial.cpython-312.pyc deleted file mode 100644 index 8c8aceb6..00000000 Binary files a/emr/migrations/__pycache__/0002_initial.cpython-312.pyc and /dev/null differ diff --git a/emr/migrations/__pycache__/0003_icd10_tenant_alter_icd10_code_and_more.cpython-312.pyc b/emr/migrations/__pycache__/0003_icd10_tenant_alter_icd10_code_and_more.cpython-312.pyc deleted file mode 100644 index deb289bb..00000000 Binary files a/emr/migrations/__pycache__/0003_icd10_tenant_alter_icd10_code_and_more.cpython-312.pyc and /dev/null differ diff --git a/emr/migrations/__pycache__/0004_alter_encounter_status.cpython-312.pyc b/emr/migrations/__pycache__/0004_alter_encounter_status.cpython-312.pyc deleted file mode 100644 index d7860e94..00000000 Binary files a/emr/migrations/__pycache__/0004_alter_encounter_status.cpython-312.pyc and /dev/null differ diff --git a/emr/templates/emr/encounters/encounter_detail.html b/emr/templates/emr/encounters/encounter_detail.html index d6dca332..f16cc660 100644 --- a/emr/templates/emr/encounters/encounter_detail.html +++ b/emr/templates/emr/encounters/encounter_detail.html @@ -29,7 +29,7 @@
-
+

Encounter Information

@@ -204,7 +204,7 @@ -
+

Encounter Details

@@ -247,6 +247,18 @@ Radiology + +
+ +
+
+
Problems Identified
+ +
+ + {% if problems %} +
+ + + + + + + + + + + + + {% for problem in problems %} + + + + + + + + + {% endfor %} + +
ProblemTypePriorityStatusOnset DateActions
+ {{ problem.problem_name }} + {% if problem.problem_code %} +
{{ problem.problem_code }} + {% endif %} +
{{ problem.get_problem_type_display }} + + {{ problem.get_priority_display }} + + + + {{ problem.get_status_display }} + + + {% if problem.onset_date %} + {{ problem.onset_date|date:"M d, Y" }} + {% else %} + Not specified + {% endif %} + + + + +
+
+ {% else %} +
+ +

No problems identified for this encounter.

+ +
+ {% endif %} +
+ + + +
+
+
Care Plans
+ +
+ + {% if object.patient.care_plans.exists %} +
+ + + + + + + + + + + + + + {% for care_plan in object.patient.care_plans.all %} + + + + + + + + + + {% endfor %} + +
TitleTypeStatusPriorityStart DateProgressActions
{{ care_plan.title }}{{ care_plan.get_plan_type_display }} + + {{ care_plan.get_status_display }} + + + + {{ care_plan.get_priority_display }} + + {{ care_plan.start_date|date:"M d, Y" }} +
+
+ {{ care_plan.completion_percentage }}% +
+
+
+ + + +
+
+ {% else %} +
+ +

No care plans for this patient.

+ +
+ {% endif %} +
+ +
@@ -725,7 +895,7 @@
-
+

Quick Actions

@@ -767,6 +937,22 @@ Add Clinical Note + + + + @@ -776,7 +962,7 @@ -
+

Patient Information

@@ -830,7 +1016,7 @@ -
+

Related Information

@@ -878,7 +1064,7 @@ -
+

Encounter Timeline

@@ -938,6 +1124,24 @@
+ + + + + + {% endblock %} {% block js %} @@ -945,6 +1149,8 @@ + + diff --git a/emr/templates/emr/partials/problem_form_modal.html b/emr/templates/emr/partials/problem_form_modal.html new file mode 100644 index 00000000..a9084455 --- /dev/null +++ b/emr/templates/emr/partials/problem_form_modal.html @@ -0,0 +1,183 @@ + + +
+ {% csrf_token %} + + + + +
+ + diff --git a/emr/urls.py b/emr/urls.py index 8c61c736..bc8185dc 100644 --- a/emr/urls.py +++ b/emr/urls.py @@ -56,7 +56,8 @@ urlpatterns = [ # Actions # path('record-create/', views.RecordCreateView.as_view(), name='record_create'), path('encounter//vitals/add/', views.add_vital_signs, name='add_vital_signs'), - path('patient//problem/add/', views.add_problem, name='add_problem'), + path('encounter//problem/add/', views.add_problem, name='add_problem'), + path('encounter//care-plan/add/', views.add_care_plan, name='add_care_plan'), path('encounter//status/', views.update_encounter_status, name='update_encounter_status'), path('note//sign/', views.sign_note, name='sign_note'), path('problem//resolve/', views.resolve_problem, name='resolve_problem'), diff --git a/emr/views.py b/emr/views.py index a21dbce9..9efec5ab 100644 --- a/emr/views.py +++ b/emr/views.py @@ -229,7 +229,9 @@ class EncounterDetailView(LoginRequiredMixin, DetailView): ).prefetch_related( 'vital_signs__measured_by', 'clinical_notes__author', - 'problems_identified__diagnosing_provider' + 'problems_identified__diagnosing_provider', + 'problems_identified__care_plans', + 'problems_identified', ) def get_context_data(self, **kwargs): @@ -1863,52 +1865,127 @@ def add_vital_signs(request, pk): @login_required -def add_problem(request, patient_id): +def add_problem(request, encounter_id): """ - HTMX endpoint for adding a problem. + HTMX endpoint for adding a problem to an encounter. """ + tenant = request.user.tenant + encounter = get_object_or_404(Encounter, id=encounter_id, tenant=tenant) + if request.method == 'POST': - patient = get_object_or_404( - PatientProfile, - id=patient_id, - tenant=request.user.tenant - ) - - problem_data = { - 'tenant': request.user.tenant, - 'patient': patient, - 'problem_name': request.POST.get('problem_name'), - 'problem_type': request.POST.get('problem_type', 'DIAGNOSIS'), - 'severity': request.POST.get('severity'), - 'priority': request.POST.get('priority', 'MEDIUM'), + form = ProblemListForm(request.POST, tenant=tenant) + if form.is_valid(): + problem = form.save(commit=False) + problem.tenant = tenant + problem.patient = encounter.patient + problem.diagnosing_provider = request.user + problem.related_encounter = encounter + problem.save() + + # Log the action + try: + AuditLogEntry.objects.create( + tenant=tenant, + user=request.user, + event_type='CREATE', + event_category='CLINICAL_DATA', + action='Add Problem', + description=f'Problem added: {problem.problem_name}', + content_type=ContentType.objects.get_for_model(ProblemList), + object_id=problem.pk, + object_repr=str(problem), + patient_id=str(encounter.patient.patient_id), + patient_mrn=encounter.patient.mrn, + changes={'problem_type': problem.problem_type, 'status': problem.status} + ) + except Exception as e: + print(f"Audit logging failed: {e}") + + messages.success(request, f'Problem "{problem.problem_name}" added successfully') + return redirect('emr:encounter_detail', pk=encounter_id) + else: + # Return form with errors + return render(request, 'emr/partials/problem_form_modal.html', { + 'form': form, + 'encounter': encounter, + 'patient': encounter.patient + }) + else: + # GET request - show form + form = ProblemListForm(tenant=tenant, initial={ + 'patient': encounter.patient, 'diagnosing_provider': request.user, - 'clinical_notes': request.POST.get('clinical_notes', ''), - } + 'related_encounter': encounter, + 'status': 'ACTIVE', + 'priority': 'MEDIUM' + }) + return render(request, 'emr/partials/problem_form_modal.html', { + 'form': form, + 'encounter': encounter, + 'patient': encounter.patient + }) - # Add optional fields - if request.POST.get('problem_code'): - problem_data['problem_code'] = request.POST.get('problem_code') - problem_data['coding_system'] = request.POST.get('coding_system', 'ICD10') - if request.POST.get('onset_date'): - problem_data['onset_date'] = request.POST.get('onset_date') - - # Create problem - problem = ProblemList.objects.create(**problem_data) - - # Log the action - AuditLogger.log_event( - user=request.user, - action='PROBLEM_ADDED', - model='ProblemList', - object_id=problem.id, - details=f"Problem added for {patient.get_full_name()}: {problem.problem_name}" - ) - - messages.success(request, f'Problem "{problem.problem_name}" added successfully') - return JsonResponse({'success': True, 'problem_id': problem.id}) - - return JsonResponse({'error': 'Invalid request'}, status=400) +@login_required +def add_care_plan(request, encounter_id): + """ + HTMX endpoint for adding a care plan to an encounter. + """ + tenant = request.user.tenant + encounter = get_object_or_404(Encounter, id=encounter_id, tenant=tenant) + + if request.method == 'POST': + form = CarePlanForm(request.POST, tenant=tenant) + if form.is_valid(): + care_plan = form.save(commit=False) + care_plan.tenant = tenant + care_plan.patient = encounter.patient + care_plan.primary_provider = request.user + care_plan.save() + form.save_m2m() # Save many-to-many relationships + + # Log the action + try: + AuditLogEntry.objects.create( + tenant=tenant, + user=request.user, + event_type='CREATE', + event_category='CARE_PLAN', + action='Add Care Plan', + description=f'Care plan created: {care_plan.title}', + content_type=ContentType.objects.get_for_model(CarePlan), + object_id=care_plan.pk, + object_repr=str(care_plan), + patient_id=str(encounter.patient.patient_id), + patient_mrn=encounter.patient.mrn, + changes={'plan_type': care_plan.plan_type, 'status': care_plan.status} + ) + except Exception as e: + print(f"Audit logging failed: {e}") + + messages.success(request, f'Care plan "{care_plan.title}" created successfully') + return redirect('emr:encounter_detail', pk=encounter_id) + else: + # Return form with errors + return render(request, 'emr/partials/care_plan_form_modal.html', { + 'form': form, + 'encounter': encounter, + 'patient': encounter.patient + }) + else: + # GET request - show form + form = CarePlanForm(tenant=tenant, initial={ + 'patient': encounter.patient, + 'primary_provider': request.user, + 'start_date': timezone.now().date(), + 'status': 'DRAFT', + 'priority': 'ROUTINE' + }) + return render(request, 'emr/partials/care_plan_form_modal.html', { + 'form': form, + 'encounter': encounter, + 'patient': encounter.patient + }) @login_required @@ -2012,7 +2089,6 @@ def get_status_class(status): return status_classes.get(status, 'secondary') - def _norm_code(s: str) -> str: return (s or "").upper().replace(" ", "") diff --git a/facility_management/__pycache__/forms.cpython-312.pyc b/facility_management/__pycache__/forms.cpython-312.pyc index 90335704..2c60cf00 100644 Binary files a/facility_management/__pycache__/forms.cpython-312.pyc and b/facility_management/__pycache__/forms.cpython-312.pyc differ diff --git a/facility_management/forms.py b/facility_management/forms.py index 69323da3..2a86e507 100644 --- a/facility_management/forms.py +++ b/facility_management/forms.py @@ -21,7 +21,6 @@ class BuildingForm(forms.ModelForm): 'name': forms.TextInput(attrs={'class': 'form-control'}), 'code': forms.TextInput(attrs={'class': 'form-control'}), 'building_type': forms.Select(attrs={'class': 'form-select'}), - 'airport_code': forms.TextInput(attrs={'class': 'form-control'}), 'address': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'floor_count': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}), 'total_area_sqm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}), @@ -185,8 +184,9 @@ class MaintenanceRequestForm(forms.ModelForm): model = MaintenanceRequest fields = [ 'title', 'description', 'maintenance_type', 'building', - 'floor', 'room', 'asset', 'priority', 'scheduled_date', 'actual_cost', - 'estimated_cost', 'notes', 'assigned_to', 'scheduled_date', 'estimated_hours', 'status', + 'floor', 'room', 'asset', 'priority', 'scheduled_date', + 'estimated_cost', 'actual_cost', 'notes', 'assigned_to', + 'estimated_hours', 'status', 'completion_notes' ] widgets = { 'title': forms.TextInput(attrs={'class': 'form-control'}), @@ -199,11 +199,12 @@ class MaintenanceRequestForm(forms.ModelForm): 'priority': forms.Select(attrs={'class': 'form-select'}), 'scheduled_date': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}), 'estimated_cost': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}), + 'actual_cost': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}), 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'assigned_to': forms.Select(attrs={'class': 'form-select'}), - 'estimated_hours': forms.NumberInput(attrs={'class': 'form-control', 'type': 'number'}), + 'estimated_hours': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.5', 'min': '0'}), 'status': forms.Select(attrs={'class': 'form-select'}), - 'actual_cost': forms.NumberInput(attrs={'class': 'form-control', 'type': 'number'}), + 'completion_notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}), } def __init__(self, *args, **kwargs): @@ -428,4 +429,3 @@ class MaintenanceFilterForm(forms.Form): super().__init__(*args, **kwargs) self.fields['assigned_to'].queryset = User.objects.filter(is_active=True) - diff --git a/facility_management/migrations/0001_initial.py b/facility_management/migrations/0001_initial.py deleted file mode 100644 index 7b7a96e7..00000000 --- a/facility_management/migrations/0001_initial.py +++ /dev/null @@ -1,1194 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.core.validators -import django.db.models.deletion -import uuid -from decimal import Decimal -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("core", "0001_initial"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="AssetCategory", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=100, unique=True)), - ("code", models.CharField(max_length=20, unique=True)), - ("description", models.TextField(blank=True)), - ("is_active", models.BooleanField(default=True)), - ( - "parent_category", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.assetcategory", - ), - ), - ], - options={ - "verbose_name_plural": "Asset Categories", - "db_table": "facility_management_asset_categories", - "ordering": ["name"], - }, - ), - migrations.CreateModel( - name="Building", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=100)), - ( - "building_id", - models.UUIDField(default=uuid.uuid4, editable=False, unique=True), - ), - ("code", models.CharField(max_length=10, unique=True)), - ( - "building_type", - models.CharField( - choices=[ - ("CLINICAL", "Clinical"), - ("NON_CLINICAL", "Non Clinical"), - ("OTHER", "Other"), - ], - default="CLINICAL", - max_length=20, - ), - ), - ("address", models.TextField()), - ( - "latitude", - models.DecimalField( - blank=True, decimal_places=6, max_digits=9, null=True - ), - ), - ( - "longitude", - models.DecimalField( - blank=True, decimal_places=6, max_digits=9, null=True - ), - ), - ("floor_count", models.PositiveIntegerField(default=1)), - ( - "total_area_sqm", - models.DecimalField( - blank=True, decimal_places=2, max_digits=10, null=True - ), - ), - ( - "construction_year", - models.PositiveIntegerField(blank=True, null=True), - ), - ("architect", models.CharField(blank=True, max_length=100)), - ("contractor", models.CharField(blank=True, max_length=100)), - ("is_active", models.BooleanField(default=True)), - ("last_major_renovation", models.DateField(blank=True, null=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "facility_manager", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "tenant", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="buildings", - to="core.tenant", - ), - ), - ], - options={ - "verbose_name_plural": "Buildings", - "db_table": "facility_management_buildings", - "ordering": ["code", "name"], - }, - ), - migrations.CreateModel( - name="EnergyMeter", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("meter_id", models.CharField(max_length=50, unique=True)), - ( - "meter_type", - models.CharField( - choices=[ - ("ELECTRICITY", "Electricity"), - ("GAS", "Natural Gas"), - ("WATER", "Water"), - ("STEAM", "Steam"), - ("CHILLED_WATER", "Chilled Water"), - ("OTHER", "Other"), - ], - help_text="Meter type", - max_length=20, - ), - ), - ("location_description", models.CharField(max_length=200)), - ("manufacturer", models.CharField(blank=True, max_length=100)), - ("model", models.CharField(blank=True, max_length=100)), - ("serial_number", models.CharField(blank=True, max_length=100)), - ("installation_date", models.DateField(blank=True, null=True)), - ( - "current_reading", - models.DecimalField( - decimal_places=2, default=Decimal("0.00"), max_digits=12 - ), - ), - ("last_reading_date", models.DateTimeField(blank=True, null=True)), - ("is_active", models.BooleanField(default=True)), - ("calibration_date", models.DateField(blank=True, null=True)), - ("next_calibration_date", models.DateField(blank=True, null=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "building", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.building", - ), - ), - ], - options={ - "verbose_name_plural": "Energy Meters", - "db_table": "facility_management_energy_meters", - "ordering": ["building", "meter_type", "meter_id"], - }, - ), - migrations.CreateModel( - name="EnergyReading", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("reading_date", models.DateTimeField()), - ("reading_value", models.DecimalField(decimal_places=2, max_digits=12)), - ( - "consumption", - models.DecimalField( - blank=True, decimal_places=2, max_digits=12, null=True - ), - ), - ( - "cost", - models.DecimalField( - blank=True, decimal_places=2, max_digits=10, null=True - ), - ), - ("is_estimated", models.BooleanField(default=False)), - ("notes", models.TextField(blank=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ( - "meter", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="readings", - to="facility_management.energymeter", - ), - ), - ( - "read_by", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "ordering": ["-reading_date"], - }, - ), - migrations.CreateModel( - name="Floor", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "floor_number", - models.IntegerField( - help_text="Use negative numbers for basement levels" - ), - ), - ( - "name", - models.CharField( - help_text="e.g., Ground Floor, Mezzanine, B1", max_length=50 - ), - ), - ( - "area_sqm", - models.DecimalField( - blank=True, decimal_places=2, max_digits=10, null=True - ), - ), - ( - "ceiling_height_m", - models.DecimalField( - blank=True, decimal_places=2, max_digits=5, null=True - ), - ), - ("is_public_access", models.BooleanField(default=False)), - ( - "building", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="floors", - to="facility_management.building", - ), - ), - ], - options={ - "db_table": "facility_management_floors", - "ordering": ["building", "floor_number"], - }, - ), - migrations.CreateModel( - name="Asset", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "asset_id", - models.UUIDField(default=uuid.uuid4, editable=False, unique=True), - ), - ("name", models.CharField(max_length=200)), - ("location_description", models.CharField(blank=True, max_length=200)), - ("manufacturer", models.CharField(blank=True, max_length=100)), - ("model", models.CharField(blank=True, max_length=100)), - ("serial_number", models.CharField(blank=True, max_length=100)), - ("purchase_date", models.DateField(blank=True, null=True)), - ( - "purchase_cost", - models.DecimalField( - blank=True, decimal_places=2, max_digits=12, null=True - ), - ), - ( - "current_value", - models.DecimalField( - blank=True, decimal_places=2, max_digits=12, null=True - ), - ), - ( - "depreciation_rate", - models.DecimalField( - decimal_places=2, - default=Decimal("10.00"), - help_text="Annual depreciation percentage", - max_digits=5, - ), - ), - ("warranty_start_date", models.DateField(blank=True, null=True)), - ("warranty_end_date", models.DateField(blank=True, null=True)), - ("service_provider", models.CharField(blank=True, max_length=100)), - ( - "service_contract_number", - models.CharField(blank=True, max_length=50), - ), - ( - "status", - models.CharField( - choices=[ - ("OPERATIONAL", "Operational"), - ("MAINTENANCE", "Under Maintenance"), - ("REPAIR", "Needs Repair"), - ("RETIRED", "Retired"), - ("DISPOSED", "Disposed"), - ], - default="OPERATIONAL", - max_length=20, - ), - ), - ( - "condition", - models.CharField( - choices=[ - ("EXCELLENT", "Excellent"), - ("GOOD", "Good"), - ("FAIR", "Fair"), - ("POOR", "Poor"), - ("CRITICAL", "Critical"), - ], - default="GOOD", - help_text="Current condition of the asset", - max_length=20, - ), - ), - ("last_inspection_date", models.DateField(blank=True, null=True)), - ("next_maintenance_date", models.DateField(blank=True, null=True)), - ("notes", models.TextField(blank=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "assigned_to", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "category", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.assetcategory", - ), - ), - ( - "building", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.building", - ), - ), - ( - "floor", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.floor", - ), - ), - ], - options={ - "verbose_name_plural": "Assets", - "db_table": "facility_management_assets", - "ordering": ["asset_id"], - }, - ), - migrations.CreateModel( - name="MaintenanceType", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=100, unique=True)), - ("code", models.CharField(max_length=20, unique=True)), - ("description", models.TextField(blank=True)), - ( - "estimated_duration_hours", - models.DecimalField( - blank=True, decimal_places=2, max_digits=5, null=True - ), - ), - ("is_active", models.BooleanField(default=True)), - ], - options={ - "verbose_name_plural": "Maintenance Types", - "db_table": "facility_management_maintenance_types", - "ordering": ["name"], - "indexes": [ - models.Index(fields=["code"], name="facility_ma_code_54340b_idx") - ], - }, - ), - migrations.CreateModel( - name="Room", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("room_number", models.CharField(max_length=20)), - ("name", models.CharField(blank=True, max_length=100)), - ( - "area_sqm", - models.DecimalField( - blank=True, decimal_places=2, max_digits=8, null=True - ), - ), - ( - "capacity", - models.PositiveIntegerField( - blank=True, help_text="Maximum occupancy", null=True - ), - ), - ( - "occupancy_status", - models.CharField( - choices=[ - ("VACANT", "Vacant"), - ("OCCUPIED", "Occupied"), - ("MAINTENANCE", "Under Maintenance"), - ("RESERVED", "Reserved"), - ], - default="VACANT", - max_length=20, - ), - ), - ( - "is_accessible", - models.BooleanField( - default=True, help_text="ADA/accessibility compliant" - ), - ), - ("notes", models.TextField(blank=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "floor", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="rooms", - to="facility_management.floor", - ), - ), - ], - options={ - "db_table": "facility_management_rooms", - "ordering": ["floor", "room_number"], - }, - ), - migrations.CreateModel( - name="MaintenanceSchedule", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=200)), - ("description", models.TextField()), - ( - "frequency_interval", - models.IntegerField( - choices=[ - (1, "Daily"), - (7, "Weekly"), - (30, "Monthly"), - (90, "Quarterly"), - (182, "Semi-Annual"), - (365, "Annual"), - ], - default=365, - ), - ), - ("start_date", models.DateField()), - ("end_date", models.DateField(blank=True, null=True)), - ( - "estimated_duration_hours", - models.DecimalField( - blank=True, decimal_places=2, max_digits=5, null=True - ), - ), - ("is_active", models.BooleanField(default=True)), - ("last_generated_date", models.DateField(blank=True, null=True)), - ("next_due_date", models.DateField(blank=True, null=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ( - "asset", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.asset", - ), - ), - ( - "assigned_to", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "building", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.building", - ), - ), - ( - "maintenance_type", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.maintenancetype", - ), - ), - ( - "room", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.room", - ), - ), - ], - options={ - "db_table": "facility_management_maintenance_schedules", - "ordering": ["next_due_date"], - }, - ), - migrations.CreateModel( - name="MaintenanceRequest", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "request_id", - models.CharField(editable=False, max_length=50, unique=True), - ), - ("title", models.CharField(max_length=200)), - ("description", models.TextField()), - ( - "priority", - models.CharField( - choices=[ - ("LOW", "Low"), - ("MEDIUM", "Medium"), - ("HIGH", "High"), - ("URGENT", "Urgent"), - ("EMERGENCY", "Emergency"), - ], - default="MEDIUM", - max_length=20, - ), - ), - ( - "status", - models.CharField( - choices=[ - ("SUBMITTED", "Submitted"), - ("ASSIGNED", "Assigned"), - ("IN_PROGRESS", "In Progress"), - ("COMPLETED", "Completed"), - ("CANCELLED", "Cancelled"), - ("ON_HOLD", "On Hold"), - ], - default="SUBMITTED", - help_text="Current status of the request", - max_length=20, - ), - ), - ("requested_date", models.DateTimeField(auto_now_add=True)), - ("scheduled_date", models.DateTimeField(blank=True, null=True)), - ("started_date", models.DateTimeField(blank=True, null=True)), - ("completed_date", models.DateTimeField(blank=True, null=True)), - ( - "estimated_hours", - models.DecimalField( - blank=True, decimal_places=2, max_digits=5, null=True - ), - ), - ( - "estimated_cost", - models.DecimalField( - blank=True, decimal_places=2, max_digits=10, null=True - ), - ), - ( - "actual_cost", - models.DecimalField( - blank=True, decimal_places=2, max_digits=10, null=True - ), - ), - ("notes", models.TextField(blank=True)), - ("completion_notes", models.TextField(blank=True)), - ( - "asset", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.asset", - ), - ), - ( - "assigned_to", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="assigned_maintenance", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "building", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.building", - ), - ), - ( - "floor", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.floor", - ), - ), - ( - "requested_by", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="maintenance_requests", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "maintenance_type", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.maintenancetype", - ), - ), - ( - "room", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.room", - ), - ), - ], - options={ - "verbose_name_plural": "Maintenance Requests", - "db_table": "facility_management_maintenance_requests", - "ordering": ["-requested_date"], - }, - ), - migrations.CreateModel( - name="Inspection", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("inspection_id", models.CharField(max_length=50, unique=True)), - ( - "inspection_type", - models.CharField( - choices=[ - ("SAFETY", "Safety Inspection"), - ("FIRE", "Fire Safety"), - ("HEALTH", "Health Inspection"), - ("SECURITY", "Security Audit"), - ("ENVIRONMENTAL", "Environmental"), - ("STRUCTURAL", "Structural"), - ("ELECTRICAL", "Electrical"), - ("HVAC", "HVAC"), - ("OTHER", "Other"), - ], - help_text="Type of inspection", - max_length=20, - ), - ), - ("title", models.CharField(max_length=200)), - ("description", models.TextField(blank=True)), - ("scheduled_date", models.DateTimeField()), - ( - "estimated_duration_hours", - models.DecimalField( - blank=True, decimal_places=2, max_digits=5, null=True - ), - ), - ( - "inspector_external", - models.CharField( - blank=True, help_text="External inspector name", max_length=100 - ), - ), - ( - "inspector_organization", - models.CharField(blank=True, max_length=100), - ), - ( - "status", - models.CharField( - choices=[ - ("SCHEDULED", "Scheduled"), - ("IN_PROGRESS", "In Progress"), - ("COMPLETED", "Completed"), - ("CANCELLED", "Cancelled"), - ], - default="SCHEDULED", - help_text="Current status of the inspection", - max_length=20, - ), - ), - ("started_date", models.DateTimeField(blank=True, null=True)), - ("completed_date", models.DateTimeField(blank=True, null=True)), - ( - "overall_rating", - models.CharField( - blank=True, help_text="Pass/Fail/Conditional", max_length=20 - ), - ), - ("findings", models.TextField(blank=True)), - ("recommendations", models.TextField(blank=True)), - ("requires_followup", models.BooleanField(default=False)), - ("followup_date", models.DateField(blank=True, null=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "assets", - models.ManyToManyField(blank=True, to="facility_management.asset"), - ), - ( - "building", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.building", - ), - ), - ( - "floors", - models.ManyToManyField(blank=True, to="facility_management.floor"), - ), - ( - "inspector", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="inspections_conducted", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "rooms", - models.ManyToManyField(blank=True, to="facility_management.room"), - ), - ], - options={ - "verbose_name_plural": "Inspections", - "db_table": "facility_management_inspections", - "ordering": ["-scheduled_date"], - }, - ), - migrations.AddField( - model_name="asset", - name="room", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.room", - ), - ), - migrations.CreateModel( - name="SpaceReservation", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("reservation_id", models.CharField(max_length=50, unique=True)), - ("title", models.CharField(max_length=200)), - ("description", models.TextField(blank=True)), - ("start_datetime", models.DateTimeField()), - ("end_datetime", models.DateTimeField()), - ("contact_person", models.CharField(blank=True, max_length=100)), - ("contact_email", models.EmailField(blank=True, max_length=254)), - ("contact_phone", models.CharField(blank=True, max_length=20)), - ( - "expected_attendees", - models.PositiveIntegerField(blank=True, null=True), - ), - ("setup_requirements", models.TextField(blank=True)), - ("catering_required", models.BooleanField(default=False)), - ( - "av_equipment_required", - models.BooleanField( - default=False, - help_text="Whether audio/visual equipment (projector, microphone, etc.) is needed", - ), - ), - ( - "status", - models.CharField( - choices=[ - ("PENDING", "Pending"), - ("CONFIRMED", "Confirmed"), - ("CANCELLED", "Cancelled"), - ("COMPLETED", "Completed"), - ("NO_SHOW", "No Show"), - ], - default="PENDING", - max_length=20, - ), - ), - ("approved_at", models.DateTimeField(blank=True, null=True)), - ( - "hourly_rate", - models.DecimalField( - blank=True, decimal_places=2, max_digits=8, null=True - ), - ), - ( - "total_cost", - models.DecimalField( - blank=True, decimal_places=2, max_digits=10, null=True - ), - ), - ("notes", models.TextField(blank=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "approved_by", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="approved_reservations", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "reserved_by", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "room", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.room", - ), - ), - ], - options={ - "verbose_name_plural": "Space Reservations", - "db_table": "facility_management_space_reservations", - "ordering": ["-start_datetime"], - }, - ), - migrations.CreateModel( - name="Vendor", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=200)), - ( - "vendor_type", - models.CharField( - choices=[ - ("MAINTENANCE", "Maintenance Contractor"), - ("CLEANING", "Cleaning Service"), - ("SECURITY", "Security Service"), - ("LANDSCAPING", "Landscaping"), - ("HVAC", "HVAC Service"), - ("ELECTRICAL", "Electrical Service"), - ("PLUMBING", "Plumbing Service"), - ("IT", "IT Service"), - ("OTHER", "Other"), - ], - max_length=20, - ), - ), - ("contact_person", models.CharField(max_length=100)), - ("email", models.EmailField(max_length=254)), - ("phone", models.CharField(max_length=20)), - ("address", models.TextField()), - ( - "crn", - models.CharField( - blank=True, - max_length=10, - null=True, - verbose_name="Commercial Registration Number", - ), - ), - ( - "vrn", - models.CharField( - blank=True, - max_length=15, - null=True, - verbose_name="VAT Registration Number", - ), - ), - ( - "rating", - models.DecimalField( - blank=True, - decimal_places=2, - max_digits=3, - null=True, - validators=[ - django.core.validators.MinValueValidator(Decimal("0.00")), - django.core.validators.MaxValueValidator(Decimal("5.00")), - ], - ), - ), - ("total_contracts", models.PositiveIntegerField(default=0)), - ("is_active", models.BooleanField(default=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "tenant", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="core.tenant" - ), - ), - ], - options={ - "verbose_name_plural": "Vendors", - "db_table": "facility_management_vendors", - "ordering": ["name"], - }, - ), - migrations.CreateModel( - name="ServiceContract", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("contract_number", models.CharField(max_length=50, unique=True)), - ("title", models.CharField(max_length=200)), - ("description", models.TextField()), - ("start_date", models.DateField()), - ("end_date", models.DateField()), - ( - "contract_value", - models.DecimalField(decimal_places=2, max_digits=12), - ), - ( - "payment_terms", - models.CharField(help_text="e.g., Net 30 days", max_length=100), - ), - ( - "service_areas", - models.TextField(help_text="Description of service areas"), - ), - ( - "status", - models.CharField( - choices=[ - ("DRAFT", "Draft"), - ("ACTIVE", "Active"), - ("EXPIRED", "Expired"), - ("TERMINATED", "Terminated"), - ], - default="DRAFT", - max_length=20, - ), - ), - ("auto_renewal", models.BooleanField(default=False)), - ("renewal_notice_days", models.PositiveIntegerField(default=30)), - ("notes", models.TextField(blank=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "buildings", - models.ManyToManyField( - blank=True, to="facility_management.building" - ), - ), - ( - "contract_manager", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "vendor", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="facility_management.vendor", - ), - ), - ], - options={ - "verbose_name_plural": "Service Contracts", - "db_table": "facility_management_service_contracts", - "ordering": ["-start_date"], - }, - ), - migrations.AddIndex( - model_name="assetcategory", - index=models.Index(fields=["code"], name="facility_ma_code_466577_idx"), - ), - migrations.AddIndex( - model_name="building", - index=models.Index( - fields=["code", "name"], name="facility_ma_code_61bb9d_idx" - ), - ), - migrations.AlterUniqueTogether( - name="building", - unique_together={("code", "name")}, - ), - migrations.AlterUniqueTogether( - name="energyreading", - unique_together={("meter", "reading_date")}, - ), - migrations.AddIndex( - model_name="floor", - index=models.Index( - fields=["building", "floor_number"], - name="facility_ma_buildin_884b34_idx", - ), - ), - migrations.AlterUniqueTogether( - name="floor", - unique_together={("building", "floor_number")}, - ), - migrations.AddIndex( - model_name="room", - index=models.Index( - fields=["floor", "room_number"], name="facility_ma_floor_i_1f4028_idx" - ), - ), - migrations.AlterUniqueTogether( - name="room", - unique_together={("floor", "room_number")}, - ), - migrations.AddIndex( - model_name="maintenanceschedule", - index=models.Index( - fields=["asset", "building", "room"], - name="facility_ma_asset_i_fe5ae7_idx", - ), - ), - migrations.AlterUniqueTogether( - name="maintenanceschedule", - unique_together={("asset", "building", "room")}, - ), - migrations.AddIndex( - model_name="maintenancerequest", - index=models.Index( - fields=["building", "floor", "room", "asset"], - name="facility_ma_buildin_06372a_idx", - ), - ), - migrations.AddIndex( - model_name="asset", - index=models.Index( - fields=["building", "floor", "room", "serial_number"], - name="facility_ma_buildin_2c82c7_idx", - ), - ), - migrations.AlterUniqueTogether( - name="asset", - unique_together={("building", "floor", "room", "serial_number")}, - ), - migrations.AddIndex( - model_name="vendor", - index=models.Index( - fields=["tenant", "name"], name="facility_ma_tenant__cc4314_idx" - ), - ), - migrations.AddIndex( - model_name="servicecontract", - index=models.Index( - fields=["vendor"], name="facility_ma_vendor__870e50_idx" - ), - ), - ] diff --git a/facility_management/migrations/__pycache__/0001_initial.cpython-312.pyc b/facility_management/migrations/__pycache__/0001_initial.cpython-312.pyc deleted file mode 100644 index 864953d6..00000000 Binary files a/facility_management/migrations/__pycache__/0001_initial.cpython-312.pyc and /dev/null differ diff --git a/facility_management/templates/facility_management/buildings/form.html b/facility_management/templates/facility_management/buildings/form.html index 33838559..0b871a44 100644 --- a/facility_management/templates/facility_management/buildings/form.html +++ b/facility_management/templates/facility_management/buildings/form.html @@ -50,7 +50,7 @@
-
+
{{ form.building_type }} @@ -59,16 +59,6 @@ {% endif %}
-
-
- - {{ form.airport_code }} - {% if form.airport_code.errors %} -
{{ form.airport_code.errors.0 }}
- {% endif %} - IATA airport code -
-
@@ -256,4 +246,3 @@
{% endblock %} - diff --git a/facility_management/templates/facility_management/maintenance/form.html b/facility_management/templates/facility_management/maintenance/form.html index 1add56a5..da3d8bb8 100644 --- a/facility_management/templates/facility_management/maintenance/form.html +++ b/facility_management/templates/facility_management/maintenance/form.html @@ -144,6 +144,29 @@
+ + {% if object %} +
+
+
+ + {{ form.started_date }} + {% if form.started_date.errors %} +
{{ form.started_date.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.completed_date }} + {% if form.completed_date.errors %} +
{{ form.completed_date.errors.0 }}
+ {% endif %} +
+
+
+ {% endif %}
@@ -179,30 +202,8 @@
- {% if object and object.status in 'completed,cancelled' %}
Completion Details
-
-
-
- - {{ form.completed_date }} - {% if form.completed_date.errors %} -
{{ form.completed_date.errors.0 }}
- {% endif %} -
-
-
-
- - {{ form.actual_hours }} - {% if form.actual_hours.errors %} -
{{ form.actual_hours.errors.0 }}
- {% endif %} -
-
-
-
{{ form.completion_notes }} @@ -211,7 +212,6 @@ {% endif %} Work performed, parts used, recommendations
- {% endif %}
@@ -298,4 +298,3 @@
{% endblock %} - diff --git a/fix_all_constraints.py b/fix_all_constraints.py new file mode 100644 index 00000000..ef5d11f2 --- /dev/null +++ b/fix_all_constraints.py @@ -0,0 +1,58 @@ +""" +Comprehensive fix for all database foreign key constraints. +""" +import sqlite3 + +# Connect to SQLite database +db_path = 'db.sqlite3' +conn = sqlite3.connect(db_path) +cursor = conn.cursor() + +print("Fixing all database constraints...") + +# Disable foreign key checks +cursor.execute("PRAGMA foreign_keys = OFF;") + +# Get all tables with tenant_id that have violations +tables_with_violations = [ + 'radiology_imaging_study', + 'accounts_user', + 'hr_department', + 'hr_training_program', + 'operating_theatre_operating_room', + 'patients_patient_profile', +] + +for table in tables_with_violations: + try: + # Delete rows with invalid tenant_id + cursor.execute(f""" + DELETE FROM {table} + WHERE tenant_id NOT IN (SELECT id FROM core_tenant) + """) + affected = cursor.rowcount + if affected > 0: + print(f" Deleted {affected} rows from {table} with invalid tenant_id") + except sqlite3.OperationalError as e: + print(f" Error fixing {table}: {e}") + +# Commit changes +conn.commit() + +# Re-enable foreign key checks +cursor.execute("PRAGMA foreign_keys = ON;") + +# Verify constraints +cursor.execute("PRAGMA foreign_key_check;") +violations = cursor.fetchall() + +if violations: + print(f"\n⚠ Still have {len(violations)} foreign key violations") + print("First 10 violations:") + for violation in violations[:10]: + print(f" {violation}") +else: + print("\n✓ All foreign key constraints are now valid!") + +conn.close() +print("\nDatabase fix completed!") diff --git a/fix_db_constraints.py b/fix_db_constraints.py new file mode 100644 index 00000000..579e401b --- /dev/null +++ b/fix_db_constraints.py @@ -0,0 +1,70 @@ +""" +Fix database foreign key constraints before running new migrations. +""" +import os +import django +import sqlite3 + +# Setup Django +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hospital_management.settings') +django.setup() + +# Connect to SQLite database +db_path = 'db.sqlite3' +conn = sqlite3.connect(db_path) +cursor = conn.cursor() + +print("Fixing database constraints...") + +# Disable foreign key checks temporarily +cursor.execute("PRAGMA foreign_keys = OFF;") + +# Fix operating_theatre_surgical_note_template with invalid tenant_id +print("Fixing operating_theatre_surgical_note_template...") +cursor.execute(""" + DELETE FROM operating_theatre_surgical_note_template + WHERE tenant_id NOT IN (SELECT id FROM core_tenant) +""") +affected = cursor.rowcount +print(f" Deleted {affected} rows with invalid tenant_id") + +# Check for other tables with similar issues +tables_to_check = [ + 'facility_management_maintenancerequest', + 'facility_management_workorder', + 'facility_management_asset', +] + +for table in tables_to_check: + try: + cursor.execute(f""" + UPDATE {table} + SET tenant_id = 1 + WHERE tenant_id NOT IN (SELECT id FROM core_tenant) + """) + affected = cursor.rowcount + if affected > 0: + print(f" Fixed {affected} rows in {table}") + except sqlite3.OperationalError as e: + # Table might not exist yet + print(f" Skipping {table}: {e}") + +# Commit changes +conn.commit() + +# Re-enable foreign key checks +cursor.execute("PRAGMA foreign_keys = ON;") + +# Verify constraints +cursor.execute("PRAGMA foreign_key_check;") +violations = cursor.fetchall() + +if violations: + print("\nRemaining foreign key violations:") + for violation in violations: + print(f" {violation}") +else: + print("\n✓ All foreign key constraints are now valid!") + +conn.close() +print("\nDatabase constraints fixed successfully!") diff --git a/hospital_management/__pycache__/settings.cpython-312.pyc b/hospital_management/__pycache__/settings.cpython-312.pyc index 7be632df..6d22f801 100644 Binary files a/hospital_management/__pycache__/settings.cpython-312.pyc and b/hospital_management/__pycache__/settings.cpython-312.pyc differ diff --git a/hospital_management/settings.py b/hospital_management/settings.py index 909f0ae8..f8b0c1b3 100644 --- a/hospital_management/settings.py +++ b/hospital_management/settings.py @@ -72,6 +72,7 @@ LOCAL_APPS = [ 'integration', 'quality', 'facility_management', + 'insurance_approvals.apps.InsuranceApprovalsConfig', ] INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS diff --git a/hr/__pycache__/admin.cpython-312.pyc b/hr/__pycache__/admin.cpython-312.pyc index 732956e6..6770d38f 100644 Binary files a/hr/__pycache__/admin.cpython-312.pyc and b/hr/__pycache__/admin.cpython-312.pyc differ diff --git a/hr/__pycache__/forms.cpython-312.pyc b/hr/__pycache__/forms.cpython-312.pyc index e6c382ad..05624b30 100644 Binary files a/hr/__pycache__/forms.cpython-312.pyc and b/hr/__pycache__/forms.cpython-312.pyc differ diff --git a/hr/__pycache__/urls.cpython-312.pyc b/hr/__pycache__/urls.cpython-312.pyc index f61b9100..209369dc 100644 Binary files a/hr/__pycache__/urls.cpython-312.pyc and b/hr/__pycache__/urls.cpython-312.pyc differ diff --git a/hr/__pycache__/views.cpython-312.pyc b/hr/__pycache__/views.cpython-312.pyc index 49fedc93..10f215b2 100644 Binary files a/hr/__pycache__/views.cpython-312.pyc and b/hr/__pycache__/views.cpython-312.pyc differ diff --git a/hr/admin.py b/hr/admin.py index 9562e9ec..53d16815 100644 --- a/hr/admin.py +++ b/hr/admin.py @@ -6,11 +6,16 @@ from django.contrib import admin from django.utils.html import format_html from django.urls import reverse from django.utils.safestring import mark_safe +from django.db.models import Count, Avg, Sum from decimal import Decimal -from datetime import date +from datetime import date, timedelta from .models import * +# ============================================================================ +# INLINE ADMIN CLASSES +# ============================================================================ + class ScheduleInline(admin.TabularInline): """ Inline admin for employee schedules. @@ -46,7 +51,7 @@ class PerformanceReviewInline(admin.TabularInline): fields = [ 'review_date', 'review_type', 'overall_rating', 'status' ] - + readonly_fields = ['review_id'] class TrainingRecordInline(admin.TabularInline): @@ -56,12 +61,67 @@ class TrainingRecordInline(admin.TabularInline): model = TrainingRecord extra = 0 fields = [ - 'completion_date', + 'program', 'session', 'completion_date', 'status', 'passed' ] readonly_fields = ['record_id'] +class ProgramModuleInline(admin.TabularInline): + """ + Inline admin for training program modules. + """ + model = ProgramModule + extra = 0 + fields = ['order', 'title', 'hours'] + ordering = ['order'] + + +class ProgramPrerequisiteInline(admin.TabularInline): + """ + Inline admin for training program prerequisites. + """ + model = ProgramPrerequisite + extra = 0 + fk_name = 'program' + fields = ['required_program'] + + +class TrainingSessionInline(admin.TabularInline): + """ + Inline admin for training sessions. + """ + model = TrainingSession + extra = 0 + fields = [ + 'title', 'start_at', 'end_at', 'instructor', + 'delivery_method', 'capacity' + ] + readonly_fields = ['session_id'] + + +class TrainingAttendanceInline(admin.TabularInline): + """ + Inline admin for training attendance. + """ + model = TrainingAttendance + extra = 0 + fields = [ + 'checked_in_at', 'checked_out_at', 'status', 'notes' + ] + + +class TrainingAssessmentInline(admin.TabularInline): + """ + Inline admin for training assessments. + """ + model = TrainingAssessment + extra = 0 + fields = [ + 'name', 'max_score', 'score', 'passed', 'taken_at' + ] + + @admin.register(Employee) class EmployeeAdmin(admin.ModelAdmin): """ @@ -741,121 +801,581 @@ class PerformanceReviewAdmin(admin.ModelAdmin): return qs.select_related('employee', 'reviewer') -# @admin.register(TrainingRecord) -# class TrainingRecordAdmin(admin.ModelAdmin): -# """ -# Admin interface for training records. -# """ -# list_display = [ -# 'employee_name', 'completion_date', 'status', -# 'passed', 'expiry_status_display' -# ] -# -# search_fields = [ -# 'employee__first_name', 'employee__last_name', -# 'employee__employee_id', 'training_name' -# ] -# readonly_fields = [ -# 'record_id', 'created_at', 'updated_at' -# ] -# fieldsets = [ -# ('Training Information', { -# 'fields': [ -# 'record_id', 'employee', 'training_description' -# ] -# }), -# -# ('Training Provider', { -# 'fields': [ -# 'training_provider', 'instructor' -# ] -# }), -# ('Training Dates', { -# 'fields': [ -# 'completion_date', 'expiry_date' -# ] -# }), -# ('Training Details', { -# 'fields': [ -# 'duration_hours', 'credits_earned' -# ] -# }), -# ('Training Status', { -# 'fields': [ -# 'status' -# ] -# }), -# ('Results', { -# 'fields': [ -# 'score', 'passed' -# ] -# }), -# ('Certification Information', { -# 'fields': [ -# 'certificate_number', 'certification_body' -# ] -# }), -# ('Cost Information', { -# 'fields': [ -# 'training_cost' -# ] -# }), -# ('Expiry Information', { -# 'fields': [ -# 'is_expired', 'days_to_expiry', 'is_due_for_renewal' -# ], -# 'classes': ['collapse'] -# }), -# ('Notes', { -# 'fields': [ -# 'notes' -# ], -# 'classes': ['collapse'] -# }), -# ('Related Information', { -# 'fields': [ -# 'employee__tenant' -# ], -# 'classes': ['collapse'] -# }), -# ('Metadata', { -# 'fields': [ -# 'created_at', 'updated_at', 'created_by' -# ], -# 'classes': ['collapse'] -# }) -# ] -# -# -# def employee_name(self, obj): -# """Display employee name.""" -# return obj.employee.get_full_name() -# employee_name.short_description = 'Employee' -# -# def expiry_status_display(self, obj): -# """Display expiry status with color coding.""" -# if not obj.expiry_date: -# return format_html('No Expiry') -# -# if obj.is_expired: -# return format_html('⚠️ Expired') -# -# if obj.is_due_for_renewal: -# return format_html('⚠️ Due Soon') -# -# return format_html('✓ Valid') -# expiry_status_display.short_description = 'Expiry Status' -# -# def get_queryset(self, request): -# """Filter by user's tenant.""" -# qs = super().get_queryset(request) -# if hasattr(request.user, 'tenant'): -# qs = qs.filter(employee__tenant=request.user.tenant) -# return qs.select_related('employee', 'created_by') +# ============================================================================ +# TRAINING ADMIN CLASSES +# ============================================================================ +@admin.register(TrainingPrograms) +class TrainingProgramsAdmin(admin.ModelAdmin): + """ + Admin interface for training programs. + """ + list_display = [ + 'name', 'program_type', 'program_provider', 'instructor', + 'duration_hours', 'cost', 'is_certified', 'validity_display' + ] + list_filter = [ + 'tenant', 'program_type', 'is_certified', 'program_provider' + ] + search_fields = [ + 'name', 'description', 'program_provider' + ] + readonly_fields = [ + 'program_id', 'created_at', 'updated_at' + ] + fieldsets = [ + ('Program Information', { + 'fields': [ + 'program_id', 'tenant', 'name', 'description' + ] + }), + ('Program Details', { + 'fields': [ + 'program_type', 'program_provider', 'instructor' + ] + }), + ('Schedule Information', { + 'fields': [ + 'start_date', 'end_date', 'duration_hours' + ] + }), + ('Cost Information', { + 'fields': [ + 'cost' + ] + }), + ('Certification Information', { + 'fields': [ + 'is_certified', 'validity_days', 'notify_before_days' + ] + }), + ('Metadata', { + 'fields': [ + 'created_at', 'updated_at', 'created_by' + ], + 'classes': ['collapse'] + }) + ] + inlines = [ProgramModuleInline, ProgramPrerequisiteInline, TrainingSessionInline] + date_hierarchy = 'start_date' + + def validity_display(self, obj): + """Display validity information.""" + if obj.is_certified and obj.validity_days: + return f"{obj.validity_days} days" + return "No expiry" + validity_display.short_description = 'Validity' + + def get_queryset(self, request): + """Filter by user's tenant.""" + qs = super().get_queryset(request) + if hasattr(request.user, 'tenant'): + qs = qs.filter(tenant=request.user.tenant) + return qs.select_related('instructor', 'created_by') + + +@admin.register(ProgramModule) +class ProgramModuleAdmin(admin.ModelAdmin): + """ + Admin interface for program modules. + """ + list_display = [ + 'program', 'order', 'title', 'hours' + ] + list_filter = [ + 'program__tenant', 'program' + ] + search_fields = [ + 'title', 'program__name' + ] + ordering = ['program', 'order'] + + def get_queryset(self, request): + """Filter by user's tenant.""" + qs = super().get_queryset(request) + if hasattr(request.user, 'tenant'): + qs = qs.filter(program__tenant=request.user.tenant) + return qs.select_related('program') + + +@admin.register(ProgramPrerequisite) +class ProgramPrerequisiteAdmin(admin.ModelAdmin): + """ + Admin interface for program prerequisites. + """ + list_display = [ + 'program', 'required_program' + ] + list_filter = [ + 'program__tenant', 'program' + ] + search_fields = [ + 'program__name', 'required_program__name' + ] + + def get_queryset(self, request): + """Filter by user's tenant.""" + qs = super().get_queryset(request) + if hasattr(request.user, 'tenant'): + qs = qs.filter(program__tenant=request.user.tenant) + return qs.select_related('program', 'required_program') + + +@admin.register(TrainingSession) +class TrainingSessionAdmin(admin.ModelAdmin): + """ + Admin interface for training sessions. + """ + list_display = [ + 'program', 'title_display', 'instructor', 'start_at', + 'end_at', 'delivery_method', 'capacity', 'enrollment_count' + ] + list_filter = [ + 'program__tenant', 'delivery_method', 'start_at' + ] + search_fields = [ + 'title', 'program__name', 'instructor__first_name', 'instructor__last_name' + ] + readonly_fields = [ + 'session_id', 'enrollment_count', 'created_at' + ] + fieldsets = [ + ('Session Information', { + 'fields': [ + 'session_id', 'program', 'title' + ] + }), + ('Instructor Information', { + 'fields': [ + 'instructor' + ] + }), + ('Schedule Information', { + 'fields': [ + 'start_at', 'end_at' + ] + }), + ('Delivery Information', { + 'fields': [ + 'delivery_method', 'location' + ] + }), + ('Capacity Information', { + 'fields': [ + 'capacity', 'enrollment_count' + ] + }), + ('Cost Override', { + 'fields': [ + 'cost_override', 'hours_override' + ] + }), + ('Metadata', { + 'fields': [ + 'created_at', 'created_by' + ], + 'classes': ['collapse'] + }) + ] + date_hierarchy = 'start_at' + + def title_display(self, obj): + """Display session title or program name.""" + return obj.title or obj.program.name + title_display.short_description = 'Title' + + def enrollment_count(self, obj): + """Display enrollment count.""" + return obj.enrollments.count() + enrollment_count.short_description = 'Enrollments' + + def get_queryset(self, request): + """Filter by user's tenant.""" + qs = super().get_queryset(request) + if hasattr(request.user, 'tenant'): + qs = qs.filter(program__tenant=request.user.tenant) + return qs.select_related('program', 'instructor', 'created_by') + + +@admin.register(TrainingRecord) +class TrainingRecordAdmin(admin.ModelAdmin): + """ + Admin interface for training records (enrollments). + """ + list_display = [ + 'employee_name', 'program', 'session', 'enrolled_at', + 'completion_date', 'status', 'passed', 'expiry_status_display' + ] + list_filter = [ + 'employee__tenant', 'status', 'passed', 'program__program_type', + 'enrolled_at', 'completion_date' + ] + search_fields = [ + 'employee__first_name', 'employee__last_name', + 'employee__employee_id', 'program__name' + ] + readonly_fields = [ + 'record_id', 'enrolled_at', 'hours', 'effective_cost', + 'eligible_for_certificate', 'completion_percentage', + 'created_at', 'updated_at' + ] + fieldsets = [ + ('Enrollment Information', { + 'fields': [ + 'record_id', 'employee', 'program', 'session' + ] + }), + ('Enrollment Dates', { + 'fields': [ + 'enrolled_at', 'started_at', 'completion_date', 'expiry_date' + ] + }), + ('Status Information', { + 'fields': [ + 'status', 'passed' + ] + }), + ('Performance Information', { + 'fields': [ + 'credits_earned', 'score' + ] + }), + ('Cost Information', { + 'fields': [ + 'cost_paid', 'effective_cost' + ] + }), + ('Calculated Information', { + 'fields': [ + 'hours', 'eligible_for_certificate', 'completion_percentage' + ], + 'classes': ['collapse'] + }), + ('Notes', { + 'fields': [ + 'notes' + ], + 'classes': ['collapse'] + }), + ('Metadata', { + 'fields': [ + 'created_at', 'updated_at', 'created_by' + ], + 'classes': ['collapse'] + }) + ] + inlines = [TrainingAttendanceInline, TrainingAssessmentInline] + date_hierarchy = 'enrolled_at' + + def employee_name(self, obj): + """Display employee name.""" + return obj.employee.get_full_name() + employee_name.short_description = 'Employee' + + def expiry_status_display(self, obj): + """Display expiry status with color coding.""" + if not obj.expiry_date: + return format_html('No Expiry') + + days_to_expiry = (obj.expiry_date - date.today()).days + if days_to_expiry < 0: + return format_html('⚠️ Expired') + elif days_to_expiry <= 30: + return format_html('⚠️ Expires Soon') + else: + return format_html('✓ Valid') + expiry_status_display.short_description = 'Expiry Status' + + def get_queryset(self, request): + """Filter by user's tenant.""" + qs = super().get_queryset(request) + if hasattr(request.user, 'tenant'): + qs = qs.filter(employee__tenant=request.user.tenant) + return qs.select_related('employee', 'program', 'session', 'created_by') + + +@admin.register(TrainingAttendance) +class TrainingAttendanceAdmin(admin.ModelAdmin): + """ + Admin interface for training attendance. + """ + list_display = [ + 'employee_name', 'program_name', 'checked_in_at', + 'checked_out_at', 'status', 'duration_display' + ] + list_filter = [ + 'enrollment__employee__tenant', 'status', 'checked_in_at' + ] + search_fields = [ + 'enrollment__employee__first_name', 'enrollment__employee__last_name', + 'enrollment__program__name' + ] + readonly_fields = [ + 'duration_display' + ] + fieldsets = [ + ('Attendance Information', { + 'fields': [ + 'enrollment' + ] + }), + ('Check-in/out Times', { + 'fields': [ + 'checked_in_at', 'checked_out_at' + ] + }), + ('Status Information', { + 'fields': [ + 'status' + ] + }), + ('Duration Information', { + 'fields': [ + 'duration_display' + ], + 'classes': ['collapse'] + }), + ('Notes', { + 'fields': [ + 'notes' + ], + 'classes': ['collapse'] + }) + ] + date_hierarchy = 'checked_in_at' + + def employee_name(self, obj): + """Display employee name.""" + return obj.enrollment.employee.get_full_name() + employee_name.short_description = 'Employee' + + def program_name(self, obj): + """Display program name.""" + return obj.enrollment.program.name + program_name.short_description = 'Program' + + def duration_display(self, obj): + """Display attendance duration.""" + if obj.checked_in_at and obj.checked_out_at: + duration = obj.checked_out_at - obj.checked_in_at + hours = duration.total_seconds() / 3600 + return f"{hours:.2f} hours" + return "Incomplete" + duration_display.short_description = 'Duration' + + def get_queryset(self, request): + """Filter by user's tenant.""" + qs = super().get_queryset(request) + if hasattr(request.user, 'tenant'): + qs = qs.filter(enrollment__employee__tenant=request.user.tenant) + return qs.select_related('enrollment__employee', 'enrollment__program') + + +@admin.register(TrainingAssessment) +class TrainingAssessmentAdmin(admin.ModelAdmin): + """ + Admin interface for training assessments. + """ + list_display = [ + 'employee_name', 'program_name', 'name', + 'score', 'max_score', 'percentage_display', 'passed', 'taken_at' + ] + list_filter = [ + 'enrollment__employee__tenant', 'passed', 'taken_at' + ] + search_fields = [ + 'enrollment__employee__first_name', 'enrollment__employee__last_name', + 'enrollment__program__name', 'name' + ] + readonly_fields = [ + 'percentage_display' + ] + fieldsets = [ + ('Assessment Information', { + 'fields': [ + 'enrollment', 'name' + ] + }), + ('Score Information', { + 'fields': [ + 'max_score', 'score', 'percentage_display', 'passed' + ] + }), + ('Date Information', { + 'fields': [ + 'taken_at' + ] + }), + ('Notes', { + 'fields': [ + 'notes' + ], + 'classes': ['collapse'] + }) + ] + date_hierarchy = 'taken_at' + + def employee_name(self, obj): + """Display employee name.""" + return obj.enrollment.employee.get_full_name() + employee_name.short_description = 'Employee' + + def program_name(self, obj): + """Display program name.""" + return obj.enrollment.program.name + program_name.short_description = 'Program' + + def percentage_display(self, obj): + """Display score percentage.""" + if obj.score is not None and obj.max_score > 0: + percentage = (obj.score / obj.max_score) * 100 + color = 'green' if percentage >= 70 else 'orange' if percentage >= 50 else 'red' + return format_html( + '{:.1f}%', + color, percentage + ) + return "N/A" + percentage_display.short_description = 'Percentage' + + def get_queryset(self, request): + """Filter by user's tenant.""" + qs = super().get_queryset(request) + if hasattr(request.user, 'tenant'): + qs = qs.filter(enrollment__employee__tenant=request.user.tenant) + return qs.select_related('enrollment__employee', 'enrollment__program') + + +@admin.register(TrainingCertificates) +class TrainingCertificatesAdmin(admin.ModelAdmin): + """ + Admin interface for training certificates. + """ + list_display = [ + 'employee_name', 'certificate_name', 'program', + 'issued_date', 'expiry_date', 'expiry_status_display', + 'certificate_number' + ] + list_filter = [ + 'employee__tenant', 'program', 'issued_date', 'expiry_date' + ] + search_fields = [ + 'employee__first_name', 'employee__last_name', + 'certificate_name', 'certificate_number', 'program__name' + ] + readonly_fields = [ + 'certificate_id', 'is_expired', 'days_to_expiry', + 'created_at', 'updated_at' + ] + fieldsets = [ + ('Certificate Information', { + 'fields': [ + 'certificate_id', 'employee', 'program', 'enrollment' + ] + }), + ('Certificate Details', { + 'fields': [ + 'certificate_name', 'certificate_number', 'certification_body' + ] + }), + ('Date Information', { + 'fields': [ + 'issued_date', 'expiry_date' + ] + }), + ('File Information', { + 'fields': [ + 'file' + ] + }), + ('Signature Information', { + 'fields': [ + 'created_by', 'signed_by' + ] + }), + ('Status Information', { + 'fields': [ + 'is_expired', 'days_to_expiry' + ], + 'classes': ['collapse'] + }), + ('Metadata', { + 'fields': [ + 'created_at', 'updated_at' + ], + 'classes': ['collapse'] + }) + ] + date_hierarchy = 'issued_date' + + def employee_name(self, obj): + """Display employee name.""" + return obj.employee.get_full_name() + employee_name.short_description = 'Employee' + + def expiry_status_display(self, obj): + """Display expiry status with color coding.""" + if not obj.expiry_date: + return format_html('No Expiry') + + if obj.is_expired: + return format_html('⚠️ Expired') + + if obj.days_to_expiry is not None and obj.days_to_expiry <= 30: + return format_html('⚠️ Expires Soon') + + return format_html('✓ Valid') + expiry_status_display.short_description = 'Expiry Status' + + def get_queryset(self, request): + """Filter by user's tenant.""" + qs = super().get_queryset(request) + if hasattr(request.user, 'tenant'): + qs = qs.filter(employee__tenant=request.user.tenant) + return qs.select_related('employee', 'program', 'enrollment', 'created_by', 'signed_by') + + +# ============================================================================ +# CUSTOM ADMIN ACTIONS +# ============================================================================ + +def mark_training_completed(modeladmin, request, queryset): + """Mark selected training records as completed.""" + updated = queryset.update(status='COMPLETED', completion_date=date.today()) + modeladmin.message_user(request, f'{updated} training records marked as completed.') +mark_training_completed.short_description = "Mark selected training as completed" + +def mark_training_passed(modeladmin, request, queryset): + """Mark selected training records as passed.""" + updated = queryset.update(passed=True) + modeladmin.message_user(request, f'{updated} training records marked as passed.') +mark_training_passed.short_description = "Mark selected training as passed" + +def generate_certificates(modeladmin, request, queryset): + """Generate certificates for completed training.""" + count = 0 + for record in queryset.filter(status='COMPLETED', passed=True): + if record.program.is_certified and not hasattr(record, 'certificate'): + TrainingCertificates.objects.create( + program=record.program, + employee=record.employee, + enrollment=record, + certificate_name=f"{record.program.name} Certificate", + expiry_date=TrainingCertificates.compute_expiry(record.program, date.today()), + created_by=request.user + ) + count += 1 + modeladmin.message_user(request, f'{count} certificates generated.') +generate_certificates.short_description = "Generate certificates for completed training" + +# Add actions to TrainingRecord admin +TrainingRecordAdmin.actions = [mark_training_completed, mark_training_passed, generate_certificates] + + +# ============================================================================ +# ADMIN SITE CUSTOMIZATION +# ============================================================================ # Customize admin site admin.site.site_header = "Hospital Management System - HR" admin.site.site_title = "HR Admin" admin.site.index_title = "Human Resources Administration" - diff --git a/hr/forms.py b/hr/forms.py index 5be22434..58475279 100644 --- a/hr/forms.py +++ b/hr/forms.py @@ -36,128 +36,128 @@ class EmployeeForm(forms.ModelForm): ] widgets = { 'first_name': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'First name' }), 'last_name': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Last name' }), 'father_name': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Father name' }), 'grandfather_name': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Grandfather name' }), 'email': forms.EmailInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'employee@hospital.com' }), 'phone': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': '+1-555-123-4567' }), 'mobile_phone': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': '+1-555-123-4567' }), 'address_line_1': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Street address' }), 'address_line_2': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Apt, Suite, etc.' }), 'city': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'City' }), 'state': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'State/Province' }), 'postal_code': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Postal/ZIP code' }), 'country': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Country' }), - 'department': forms.Select(attrs={'class': 'form-control'}), + 'department': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'job_title': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Job title/position' }), - 'employment_status': forms.Select(attrs={'class': 'form-control'}), - 'employment_type': forms.Select(attrs={'class': 'form-control'}), + 'employment_status': forms.Select(attrs={'class': 'form-select form-select-sm'}), + 'employment_type': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'hire_date': forms.DateInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'date' }), 'termination_date': forms.DateInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'date' }), 'annual_salary': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'step': '0.01', 'min': '0' }), 'hourly_rate': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'step': '0.01', 'min': '0' }), - 'supervisor': forms.Select(attrs={'class': 'form-control'}), + 'supervisor': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'emergency_contact_name': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Emergency contact name' }), 'emergency_contact_relationship': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Relationship to employee' }), 'emergency_contact_phone': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Emergency contact phone' }), 'date_of_birth': forms.DateInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'date' }), - 'gender': forms.Select(attrs={'class': 'form-control'}), - 'marital_status': forms.Select(attrs={'class': 'form-control'}), + 'gender': forms.Select(attrs={'class': 'form-select form-select-sm'}), + 'marital_status': forms.Select(attrs={'class': 'form-control form-control-sm'}), 'standard_hours_per_week': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'step': '0.5', 'min': '0', 'max': '168' }), 'fte_percentage': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'step': '1', 'min': '0', 'max': '100' }), 'license_number': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Professional license number' }), 'license_expiry_date': forms.DateInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'date' }), 'certifications': forms.Textarea(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'rows': 3, 'placeholder': 'List of certifications' }), 'notes': forms.Textarea(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'rows': 3, 'placeholder': 'Additional notes' }), @@ -263,10 +263,6 @@ class EmployeeForm(forms.ModelForm): return cleaned_data -# =========================== -# Training Program Forms -# =========================== - class TrainingProgramForm(forms.ModelForm): """ Form for creating and updating training programs. @@ -281,45 +277,45 @@ class TrainingProgramForm(forms.ModelForm): ] widgets = { 'name': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Training program name' }), 'description': forms.Textarea(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'rows': 3, 'placeholder': 'Program description' }), - 'program_type': forms.Select(attrs={'class': 'form-control'}), + 'program_type': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'program_provider': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Training provider/organization' }), - 'instructor': forms.Select(attrs={'class': 'form-control'}), + 'instructor': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'start_date': forms.DateInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'date' }), 'end_date': forms.DateInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'date' }), 'duration_hours': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'step': '0.5', 'min': '0' }), 'cost': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'step': '0.01', 'min': '0' }), 'is_certified': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'validity_days': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'min': '1' }), 'notify_before_days': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'min': '1' }), } @@ -373,36 +369,36 @@ class TrainingSessionForm(forms.ModelForm): 'cost_override', 'hours_override' ] widgets = { - 'program': forms.Select(attrs={'class': 'form-control'}), + 'program': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'title': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Session title (optional)' }), - 'instructor': forms.Select(attrs={'class': 'form-control'}), - 'delivery_method': forms.Select(attrs={'class': 'form-control'}), + 'instructor': forms.Select(attrs={'class': 'form-select form-select-sm'}), + 'delivery_method': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'start_at': forms.DateTimeInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'datetime-local' }), 'end_at': forms.DateTimeInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'datetime-local' }), 'location': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Training location' }), 'capacity': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'min': '1' }), 'cost_override': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'step': '0.01', 'min': '0' }), 'hours_override': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'step': '0.5', 'min': '0' }), @@ -451,17 +447,17 @@ class ProgramModuleForm(forms.ModelForm): model = ProgramModule fields = ['program', 'title', 'order', 'hours'] widgets = { - 'program': forms.Select(attrs={'class': 'form-control'}), + 'program': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'title': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Module title' }), 'order': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'min': '1' }), 'hours': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'step': '0.5', 'min': '0' }), @@ -491,18 +487,18 @@ class TrainingAttendanceForm(forms.ModelForm): model = TrainingAttendance fields = ['enrollment', 'checked_in_at', 'checked_out_at', 'status', 'notes'] widgets = { - 'enrollment': forms.Select(attrs={'class': 'form-control'}), + 'enrollment': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'checked_in_at': forms.DateTimeInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'datetime-local' }), 'checked_out_at': forms.DateTimeInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'datetime-local' }), - 'status': forms.Select(attrs={'class': 'form-control'}), + 'status': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'notes': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Attendance notes' }), } @@ -536,28 +532,28 @@ class TrainingAssessmentForm(forms.ModelForm): model = TrainingAssessment fields = ['enrollment', 'name', 'max_score', 'score', 'passed', 'taken_at', 'notes'] widgets = { - 'enrollment': forms.Select(attrs={'class': 'form-control'}), + 'enrollment': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'name': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Assessment name' }), 'max_score': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'step': '0.01', 'min': '0' }), 'score': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'step': '0.01', 'min': '0' }), 'passed': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'taken_at': forms.DateTimeInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'datetime-local' }), 'notes': forms.Textarea(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'rows': 3, 'placeholder': 'Assessment notes' }), @@ -592,26 +588,26 @@ class TrainingCertificateForm(forms.ModelForm): 'signed_by' ] widgets = { - 'program': forms.Select(attrs={'class': 'form-control'}), - 'employee': forms.Select(attrs={'class': 'form-control'}), - 'enrollment': forms.Select(attrs={'class': 'form-control'}), + 'program': forms.Select(attrs={'class': 'form-select form-select-sm'}), + 'employee': forms.Select(attrs={'class': 'form-select form-select-sm'}), + 'enrollment': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'certificate_name': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Certificate name' }), 'certificate_number': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Certificate number' }), 'certification_body': forms.TextInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'placeholder': 'Issuing organization' }), 'expiry_date': forms.DateInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'date' }), - 'signed_by': forms.Select(attrs={'class': 'form-control'}), + 'signed_by': forms.Select(attrs={'class': 'form-select form-select-sm'}), } help_texts = { 'certificate_number': 'Unique certificate identifier', @@ -662,10 +658,6 @@ class TrainingCertificateForm(forms.ModelForm): return cleaned_data -# =========================== -# Search and Filter Forms -# =========================== - class TrainingSearchForm(forms.Form): """ Form for searching and filtering training records. @@ -744,7 +736,7 @@ class DepartmentForm(forms.ModelForm): 'class': 'form-control', 'placeholder': 'Department name' }), - 'department_code': forms.TextInput(attrs={ + 'code': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Unique department code' }), @@ -777,7 +769,7 @@ class DepartmentForm(forms.ModelForm): }), } help_texts = { - 'department_code': 'Unique code to identify this department', + 'code': 'Unique code to identify this department', 'department_head': 'Department manager (optional)', 'annual_budget': 'Annual budget for this department', 'parent_department': 'Parent department (if any)', @@ -804,18 +796,18 @@ class DepartmentForm(forms.ModelForm): pk=self.instance.pk ) - def clean_department_code(self): - department_code = self.cleaned_data.get('department_code') - if department_code: + def clean_code(self): + code = self.cleaned_data.get('code') + if code: # Check for uniqueness within tenant (excluding current instance) - queryset = Department.objects.filter(department_code=department_code) + queryset = Department.objects.filter(code=code) if self.instance.pk: queryset = queryset.exclude(pk=self.instance.pk) if queryset.exists(): raise ValidationError('Department code must be unique.') - return department_code + return code class ScheduleForm(forms.ModelForm): @@ -823,6 +815,34 @@ class ScheduleForm(forms.ModelForm): Form for creating and updating schedules. """ + # Additional fields to match template expectations + start_date = forms.DateField( + required=True, + widget=forms.DateInput(attrs={ + 'class': 'form-control', + 'type': 'date' + }), + help_text='Schedule start date' + ) + + department = forms.ModelChoiceField( + queryset=Department.objects.none(), + required=False, + widget=forms.Select(attrs={'class': 'form-control'}), + help_text='Department for this schedule' + ) + + status = forms.ChoiceField( + choices=[ + ('DRAFT', 'Draft'), + ('PUBLISHED', 'Published'), + ('ARCHIVED', 'Archived'), + ], + initial='DRAFT', + widget=forms.Select(attrs={'class': 'form-control'}), + help_text='Schedule status' + ) + class Meta: model = Schedule fields = [ @@ -842,10 +862,7 @@ class ScheduleForm(forms.ModelForm): 'placeholder': 'Schedule description' }), 'schedule_type': forms.Select(attrs={'class': 'form-control'}), - 'effective_date': forms.DateInput(attrs={ - 'class': 'form-control', - 'type': 'date' - }), + 'effective_date': forms.HiddenInput(), 'end_date': forms.DateInput(attrs={ 'class': 'form-control', 'type': 'date' @@ -855,7 +872,7 @@ class ScheduleForm(forms.ModelForm): 'rows': 5, 'placeholder': '{"monday": {"start": "09:00", "end": "17:00"}, ...}' }), - 'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}), + 'is_active': forms.HiddenInput(), 'notes': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 3, @@ -874,21 +891,64 @@ class ScheduleForm(forms.ModelForm): super().__init__(*args, **kwargs) if user and hasattr(user, 'tenant'): - # Filter employee by tenant + # Filter employee and department by tenant self.fields['employee'].queryset = Employee.objects.filter( tenant=user.tenant, employment_status='ACTIVE' ).order_by('last_name', 'first_name') + + self.fields['department'].queryset = Department.objects.filter( + tenant=user.tenant, + is_active=True + ).order_by('name') + + # Initialize template-compatible fields from model fields + if self.instance and self.instance.pk: + self.fields['start_date'].initial = self.instance.effective_date + + # Set status based on is_active field + if self.instance.is_active: + self.fields['status'].initial = 'PUBLISHED' + else: + self.fields['status'].initial = 'DRAFT' - def clean_end_date(self): - end_date = self.cleaned_data.get('end_date') - effective_date = self.cleaned_data.get('effective_date') + def clean(self): + cleaned_data = super().clean() + + # Map template fields to model fields + start_date = cleaned_data.get('start_date') + status = cleaned_data.get('status') + end_date = cleaned_data.get('end_date') + + # Set model field values from template fields + if start_date: + cleaned_data['effective_date'] = start_date + + if status: + cleaned_data['is_active'] = (status == 'PUBLISHED') + + # Validation + effective_date = cleaned_data.get('effective_date') if end_date and effective_date: if end_date < effective_date: - raise ValidationError('End date cannot be before effective date.') + self.add_error('end_date', 'End date cannot be before start date.') - return end_date + return cleaned_data + + def save(self, commit=True): + instance = super().save(commit=False) + + # Ensure model fields are set from template fields + if hasattr(self, 'cleaned_data'): + if self.cleaned_data.get('start_date'): + instance.effective_date = self.cleaned_data['start_date'] + if self.cleaned_data.get('status'): + instance.is_active = (self.cleaned_data['status'] == 'PUBLISHED') + + if commit: + instance.save() + return instance class ScheduleAssignmentForm(forms.ModelForm): @@ -981,237 +1041,372 @@ class ScheduleAssignmentForm(forms.ModelForm): class TimeEntryForm(forms.ModelForm): """ - Form for creating and updating time entries. + Form for creating and updating time entries with template-compatible field names. """ - + + # Template-compatible field names + date = forms.DateField( + required=True, + widget=forms.DateInput(attrs={ + 'class': 'form-control', + 'type': 'date' + }), + help_text='Work date' + ) + + start_time = forms.TimeField( + required=True, + widget=forms.TimeInput(attrs={ + 'class': 'form-control', + 'type': 'time' + }), + help_text='Clock in time' + ) + + end_time = forms.TimeField( + required=True, + widget=forms.TimeInput(attrs={ + 'class': 'form-control', + 'type': 'time' + }), + help_text='Clock out time' + ) + + hours = forms.DecimalField( + max_digits=5, + decimal_places=2, + required=True, + widget=forms.NumberInput(attrs={ + 'class': 'form-control', + 'step': '0.01', + 'readonly': True + }), + help_text='Total hours (calculated automatically)' + ) + + break_duration = forms.IntegerField( + required=False, + min_value=0, + widget=forms.NumberInput(attrs={ + 'class': 'form-control', + 'min': '0' + }), + help_text='Break duration in minutes' + ) + + description = forms.CharField( + required=False, + widget=forms.Textarea(attrs={ + 'class': 'form-control', + 'rows': 3, + 'placeholder': 'Provide details about the work performed during this time period' + }), + help_text='Work description' + ) + + schedule_assignment = forms.ModelChoiceField( + queryset=ScheduleAssignment.objects.none(), + required=False, + widget=forms.Select(attrs={ + 'class': 'form-control' + }), + help_text='Link this time entry to a schedule assignment' + ) + + is_overtime = forms.BooleanField( + required=False, + widget=forms.HiddenInput(), + help_text='Is this overtime work' + ) + + auto_approve = forms.BooleanField( + required=False, + widget=forms.CheckboxInput(attrs={ + 'class': 'form-check-input' + }), + help_text='Auto-approve this time entry' + ) + class Meta: model = TimeEntry fields = [ - 'employee', 'work_date', 'clock_in_time', 'clock_out_time', - 'break_start_time', 'break_end_time', 'lunch_start_time', - 'lunch_end_time', 'entry_type', 'department', 'location', - 'status', 'notes' + 'employee', 'department', 'location', 'entry_type', 'status' ] widgets = { - 'employee': forms.Select(attrs={'class': 'form-control'}), - 'work_date': forms.DateInput(attrs={ - 'class': 'form-control', - 'type': 'date' + 'employee': forms.Select(attrs={ + 'class': 'form-control' }), - 'clock_in_time': forms.DateTimeInput(attrs={ - 'class': 'form-control', - 'type': 'datetime-local' + 'department': forms.Select(attrs={ + 'class': 'form-control' }), - 'clock_out_time': forms.DateTimeInput(attrs={ - 'class': 'form-control', - 'type': 'datetime-local' - }), - 'break_start_time': forms.DateTimeInput(attrs={ - 'class': 'form-control', - 'type': 'datetime-local' - }), - 'break_end_time': forms.DateTimeInput(attrs={ - 'class': 'form-control', - 'type': 'datetime-local' - }), - 'lunch_start_time': forms.DateTimeInput(attrs={ - 'class': 'form-control', - 'type': 'datetime-local' - }), - 'lunch_end_time': forms.DateTimeInput(attrs={ - 'class': 'form-control', - 'type': 'datetime-local' - }), - 'entry_type': forms.Select(attrs={'class': 'form-control'}), - 'department': forms.Select(attrs={'class': 'form-control'}), 'location': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Work location' }), - 'status': forms.Select(attrs={'class': 'form-control'}), - 'notes': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 2, - 'placeholder': 'Time entry notes' - }), + 'entry_type': forms.HiddenInput(), + 'status': forms.HiddenInput(), } - help_texts = { - 'work_date': 'Date of work', - 'clock_in_time': 'Clock in date and time', - 'clock_out_time': 'Clock out date and time', - 'entry_type': 'Type of time entry', - 'status': 'Current status of the time entry', - } - + def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) - + if user and hasattr(user, 'tenant'): # Filter employee and department by tenant self.fields['employee'].queryset = Employee.objects.filter( tenant=user.tenant, employment_status='ACTIVE' ).order_by('last_name', 'first_name') - + self.fields['department'].queryset = Department.objects.filter( tenant=user.tenant, is_active=True ).order_by('name') - + + # Filter schedule assignments for today + today = timezone.now().date() + self.fields['schedule_assignment'].queryset = ScheduleAssignment.objects.filter( + schedule__employee__tenant=user.tenant, + assignment_date=today + ).select_related('schedule__employee') + + # Set initial values for template compatibility + if self.instance and self.instance.pk: + self.fields['date'].initial = self.instance.work_date + self.fields['start_time'].initial = self.instance.clock_in_time.time() if self.instance.clock_in_time else None + self.fields['end_time'].initial = self.instance.clock_out_time.time() if self.instance.clock_out_time else None + self.fields['hours'].initial = self.instance.total_hours + self.fields['description'].initial = self.instance.notes + + # Calculate break duration + if self.instance.break_start_time and self.instance.break_end_time: + break_duration = (self.instance.break_end_time - self.instance.break_start_time).total_seconds() / 60 + self.fields['break_duration'].initial = int(break_duration) + + # Set overtime based on entry type + self.fields['is_overtime'].initial = (self.instance.entry_type == 'OVERTIME') + def clean(self): cleaned_data = super().clean() + + # Map template fields to model fields + date = cleaned_data.get('date') + start_time = cleaned_data.get('start_time') + end_time = cleaned_data.get('end_time') + hours = cleaned_data.get('hours') + break_duration = cleaned_data.get('break_duration', 0) + description = cleaned_data.get('description') + schedule_assignment = cleaned_data.get('schedule_assignment') + is_overtime = cleaned_data.get('is_overtime') + + # Set model field values + if date: + cleaned_data['work_date'] = date + + # Combine date and time for datetime fields + if date and start_time: + cleaned_data['clock_in_time'] = timezone.datetime.combine(date, start_time) + + if date and end_time: + cleaned_data['clock_out_time'] = timezone.datetime.combine(date, end_time) + + if hours: + cleaned_data['total_hours'] = hours + + if description: + cleaned_data['notes'] = description + + if is_overtime: + cleaned_data['entry_type'] = 'OVERTIME' + else: + cleaned_data['entry_type'] = 'REGULAR' + + # Set status based on auto_approve + auto_approve = cleaned_data.get('auto_approve', False) + if auto_approve: + cleaned_data['status'] = 'APPROVED' + else: + cleaned_data['status'] = 'SUBMITTED' + + # Validation clock_in_time = cleaned_data.get('clock_in_time') clock_out_time = cleaned_data.get('clock_out_time') - break_start_time = cleaned_data.get('break_start_time') - break_end_time = cleaned_data.get('break_end_time') - lunch_start_time = cleaned_data.get('lunch_start_time') - lunch_end_time = cleaned_data.get('lunch_end_time') - + if clock_in_time and clock_out_time: if clock_out_time <= clock_in_time: - self.add_error('clock_out_time', 'Clock out time must be after clock in time.') - + self.add_error('end_time', 'End time must be after start time.') + # Check for reasonable shift length (max 24 hours) duration = clock_out_time - clock_in_time if duration.total_seconds() > 24 * 3600: - self.add_error('clock_out_time', 'Shift duration cannot exceed 24 hours.') - - # Validate break times - if break_start_time and not break_end_time: - self.add_error('break_end_time', 'Break end time is required if break start time is provided.') - - if break_end_time and not break_start_time: - self.add_error('break_start_time', 'Break start time is required if break end time is provided.') - - if break_start_time and break_end_time: - if break_end_time <= break_start_time: - self.add_error('break_end_time', 'Break end time must be after break start time.') - - if clock_in_time and break_start_time < clock_in_time: - self.add_error('break_start_time', 'Break start time must be after clock in time.') - - if clock_out_time and break_end_time > clock_out_time: - self.add_error('break_end_time', 'Break end time must be before clock out time.') - - # Validate lunch times - if lunch_start_time and not lunch_end_time: - self.add_error('lunch_end_time', 'Lunch end time is required if lunch start time is provided.') - - if lunch_end_time and not lunch_start_time: - self.add_error('lunch_start_time', 'Lunch start time is required if lunch end time is provided.') - - if lunch_start_time and lunch_end_time: - if lunch_end_time <= lunch_start_time: - self.add_error('lunch_end_time', 'Lunch end time must be after lunch start time.') - - if clock_in_time and lunch_start_time < clock_in_time: - self.add_error('lunch_start_time', 'Lunch start time must be after clock in time.') - - if clock_out_time and lunch_end_time > clock_out_time: - self.add_error('lunch_end_time', 'Lunch end time must be before clock out time.') - + self.add_error('end_time', 'Shift duration cannot exceed 24 hours.') + return cleaned_data + def save(self, commit=True): + instance = super().save(commit=False) + + # Ensure model fields are set from template fields + if hasattr(self, 'cleaned_data'): + if self.cleaned_data.get('work_date'): + instance.work_date = self.cleaned_data['work_date'] + if self.cleaned_data.get('clock_in_time'): + instance.clock_in_time = self.cleaned_data['clock_in_time'] + if self.cleaned_data.get('clock_out_time'): + instance.clock_out_time = self.cleaned_data['clock_out_time'] + if self.cleaned_data.get('total_hours'): + instance.total_hours = self.cleaned_data['total_hours'] + if self.cleaned_data.get('notes'): + instance.notes = self.cleaned_data['notes'] + if self.cleaned_data.get('entry_type'): + instance.entry_type = self.cleaned_data['entry_type'] + if self.cleaned_data.get('status'): + instance.status = self.cleaned_data['status'] + + if commit: + instance.save() + return instance + class PerformanceReviewForm(forms.ModelForm): """ - Form for creating and updating performance reviews. + Form for creating and updating performance reviews with enhanced template compatibility. """ + # Additional fields to match template expectations + period_start = forms.DateField( + required=False, + widget=forms.HiddenInput(), + help_text='Review period start date' + ) + period_end = forms.DateField( + required=False, + widget=forms.HiddenInput(), + help_text='Review period end date' + ) + due_date = forms.DateField( + required=True, + widget=forms.DateInput(attrs={ + 'class': 'form-control form-control-sm', + 'type': 'date' + }), + help_text='Review due date' + ) + overall_score = forms.DecimalField( + required=False, + max_digits=3, + decimal_places=1, + widget=forms.NumberInput(attrs={ + 'class': 'form-control form-control-sm', + 'step': '0.1', + 'min': '1', + 'max': '5' + }), + help_text='Overall score (1-5)' + ) + completion_percentage = forms.IntegerField( + required=False, + widget=forms.NumberInput(attrs={ + 'class': 'form-control form-control-sm', + 'min': '0', + 'max': '100' + }), + help_text='Completion percentage for in-progress reviews' + ) + reviewer_comments = forms.CharField( + required=False, + widget=forms.Textarea(attrs={ + 'class': 'form-control form-control-sm', + 'rows': 4, + 'placeholder': 'Reviewer comments and feedback' + }), + help_text='Comments from the reviewer' + ) + attachments = forms.FileField( + required=False, + widget=forms.FileInput(attrs={ + 'class': 'form-control form-control-sm' + }), + help_text='Upload supporting documents' + ) + class Meta: model = PerformanceReview fields = [ - 'employee', 'review_period_start', 'review_period_end', - 'review_date', 'review_type', 'overall_rating', - 'competency_ratings', 'goals_achieved', 'goals_not_achieved', - 'future_goals', 'strengths', 'areas_for_improvement', - 'development_plan', 'training_recommendations', - 'employee_comments', 'employee_signature_date', - 'status', 'notes' + 'employee', 'reviewer', 'review_type', 'status', + 'review_period_start', 'review_period_end', 'review_date', + 'overall_rating', 'competency_ratings', 'goals_achieved', + 'goals_not_achieved', 'future_goals', 'strengths', + 'areas_for_improvement', 'development_plan', + 'training_recommendations', 'employee_comments', + 'employee_signature_date', 'notes' ] widgets = { - 'employee': forms.Select(attrs={'class': 'form-control'}), - 'review_period_start': forms.DateInput(attrs={ - 'class': 'form-control', - 'type': 'date' + 'employee': forms.Select(attrs={ + 'class': 'form-select form-select-sm', + 'data-placeholder': 'Select employee...' }), - 'review_period_end': forms.DateInput(attrs={ - 'class': 'form-control', - 'type': 'date' + 'reviewer': forms.Select(attrs={ + 'class': 'form-select form-select-sm', + 'data-placeholder': 'Select reviewer...' }), - 'review_date': forms.DateInput(attrs={ - 'class': 'form-control', - 'type': 'date' + 'review_type': forms.Select(attrs={ + 'class': 'form-select form-select-sm' }), - 'review_type': forms.Select(attrs={'class': 'form-control'}), - 'overall_rating': forms.NumberInput(attrs={ - 'class': 'form-control', - 'step': '0.1', - 'min': '1', - 'max': '5' - }), - 'competency_ratings': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 5, - 'placeholder': '{"communication": 4, "teamwork": 5, ...}' - }), - 'goals_achieved': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 3, - 'placeholder': 'Goals achieved during review period' - }), - 'goals_not_achieved': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 3, - 'placeholder': 'Goals not achieved during review period' - }), - 'future_goals': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 3, - 'placeholder': 'Goals for next review period' + 'status': forms.Select(attrs={ + 'class': 'form-select form-select-sm' }), + 'review_period_start': forms.HiddenInput(), + 'review_period_end': forms.HiddenInput(), + 'review_date': forms.HiddenInput(), + 'overall_rating': forms.HiddenInput(), + 'competency_ratings': forms.HiddenInput(), + 'goals_achieved': forms.HiddenInput(), + 'goals_not_achieved': forms.HiddenInput(), + 'future_goals': forms.HiddenInput(), 'strengths': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 3, - 'placeholder': 'Employee strengths' + 'class': 'form-control form-control-sm', + 'rows': 4, + 'placeholder': 'List employee strengths (one per line)' }), 'areas_for_improvement': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 3, - 'placeholder': 'Areas for improvement' + 'class': 'form-control form-control-sm', + 'rows': 4, + 'placeholder': 'List areas for improvement (one per line)' }), 'development_plan': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 3, - 'placeholder': 'Professional development plan' + 'class': 'form-control form-control-sm', + 'rows': 4, + 'placeholder': 'Outline professional development plan' }), 'training_recommendations': forms.Textarea(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'rows': 3, - 'placeholder': 'Training recommendations' + 'placeholder': 'Recommended training programs' }), 'employee_comments': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 3, - 'placeholder': 'Employee comments' + 'class': 'form-control form-control-sm', + 'rows': 4, + 'placeholder': 'Employee self-assessment and comments' }), 'employee_signature_date': forms.DateInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'date' }), - 'status': forms.Select(attrs={'class': 'form-control'}), 'notes': forms.Textarea(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'rows': 3, - 'placeholder': 'Additional notes' + 'placeholder': 'Additional notes and observations' }), } help_texts = { + 'employee': 'Employee being reviewed', + 'reviewer': 'Person conducting the review', 'review_type': 'Type of performance review', - 'overall_rating': 'Overall rating (1-5)', - 'competency_ratings': 'Individual competency ratings as JSON', 'status': 'Current status of the review', + 'overall_rating': 'Overall performance rating (1-5)', + 'competency_ratings': 'Individual competency ratings as JSON', + 'employee_signature_date': 'Date employee acknowledged review', } def __init__(self, *args, **kwargs): @@ -1219,14 +1414,49 @@ class PerformanceReviewForm(forms.ModelForm): super().__init__(*args, **kwargs) if user and hasattr(user, 'tenant'): - # Filter employee by tenant + # Filter employee and reviewer by tenant self.fields['employee'].queryset = Employee.objects.filter( tenant=user.tenant, employment_status='ACTIVE' ).order_by('last_name', 'first_name') + + self.fields['reviewer'].queryset = Employee.objects.filter( + tenant=user.tenant, + employment_status='ACTIVE' + ).order_by('last_name', 'first_name') + + # Initialize template-compatible fields from model fields + if self.instance and self.instance.pk: + self.fields['period_start'].initial = self.instance.review_period_start + self.fields['period_end'].initial = self.instance.review_period_end + self.fields['due_date'].initial = self.instance.review_date + self.fields['overall_score'].initial = self.instance.overall_rating + # Set reviewer_comments from notes field for backward compatibility + self.fields['reviewer_comments'].initial = self.instance.notes def clean(self): cleaned_data = super().clean() + + # Map template fields to model fields + period_start = cleaned_data.get('period_start') + period_end = cleaned_data.get('period_end') + due_date = cleaned_data.get('due_date') + overall_score = cleaned_data.get('overall_score') + reviewer_comments = cleaned_data.get('reviewer_comments') + + # Set model field values from template fields + if period_start: + cleaned_data['review_period_start'] = period_start + if period_end: + cleaned_data['review_period_end'] = period_end + if due_date: + cleaned_data['review_date'] = due_date + if overall_score: + cleaned_data['overall_rating'] = overall_score + if reviewer_comments: + cleaned_data['notes'] = reviewer_comments + + # Validation review_period_start = cleaned_data.get('review_period_start') review_period_end = cleaned_data.get('review_period_end') review_date = cleaned_data.get('review_date') @@ -1235,16 +1465,36 @@ class PerformanceReviewForm(forms.ModelForm): if review_period_start and review_period_end: if review_period_end < review_period_start: - self.add_error('review_period_end', 'Review period end date must be after start date.') + self.add_error('period_end', 'Review period end date must be after start date.') if review_date and review_period_end: if review_date < review_period_end: - self.add_error('review_date', 'Review date should be on or after the review period end date.') + self.add_error('due_date', 'Review date should be on or after the review period end date.') if status == 'ACKNOWLEDGED' and not employee_signature_date: self.add_error('employee_signature_date', 'Employee signature date is required for acknowledged reviews.') return cleaned_data + + def save(self, commit=True): + instance = super().save(commit=False) + + # Ensure model fields are set from template fields + if hasattr(self, 'cleaned_data'): + if self.cleaned_data.get('period_start'): + instance.review_period_start = self.cleaned_data['period_start'] + if self.cleaned_data.get('period_end'): + instance.review_period_end = self.cleaned_data['period_end'] + if self.cleaned_data.get('due_date'): + instance.review_date = self.cleaned_data['due_date'] + if self.cleaned_data.get('overall_score'): + instance.overall_rating = self.cleaned_data['overall_score'] + if self.cleaned_data.get('reviewer_comments'): + instance.notes = self.cleaned_data['reviewer_comments'] + + if commit: + instance.save() + return instance class TrainingRecordForm(forms.ModelForm): @@ -1259,37 +1509,37 @@ class TrainingRecordForm(forms.ModelForm): 'status', 'credits_earned', 'score', 'passed', 'notes', 'cost_paid' ] widgets = { - 'employee': forms.Select(attrs={'class': 'form-control'}), - 'program': forms.Select(attrs={'class': 'form-control'}), - 'session': forms.Select(attrs={'class': 'form-control'}), + 'employee': forms.Select(attrs={'class': 'form-select form-select-sm'}), + 'program': forms.Select(attrs={'class': 'form-select form-select-sm'}), + 'session': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'started_at': forms.DateTimeInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'datetime-local' }), 'completion_date': forms.DateInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'type': 'date' }), - 'status': forms.Select(attrs={'class': 'form-control'}), + 'status': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'credits_earned': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'step': '0.1', 'min': '0' }), 'score': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'step': '0.01', 'min': '0', 'max': '100' }), 'passed': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'cost_paid': forms.NumberInput(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'step': '0.01', 'min': '0' }), 'notes': forms.Textarea(attrs={ - 'class': 'form-control', + 'class': 'form-control form-control-sm', 'rows': 3, 'placeholder': 'Training notes and comments' }), diff --git a/hr/migrations/0001_initial.py b/hr/migrations/0001_initial.py deleted file mode 100644 index 6bd0996d..00000000 --- a/hr/migrations/0001_initial.py +++ /dev/null @@ -1,1921 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-26 18:33 - -import django.core.validators -import django.db.models.deletion -import uuid -from decimal import Decimal -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("core", "0001_initial"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="Department", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "department_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique department identifier", - unique=True, - ), - ), - ( - "code", - models.CharField( - help_text="Department code (e.g., CARD, EMER, SURG)", - max_length=20, - ), - ), - ("name", models.CharField(help_text="Department name", max_length=100)), - ( - "description", - models.TextField( - blank=True, help_text="Department description", null=True - ), - ), - ( - "department_type", - models.CharField( - choices=[ - ("CLINICAL", "Clinical"), - ("ADMINISTRATIVE", "Administrative"), - ("SUPPORT", "Support"), - ("ANCILLARY", "Ancillary"), - ("EXECUTIVE", "Executive"), - ], - help_text="Department type", - max_length=20, - ), - ), - ( - "phone", - models.CharField( - blank=True, - help_text="Department phone number", - max_length=20, - null=True, - ), - ), - ( - "extension", - models.CharField( - blank=True, - help_text="Phone extension", - max_length=10, - null=True, - ), - ), - ( - "email", - models.EmailField( - blank=True, - help_text="Department email", - max_length=254, - null=True, - ), - ), - ( - "annual_budget", - models.DecimalField( - blank=True, - decimal_places=2, - help_text="Annual budget", - max_digits=12, - null=True, - ), - ), - ( - "cost_center", - models.CharField( - blank=True, - help_text="Cost center code", - max_length=20, - null=True, - ), - ), - ( - "authorized_positions", - models.PositiveIntegerField( - default=0, help_text="Number of authorized positions" - ), - ), - ( - "location", - models.CharField( - blank=True, - help_text="Department location", - max_length=100, - null=True, - ), - ), - ( - "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( - blank=True, - default=dict, - help_text="Operating hours by day of week", - ), - ), - ( - "accreditation_required", - models.BooleanField( - default=False, - help_text="Department requires special accreditation", - ), - ), - ( - "accreditation_body", - models.CharField( - blank=True, - help_text="Accrediting body (e.g., Joint Commission, CAP)", - max_length=100, - null=True, - ), - ), - ( - "last_inspection_date", - models.DateField( - blank=True, help_text="Last inspection date", null=True - ), - ), - ( - "next_inspection_date", - models.DateField( - blank=True, - help_text="Next scheduled inspection date", - null=True, - ), - ), - ( - "notes", - models.TextField( - blank=True, help_text="Department notes", null=True - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "parent_department", - models.ForeignKey( - blank=True, - help_text="Parent department", - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="sub_departments", - to="hr.department", - ), - ), - ( - "tenant", - models.ForeignKey( - help_text="Organization tenant", - on_delete=django.db.models.deletion.CASCADE, - related_name="departments", - to="core.tenant", - ), - ), - ], - options={ - "verbose_name": "Department", - "verbose_name_plural": "Departments", - "db_table": "hr_department", - "ordering": ["name"], - }, - ), - migrations.CreateModel( - name="Employee", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "employee_id", - models.CharField(editable=False, max_length=50, unique=True), - ), - ("identification_number", models.CharField(blank=True, max_length=20)), - ( - "id_type", - models.CharField( - choices=[ - ("NATIONAL_ID", "National ID"), - ("IQAMA", "IQAMA"), - ("PASSPORT", "Passport"), - ("OTHER", "Other"), - ], - default="NATIONAL_ID", - max_length=20, - ), - ), - ("first_name", models.CharField(blank=True, max_length=50)), - ( - "father_name", - models.CharField(blank=True, max_length=100, null=True), - ), - ( - "grandfather_name", - models.CharField(blank=True, max_length=100, null=True), - ), - ("last_name", models.CharField(blank=True, max_length=50)), - ("email", models.EmailField(blank=True, max_length=254, null=True)), - ( - "phone", - models.CharField( - blank=True, - max_length=16, - null=True, - validators=[ - django.core.validators.RegexValidator( - message="Use E.164 format: +9665XXXXXXXX", - regex="^\\+?9665\\d{8}$", - ) - ], - ), - ), - ( - "mobile_phone", - models.CharField( - blank=True, - max_length=16, - null=True, - validators=[ - django.core.validators.RegexValidator( - message="Use E.164 format: +9665XXXXXXXX", - regex="^\\+?9665\\d{8}$", - ) - ], - ), - ), - ( - "address_line_1", - models.CharField(blank=True, max_length=100, null=True), - ), - ( - "address_line_2", - models.CharField(blank=True, max_length=100, null=True), - ), - ("city", models.CharField(blank=True, max_length=50, null=True)), - ("postal_code", models.CharField(blank=True, max_length=10, null=True)), - ("country", models.CharField(blank=True, max_length=50, null=True)), - ("date_of_birth", models.DateField(blank=True, null=True)), - ( - "gender", - models.CharField( - blank=True, - choices=[ - ("MALE", "Male"), - ("FEMALE", "Female"), - ("OTHER", "Other"), - ], - max_length=20, - null=True, - ), - ), - ( - "marital_status", - models.CharField( - blank=True, - choices=[ - ("SINGLE", "Single"), - ("MARRIED", "Married"), - ("DIVORCED", "Divorced"), - ("WIDOWED", "Widowed"), - ("SEPARATED", "Separated"), - ("OTHER", "Other"), - ], - max_length=20, - null=True, - ), - ), - ( - "user_timezone", - models.CharField(default="Asia/Riyadh", max_length=50), - ), - ("language", models.CharField(default="ar", max_length=10)), - ( - "theme", - models.CharField( - choices=[ - ("LIGHT", "Light"), - ("DARK", "Dark"), - ("AUTO", "Auto"), - ], - default="LIGHT", - max_length=20, - ), - ), - ( - "role", - models.CharField( - choices=[ - ("SUPER_ADMIN", "Super Administrator"), - ("ADMIN", "Administrator"), - ("PHYSICIAN", "Physician"), - ("SURGEON", "Surgeon"), - ("NURSE", "Nurse"), - ("NURSE_PRACTITIONER", "Nurse Practitioner"), - ("PHYSICIAN_ASSISTANT", "Physician Assistant"), - ("SURGICAL_TECHNICIAN", "Surgical Technician"), - ("ANESTHESIOLOGIST", "Anesthesiologist"), - ( - "ANESTHESIOLOGIST_ASSOCIATE", - "Anesthesiologist Associate", - ), - ("CLINICAL_NURSE_ASSOCIATE", "Clinical Nurse Associate"), - ("CLINICAL_NURSE_SPECIALIST", "Clinical Nurse Specialist"), - ("CLINICAL_NURSE_MANAGER", "Clinical Nurse Manager"), - ("CLINICAL_NURSE_TECHNICIAN", "Clinical Nurse Technician"), - ( - "CLINICAL_NURSE_COORDINATOR", - "Clinical Nurse Coordinator", - ), - ("FELLOW", "Fellow"), - ("INTERN", "Intern"), - ("INTERNSHIP", "Internship"), - ("RESIDENT", "Resident"), - ("WORK_FROM_HOME", "Work from Home"), - ("WORK_FROM_HOME_PART_TIME", "Work from Home Part-time"), - ("PHARMACIST", "Pharmacist"), - ("PHARMACY_TECH", "Pharmacy Technician"), - ("LAB_TECH", "Laboratory Technician"), - ("RADIOLOGIST", "Radiologist"), - ("RAD_TECH", "Radiology Technician"), - ("RAD_SUPERVISOR", "Radiology Supervisor"), - ("THERAPIST", "Therapist"), - ("SOCIAL_WORKER", "Social Worker"), - ("CASE_MANAGER", "Case Manager"), - ("BILLING_SPECIALIST", "Billing Specialist"), - ("REGISTRATION", "Registration Staff"), - ("SCHEDULER", "Scheduler"), - ("MEDICAL_ASSISTANT", "Medical Assistant"), - ("CLERICAL", "Clerical Staff"), - ("IT_SUPPORT", "IT Support"), - ("QUALITY_ASSURANCE", "Quality Assurance"), - ("COMPLIANCE", "Compliance Officer"), - ("SECURITY", "Security"), - ("MAINTENANCE", "Maintenance"), - ("VOLUNTEER", "Volunteer"), - ("STUDENT", "Student"), - ("RESEARCHER", "Researcher"), - ("CONSULTANT", "Consultant"), - ("VENDOR", "Vendor"), - ("GUEST", "Guest"), - ], - default="GUEST", - max_length=50, - ), - ), - ( - "job_title", - models.CharField( - blank=True, help_text="Job title", max_length=100, null=True - ), - ), - ( - "license_number", - models.CharField( - blank=True, - help_text="Professional license number", - max_length=50, - null=True, - ), - ), - ( - "license_expiry_date", - models.DateField( - blank=True, help_text="License expiry date", null=True - ), - ), - ( - "license_state", - models.CharField( - blank=True, - help_text="Issuing state/authority", - max_length=50, - null=True, - ), - ), - ( - "dea_number", - models.CharField( - blank=True, - help_text="DEA number (if applicable)", - max_length=20, - null=True, - ), - ), - ( - "npi_number", - models.CharField( - blank=True, - help_text="NPI (if applicable)", - max_length=10, - null=True, - ), - ), - ( - "employment_status", - models.CharField( - choices=[ - ("ACTIVE", "Active"), - ("INACTIVE", "Inactive"), - ("TERMINATED", "Terminated"), - ("SUSPENDED", "Suspended"), - ("LEAVE", "On Leave"), - ("RETIRED", "Retired"), - ], - default="ACTIVE", - max_length=20, - ), - ), - ( - "employment_type", - models.CharField( - blank=True, - choices=[ - ("FULL_TIME", "Full Time"), - ("PART_TIME", "Part Time"), - ("CONTRACT", "Contract"), - ("TEMPORARY", "Temporary"), - ("INTERN", "Intern"), - ("VOLUNTEER", "Volunteer"), - ("PER_DIEM", "Per Diem"), - ("CONSULTANT", "Consultant"), - ], - help_text="Employment type", - max_length=20, - null=True, - ), - ), - ( - "hire_date", - models.DateField(blank=True, help_text="Hire date", null=True), - ), - ( - "termination_date", - models.DateField( - blank=True, help_text="Termination date", null=True - ), - ), - ( - "hourly_rate", - models.DecimalField( - blank=True, decimal_places=2, max_digits=10, null=True - ), - ), - ( - "standard_hours_per_week", - models.DecimalField( - decimal_places=2, default=Decimal("40.00"), max_digits=5 - ), - ), - ( - "annual_salary", - models.DecimalField( - blank=True, decimal_places=2, max_digits=12, null=True - ), - ), - ( - "fte_percentage", - models.DecimalField( - decimal_places=2, - default=Decimal("100.00"), - max_digits=5, - validators=[ - django.core.validators.MinValueValidator(0), - django.core.validators.MaxValueValidator(100), - ], - ), - ), - ( - "profile_picture", - models.ImageField( - blank=True, null=True, upload_to="profile_pictures/" - ), - ), - ( - "bio", - models.TextField( - blank=True, help_text="Professional bio", null=True - ), - ), - ( - "emergency_contact_name", - models.CharField(blank=True, max_length=100, null=True), - ), - ( - "emergency_contact_relationship", - models.CharField(blank=True, max_length=50, null=True), - ), - ( - "emergency_contact_phone", - models.CharField(blank=True, max_length=20, null=True), - ), - ("notes", models.TextField(blank=True, null=True)), - ("is_verified", models.BooleanField(default=False)), - ("is_approved", models.BooleanField(default=False)), - ("approval_date", models.DateTimeField(blank=True, null=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "approved_by", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="approved_employees", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "created_by", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_employees", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "department", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="employees", - to="hr.department", - ), - ), - ( - "supervisor", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="direct_reports", - to="hr.employee", - ), - ), - ( - "tenant", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="employees", - to="core.tenant", - ), - ), - ( - "user", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - related_name="employee_profile", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "verbose_name": "Employee", - "verbose_name_plural": "Employees", - "db_table": "hr_employee", - "ordering": ["last_name", "first_name"], - }, - ), - migrations.AddField( - model_name="department", - name="created_by", - field=models.ForeignKey( - blank=True, - help_text="User who created the department", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_hr_departments", - to="hr.employee", - ), - ), - migrations.AddField( - model_name="department", - name="department_head", - field=models.ForeignKey( - blank=True, - help_text="Department head", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="headed_departments", - to="hr.employee", - ), - ), - migrations.CreateModel( - name="PerformanceReview", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "review_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique review identifier", - ), - ), - ( - "review_period_start", - models.DateField(help_text="Review period start date"), - ), - ( - "review_period_end", - models.DateField(help_text="Review period end date"), - ), - ("review_date", models.DateField(help_text="Review date")), - ( - "review_type", - models.CharField( - choices=[ - ("ANNUAL", "Annual Review"), - ("PROBATIONARY", "Probationary Review"), - ("MID_YEAR", "Mid-Year Review"), - ("PROJECT", "Project Review"), - ("DISCIPLINARY", "Disciplinary Review"), - ("PROMOTION", "Promotion Review"), - ("OTHER", "Other Review"), - ], - help_text="Review type", - max_length=20, - ), - ), - ( - "overall_rating", - models.DecimalField( - decimal_places=1, - help_text="Overall rating (1-5)", - max_digits=3, - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(5), - ], - ), - ), - ( - "competency_ratings", - models.JSONField( - default=dict, help_text="Individual competency ratings" - ), - ), - ( - "goals_achieved", - models.TextField( - blank=True, - help_text="Goals achieved during review period", - null=True, - ), - ), - ( - "goals_not_achieved", - models.TextField( - blank=True, - help_text="Goals not achieved during review period", - null=True, - ), - ), - ( - "future_goals", - models.TextField( - blank=True, help_text="Goals for next review period", null=True - ), - ), - ( - "strengths", - models.TextField( - blank=True, help_text="Employee strengths", null=True - ), - ), - ( - "areas_for_improvement", - models.TextField( - blank=True, help_text="Areas for improvement", null=True - ), - ), - ( - "development_plan", - models.TextField( - blank=True, help_text="Professional development plan", null=True - ), - ), - ( - "training_recommendations", - models.TextField( - blank=True, help_text="Training recommendations", null=True - ), - ), - ( - "employee_comments", - models.TextField( - blank=True, help_text="Employee comments", null=True - ), - ), - ( - "employee_signature_date", - models.DateField( - blank=True, help_text="Employee signature date", null=True - ), - ), - ( - "status", - models.CharField( - choices=[ - ("DRAFT", "Draft"), - ("SUBMITTED", "Submitted"), - ("IN_PROGRESS", "In Progress"), - ("COMPLETED", "Completed"), - ("ACKNOWLEDGED", "Acknowledged by Employee"), - ("DISPUTED", "Disputed"), - ], - default="DRAFT", - help_text="Review status", - max_length=20, - ), - ), - ( - "notes", - models.TextField( - blank=True, help_text="Additional notes", null=True - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "employee", - models.ForeignKey( - help_text="Employee being reviewed", - on_delete=django.db.models.deletion.CASCADE, - related_name="performance_reviews", - to="hr.employee", - ), - ), - ( - "reviewer", - models.ForeignKey( - blank=True, - help_text="Reviewer", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="conducted_reviews", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "verbose_name": "Performance Review", - "verbose_name_plural": "Performance Reviews", - "db_table": "hr_performance_review", - "ordering": ["-review_date"], - }, - ), - migrations.CreateModel( - name="Schedule", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "schedule_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique schedule identifier", - unique=True, - ), - ), - ("name", models.CharField(help_text="Schedule name", max_length=100)), - ( - "description", - models.TextField( - blank=True, help_text="Schedule description", null=True - ), - ), - ( - "schedule_type", - models.CharField( - choices=[ - ("REGULAR", "Regular"), - ("ROTATING", "Rotating"), - ("FLEXIBLE", "Flexible"), - ("ON_CALL", "On-Call"), - ("TEMPORARY", "Temporary"), - ], - help_text="Schedule type", - max_length=20, - ), - ), - ("effective_date", models.DateField(help_text="Effective date")), - ( - "end_date", - models.DateField(blank=True, help_text="End date", null=True), - ), - ( - "schedule_pattern", - models.JSONField( - default=dict, help_text="Schedule pattern configuration" - ), - ), - ( - "is_active", - models.BooleanField(default=True, help_text="Schedule is active"), - ), - ( - "approval_date", - models.DateTimeField( - blank=True, help_text="Approval date and time", null=True - ), - ), - ( - "notes", - models.TextField(blank=True, help_text="Schedule notes", null=True), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "approved_by", - models.ForeignKey( - blank=True, - help_text="User who approved the schedule", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="approved_schedules", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "created_by", - models.ForeignKey( - blank=True, - help_text="User who created the schedule", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_schedules", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "employee", - models.ForeignKey( - help_text="Employee", - on_delete=django.db.models.deletion.CASCADE, - related_name="schedules", - to="hr.employee", - ), - ), - ], - options={ - "verbose_name": "Schedule", - "verbose_name_plural": "Schedules", - "db_table": "hr_schedule", - "ordering": ["-effective_date"], - }, - ), - migrations.CreateModel( - name="ScheduleAssignment", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "assignment_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique assignment identifier", - unique=True, - ), - ), - ("assignment_date", models.DateField(help_text="Assignment date")), - ("start_time", models.TimeField(help_text="Start time")), - ("end_time", models.TimeField(help_text="End time")), - ( - "shift_type", - models.CharField( - choices=[ - ("DAY", "Day Shift"), - ("EVENING", "Evening Shift"), - ("NIGHT", "Night Shift"), - ("WEEKEND", "Weekend Shift"), - ("HOLIDAY", "Holiday Shift"), - ("ON_CALL", "On-Call"), - ("OVERTIME", "Overtime"), - ], - help_text="Shift type", - max_length=20, - ), - ), - ( - "location", - models.CharField( - blank=True, - help_text="Specific location", - max_length=100, - null=True, - ), - ), - ( - "status", - models.CharField( - choices=[ - ("SCHEDULED", "Scheduled"), - ("CONFIRMED", "Confirmed"), - ("COMPLETED", "Completed"), - ("CANCELLED", "Cancelled"), - ("NO_SHOW", "No Show"), - ], - default="SCHEDULED", - help_text="Assignment status", - max_length=20, - ), - ), - ( - "break_minutes", - models.PositiveIntegerField( - default=0, help_text="Break time in minutes" - ), - ), - ( - "lunch_minutes", - models.PositiveIntegerField( - default=0, help_text="Lunch time in minutes" - ), - ), - ( - "notes", - models.TextField( - blank=True, help_text="Assignment notes", null=True - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "department", - models.ForeignKey( - blank=True, - help_text="Department", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="schedule_assignments", - to="hr.department", - ), - ), - ( - "schedule", - models.ForeignKey( - help_text="Schedule", - on_delete=django.db.models.deletion.CASCADE, - related_name="assignments", - to="hr.schedule", - ), - ), - ], - options={ - "verbose_name": "Schedule Assignment", - "verbose_name_plural": "Schedule Assignments", - "db_table": "hr_schedule_assignment", - "ordering": ["assignment_date", "start_time"], - }, - ), - migrations.CreateModel( - name="TimeEntry", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "entry_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - help_text="Unique time entry identifier", - unique=True, - ), - ), - ("work_date", models.DateField(help_text="Work date")), - ( - "clock_in_time", - models.DateTimeField( - blank=True, help_text="Clock in time", null=True - ), - ), - ( - "clock_out_time", - models.DateTimeField( - blank=True, help_text="Clock out time", null=True - ), - ), - ( - "break_start_time", - models.DateTimeField( - blank=True, help_text="Break start time", null=True - ), - ), - ( - "break_end_time", - models.DateTimeField( - blank=True, help_text="Break end time", null=True - ), - ), - ( - "lunch_start_time", - models.DateTimeField( - blank=True, help_text="Lunch start time", null=True - ), - ), - ( - "lunch_end_time", - models.DateTimeField( - blank=True, help_text="Lunch end time", null=True - ), - ), - ( - "regular_hours", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Regular hours worked", - max_digits=5, - ), - ), - ( - "overtime_hours", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Overtime hours worked", - max_digits=5, - ), - ), - ( - "total_hours", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - help_text="Total hours worked", - max_digits=5, - ), - ), - ( - "entry_type", - models.CharField( - choices=[ - ("REGULAR", "Regular Time"), - ("OVERTIME", "Overtime"), - ("HOLIDAY", "Holiday"), - ("VACATION", "Vacation"), - ("SICK", "Sick Leave"), - ("PERSONAL", "Personal Time"), - ("TRAINING", "Training"), - ], - default="REGULAR", - help_text="Entry type", - max_length=20, - ), - ), - ( - "location", - models.CharField( - blank=True, help_text="Work location", max_length=100, null=True - ), - ), - ( - "approval_date", - models.DateTimeField( - blank=True, help_text="Approval date and time", null=True - ), - ), - ( - "status", - models.CharField( - choices=[ - ("DRAFT", "Draft"), - ("SUBMITTED", "Submitted"), - ("APPROVED", "Approved"), - ("REJECTED", "Rejected"), - ("PAID", "Paid"), - ], - default="DRAFT", - help_text="Entry status", - max_length=20, - ), - ), - ( - "notes", - models.TextField( - blank=True, help_text="Time entry notes", null=True - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "approved_by", - models.ForeignKey( - blank=True, - help_text="User who approved the time entry", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="approved_time_entries", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "department", - models.ForeignKey( - blank=True, - help_text="Department", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="time_entries", - to="hr.department", - ), - ), - ( - "employee", - models.ForeignKey( - help_text="Employee", - on_delete=django.db.models.deletion.CASCADE, - related_name="time_entries", - to="hr.employee", - ), - ), - ], - options={ - "verbose_name": "Time Entry", - "verbose_name_plural": "Time Entries", - "db_table": "hr_time_entry", - "ordering": ["-work_date"], - }, - ), - migrations.CreateModel( - name="TrainingPrograms", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "program_id", - models.UUIDField(default=uuid.uuid4, editable=False, unique=True), - ), - ("name", models.CharField(max_length=200)), - ("description", models.TextField(blank=True, null=True)), - ( - "program_type", - models.CharField( - choices=[ - ("ORIENTATION", "Orientation"), - ("MANDATORY", "Mandatory Training"), - ("CONTINUING_ED", "Continuing Education"), - ("CERTIFICATION", "Certification"), - ("SKILLS", "Skills Training"), - ("SAFETY", "Safety Training"), - ("COMPLIANCE", "Compliance Training"), - ("LEADERSHIP", "Leadership Development"), - ("TECHNICAL", "Technical Training"), - ("OTHER", "Other"), - ], - max_length=20, - ), - ), - ( - "program_provider", - models.CharField(blank=True, max_length=200, null=True), - ), - ("start_date", models.DateField(blank=True, null=True)), - ("end_date", models.DateField(blank=True, null=True)), - ( - "duration_hours", - models.DecimalField( - decimal_places=2, default=Decimal("0.00"), max_digits=5 - ), - ), - ( - "cost", - models.DecimalField( - decimal_places=2, default=Decimal("0.00"), max_digits=10 - ), - ), - ("is_certified", models.BooleanField(default=False)), - ("validity_days", models.PositiveIntegerField(blank=True, null=True)), - ( - "notify_before_days", - models.PositiveIntegerField(blank=True, null=True), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "created_by", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_training_programs", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "instructor", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="instructor_programs", - to="hr.employee", - ), - ), - ( - "tenant", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="training_programs", - to="core.tenant", - ), - ), - ], - options={ - "db_table": "hr_training_program", - "ordering": ["name"], - }, - ), - migrations.CreateModel( - name="ProgramPrerequisite", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "program", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="prerequisites", - to="hr.trainingprograms", - ), - ), - ( - "required_program", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="unlocking_programs", - to="hr.trainingprograms", - ), - ), - ], - options={ - "db_table": "hr_training_program_prerequisite", - }, - ), - migrations.CreateModel( - name="ProgramModule", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("title", models.CharField(max_length=200)), - ("order", models.PositiveIntegerField(default=1)), - ( - "hours", - models.DecimalField( - decimal_places=2, default=Decimal("0.00"), max_digits=5 - ), - ), - ( - "program", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="modules", - to="hr.trainingprograms", - ), - ), - ], - options={ - "db_table": "hr_training_program_module", - "ordering": ["program", "order"], - }, - ), - migrations.CreateModel( - name="TrainingRecord", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "record_id", - models.UUIDField(default=uuid.uuid4, editable=False, unique=True), - ), - ("enrolled_at", models.DateTimeField(auto_now_add=True)), - ("started_at", models.DateTimeField(blank=True, null=True)), - ("completion_date", models.DateField(blank=True, null=True)), - ("expiry_date", models.DateField(blank=True, null=True)), - ( - "status", - models.CharField( - choices=[ - ("SCHEDULED", "Scheduled"), - ("IN_PROGRESS", "In Progress"), - ("COMPLETED", "Completed"), - ("CANCELLED", "Cancelled"), - ("NO_SHOW", "No Show"), - ("FAILED", "Failed"), - ("WAITLISTED", "Waitlisted"), - ], - default="SCHEDULED", - max_length=20, - ), - ), - ( - "credits_earned", - models.DecimalField( - decimal_places=2, default=Decimal("0.00"), max_digits=5 - ), - ), - ( - "score", - models.DecimalField( - blank=True, decimal_places=2, max_digits=5, null=True - ), - ), - ("passed", models.BooleanField(default=False)), - ("notes", models.TextField(blank=True, null=True)), - ( - "cost_paid", - models.DecimalField( - blank=True, decimal_places=2, max_digits=10, null=True - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "created_by", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_training_records", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "employee", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="training_records", - to="hr.employee", - ), - ), - ( - "program", - models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="training_records", - to="hr.trainingprograms", - ), - ), - ], - options={ - "verbose_name": "Training Enrollment", - "verbose_name_plural": "Training Enrollments", - "db_table": "hr_training_record", - "ordering": ["-enrolled_at"], - }, - ), - migrations.CreateModel( - name="TrainingCertificates", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "certificate_id", - models.UUIDField(default=uuid.uuid4, editable=False, unique=True), - ), - ("certificate_name", models.CharField(max_length=200)), - ( - "certificate_number", - models.CharField(blank=True, max_length=50, null=True), - ), - ( - "certification_body", - models.CharField(blank=True, max_length=200, null=True), - ), - ("issued_date", models.DateField(auto_now_add=True)), - ("expiry_date", models.DateField(blank=True, null=True)), - ( - "file", - models.FileField(blank=True, null=True, upload_to="certificates/"), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "created_by", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_training_certificates", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "employee", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="training_certificates", - to="hr.employee", - ), - ), - ( - "signed_by", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="signed_training_certificates", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "program", - models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="certificates", - to="hr.trainingprograms", - ), - ), - ( - "enrollment", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - related_name="certificate", - to="hr.trainingrecord", - ), - ), - ], - options={ - "verbose_name": "Training Certificate", - "verbose_name_plural": "Training Certificates", - "db_table": "hr_training_certificate", - "ordering": ["-issued_date"], - }, - ), - migrations.CreateModel( - name="TrainingAttendance", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("checked_in_at", models.DateTimeField(blank=True, null=True)), - ("checked_out_at", models.DateTimeField(blank=True, null=True)), - ( - "status", - models.CharField( - choices=[ - ("PRESENT", "Present"), - ("LATE", "Late"), - ("ABSENT", "Absent"), - ("EXCUSED", "Excused"), - ], - default="PRESENT", - max_length=10, - ), - ), - ("notes", models.CharField(blank=True, max_length=255, null=True)), - ( - "enrollment", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="attendance", - to="hr.trainingrecord", - ), - ), - ], - options={ - "db_table": "hr_training_attendance", - "ordering": ["enrollment_id", "checked_in_at"], - }, - ), - migrations.CreateModel( - name="TrainingAssessment", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=200)), - ( - "max_score", - models.DecimalField(decimal_places=2, default=100, max_digits=7), - ), - ( - "score", - models.DecimalField( - blank=True, decimal_places=2, max_digits=7, null=True - ), - ), - ("passed", models.BooleanField(default=False)), - ("taken_at", models.DateTimeField(blank=True, null=True)), - ("notes", models.TextField(blank=True, null=True)), - ( - "enrollment", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="assessments", - to="hr.trainingrecord", - ), - ), - ], - options={ - "db_table": "hr_training_assessment", - "ordering": ["-taken_at"], - }, - ), - migrations.CreateModel( - name="TrainingSession", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "session_id", - models.UUIDField(default=uuid.uuid4, editable=False, unique=True), - ), - ( - "title", - models.CharField( - blank=True, - help_text="Optional run title; falls back to program name", - max_length=200, - null=True, - ), - ), - ( - "delivery_method", - models.CharField( - choices=[ - ("IN_PERSON", "In Person"), - ("VIRTUAL", "Virtual"), - ("HYBRID", "Hybrid"), - ("SELF_PACED", "Self Paced"), - ], - default="IN_PERSON", - max_length=12, - ), - ), - ("start_at", models.DateTimeField()), - ("end_at", models.DateTimeField()), - ("location", models.CharField(blank=True, max_length=200, null=True)), - ("capacity", models.PositiveIntegerField(default=0)), - ( - "cost_override", - models.DecimalField( - blank=True, decimal_places=2, max_digits=10, null=True - ), - ), - ( - "hours_override", - models.DecimalField( - blank=True, decimal_places=2, max_digits=5, null=True - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ( - "created_by", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="created_training_sessions", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "instructor", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="instructed_sessions", - to="hr.employee", - ), - ), - ( - "program", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="sessions", - to="hr.trainingprograms", - ), - ), - ], - options={ - "verbose_name": "Training Session", - "verbose_name_plural": "Training Sessions", - "db_table": "hr_training_session", - "ordering": ["-start_at"], - }, - ), - migrations.AddField( - model_name="trainingrecord", - name="session", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="enrollments", - to="hr.trainingsession", - ), - ), - migrations.AddIndex( - model_name="employee", - index=models.Index( - fields=["tenant", "employee_id"], name="hr_employee_tenant__d723cf_idx" - ), - ), - migrations.AddIndex( - model_name="employee", - index=models.Index( - fields=["tenant", "role"], name="hr_employee_tenant__1c6def_idx" - ), - ), - migrations.AddIndex( - model_name="employee", - index=models.Index( - fields=["tenant", "department"], name="hr_employee_tenant__1baa3c_idx" - ), - ), - migrations.AddIndex( - model_name="employee", - index=models.Index( - fields=["tenant", "employment_status"], - name="hr_employee_tenant__54b199_idx", - ), - ), - migrations.AddIndex( - model_name="department", - index=models.Index( - fields=["tenant", "department_type"], - name="hr_departme_tenant__87fa85_idx", - ), - ), - migrations.AddIndex( - model_name="department", - index=models.Index(fields=["code"], name="hr_departme_code_d27daf_idx"), - ), - migrations.AddIndex( - model_name="department", - index=models.Index(fields=["name"], name="hr_departme_name_00be75_idx"), - ), - migrations.AddIndex( - model_name="department", - index=models.Index( - fields=["is_active"], name="hr_departme_is_acti_b443d7_idx" - ), - ), - migrations.AlterUniqueTogether( - name="department", - unique_together={("tenant", "code")}, - ), - migrations.AddIndex( - model_name="performancereview", - index=models.Index( - fields=["employee", "review_date"], - name="hr_performa_employe_604c29_idx", - ), - ), - migrations.AddIndex( - model_name="performancereview", - index=models.Index( - fields=["review_type"], name="hr_performa_review__a7c664_idx" - ), - ), - migrations.AddIndex( - model_name="performancereview", - index=models.Index(fields=["status"], name="hr_performa_status_460939_idx"), - ), - migrations.AddIndex( - model_name="performancereview", - index=models.Index( - fields=["overall_rating"], name="hr_performa_overall_0257f6_idx" - ), - ), - migrations.AddIndex( - model_name="schedule", - index=models.Index( - fields=["employee", "effective_date"], - name="hr_schedule_employe_2bbf02_idx", - ), - ), - migrations.AddIndex( - model_name="schedule", - index=models.Index( - fields=["schedule_type"], name="hr_schedule_schedul_19e2a5_idx" - ), - ), - migrations.AddIndex( - model_name="schedule", - index=models.Index( - fields=["is_active"], name="hr_schedule_is_acti_2e906a_idx" - ), - ), - migrations.AddIndex( - model_name="scheduleassignment", - index=models.Index( - fields=["schedule", "assignment_date"], - name="hr_schedule_schedul_85a37e_idx", - ), - ), - migrations.AddIndex( - model_name="scheduleassignment", - index=models.Index( - fields=["assignment_date"], name="hr_schedule_assignm_cf8fba_idx" - ), - ), - migrations.AddIndex( - model_name="scheduleassignment", - index=models.Index(fields=["status"], name="hr_schedule_status_a9da0d_idx"), - ), - migrations.AddIndex( - model_name="timeentry", - index=models.Index( - fields=["employee", "work_date"], name="hr_time_ent_employe_9f8566_idx" - ), - ), - migrations.AddIndex( - model_name="timeentry", - index=models.Index( - fields=["work_date"], name="hr_time_ent_work_da_898420_idx" - ), - ), - migrations.AddIndex( - model_name="timeentry", - index=models.Index(fields=["status"], name="hr_time_ent_status_c67efb_idx"), - ), - migrations.AddIndex( - model_name="timeentry", - index=models.Index( - fields=["entry_type"], name="hr_time_ent_entry_t_eba769_idx" - ), - ), - migrations.AddIndex( - model_name="trainingprograms", - index=models.Index( - fields=["tenant", "program_type"], name="hr_training_tenant__d8062d_idx" - ), - ), - migrations.AddIndex( - model_name="trainingprograms", - index=models.Index( - fields=["tenant", "is_certified"], name="hr_training_tenant__4892af_idx" - ), - ), - migrations.AlterUniqueTogether( - name="trainingprograms", - unique_together={("tenant", "name")}, - ), - migrations.AlterUniqueTogether( - name="programprerequisite", - unique_together={("program", "required_program")}, - ), - migrations.AddIndex( - model_name="programmodule", - index=models.Index( - fields=["program", "order"], name="hr_training_program_f99b01_idx" - ), - ), - migrations.AlterUniqueTogether( - name="programmodule", - unique_together={("program", "order")}, - ), - migrations.AddIndex( - model_name="trainingcertificates", - index=models.Index( - fields=["certificate_number"], name="hr_training_certifi_928fdb_idx" - ), - ), - migrations.AlterUniqueTogether( - name="trainingcertificates", - unique_together={("employee", "program", "enrollment")}, - ), - migrations.AddIndex( - model_name="trainingattendance", - index=models.Index( - fields=["enrollment"], name="hr_training_enrollm_104874_idx" - ), - ), - migrations.AddIndex( - model_name="trainingassessment", - index=models.Index( - fields=["enrollment"], name="hr_training_enrollm_71763a_idx" - ), - ), - migrations.AlterUniqueTogether( - name="trainingrecord", - unique_together={("employee", "session")}, - ), - ] diff --git a/hr/migrations/__pycache__/0001_initial.cpython-312.pyc b/hr/migrations/__pycache__/0001_initial.cpython-312.pyc deleted file mode 100644 index a2ec4e8d..00000000 Binary files a/hr/migrations/__pycache__/0001_initial.cpython-312.pyc and /dev/null differ diff --git a/hr/templates/hr/employee_confirm_delete.html b/hr/templates/forDelete/employee_confirm_delete.html similarity index 100% rename from hr/templates/hr/employee_confirm_delete.html rename to hr/templates/forDelete/employee_confirm_delete.html diff --git a/hr/templates/hr/assignments/schedule_assignment_list.html b/hr/templates/hr/assignments/schedule_assignment_list.html index 2aaa0202..8f930a49 100644 --- a/hr/templates/hr/assignments/schedule_assignment_list.html +++ b/hr/templates/hr/assignments/schedule_assignment_list.html @@ -441,45 +441,7 @@ {% if is_paginated %} -
- -
+ {% include 'partial/pagination.html' %} {% endif %}
@@ -494,7 +456,7 @@ {% block js %} - + diff --git a/hr/templates/hr/dashboard.html b/hr/templates/hr/dashboard.html index 9be28991..574c65cf 100644 --- a/hr/templates/hr/dashboard.html +++ b/hr/templates/hr/dashboard.html @@ -188,15 +188,9 @@

- Recent Activity + Departments Tree

- - - View All - @@ -204,63 +198,64 @@
-
- - {% for employee in recent_hires %} -
-
-
-
New Employee Hired
-

- {{ employee.get_full_name }} joined - {% if employee.department %}{{ employee.department.name }}{% else %}the organization{% endif %} - as {{ employee.job_title|default:"Employee" }} -

- {{ employee.hire_date|timesince }} ago -
-
- {% endfor %} - - - {% for review in recent_reviews %} -
-
-
-
Performance Review Completed
-

- {{ review.employee.get_full_name }} received a performance review - {% if review.overall_rating %} - with rating: {{ review.get_overall_rating_display }} - {% endif %} -

- {{ review.review_date|timesince }} ago -
-
- {% endfor %} - - - {% for training in recent_training %} -
-
-
-
Training Completed
-

- {{ training.employee.get_full_name }} completed - "{{ training.training_name }}" -

- {{ training.completion_date|timesince }} ago -
-
- {% endfor %} - - {% if not recent_hires and not recent_reviews and not recent_training %} -
- -
No recent activity
-

Recent HR activities will appear here

-
- {% endif %} -
+ {% include 'hr/departments/department_tree.html' %} +{#
#} +{# #} +{# {% for employee in recent_hires %}#} +{#
#} +{#
#} +{#
#} +{#
New Employee Hired
#} +{#

#} +{# {{ employee.get_full_name }} joined #} +{# {% if employee.department %}{{ employee.department.name }}{% else %}the organization{% endif %}#} +{# as {{ employee.job_title|default:"Employee" }}#} +{#

#} +{# {{ employee.hire_date|timesince }} ago#} +{#
#} +{#
#} +{# {% endfor %}#} +{##} +{# #} +{# {% for review in recent_reviews %}#} +{#
#} +{#
#} +{#
#} +{#
Performance Review Completed
#} +{#

#} +{# {{ review.employee.get_full_name }} received a performance review#} +{# {% if review.overall_rating %}#} +{# with rating: {{ review.get_overall_rating_display }}#} +{# {% endif %}#} +{#

#} +{# {{ review.review_date|timesince }} ago#} +{#
#} +{#
#} +{# {% endfor %}#} +{##} +{# #} +{# {% for training in recent_training %}#} +{#
#} +{#
#} +{#
#} +{#
Training Completed
#} +{#

#} +{# {{ training.employee.get_full_name }} completed #} +{# "{{ training.training_name }}"#} +{#

#} +{# {{ training.completion_date|timesince }} ago#} +{#
#} +{#
#} +{# {% endfor %}#} +{##} +{# {% if not recent_hires and not recent_reviews and not recent_training %}#} +{#
#} +{# #} +{#
No recent activity
#} +{#

Recent HR activities will appear here

#} +{#
#} +{# {% endif %}#} +{#
#}
diff --git a/hr/templates/hr/departments/assign_department_head.html b/hr/templates/hr/departments/assign_department_head.html index 1e1d396f..2bf77d2b 100644 --- a/hr/templates/hr/departments/assign_department_head.html +++ b/hr/templates/hr/departments/assign_department_head.html @@ -2,7 +2,63 @@ {% load static %} {% block title %}Assign Department Head - {{ department.name }}{% endblock %} +{% block css %} + +{% endblock %} {% block content %}
@@ -15,7 +71,7 @@
{{ department.code }} - {{ department.name }}
@@ -140,7 +196,7 @@