update
This commit is contained in:
parent
35be20ae4c
commit
23158e9fbf
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
@ -14,4 +14,7 @@
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="uv (hospital_management_system_v4)" project-jdk-type="Python SDK" />
|
||||
<component name="PythonCompatibilityInspectionAdvertiser">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
||||
BIN
accounts/__pycache__/flows.cpython-312.pyc
Normal file
BIN
accounts/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
800
accounts/flows.py
Normal file
800
accounts/flows.py
Normal file
@ -0,0 +1,800 @@
|
||||
# """
|
||||
# Viewflow workflows for accounts app.
|
||||
# Provides user management, authentication, and security workflows.
|
||||
# """
|
||||
#
|
||||
# from viewflow import this, jsonstore
|
||||
# from viewflow.workflow import lock, flow, act
|
||||
# # from viewflow.base import flow_func
|
||||
# # from viewflow.workflow.base import Flow
|
||||
# from viewflow.workflow import celery
|
||||
# # from viewflow.decorators import flow_view
|
||||
# from viewflow.jsonstore import CharField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.fields import ModelField
|
||||
# from viewflow.workflow.flow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.workflow.models import Process, Task
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import User, TwoFactorDevice, SocialAccount, UserSession, PasswordHistory
|
||||
# from .views import *
|
||||
#
|
||||
#
|
||||
# class UserOnboardingProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for user onboarding
|
||||
# """
|
||||
# user = ModelField(User, help_text='Associated user')
|
||||
#
|
||||
# # Process status tracking
|
||||
# registration_submitted = models.BooleanField(default=False)
|
||||
# account_created = models.BooleanField(default=False)
|
||||
# email_verified = models.BooleanField(default=False)
|
||||
# profile_completed = models.BooleanField(default=False)
|
||||
# permissions_assigned = models.BooleanField(default=False)
|
||||
# security_setup = models.BooleanField(default=False)
|
||||
# training_completed = models.BooleanField(default=False)
|
||||
# onboarding_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'User Onboarding Process'
|
||||
# verbose_name_plural = 'User Onboarding Processes'
|
||||
#
|
||||
#
|
||||
# class UserOnboardingFlow(flow.Flow):
|
||||
# """
|
||||
# User Onboarding Workflow
|
||||
#
|
||||
# This flow manages complete user onboarding from registration
|
||||
# through account setup, security configuration, and training.
|
||||
# """
|
||||
#
|
||||
# process_class = UserOnboardingProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_user_onboarding)
|
||||
# .Next(this.register_user)
|
||||
# )
|
||||
#
|
||||
# register_user = (
|
||||
# flow_view(UserRegistrationView)
|
||||
# .Permission('accounts.can_register_users')
|
||||
# .Next(this.create_account)
|
||||
# )
|
||||
#
|
||||
# create_account = (
|
||||
# flow_func(this.setup_user_account)
|
||||
# .Next(this.verify_email)
|
||||
# )
|
||||
#
|
||||
# verify_email = (
|
||||
# flow_view(AccountActivationView)
|
||||
# .Permission('accounts.can_activate_accounts')
|
||||
# .Next(this.complete_profile)
|
||||
# )
|
||||
#
|
||||
# complete_profile = (
|
||||
# flow_func(this.setup_user_profile)
|
||||
# .Next(this.assign_permissions)
|
||||
# )
|
||||
#
|
||||
# assign_permissions = (
|
||||
# flow_view(PermissionManagementView)
|
||||
# .Permission('accounts.can_manage_permissions')
|
||||
# .Next(this.setup_security)
|
||||
# )
|
||||
#
|
||||
# setup_security = (
|
||||
# flow_view(TwoFactorSetupView)
|
||||
# .Permission('accounts.can_setup_security')
|
||||
# .Next(this.complete_training)
|
||||
# )
|
||||
#
|
||||
# complete_training = (
|
||||
# flow_func(this.assign_training_modules)
|
||||
# .Next(this.finalize_onboarding)
|
||||
# )
|
||||
#
|
||||
# finalize_onboarding = (
|
||||
# flow_func(this.complete_user_onboarding)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_user_onboarding)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_user_onboarding(self, activation):
|
||||
# """Initialize the user onboarding process"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Send onboarding notification
|
||||
# self.notify_onboarding_start(user)
|
||||
#
|
||||
# # Create onboarding checklist
|
||||
# self.create_onboarding_checklist(user)
|
||||
#
|
||||
# def setup_user_account(self, activation):
|
||||
# """Setup user account with initial configuration"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Configure account settings
|
||||
# self.configure_account_settings(user)
|
||||
#
|
||||
# # Mark account created
|
||||
# process.account_created = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send welcome email
|
||||
# self.send_welcome_email(user)
|
||||
#
|
||||
# def setup_user_profile(self, activation):
|
||||
# """Setup user profile information"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Complete profile setup
|
||||
# self.complete_profile_setup(user)
|
||||
#
|
||||
# # Mark profile completed
|
||||
# process.profile_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Generate employee ID if needed
|
||||
# self.generate_employee_id(user)
|
||||
#
|
||||
# def assign_training_modules(self, activation):
|
||||
# """Assign required training modules"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Assign role-based training
|
||||
# self.assign_role_training(user)
|
||||
#
|
||||
# # Mark training assigned
|
||||
# process.training_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send training notifications
|
||||
# self.notify_training_assignment(user)
|
||||
#
|
||||
# def complete_user_onboarding(self, activation):
|
||||
# """Complete the user onboarding process"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Activate user account
|
||||
# user.is_active = True
|
||||
# user.save()
|
||||
#
|
||||
# # Mark onboarding completed
|
||||
# process.onboarding_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_onboarding_completion(user)
|
||||
#
|
||||
# # Create initial session
|
||||
# self.create_initial_session(user)
|
||||
#
|
||||
# def end_user_onboarding(self, activation):
|
||||
# """End the user onboarding workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate onboarding summary
|
||||
# self.generate_onboarding_summary(process.user)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_onboarding_start(self, user):
|
||||
# """Notify onboarding start"""
|
||||
# # Notify HR and IT teams
|
||||
# hr_staff = User.objects.filter(groups__name='HR Staff')
|
||||
# for staff in hr_staff:
|
||||
# send_mail(
|
||||
# subject=f'New User Onboarding: {user.get_full_name()}',
|
||||
# message=f'User onboarding process started for {user.username}.',
|
||||
# from_email='accounts@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_onboarding_checklist(self, user):
|
||||
# """Create onboarding checklist"""
|
||||
# # This would create a checklist for the user
|
||||
# pass
|
||||
#
|
||||
# def configure_account_settings(self, user):
|
||||
# """Configure initial account settings"""
|
||||
# # This would set up default account settings
|
||||
# pass
|
||||
#
|
||||
# def send_welcome_email(self, user):
|
||||
# """Send welcome email to new user"""
|
||||
# if user.email:
|
||||
# send_mail(
|
||||
# subject='Welcome to Hospital Management System',
|
||||
# message=f'Welcome {user.get_full_name()}! Your account has been created.',
|
||||
# from_email='accounts@hospital.com',
|
||||
# recipient_list=[user.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def complete_profile_setup(self, user):
|
||||
# """Complete user profile setup"""
|
||||
# # This would complete profile configuration
|
||||
# pass
|
||||
#
|
||||
# def generate_employee_id(self, user):
|
||||
# """Generate employee ID for user"""
|
||||
# if not user.employee_id:
|
||||
# # Generate unique employee ID
|
||||
# user.employee_id = f"EMP{user.id:06d}"
|
||||
# user.save()
|
||||
#
|
||||
# def assign_role_training(self, user):
|
||||
# """Assign role-based training modules"""
|
||||
# # This would assign training based on user role
|
||||
# pass
|
||||
#
|
||||
# def notify_training_assignment(self, user):
|
||||
# """Notify training assignment"""
|
||||
# if user.email:
|
||||
# send_mail(
|
||||
# subject='Training Modules Assigned',
|
||||
# message='Training modules have been assigned to your account.',
|
||||
# from_email='training@hospital.com',
|
||||
# recipient_list=[user.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_onboarding_completion(self, user):
|
||||
# """Notify onboarding completion"""
|
||||
# if user.email:
|
||||
# send_mail(
|
||||
# subject='Onboarding Complete',
|
||||
# message='Your onboarding process has been completed successfully.',
|
||||
# from_email='accounts@hospital.com',
|
||||
# recipient_list=[user.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_initial_session(self, user):
|
||||
# """Create initial user session"""
|
||||
# # This would create the first user session
|
||||
# pass
|
||||
#
|
||||
# def generate_onboarding_summary(self, user):
|
||||
# """Generate onboarding summary"""
|
||||
# # This would generate onboarding completion report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class SecurityManagementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for security management
|
||||
# """
|
||||
# user = ModelField(User, help_text='Associated user')
|
||||
#
|
||||
# # Process status tracking
|
||||
# security_assessment = models.BooleanField(default=False)
|
||||
# two_factor_setup = models.BooleanField(default=False)
|
||||
# password_policy_applied = models.BooleanField(default=False)
|
||||
# session_configured = models.BooleanField(default=False)
|
||||
# audit_completed = models.BooleanField(default=False)
|
||||
# compliance_verified = models.BooleanField(default=False)
|
||||
# security_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Security Management Process'
|
||||
# verbose_name_plural = 'Security Management Processes'
|
||||
#
|
||||
#
|
||||
# class SecurityManagementFlow(flow.Flow):
|
||||
# """
|
||||
# Security Management Workflow
|
||||
#
|
||||
# This flow manages user security setup including two-factor
|
||||
# authentication, password policies, and compliance verification.
|
||||
# """
|
||||
#
|
||||
# process_class = SecurityManagementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_security_management)
|
||||
# .Next(this.assess_security)
|
||||
# )
|
||||
#
|
||||
# assess_security = (
|
||||
# flow_view(SecurityAuditView)
|
||||
# .Permission('accounts.can_audit_security')
|
||||
# .Next(this.setup_two_factor)
|
||||
# )
|
||||
#
|
||||
# setup_two_factor = (
|
||||
# flow_view(TwoFactorSetupView)
|
||||
# .Permission('accounts.can_setup_two_factor')
|
||||
# .Next(this.apply_password_policy)
|
||||
# )
|
||||
#
|
||||
# apply_password_policy = (
|
||||
# flow_func(this.enforce_password_policy)
|
||||
# .Next(this.configure_session)
|
||||
# )
|
||||
#
|
||||
# configure_session = (
|
||||
# flow_view(SessionManagementView)
|
||||
# .Permission('accounts.can_manage_sessions')
|
||||
# .Next(this.complete_audit)
|
||||
# )
|
||||
#
|
||||
# complete_audit = (
|
||||
# flow_func(this.perform_security_audit)
|
||||
# .Next(this.verify_compliance)
|
||||
# )
|
||||
#
|
||||
# verify_compliance = (
|
||||
# flow_view(ComplianceCheckView)
|
||||
# .Permission('accounts.can_verify_compliance')
|
||||
# .Next(this.finalize_security)
|
||||
# )
|
||||
#
|
||||
# finalize_security = (
|
||||
# flow_func(this.complete_security_management)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_security_management)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_security_management(self, activation):
|
||||
# """Initialize the security management process"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Send security setup notification
|
||||
# self.notify_security_setup(user)
|
||||
#
|
||||
# # Create security checklist
|
||||
# self.create_security_checklist(user)
|
||||
#
|
||||
# def enforce_password_policy(self, activation):
|
||||
# """Enforce password policy requirements"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Apply password policy
|
||||
# self.apply_password_requirements(user)
|
||||
#
|
||||
# # Mark password policy applied
|
||||
# process.password_policy_applied = True
|
||||
# process.save()
|
||||
#
|
||||
# # Create password history entry
|
||||
# self.create_password_history(user)
|
||||
#
|
||||
# def perform_security_audit(self, activation):
|
||||
# """Perform comprehensive security audit"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Conduct security audit
|
||||
# audit_results = self.conduct_security_audit(user)
|
||||
#
|
||||
# # Mark audit completed
|
||||
# process.audit_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store audit results
|
||||
# self.store_audit_results(user, audit_results)
|
||||
#
|
||||
# def complete_security_management(self, activation):
|
||||
# """Complete the security management process"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Mark security completed
|
||||
# process.security_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_security_completion(user)
|
||||
#
|
||||
# # Schedule security review
|
||||
# self.schedule_security_review(user)
|
||||
#
|
||||
# def end_security_management(self, activation):
|
||||
# """End the security management workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate security summary
|
||||
# self.generate_security_summary(process.user)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_security_setup(self, user):
|
||||
# """Notify security setup start"""
|
||||
# security_team = User.objects.filter(groups__name='Security Team')
|
||||
# for staff in security_team:
|
||||
# send_mail(
|
||||
# subject=f'Security Setup: {user.get_full_name()}',
|
||||
# message=f'Security setup process started for {user.username}.',
|
||||
# from_email='security@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_security_checklist(self, user):
|
||||
# """Create security setup checklist"""
|
||||
# # This would create security checklist
|
||||
# pass
|
||||
#
|
||||
# def apply_password_requirements(self, user):
|
||||
# """Apply password policy requirements"""
|
||||
# # This would enforce password policy
|
||||
# pass
|
||||
#
|
||||
# def create_password_history(self, user):
|
||||
# """Create password history entry"""
|
||||
# # This would create password history record
|
||||
# pass
|
||||
#
|
||||
# def conduct_security_audit(self, user):
|
||||
# """Conduct comprehensive security audit"""
|
||||
# # This would perform security audit
|
||||
# return {'status': 'passed', 'issues': []}
|
||||
#
|
||||
# def store_audit_results(self, user, results):
|
||||
# """Store security audit results"""
|
||||
# # This would store audit results
|
||||
# pass
|
||||
#
|
||||
# def notify_security_completion(self, user):
|
||||
# """Notify security setup completion"""
|
||||
# if user.email:
|
||||
# send_mail(
|
||||
# subject='Security Setup Complete',
|
||||
# message='Your security setup has been completed successfully.',
|
||||
# from_email='security@hospital.com',
|
||||
# recipient_list=[user.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def schedule_security_review(self, user):
|
||||
# """Schedule periodic security review"""
|
||||
# # Schedule security review task
|
||||
# schedule_security_review.apply_async(
|
||||
# args=[user.id],
|
||||
# countdown=86400 * 90 # 90 days
|
||||
# )
|
||||
#
|
||||
# def generate_security_summary(self, user):
|
||||
# """Generate security setup summary"""
|
||||
# # This would generate security summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class AccountDeactivationProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for account deactivation
|
||||
# """
|
||||
# user = ModelField(User, help_text='Associated user')
|
||||
#
|
||||
# # Process status tracking
|
||||
# deactivation_requested = models.BooleanField(default=False)
|
||||
# data_backup_completed = models.BooleanField(default=False)
|
||||
# access_revoked = models.BooleanField(default=False)
|
||||
# sessions_terminated = models.BooleanField(default=False)
|
||||
# notifications_sent = models.BooleanField(default=False)
|
||||
# account_archived = models.BooleanField(default=False)
|
||||
# deactivation_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Account Deactivation Process'
|
||||
# verbose_name_plural = 'Account Deactivation Processes'
|
||||
#
|
||||
#
|
||||
# class AccountDeactivationFlow(flow.Flow):
|
||||
# """
|
||||
# Account Deactivation Workflow
|
||||
#
|
||||
# This flow manages secure account deactivation including
|
||||
# data backup, access revocation, and proper archival.
|
||||
# """
|
||||
#
|
||||
# process_class = AccountDeactivationProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_account_deactivation)
|
||||
# .Next(this.request_deactivation)
|
||||
# )
|
||||
#
|
||||
# request_deactivation = (
|
||||
# flow_view(AccountDeactivationView)
|
||||
# .Permission('accounts.can_deactivate_accounts')
|
||||
# .Next(this.backup_data)
|
||||
# )
|
||||
#
|
||||
# backup_data = (
|
||||
# flow_func(this.perform_data_backup)
|
||||
# .Next(this.revoke_access)
|
||||
# )
|
||||
#
|
||||
# revoke_access = (
|
||||
# flow_func(this.revoke_user_access)
|
||||
# .Next(this.terminate_sessions)
|
||||
# )
|
||||
#
|
||||
# terminate_sessions = (
|
||||
# flow_func(this.end_user_sessions)
|
||||
# .Next(this.send_notifications)
|
||||
# )
|
||||
#
|
||||
# send_notifications = (
|
||||
# flow_func(this.notify_deactivation)
|
||||
# .Next(this.archive_account)
|
||||
# )
|
||||
#
|
||||
# archive_account = (
|
||||
# flow_func(this.archive_user_account)
|
||||
# .Next(this.complete_deactivation)
|
||||
# )
|
||||
#
|
||||
# complete_deactivation = (
|
||||
# flow_func(this.finalize_account_deactivation)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_account_deactivation)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_account_deactivation(self, activation):
|
||||
# """Initialize the account deactivation process"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Send deactivation notification
|
||||
# self.notify_deactivation_start(user)
|
||||
#
|
||||
# # Create deactivation checklist
|
||||
# self.create_deactivation_checklist(user)
|
||||
#
|
||||
# def perform_data_backup(self, activation):
|
||||
# """Perform user data backup"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Backup user data
|
||||
# self.backup_user_data(user)
|
||||
#
|
||||
# # Mark backup completed
|
||||
# process.data_backup_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Verify backup integrity
|
||||
# self.verify_backup_integrity(user)
|
||||
#
|
||||
# def revoke_user_access(self, activation):
|
||||
# """Revoke user access and permissions"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Revoke all permissions
|
||||
# self.revoke_permissions(user)
|
||||
#
|
||||
# # Mark access revoked
|
||||
# process.access_revoked = True
|
||||
# process.save()
|
||||
#
|
||||
# # Log access revocation
|
||||
# self.log_access_revocation(user)
|
||||
#
|
||||
# def end_user_sessions(self, activation):
|
||||
# """Terminate all user sessions"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # End all active sessions
|
||||
# self.terminate_all_sessions(user)
|
||||
#
|
||||
# # Mark sessions terminated
|
||||
# process.sessions_terminated = True
|
||||
# process.save()
|
||||
#
|
||||
# # Log session termination
|
||||
# self.log_session_termination(user)
|
||||
#
|
||||
# def notify_deactivation(self, activation):
|
||||
# """Send deactivation notifications"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Send notifications to relevant parties
|
||||
# self.send_deactivation_notifications(user)
|
||||
#
|
||||
# # Mark notifications sent
|
||||
# process.notifications_sent = True
|
||||
# process.save()
|
||||
#
|
||||
# def archive_user_account(self, activation):
|
||||
# """Archive user account"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Archive account data
|
||||
# self.archive_account_data(user)
|
||||
#
|
||||
# # Mark account archived
|
||||
# process.account_archived = True
|
||||
# process.save()
|
||||
#
|
||||
# # Update account status
|
||||
# self.update_account_status(user)
|
||||
#
|
||||
# def finalize_account_deactivation(self, activation):
|
||||
# """Finalize the account deactivation process"""
|
||||
# process = activation.process
|
||||
# user = process.user
|
||||
#
|
||||
# # Deactivate user account
|
||||
# user.is_active = False
|
||||
# user.save()
|
||||
#
|
||||
# # Mark deactivation completed
|
||||
# process.deactivation_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send final notifications
|
||||
# self.notify_deactivation_completion(user)
|
||||
#
|
||||
# def end_account_deactivation(self, activation):
|
||||
# """End the account deactivation workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate deactivation summary
|
||||
# self.generate_deactivation_summary(process.user)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_deactivation_start(self, user):
|
||||
# """Notify deactivation start"""
|
||||
# hr_staff = User.objects.filter(groups__name='HR Staff')
|
||||
# for staff in hr_staff:
|
||||
# send_mail(
|
||||
# subject=f'Account Deactivation: {user.get_full_name()}',
|
||||
# message=f'Account deactivation process started for {user.username}.',
|
||||
# from_email='accounts@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_deactivation_checklist(self, user):
|
||||
# """Create deactivation checklist"""
|
||||
# # This would create deactivation checklist
|
||||
# pass
|
||||
#
|
||||
# def backup_user_data(self, user):
|
||||
# """Backup user data"""
|
||||
# # This would backup all user data
|
||||
# pass
|
||||
#
|
||||
# def verify_backup_integrity(self, user):
|
||||
# """Verify backup integrity"""
|
||||
# # This would verify backup completeness
|
||||
# pass
|
||||
#
|
||||
# def revoke_permissions(self, user):
|
||||
# """Revoke all user permissions"""
|
||||
# # This would revoke all permissions
|
||||
# user.groups.clear()
|
||||
# user.user_permissions.clear()
|
||||
#
|
||||
# def log_access_revocation(self, user):
|
||||
# """Log access revocation"""
|
||||
# # This would log the access revocation
|
||||
# pass
|
||||
#
|
||||
# def terminate_all_sessions(self, user):
|
||||
# """Terminate all user sessions"""
|
||||
# user.user_sessions.filter(is_active=True).update(
|
||||
# is_active=False,
|
||||
# ended_at=timezone.now()
|
||||
# )
|
||||
#
|
||||
# def log_session_termination(self, user):
|
||||
# """Log session termination"""
|
||||
# # This would log session termination
|
||||
# pass
|
||||
#
|
||||
# def send_deactivation_notifications(self, user):
|
||||
# """Send deactivation notifications"""
|
||||
# # This would send notifications to relevant parties
|
||||
# pass
|
||||
#
|
||||
# def archive_account_data(self, user):
|
||||
# """Archive account data"""
|
||||
# # This would archive account data
|
||||
# pass
|
||||
#
|
||||
# def update_account_status(self, user):
|
||||
# """Update account status"""
|
||||
# # This would update account status
|
||||
# pass
|
||||
#
|
||||
# def notify_deactivation_completion(self, user):
|
||||
# """Notify deactivation completion"""
|
||||
# # This would notify completion
|
||||
# pass
|
||||
#
|
||||
# def generate_deactivation_summary(self, user):
|
||||
# """Generate deactivation summary"""
|
||||
# # This would generate deactivation summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def schedule_security_review(user_id):
|
||||
# """Background task to schedule security review"""
|
||||
# try:
|
||||
# user = User.objects.get(id=user_id)
|
||||
#
|
||||
# # Create security review task
|
||||
# # This would create a security review task
|
||||
#
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def cleanup_expired_sessions():
|
||||
# """Background task to cleanup expired sessions"""
|
||||
# try:
|
||||
# # Cleanup expired sessions
|
||||
# expired_sessions = UserSession.objects.filter(
|
||||
# expires_at__lt=timezone.now(),
|
||||
# is_active=True
|
||||
# )
|
||||
#
|
||||
# for session in expired_sessions:
|
||||
# session.end_session()
|
||||
#
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def audit_user_accounts():
|
||||
# """Background task to audit user accounts"""
|
||||
# try:
|
||||
# # This would perform periodic user account audits
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def enforce_password_expiry():
|
||||
# """Background task to enforce password expiry"""
|
||||
# try:
|
||||
# # This would enforce password expiry policies
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def generate_security_reports():
|
||||
# """Background task to generate security reports"""
|
||||
# try:
|
||||
# # This would generate periodic security reports
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
@ -223,3 +223,721 @@ class PasswordChangeForm(forms.Form):
|
||||
self.user.save()
|
||||
return self.user
|
||||
|
||||
|
||||
# from django import forms
|
||||
# from django.contrib.auth.forms import UserCreationForm, PasswordChangeForm
|
||||
# from django.contrib.auth.models import User, Group
|
||||
# from django.core.exceptions import ValidationError
|
||||
# from django.utils import timezone
|
||||
# from django.contrib.auth.password_validation import validate_password
|
||||
# from crispy_forms.helper import FormHelper
|
||||
# from crispy_forms.layout import Layout, Fieldset, Submit, Row, Column, HTML, Div
|
||||
# from crispy_forms.bootstrap import FormActions
|
||||
#
|
||||
# from .models import User, TwoFactorDevice, SocialAccount, UserSession, PasswordHistory
|
||||
#
|
||||
#
|
||||
# class UserRegistrationForm(UserCreationForm):
|
||||
# """
|
||||
# Form for user registration in onboarding workflow
|
||||
# """
|
||||
# first_name = forms.CharField(
|
||||
# max_length=150,
|
||||
# required=True,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# last_name = forms.CharField(
|
||||
# max_length=150,
|
||||
# required=True,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# email = forms.EmailField(
|
||||
# required=True,
|
||||
# widget=forms.EmailInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# employee_id = forms.CharField(
|
||||
# max_length=50,
|
||||
# required=False,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# department = forms.ModelChoiceField(
|
||||
# queryset=None, # Will be set in __init__
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# job_title = forms.CharField(
|
||||
# max_length=200,
|
||||
# required=True,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# phone_number = forms.CharField(
|
||||
# max_length=20,
|
||||
# required=False,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# start_date = forms.DateField(
|
||||
# required=True,
|
||||
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||||
# )
|
||||
# manager = forms.ModelChoiceField(
|
||||
# queryset=None, # Will be set in __init__
|
||||
# required=False,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = User
|
||||
# fields = [
|
||||
# 'username', 'first_name', 'last_name', 'email', 'employee_id',
|
||||
# 'department', 'job_title', 'phone_number', 'start_date', 'manager',
|
||||
# 'password1', 'password2'
|
||||
# ]
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# tenant = kwargs.pop('tenant', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# # Set querysets based on tenant
|
||||
# if tenant:
|
||||
# from core.models import Department
|
||||
# self.fields['department'].queryset = Department.objects.filter(tenant=tenant)
|
||||
# self.fields['manager'].queryset = User.objects.filter(
|
||||
# tenant=tenant,
|
||||
# is_active=True,
|
||||
# groups__name__in=['Managers', 'Department Heads']
|
||||
# )
|
||||
#
|
||||
# # Crispy forms helper
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'User Information',
|
||||
# Row(
|
||||
# Column('first_name', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('last_name', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# Row(
|
||||
# Column('username', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('email', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# Row(
|
||||
# Column('employee_id', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('phone_number', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# )
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Employment Information',
|
||||
# Row(
|
||||
# Column('department', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('job_title', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# Row(
|
||||
# Column('start_date', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('manager', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# )
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Security',
|
||||
# Row(
|
||||
# Column('password1', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('password2', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# )
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Register User', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'accounts:user_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean_email(self):
|
||||
# email = self.cleaned_data.get('email')
|
||||
# if User.objects.filter(email=email).exists():
|
||||
# raise ValidationError('A user with this email already exists.')
|
||||
# return email
|
||||
#
|
||||
# def clean_employee_id(self):
|
||||
# employee_id = self.cleaned_data.get('employee_id')
|
||||
# if employee_id and User.objects.filter(employee_id=employee_id).exists():
|
||||
# raise ValidationError('A user with this employee ID already exists.')
|
||||
# return employee_id
|
||||
#
|
||||
#
|
||||
# class AccountActivationForm(forms.Form):
|
||||
# """
|
||||
# Form for account activation in onboarding workflow
|
||||
# """
|
||||
# activation_code = forms.CharField(
|
||||
# max_length=100,
|
||||
# required=True,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# terms_accepted = forms.BooleanField(
|
||||
# required=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# privacy_policy_accepted = forms.BooleanField(
|
||||
# required=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Account Activation',
|
||||
# 'activation_code',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'terms_accepted',
|
||||
# HTML('<label class="form-check-label" for="id_terms_accepted">I accept the Terms of Service</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'privacy_policy_accepted',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_privacy_policy_accepted">I accept the Privacy Policy</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Activate Account', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'accounts:login\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class TwoFactorSetupForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for two-factor authentication setup
|
||||
# """
|
||||
# device_name = forms.CharField(
|
||||
# max_length=100,
|
||||
# required=True,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# device_type = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('totp', 'Authenticator App (TOTP)'),
|
||||
# ('sms', 'SMS'),
|
||||
# ('email', 'Email'),
|
||||
# ('backup_codes', 'Backup Codes')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# phone_number = forms.CharField(
|
||||
# max_length=20,
|
||||
# required=False,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# verification_code = forms.CharField(
|
||||
# max_length=10,
|
||||
# required=True,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = TwoFactorDevice
|
||||
# fields = ['device_name', 'device_type', 'phone_number']
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Two-Factor Authentication Setup',
|
||||
# 'device_name',
|
||||
# 'device_type',
|
||||
# 'phone_number',
|
||||
# HTML(
|
||||
# '<div class="alert alert-info">Enter the verification code from your authenticator app or device.</div>'),
|
||||
# 'verification_code'
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Setup Two-Factor Auth', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'accounts:security_settings\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean(self):
|
||||
# cleaned_data = super().clean()
|
||||
# device_type = cleaned_data.get('device_type')
|
||||
# phone_number = cleaned_data.get('phone_number')
|
||||
#
|
||||
# if device_type == 'sms' and not phone_number:
|
||||
# raise ValidationError('Phone number is required for SMS two-factor authentication.')
|
||||
#
|
||||
# return cleaned_data
|
||||
#
|
||||
#
|
||||
# class PermissionManagementForm(forms.Form):
|
||||
# """
|
||||
# Form for managing user permissions
|
||||
# """
|
||||
# groups = forms.ModelMultipleChoiceField(
|
||||
# queryset=Group.objects.all(),
|
||||
# required=False,
|
||||
# widget=forms.CheckboxSelectMultiple(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# is_staff = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# is_superuser = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# access_level = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('basic', 'Basic Access'),
|
||||
# ('standard', 'Standard Access'),
|
||||
# ('advanced', 'Advanced Access'),
|
||||
# ('admin', 'Administrator Access')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# department_access = forms.ModelMultipleChoiceField(
|
||||
# queryset=None, # Will be set in __init__
|
||||
# required=False,
|
||||
# widget=forms.CheckboxSelectMultiple(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# tenant = kwargs.pop('tenant', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# if tenant:
|
||||
# from core.models import Department
|
||||
# self.fields['department_access'].queryset = Department.objects.filter(tenant=tenant)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'User Permissions',
|
||||
# 'access_level',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'is_staff',
|
||||
# HTML('<label class="form-check-label" for="id_is_staff">Staff Status</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'is_superuser',
|
||||
# HTML('<label class="form-check-label" for="id_is_superuser">Superuser Status</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Group Memberships',
|
||||
# 'groups'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Department Access',
|
||||
# 'department_access'
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Update Permissions', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'accounts:user_detail\' user.id %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class SecurityAuditForm(forms.Form):
|
||||
# """
|
||||
# Form for security audit configuration
|
||||
# """
|
||||
# audit_type = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('comprehensive', 'Comprehensive Security Audit'),
|
||||
# ('password_policy', 'Password Policy Audit'),
|
||||
# ('access_review', 'Access Rights Review'),
|
||||
# ('session_audit', 'Session Security Audit'),
|
||||
# ('two_factor_audit', 'Two-Factor Authentication Audit')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# include_inactive_users = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# check_password_strength = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# review_login_attempts = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# analyze_session_security = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# generate_report = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Security Audit Configuration',
|
||||
# 'audit_type',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'include_inactive_users',
|
||||
# HTML('<label class="form-check-label" for="id_include_inactive_users">Include Inactive Users</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'check_password_strength',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_check_password_strength">Check Password Strength</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'review_login_attempts',
|
||||
# HTML('<label class="form-check-label" for="id_review_login_attempts">Review Login Attempts</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'analyze_session_security',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_analyze_session_security">Analyze Session Security</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'generate_report',
|
||||
# HTML('<label class="form-check-label" for="id_generate_report">Generate Audit Report</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Start Security Audit', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'accounts:security_dashboard\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class SessionManagementForm(forms.Form):
|
||||
# """
|
||||
# Form for session management configuration
|
||||
# """
|
||||
# session_timeout = forms.IntegerField(
|
||||
# min_value=5,
|
||||
# max_value=1440,
|
||||
# initial=30,
|
||||
# widget=forms.NumberInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# max_concurrent_sessions = forms.IntegerField(
|
||||
# min_value=1,
|
||||
# max_value=10,
|
||||
# initial=3,
|
||||
# widget=forms.NumberInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# require_secure_cookies = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# enable_session_monitoring = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# log_session_events = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# auto_logout_inactive = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Session Configuration',
|
||||
# Row(
|
||||
# Column('session_timeout', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('max_concurrent_sessions', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'require_secure_cookies',
|
||||
# HTML('<label class="form-check-label" for="id_require_secure_cookies">Require Secure Cookies</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'enable_session_monitoring',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_enable_session_monitoring">Enable Session Monitoring</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'log_session_events',
|
||||
# HTML('<label class="form-check-label" for="id_log_session_events">Log Session Events</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'auto_logout_inactive',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_auto_logout_inactive">Auto Logout Inactive Users</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Update Session Settings', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'accounts:security_settings\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class ComplianceCheckForm(forms.Form):
|
||||
# """
|
||||
# Form for compliance verification
|
||||
# """
|
||||
# compliance_standards = forms.MultipleChoiceField(
|
||||
# choices=[
|
||||
# ('hipaa', 'HIPAA Compliance'),
|
||||
# ('gdpr', 'GDPR Compliance'),
|
||||
# ('sox', 'SOX Compliance'),
|
||||
# ('pci_dss', 'PCI DSS Compliance'),
|
||||
# ('iso27001', 'ISO 27001 Compliance')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.CheckboxSelectMultiple(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# check_password_policies = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# verify_access_controls = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# audit_user_permissions = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# check_data_encryption = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# generate_compliance_report = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Compliance Standards',
|
||||
# 'compliance_standards'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Compliance Checks',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'check_password_policies',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_check_password_policies">Check Password Policies</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'verify_access_controls',
|
||||
# HTML('<label class="form-check-label" for="id_verify_access_controls">Verify Access Controls</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'audit_user_permissions',
|
||||
# HTML('<label class="form-check-label" for="id_audit_user_permissions">Audit User Permissions</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'check_data_encryption',
|
||||
# HTML('<label class="form-check-label" for="id_check_data_encryption">Check Data Encryption</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'generate_compliance_report',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_generate_compliance_report">Generate Compliance Report</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Start Compliance Check', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'accounts:compliance_dashboard\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class AccountDeactivationForm(forms.Form):
|
||||
# """
|
||||
# Form for account deactivation
|
||||
# """
|
||||
# deactivation_reason = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('termination', 'Employment Termination'),
|
||||
# ('resignation', 'Employee Resignation'),
|
||||
# ('transfer', 'Department Transfer'),
|
||||
# ('leave', 'Extended Leave'),
|
||||
# ('security', 'Security Concerns'),
|
||||
# ('other', 'Other Reason')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# deactivation_date = forms.DateField(
|
||||
# required=True,
|
||||
# initial=timezone.now().date(),
|
||||
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||||
# )
|
||||
# backup_data = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# transfer_ownership = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# new_owner = forms.ModelChoiceField(
|
||||
# queryset=None, # Will be set in __init__
|
||||
# required=False,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# notify_stakeholders = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# additional_notes = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 4})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# tenant = kwargs.pop('tenant', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# if tenant:
|
||||
# self.fields['new_owner'].queryset = User.objects.filter(
|
||||
# tenant=tenant,
|
||||
# is_active=True
|
||||
# ).exclude(id=self.instance.id if hasattr(self, 'instance') else None)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Deactivation Details',
|
||||
# Row(
|
||||
# Column('deactivation_reason', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('deactivation_date', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'additional_notes'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Data Management',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'backup_data',
|
||||
# HTML('<label class="form-check-label" for="id_backup_data">Backup User Data</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'transfer_ownership',
|
||||
# HTML('<label class="form-check-label" for="id_transfer_ownership">Transfer Data Ownership</label>'),
|
||||
# HTML('</div>'),
|
||||
# 'new_owner'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Notifications',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'notify_stakeholders',
|
||||
# HTML('<label class="form-check-label" for="id_notify_stakeholders">Notify Stakeholders</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Deactivate Account', css_class='btn btn-danger'),
|
||||
# HTML('<a href="{% url \'accounts:user_detail\' user.id %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean(self):
|
||||
# cleaned_data = super().clean()
|
||||
# transfer_ownership = cleaned_data.get('transfer_ownership')
|
||||
# new_owner = cleaned_data.get('new_owner')
|
||||
#
|
||||
# if transfer_ownership and not new_owner:
|
||||
# raise ValidationError('New owner must be selected when transferring ownership.')
|
||||
#
|
||||
# return cleaned_data
|
||||
#
|
||||
#
|
||||
# class PasswordResetForm(forms.Form):
|
||||
# """
|
||||
# Form for password reset
|
||||
# """
|
||||
# email = forms.EmailField(
|
||||
# required=True,
|
||||
# widget=forms.EmailInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Password Reset',
|
||||
# 'email',
|
||||
# HTML(
|
||||
# '<div class="alert alert-info">Enter your email address and we will send you a link to reset your password.</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Send Reset Link', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'accounts:login\' %}" class="btn btn-secondary">Back to Login</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean_email(self):
|
||||
# email = self.cleaned_data.get('email')
|
||||
# if not User.objects.filter(email=email, is_active=True).exists():
|
||||
# raise ValidationError('No active user found with this email address.')
|
||||
# return email
|
||||
#
|
||||
#
|
||||
# class PasswordChangeForm(PasswordChangeForm):
|
||||
# """
|
||||
# Enhanced password change form
|
||||
# """
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# # Add CSS classes
|
||||
# for field in self.fields.values():
|
||||
# field.widget.attrs['class'] = 'form-control'
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Change Password',
|
||||
# 'old_password',
|
||||
# 'new_password1',
|
||||
# 'new_password2',
|
||||
# HTML(
|
||||
# '<div class="alert alert-info">Your password must contain at least 8 characters and cannot be too similar to your other personal information.</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Change Password', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'accounts:profile\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
|
||||
@ -2474,3 +2474,650 @@ def upload_avatar(request, pk):
|
||||
#
|
||||
#
|
||||
#
|
||||
# from django.shortcuts import render, redirect, get_object_or_404
|
||||
# from django.contrib.auth import login, logout, authenticate
|
||||
# from django.contrib.auth.decorators import login_required, permission_required
|
||||
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||
# from django.contrib import messages
|
||||
# from django.views.generic import (
|
||||
# CreateView, UpdateView, DeleteView, DetailView, ListView, FormView
|
||||
# )
|
||||
# from django.urls import reverse_lazy, reverse
|
||||
# from django.http import JsonResponse, HttpResponse
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction, models
|
||||
# from django.core.mail import send_mail
|
||||
# from django.conf import settings
|
||||
# from django.contrib.auth.models import Group
|
||||
# from viewflow.workflow.flow.views import CreateProcessView, UpdateProcessView
|
||||
#
|
||||
# from .models import User, TwoFactorDevice, SocialAccount, UserSession, PasswordHistory
|
||||
# from .forms import (
|
||||
# UserRegistrationForm, AccountActivationForm, TwoFactorSetupForm,
|
||||
# PermissionManagementForm, SecurityAuditForm, SessionManagementForm,
|
||||
# ComplianceCheckForm, AccountDeactivationForm, PasswordResetForm,
|
||||
# PasswordChangeForm
|
||||
# )
|
||||
# from .flows import UserOnboardingFlow, SecurityManagementFlow, AccountDeactivationFlow
|
||||
#
|
||||
#
|
||||
# class UserRegistrationView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
||||
# """
|
||||
# View for user registration in onboarding workflow
|
||||
# """
|
||||
# model = User
|
||||
# form_class = UserRegistrationForm
|
||||
# template_name = 'accounts/user_registration.html'
|
||||
# permission_required = 'accounts.can_register_users'
|
||||
# flow_class = UserOnboardingFlow
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['tenant'] = self.request.user.tenant
|
||||
# return kwargs
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# with transaction.atomic():
|
||||
# # Create user
|
||||
# user = form.save(commit=False)
|
||||
# user.tenant = self.request.user.tenant
|
||||
# user.is_active = False # Will be activated in workflow
|
||||
# user.created_by = self.request.user
|
||||
# user.save()
|
||||
#
|
||||
# # Start onboarding workflow
|
||||
# process = self.flow_class.start.run(
|
||||
# user=user,
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'User registration initiated for {user.get_full_name()}. '
|
||||
# f'Onboarding workflow started.'
|
||||
# )
|
||||
#
|
||||
# return redirect('accounts:user_detail', pk=user.pk)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Register New User'
|
||||
# context['breadcrumbs'] = [
|
||||
# {'name': 'Home', 'url': reverse('core:dashboard')},
|
||||
# {'name': 'Users', 'url': reverse('accounts:user_list')},
|
||||
# {'name': 'Register User', 'url': ''}
|
||||
# ]
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class AccountActivationView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||
# """
|
||||
# View for account activation in onboarding workflow
|
||||
# """
|
||||
# form_class = AccountActivationForm
|
||||
# template_name = 'accounts/account_activation.html'
|
||||
# permission_required = 'accounts.can_activate_accounts'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('accounts:user_detail', kwargs={'pk': self.kwargs['pk']})
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# user = get_object_or_404(User, pk=self.kwargs['pk'])
|
||||
# activation_code = form.cleaned_data['activation_code']
|
||||
#
|
||||
# # Verify activation code (implement your verification logic)
|
||||
# if self.verify_activation_code(user, activation_code):
|
||||
# user.is_active = True
|
||||
# user.email_verified = True
|
||||
# user.activated_at = timezone.now()
|
||||
# user.save()
|
||||
#
|
||||
# # Send welcome email
|
||||
# self.send_welcome_email(user)
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Account activated successfully for {user.get_full_name()}.'
|
||||
# )
|
||||
# else:
|
||||
# messages.error(self.request, 'Invalid activation code.')
|
||||
# return self.form_invalid(form)
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def verify_activation_code(self, user, code):
|
||||
# """Verify activation code (implement your logic)"""
|
||||
# # This would implement actual verification logic
|
||||
# return True
|
||||
#
|
||||
# def send_welcome_email(self, user):
|
||||
# """Send welcome email to activated user"""
|
||||
# if user.email:
|
||||
# send_mail(
|
||||
# subject='Welcome to Hospital Management System',
|
||||
# message=f'Welcome {user.get_full_name()}! Your account has been activated.',
|
||||
# from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
# recipient_list=[user.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['user'] = get_object_or_404(User, pk=self.kwargs['pk'])
|
||||
# context['title'] = 'Activate Account'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class TwoFactorSetupView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||||
# """
|
||||
# View for two-factor authentication setup
|
||||
# """
|
||||
# model = TwoFactorDevice
|
||||
# form_class = TwoFactorSetupForm
|
||||
# template_name = 'accounts/two_factor_setup.html'
|
||||
# permission_required = 'accounts.can_setup_two_factor'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('accounts:security_settings')
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# device = form.save(commit=False)
|
||||
# device.user = self.request.user
|
||||
# device.is_active = True
|
||||
# device.created_at = timezone.now()
|
||||
#
|
||||
# # Generate device-specific configuration
|
||||
# device.configuration = self.generate_device_config(device.device_type)
|
||||
# device.save()
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# 'Two-factor authentication has been set up successfully.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def generate_device_config(self, device_type):
|
||||
# """Generate device-specific configuration"""
|
||||
# if device_type == 'totp':
|
||||
# # Generate TOTP secret
|
||||
# import pyotp
|
||||
# return {'secret': pyotp.random_base32()}
|
||||
# elif device_type == 'backup_codes':
|
||||
# # Generate backup codes
|
||||
# import secrets
|
||||
# codes = [secrets.token_hex(4) for _ in range(10)]
|
||||
# return {'codes': codes}
|
||||
# return {}
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Setup Two-Factor Authentication'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class PermissionManagementView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||
# """
|
||||
# View for managing user permissions
|
||||
# """
|
||||
# form_class = PermissionManagementForm
|
||||
# template_name = 'accounts/permission_management.html'
|
||||
# permission_required = 'accounts.can_manage_permissions'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('accounts:user_detail', kwargs={'pk': self.kwargs['pk']})
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['tenant'] = self.request.user.tenant
|
||||
# return kwargs
|
||||
#
|
||||
# def get_initial(self):
|
||||
# user = get_object_or_404(User, pk=self.kwargs['pk'])
|
||||
# return {
|
||||
# 'groups': user.groups.all(),
|
||||
# 'is_staff': user.is_staff,
|
||||
# 'is_superuser': user.is_superuser,
|
||||
# 'access_level': getattr(user, 'access_level', 'basic'),
|
||||
# 'department_access': getattr(user, 'department_access', [])
|
||||
# }
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# user = get_object_or_404(User, pk=self.kwargs['pk'])
|
||||
#
|
||||
# with transaction.atomic():
|
||||
# # Update user permissions
|
||||
# user.groups.set(form.cleaned_data['groups'])
|
||||
# user.is_staff = form.cleaned_data['is_staff']
|
||||
# user.is_superuser = form.cleaned_data['is_superuser']
|
||||
# user.access_level = form.cleaned_data['access_level']
|
||||
# user.save()
|
||||
#
|
||||
# # Log permission changes
|
||||
# self.log_permission_changes(user, form.cleaned_data)
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Permissions updated successfully for {user.get_full_name()}.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def log_permission_changes(self, user, changes):
|
||||
# """Log permission changes for audit"""
|
||||
# from core.models import AuditLogEntry
|
||||
# AuditLogEntry.objects.create(
|
||||
# tenant=user.tenant,
|
||||
# user=self.request.user,
|
||||
# event_type='PERMISSION_CHANGE',
|
||||
# action='UPDATE',
|
||||
# object_type='User',
|
||||
# object_id=str(user.id),
|
||||
# details=changes,
|
||||
# ip_address=self.request.META.get('REMOTE_ADDR'),
|
||||
# user_agent=self.request.META.get('HTTP_USER_AGENT', '')
|
||||
# )
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['user'] = get_object_or_404(User, pk=self.kwargs['pk'])
|
||||
# context['title'] = 'Manage Permissions'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class SecurityAuditView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||
# """
|
||||
# View for security audit configuration
|
||||
# """
|
||||
# form_class = SecurityAuditForm
|
||||
# template_name = 'accounts/security_audit.html'
|
||||
# permission_required = 'accounts.can_audit_security'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('accounts:security_dashboard')
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# audit_config = form.cleaned_data
|
||||
#
|
||||
# # Start security audit workflow
|
||||
# process = SecurityManagementFlow.start.run(
|
||||
# user=self.request.user,
|
||||
# audit_config=audit_config,
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# 'Security audit has been initiated. You will receive a notification when complete.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Security Audit'
|
||||
# context['recent_audits'] = self.get_recent_audits()
|
||||
# return context
|
||||
#
|
||||
# def get_recent_audits(self):
|
||||
# """Get recent security audits"""
|
||||
# # This would return recent audit records
|
||||
# return []
|
||||
#
|
||||
#
|
||||
# class SessionManagementView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||
# """
|
||||
# View for session management configuration
|
||||
# """
|
||||
# form_class = SessionManagementForm
|
||||
# template_name = 'accounts/session_management.html'
|
||||
# permission_required = 'accounts.can_manage_sessions'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('accounts:security_settings')
|
||||
#
|
||||
# def get_initial(self):
|
||||
# # Get current session settings
|
||||
# return {
|
||||
# 'session_timeout': getattr(settings, 'SESSION_COOKIE_AGE', 1800) // 60,
|
||||
# 'max_concurrent_sessions': 3,
|
||||
# 'require_secure_cookies': getattr(settings, 'SESSION_COOKIE_SECURE', True),
|
||||
# 'enable_session_monitoring': True,
|
||||
# 'log_session_events': True,
|
||||
# 'auto_logout_inactive': True
|
||||
# }
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# config = form.cleaned_data
|
||||
#
|
||||
# # Update session configuration
|
||||
# self.update_session_config(config)
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# 'Session management settings updated successfully.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def update_session_config(self, config):
|
||||
# """Update session configuration"""
|
||||
# # This would update session configuration
|
||||
# pass
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Session Management'
|
||||
# context['active_sessions'] = self.get_active_sessions()
|
||||
# return context
|
||||
#
|
||||
# def get_active_sessions(self):
|
||||
# """Get active user sessions"""
|
||||
# return UserSession.objects.filter(
|
||||
# user=self.request.user,
|
||||
# is_active=True
|
||||
# ).order_by('-created_at')
|
||||
#
|
||||
#
|
||||
# class ComplianceCheckView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||
# """
|
||||
# View for compliance verification
|
||||
# """
|
||||
# form_class = ComplianceCheckForm
|
||||
# template_name = 'accounts/compliance_check.html'
|
||||
# permission_required = 'accounts.can_verify_compliance'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('accounts:compliance_dashboard')
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# compliance_config = form.cleaned_data
|
||||
#
|
||||
# # Start compliance check
|
||||
# self.start_compliance_check(compliance_config)
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# 'Compliance check has been initiated. Results will be available shortly.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def start_compliance_check(self, config):
|
||||
# """Start compliance verification process"""
|
||||
# # This would start compliance checking
|
||||
# pass
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Compliance Check'
|
||||
# context['compliance_status'] = self.get_compliance_status()
|
||||
# return context
|
||||
#
|
||||
# def get_compliance_status(self):
|
||||
# """Get current compliance status"""
|
||||
# return {
|
||||
# 'hipaa': 'compliant',
|
||||
# 'gdpr': 'compliant',
|
||||
# 'sox': 'pending',
|
||||
# 'pci_dss': 'non_compliant',
|
||||
# 'iso27001': 'compliant'
|
||||
# }
|
||||
#
|
||||
#
|
||||
# class AccountDeactivationView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||
# """
|
||||
# View for account deactivation
|
||||
# """
|
||||
# form_class = AccountDeactivationForm
|
||||
# template_name = 'accounts/account_deactivation.html'
|
||||
# permission_required = 'accounts.can_deactivate_accounts'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('accounts:user_list')
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['tenant'] = self.request.user.tenant
|
||||
# return kwargs
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# user = get_object_or_404(User, pk=self.kwargs['pk'])
|
||||
# deactivation_config = form.cleaned_data
|
||||
#
|
||||
# # Start account deactivation workflow
|
||||
# process = AccountDeactivationFlow.start.run(
|
||||
# user=user,
|
||||
# deactivation_config=deactivation_config,
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Account deactivation initiated for {user.get_full_name()}.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['user'] = get_object_or_404(User, pk=self.kwargs['pk'])
|
||||
# context['title'] = 'Deactivate Account'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class UserListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
# """
|
||||
# View for listing users
|
||||
# """
|
||||
# model = User
|
||||
# template_name = 'accounts/user_list.html'
|
||||
# context_object_name = 'users'
|
||||
# permission_required = 'accounts.view_user'
|
||||
# paginate_by = 25
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# queryset = User.objects.filter(tenant=self.request.user.tenant)
|
||||
#
|
||||
# # Apply filters
|
||||
# search = self.request.GET.get('search')
|
||||
# if search:
|
||||
# queryset = queryset.filter(
|
||||
# models.Q(first_name__icontains=search) |
|
||||
# models.Q(last_name__icontains=search) |
|
||||
# models.Q(email__icontains=search) |
|
||||
# models.Q(username__icontains=search)
|
||||
# )
|
||||
#
|
||||
# department = self.request.GET.get('department')
|
||||
# if department:
|
||||
# queryset = queryset.filter(department_id=department)
|
||||
#
|
||||
# status = self.request.GET.get('status')
|
||||
# if status == 'active':
|
||||
# queryset = queryset.filter(is_active=True)
|
||||
# elif status == 'inactive':
|
||||
# queryset = queryset.filter(is_active=False)
|
||||
#
|
||||
# return queryset.order_by('last_name', 'first_name')
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Users'
|
||||
# context['departments'] = self.get_departments()
|
||||
# context['search'] = self.request.GET.get('search', '')
|
||||
# context['selected_department'] = self.request.GET.get('department', '')
|
||||
# context['selected_status'] = self.request.GET.get('status', '')
|
||||
# return context
|
||||
#
|
||||
# def get_departments(self):
|
||||
# """Get departments for filter"""
|
||||
# from core.models import Department
|
||||
# return Department.objects.filter(tenant=self.request.user.tenant)
|
||||
#
|
||||
#
|
||||
# class UserDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||
# """
|
||||
# View for user details
|
||||
# """
|
||||
# model = User
|
||||
# template_name = 'accounts/user_detail.html'
|
||||
# context_object_name = 'user'
|
||||
# permission_required = 'accounts.view_user'
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# return User.objects.filter(tenant=self.request.user.tenant)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# user = self.object
|
||||
# context['title'] = f'{user.get_full_name()}'
|
||||
# context['two_factor_devices'] = user.two_factor_devices.filter(is_active=True)
|
||||
# context['recent_sessions'] = user.user_sessions.order_by('-created_at')[:5]
|
||||
# context['password_history'] = user.password_history.order_by('-created_at')[:5]
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class SecurityDashboardView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
# """
|
||||
# View for security dashboard
|
||||
# """
|
||||
# template_name = 'accounts/security_dashboard.html'
|
||||
# permission_required = 'accounts.view_security_dashboard'
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# return None
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Security Dashboard'
|
||||
# context['security_metrics'] = self.get_security_metrics()
|
||||
# context['recent_alerts'] = self.get_recent_security_alerts()
|
||||
# context['compliance_status'] = self.get_compliance_status()
|
||||
# return context
|
||||
#
|
||||
# def get_security_metrics(self):
|
||||
# """Get security metrics"""
|
||||
# tenant = self.request.user.tenant
|
||||
# return {
|
||||
# 'total_users': User.objects.filter(tenant=tenant).count(),
|
||||
# 'active_users': User.objects.filter(tenant=tenant, is_active=True).count(),
|
||||
# 'users_with_2fa': User.objects.filter(
|
||||
# tenant=tenant,
|
||||
# two_factor_devices__is_active=True
|
||||
# ).distinct().count(),
|
||||
# 'failed_logins_today': 0, # Would be calculated from audit logs
|
||||
# 'password_expiring_soon': 0 # Would be calculated from password history
|
||||
# }
|
||||
#
|
||||
# def get_recent_security_alerts(self):
|
||||
# """Get recent security alerts"""
|
||||
# # This would return recent security alerts
|
||||
# return []
|
||||
#
|
||||
# def get_compliance_status(self):
|
||||
# """Get compliance status"""
|
||||
# return {
|
||||
# 'hipaa': 'compliant',
|
||||
# 'gdpr': 'compliant',
|
||||
# 'sox': 'pending',
|
||||
# 'pci_dss': 'non_compliant',
|
||||
# 'iso27001': 'compliant'
|
||||
# }
|
||||
#
|
||||
#
|
||||
# # AJAX Views
|
||||
# @login_required
|
||||
# @permission_required('accounts.can_manage_sessions')
|
||||
# def terminate_session_ajax(request, session_id):
|
||||
# """AJAX view to terminate user session"""
|
||||
# if request.method == 'POST':
|
||||
# try:
|
||||
# session = UserSession.objects.get(
|
||||
# id=session_id,
|
||||
# user__tenant=request.user.tenant
|
||||
# )
|
||||
# session.end_session()
|
||||
#
|
||||
# return JsonResponse({
|
||||
# 'success': True,
|
||||
# 'message': 'Session terminated successfully.'
|
||||
# })
|
||||
# except UserSession.DoesNotExist:
|
||||
# return JsonResponse({
|
||||
# 'success': False,
|
||||
# 'message': 'Session not found.'
|
||||
# })
|
||||
#
|
||||
# return JsonResponse({'success': False, 'message': 'Invalid request.'})
|
||||
#
|
||||
#
|
||||
# @login_required
|
||||
# @permission_required('accounts.can_reset_passwords')
|
||||
# def reset_password_ajax(request, user_id):
|
||||
# """AJAX view to reset user password"""
|
||||
# if request.method == 'POST':
|
||||
# try:
|
||||
# user = User.objects.get(
|
||||
# id=user_id,
|
||||
# tenant=request.user.tenant
|
||||
# )
|
||||
#
|
||||
# # Generate temporary password
|
||||
# import secrets
|
||||
# temp_password = secrets.token_urlsafe(12)
|
||||
# user.set_password(temp_password)
|
||||
# user.must_change_password = True
|
||||
# user.save()
|
||||
#
|
||||
# # Send password reset email
|
||||
# send_mail(
|
||||
# subject='Password Reset',
|
||||
# message=f'Your temporary password is: {temp_password}',
|
||||
# from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
# recipient_list=[user.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# return JsonResponse({
|
||||
# 'success': True,
|
||||
# 'message': 'Password reset successfully. User will receive email with temporary password.'
|
||||
# })
|
||||
# except User.DoesNotExist:
|
||||
# return JsonResponse({
|
||||
# 'success': False,
|
||||
# 'message': 'User not found.'
|
||||
# })
|
||||
#
|
||||
# return JsonResponse({'success': False, 'message': 'Invalid request.'})
|
||||
#
|
||||
#
|
||||
# @login_required
|
||||
# def user_search_ajax(request):
|
||||
# """AJAX view for user search"""
|
||||
# query = request.GET.get('q', '')
|
||||
# if len(query) < 2:
|
||||
# return JsonResponse({'users': []})
|
||||
#
|
||||
# users = User.objects.filter(
|
||||
# tenant=request.user.tenant,
|
||||
# is_active=True
|
||||
# ).filter(
|
||||
# models.Q(first_name__icontains=query) |
|
||||
# models.Q(last_name__icontains=query) |
|
||||
# models.Q(email__icontains=query) |
|
||||
# models.Q(username__icontains=query)
|
||||
# )[:10]
|
||||
#
|
||||
# user_data = [
|
||||
# {
|
||||
# 'id': user.id,
|
||||
# 'name': user.get_full_name(),
|
||||
# 'email': user.email,
|
||||
# 'department': user.department.name if user.department else ''
|
||||
# }
|
||||
# for user in users
|
||||
# ]
|
||||
#
|
||||
# return JsonResponse({'users': user_data})
|
||||
#
|
||||
|
||||
BIN
analytics/.DS_Store
vendored
Normal file
BIN
analytics/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
analytics/__pycache__/flows.cpython-312.pyc
Normal file
BIN
analytics/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
845
analytics/flows.py
Normal file
845
analytics/flows.py
Normal file
@ -0,0 +1,845 @@
|
||||
# """
|
||||
# Viewflow workflows for analytics app.
|
||||
# Provides report generation, dashboard management, and data analysis workflows.
|
||||
# """
|
||||
#
|
||||
# from viewflow import Flow, lock
|
||||
# from viewflow.base import this, flow_func
|
||||
# from viewflow.contrib import celery
|
||||
# from viewflow.decorators import flow_view
|
||||
# from viewflow.fields import CharField, ModelField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.models import Process, Task
|
||||
# from django.contrib.auth.models import User
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import Dashboard, DashboardWidget, DataSource, Report, ReportExecution, MetricDefinition, MetricValue
|
||||
# from .views import (
|
||||
# ReportGenerationView, DashboardCreationView, DataSourceConfigurationView,
|
||||
# MetricCalculationView, AnalysisView, VisualizationView, DistributionView,
|
||||
# SchedulingView, QualityAssuranceView, PerformanceOptimizationView
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class ReportGenerationProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for report generation
|
||||
# """
|
||||
# report = ModelField(Report, help_text='Associated report')
|
||||
#
|
||||
# # Process status tracking
|
||||
# report_requested = models.BooleanField(default=False)
|
||||
# data_extracted = models.BooleanField(default=False)
|
||||
# data_processed = models.BooleanField(default=False)
|
||||
# report_generated = models.BooleanField(default=False)
|
||||
# quality_checked = models.BooleanField(default=False)
|
||||
# report_distributed = models.BooleanField(default=False)
|
||||
# generation_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Report Generation Process'
|
||||
# verbose_name_plural = 'Report Generation Processes'
|
||||
#
|
||||
#
|
||||
# class ReportGenerationFlow(Flow):
|
||||
# """
|
||||
# Report Generation Workflow
|
||||
#
|
||||
# This flow manages automated report generation including
|
||||
# data extraction, processing, quality checks, and distribution.
|
||||
# """
|
||||
#
|
||||
# process_class = ReportGenerationProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_report_generation)
|
||||
# .Next(this.request_report)
|
||||
# )
|
||||
#
|
||||
# request_report = (
|
||||
# flow_view(ReportGenerationView)
|
||||
# .Permission('analytics.can_generate_reports')
|
||||
# .Next(this.extract_data)
|
||||
# )
|
||||
#
|
||||
# extract_data = (
|
||||
# flow_func(this.perform_data_extraction)
|
||||
# .Next(this.process_data)
|
||||
# )
|
||||
#
|
||||
# process_data = (
|
||||
# flow_func(this.perform_data_processing)
|
||||
# .Next(this.generate_report)
|
||||
# )
|
||||
#
|
||||
# generate_report = (
|
||||
# flow_func(this.create_report_output)
|
||||
# .Next(this.check_quality)
|
||||
# )
|
||||
#
|
||||
# check_quality = (
|
||||
# flow_view(QualityAssuranceView)
|
||||
# .Permission('analytics.can_check_quality')
|
||||
# .Next(this.distribute_report)
|
||||
# )
|
||||
#
|
||||
# distribute_report = (
|
||||
# flow_view(DistributionView)
|
||||
# .Permission('analytics.can_distribute_reports')
|
||||
# .Next(this.complete_generation)
|
||||
# )
|
||||
#
|
||||
# complete_generation = (
|
||||
# flow_func(this.finalize_report_generation)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_report_generation)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_report_generation(self, activation):
|
||||
# """Initialize the report generation process"""
|
||||
# process = activation.process
|
||||
# report = process.report
|
||||
#
|
||||
# # Create report execution record
|
||||
# execution = ReportExecution.objects.create(
|
||||
# report=report,
|
||||
# execution_type='MANUAL',
|
||||
# status='PENDING'
|
||||
# )
|
||||
#
|
||||
# # Send generation notification
|
||||
# self.notify_generation_start(report, execution)
|
||||
#
|
||||
# def perform_data_extraction(self, activation):
|
||||
# """Extract data from configured sources"""
|
||||
# process = activation.process
|
||||
# report = process.report
|
||||
#
|
||||
# # Extract data from all configured sources
|
||||
# extracted_data = self.extract_report_data(report)
|
||||
#
|
||||
# # Mark data extracted
|
||||
# process.data_extracted = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store extracted data
|
||||
# self.store_extracted_data(report, extracted_data)
|
||||
#
|
||||
# def perform_data_processing(self, activation):
|
||||
# """Process and transform extracted data"""
|
||||
# process = activation.process
|
||||
# report = process.report
|
||||
#
|
||||
# # Process data according to report configuration
|
||||
# processed_data = self.process_report_data(report)
|
||||
#
|
||||
# # Mark data processed
|
||||
# process.data_processed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store processed data
|
||||
# self.store_processed_data(report, processed_data)
|
||||
#
|
||||
# def create_report_output(self, activation):
|
||||
# """Generate report output in specified format"""
|
||||
# process = activation.process
|
||||
# report = process.report
|
||||
#
|
||||
# # Generate report in configured format
|
||||
# report_output = self.generate_report_output(report)
|
||||
#
|
||||
# # Mark report generated
|
||||
# process.report_generated = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store report output
|
||||
# self.store_report_output(report, report_output)
|
||||
#
|
||||
# def finalize_report_generation(self, activation):
|
||||
# """Finalize the report generation process"""
|
||||
# process = activation.process
|
||||
# report = process.report
|
||||
#
|
||||
# # Update report execution status
|
||||
# execution = ReportExecution.objects.filter(
|
||||
# report=report,
|
||||
# status='RUNNING'
|
||||
# ).first()
|
||||
#
|
||||
# if execution:
|
||||
# execution.status = 'COMPLETED'
|
||||
# execution.completed_at = timezone.now()
|
||||
# execution.save()
|
||||
#
|
||||
# # Mark generation completed
|
||||
# process.generation_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_generation_completion(report)
|
||||
#
|
||||
# # Schedule next execution if recurring
|
||||
# self.schedule_next_execution(report)
|
||||
#
|
||||
# def end_report_generation(self, activation):
|
||||
# """End the report generation workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate generation summary
|
||||
# self.generate_generation_summary(process.report)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_generation_start(self, report, execution):
|
||||
# """Notify report generation start"""
|
||||
# analytics_team = User.objects.filter(groups__name='Analytics Team')
|
||||
# for staff in analytics_team:
|
||||
# send_mail(
|
||||
# subject=f'Report Generation Started: {report.name}',
|
||||
# message=f'Report generation process started for "{report.name}".',
|
||||
# from_email='analytics@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def extract_report_data(self, report):
|
||||
# """Extract data from report sources"""
|
||||
# # This would implement data extraction logic
|
||||
# return {'status': 'extracted', 'records': 1000}
|
||||
#
|
||||
# def store_extracted_data(self, report, data):
|
||||
# """Store extracted data"""
|
||||
# # This would store extracted data
|
||||
# pass
|
||||
#
|
||||
# def process_report_data(self, report):
|
||||
# """Process and transform report data"""
|
||||
# # This would implement data processing logic
|
||||
# return {'status': 'processed', 'records': 1000}
|
||||
#
|
||||
# def store_processed_data(self, report, data):
|
||||
# """Store processed data"""
|
||||
# # This would store processed data
|
||||
# pass
|
||||
#
|
||||
# def generate_report_output(self, report):
|
||||
# """Generate report output"""
|
||||
# # This would generate report in specified format
|
||||
# return {'status': 'generated', 'file_path': '/reports/output.pdf'}
|
||||
#
|
||||
# def store_report_output(self, report, output):
|
||||
# """Store report output"""
|
||||
# # This would store report output
|
||||
# pass
|
||||
#
|
||||
# def notify_generation_completion(self, report):
|
||||
# """Notify report generation completion"""
|
||||
# # Notify report subscribers
|
||||
# for subscriber in report.subscribers.all():
|
||||
# if subscriber.email:
|
||||
# send_mail(
|
||||
# subject=f'Report Ready: {report.name}',
|
||||
# message=f'Your report "{report.name}" is ready for download.',
|
||||
# from_email='analytics@hospital.com',
|
||||
# recipient_list=[subscriber.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def schedule_next_execution(self, report):
|
||||
# """Schedule next report execution if recurring"""
|
||||
# if report.is_scheduled and report.schedule_config:
|
||||
# # Schedule next execution
|
||||
# schedule_report_execution.apply_async(
|
||||
# args=[report.report_id],
|
||||
# countdown=report.schedule_config.get('interval', 86400)
|
||||
# )
|
||||
#
|
||||
# def generate_generation_summary(self, report):
|
||||
# """Generate report generation summary"""
|
||||
# # This would generate generation summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class DashboardManagementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for dashboard management
|
||||
# """
|
||||
# dashboard = ModelField(Dashboard, help_text='Associated dashboard')
|
||||
#
|
||||
# # Process status tracking
|
||||
# dashboard_requested = models.BooleanField(default=False)
|
||||
# data_sources_configured = models.BooleanField(default=False)
|
||||
# widgets_created = models.BooleanField(default=False)
|
||||
# layout_configured = models.BooleanField(default=False)
|
||||
# permissions_set = models.BooleanField(default=False)
|
||||
# dashboard_tested = models.BooleanField(default=False)
|
||||
# dashboard_deployed = models.BooleanField(default=False)
|
||||
# management_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Dashboard Management Process'
|
||||
# verbose_name_plural = 'Dashboard Management Processes'
|
||||
#
|
||||
#
|
||||
# class DashboardManagementFlow(Flow):
|
||||
# """
|
||||
# Dashboard Management Workflow
|
||||
#
|
||||
# This flow manages dashboard creation, configuration,
|
||||
# and deployment including widgets and data sources.
|
||||
# """
|
||||
#
|
||||
# process_class = DashboardManagementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_dashboard_management)
|
||||
# .Next(this.request_dashboard)
|
||||
# )
|
||||
#
|
||||
# request_dashboard = (
|
||||
# flow_view(DashboardCreationView)
|
||||
# .Permission('analytics.can_create_dashboards')
|
||||
# .Next(this.configure_data_sources)
|
||||
# )
|
||||
#
|
||||
# configure_data_sources = (
|
||||
# flow_view(DataSourceConfigurationView)
|
||||
# .Permission('analytics.can_configure_data_sources')
|
||||
# .Next(this.create_widgets)
|
||||
# )
|
||||
#
|
||||
# create_widgets = (
|
||||
# flow_func(this.setup_dashboard_widgets)
|
||||
# .Next(this.configure_layout)
|
||||
# )
|
||||
#
|
||||
# configure_layout = (
|
||||
# flow_func(this.setup_dashboard_layout)
|
||||
# .Next(this.set_permissions)
|
||||
# )
|
||||
#
|
||||
# set_permissions = (
|
||||
# flow_func(this.configure_dashboard_permissions)
|
||||
# .Next(this.test_dashboard)
|
||||
# )
|
||||
#
|
||||
# test_dashboard = (
|
||||
# flow_func(this.perform_dashboard_testing)
|
||||
# .Next(this.deploy_dashboard)
|
||||
# )
|
||||
#
|
||||
# deploy_dashboard = (
|
||||
# flow_func(this.deploy_dashboard_to_production)
|
||||
# .Next(this.complete_management)
|
||||
# )
|
||||
#
|
||||
# complete_management = (
|
||||
# flow_func(this.finalize_dashboard_management)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_dashboard_management)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_dashboard_management(self, activation):
|
||||
# """Initialize the dashboard management process"""
|
||||
# process = activation.process
|
||||
# dashboard = process.dashboard
|
||||
#
|
||||
# # Send dashboard creation notification
|
||||
# self.notify_dashboard_creation(dashboard)
|
||||
#
|
||||
# def setup_dashboard_widgets(self, activation):
|
||||
# """Setup dashboard widgets"""
|
||||
# process = activation.process
|
||||
# dashboard = process.dashboard
|
||||
#
|
||||
# # Create default widgets based on dashboard type
|
||||
# self.create_default_widgets(dashboard)
|
||||
#
|
||||
# # Mark widgets created
|
||||
# process.widgets_created = True
|
||||
# process.save()
|
||||
#
|
||||
# def setup_dashboard_layout(self, activation):
|
||||
# """Setup dashboard layout"""
|
||||
# process = activation.process
|
||||
# dashboard = process.dashboard
|
||||
#
|
||||
# # Configure dashboard layout
|
||||
# self.configure_layout_settings(dashboard)
|
||||
#
|
||||
# # Mark layout configured
|
||||
# process.layout_configured = True
|
||||
# process.save()
|
||||
#
|
||||
# def configure_dashboard_permissions(self, activation):
|
||||
# """Configure dashboard permissions"""
|
||||
# process = activation.process
|
||||
# dashboard = process.dashboard
|
||||
#
|
||||
# # Set up access permissions
|
||||
# self.setup_access_permissions(dashboard)
|
||||
#
|
||||
# # Mark permissions set
|
||||
# process.permissions_set = True
|
||||
# process.save()
|
||||
#
|
||||
# def perform_dashboard_testing(self, activation):
|
||||
# """Perform dashboard testing"""
|
||||
# process = activation.process
|
||||
# dashboard = process.dashboard
|
||||
#
|
||||
# # Test dashboard functionality
|
||||
# test_results = self.test_dashboard_functionality(dashboard)
|
||||
#
|
||||
# # Mark dashboard tested
|
||||
# process.dashboard_tested = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store test results
|
||||
# self.store_test_results(dashboard, test_results)
|
||||
#
|
||||
# def deploy_dashboard_to_production(self, activation):
|
||||
# """Deploy dashboard to production"""
|
||||
# process = activation.process
|
||||
# dashboard = process.dashboard
|
||||
#
|
||||
# # Deploy dashboard
|
||||
# self.deploy_dashboard(dashboard)
|
||||
#
|
||||
# # Mark dashboard deployed
|
||||
# process.dashboard_deployed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Activate dashboard
|
||||
# dashboard.is_active = True
|
||||
# dashboard.save()
|
||||
#
|
||||
# def finalize_dashboard_management(self, activation):
|
||||
# """Finalize the dashboard management process"""
|
||||
# process = activation.process
|
||||
# dashboard = process.dashboard
|
||||
#
|
||||
# # Mark management completed
|
||||
# process.management_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_dashboard_completion(dashboard)
|
||||
#
|
||||
# # Schedule dashboard refresh
|
||||
# self.schedule_dashboard_refresh(dashboard)
|
||||
#
|
||||
# def end_dashboard_management(self, activation):
|
||||
# """End the dashboard management workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate dashboard summary
|
||||
# self.generate_dashboard_summary(process.dashboard)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_dashboard_creation(self, dashboard):
|
||||
# """Notify dashboard creation"""
|
||||
# analytics_team = User.objects.filter(groups__name='Analytics Team')
|
||||
# for staff in analytics_team:
|
||||
# send_mail(
|
||||
# subject=f'Dashboard Creation: {dashboard.name}',
|
||||
# message=f'Dashboard creation process started for "{dashboard.name}".',
|
||||
# from_email='analytics@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_default_widgets(self, dashboard):
|
||||
# """Create default widgets for dashboard"""
|
||||
# # This would create default widgets based on dashboard type
|
||||
# pass
|
||||
#
|
||||
# def configure_layout_settings(self, dashboard):
|
||||
# """Configure dashboard layout settings"""
|
||||
# # This would configure layout settings
|
||||
# pass
|
||||
#
|
||||
# def setup_access_permissions(self, dashboard):
|
||||
# """Setup dashboard access permissions"""
|
||||
# # This would configure access permissions
|
||||
# pass
|
||||
#
|
||||
# def test_dashboard_functionality(self, dashboard):
|
||||
# """Test dashboard functionality"""
|
||||
# # This would test dashboard functionality
|
||||
# return {'status': 'passed', 'issues': []}
|
||||
#
|
||||
# def store_test_results(self, dashboard, results):
|
||||
# """Store dashboard test results"""
|
||||
# # This would store test results
|
||||
# pass
|
||||
#
|
||||
# def deploy_dashboard(self, dashboard):
|
||||
# """Deploy dashboard to production"""
|
||||
# # This would deploy dashboard
|
||||
# pass
|
||||
#
|
||||
# def notify_dashboard_completion(self, dashboard):
|
||||
# """Notify dashboard completion"""
|
||||
# # Notify dashboard users
|
||||
# for user in dashboard.allowed_users.all():
|
||||
# if user.email:
|
||||
# send_mail(
|
||||
# subject=f'Dashboard Available: {dashboard.name}',
|
||||
# message=f'Dashboard "{dashboard.name}" is now available.',
|
||||
# from_email='analytics@hospital.com',
|
||||
# recipient_list=[user.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def schedule_dashboard_refresh(self, dashboard):
|
||||
# """Schedule dashboard refresh"""
|
||||
# # Schedule dashboard refresh task
|
||||
# refresh_dashboard.apply_async(
|
||||
# args=[dashboard.dashboard_id],
|
||||
# countdown=dashboard.refresh_interval
|
||||
# )
|
||||
#
|
||||
# def generate_dashboard_summary(self, dashboard):
|
||||
# """Generate dashboard summary"""
|
||||
# # This would generate dashboard summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class MetricCalculationProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for metric calculation
|
||||
# """
|
||||
# metric_definition = ModelField(MetricDefinition, help_text='Associated metric definition')
|
||||
#
|
||||
# # Process status tracking
|
||||
# calculation_triggered = models.BooleanField(default=False)
|
||||
# data_collected = models.BooleanField(default=False)
|
||||
# metrics_calculated = models.BooleanField(default=False)
|
||||
# quality_validated = models.BooleanField(default=False)
|
||||
# thresholds_checked = models.BooleanField(default=False)
|
||||
# alerts_sent = models.BooleanField(default=False)
|
||||
# calculation_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Metric Calculation Process'
|
||||
# verbose_name_plural = 'Metric Calculation Processes'
|
||||
#
|
||||
#
|
||||
# class MetricCalculationFlow(Flow):
|
||||
# """
|
||||
# Metric Calculation Workflow
|
||||
#
|
||||
# This flow manages automated metric calculation including
|
||||
# data collection, calculation, validation, and alerting.
|
||||
# """
|
||||
#
|
||||
# process_class = MetricCalculationProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_metric_calculation)
|
||||
# .Next(this.trigger_calculation)
|
||||
# )
|
||||
#
|
||||
# trigger_calculation = (
|
||||
# flow_func(this.initiate_calculation)
|
||||
# .Next(this.collect_data)
|
||||
# )
|
||||
#
|
||||
# collect_data = (
|
||||
# flow_func(this.gather_metric_data)
|
||||
# .Next(this.calculate_metrics)
|
||||
# )
|
||||
#
|
||||
# calculate_metrics = (
|
||||
# flow_view(MetricCalculationView)
|
||||
# .Permission('analytics.can_calculate_metrics')
|
||||
# .Next(this.validate_quality)
|
||||
# )
|
||||
#
|
||||
# validate_quality = (
|
||||
# flow_func(this.perform_quality_validation)
|
||||
# .Next(this.check_thresholds)
|
||||
# )
|
||||
#
|
||||
# check_thresholds = (
|
||||
# flow_func(this.evaluate_thresholds)
|
||||
# .Next(this.send_alerts)
|
||||
# )
|
||||
#
|
||||
# send_alerts = (
|
||||
# flow_func(this.dispatch_threshold_alerts)
|
||||
# .Next(this.complete_calculation)
|
||||
# )
|
||||
#
|
||||
# complete_calculation = (
|
||||
# flow_func(this.finalize_metric_calculation)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_metric_calculation)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_metric_calculation(self, activation):
|
||||
# """Initialize the metric calculation process"""
|
||||
# process = activation.process
|
||||
# metric = process.metric_definition
|
||||
#
|
||||
# # Send calculation notification
|
||||
# self.notify_calculation_start(metric)
|
||||
#
|
||||
# def initiate_calculation(self, activation):
|
||||
# """Initiate metric calculation"""
|
||||
# process = activation.process
|
||||
# metric = process.metric_definition
|
||||
#
|
||||
# # Mark calculation triggered
|
||||
# process.calculation_triggered = True
|
||||
# process.save()
|
||||
#
|
||||
# # Prepare calculation environment
|
||||
# self.prepare_calculation_environment(metric)
|
||||
#
|
||||
# def gather_metric_data(self, activation):
|
||||
# """Gather data for metric calculation"""
|
||||
# process = activation.process
|
||||
# metric = process.metric_definition
|
||||
#
|
||||
# # Collect data from configured sources
|
||||
# collected_data = self.collect_calculation_data(metric)
|
||||
#
|
||||
# # Mark data collected
|
||||
# process.data_collected = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store collected data
|
||||
# self.store_calculation_data(metric, collected_data)
|
||||
#
|
||||
# def perform_quality_validation(self, activation):
|
||||
# """Perform data quality validation"""
|
||||
# process = activation.process
|
||||
# metric = process.metric_definition
|
||||
#
|
||||
# # Validate data quality
|
||||
# quality_results = self.validate_data_quality(metric)
|
||||
#
|
||||
# # Mark quality validated
|
||||
# process.quality_validated = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store quality results
|
||||
# self.store_quality_results(metric, quality_results)
|
||||
#
|
||||
# def evaluate_thresholds(self, activation):
|
||||
# """Evaluate metric thresholds"""
|
||||
# process = activation.process
|
||||
# metric = process.metric_definition
|
||||
#
|
||||
# # Check threshold violations
|
||||
# threshold_results = self.check_metric_thresholds(metric)
|
||||
#
|
||||
# # Mark thresholds checked
|
||||
# process.thresholds_checked = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store threshold results
|
||||
# self.store_threshold_results(metric, threshold_results)
|
||||
#
|
||||
# def dispatch_threshold_alerts(self, activation):
|
||||
# """Dispatch threshold alerts"""
|
||||
# process = activation.process
|
||||
# metric = process.metric_definition
|
||||
#
|
||||
# # Send threshold alerts if needed
|
||||
# self.send_threshold_alerts(metric)
|
||||
#
|
||||
# # Mark alerts sent
|
||||
# process.alerts_sent = True
|
||||
# process.save()
|
||||
#
|
||||
# def finalize_metric_calculation(self, activation):
|
||||
# """Finalize the metric calculation process"""
|
||||
# process = activation.process
|
||||
# metric = process.metric_definition
|
||||
#
|
||||
# # Mark calculation completed
|
||||
# process.calculation_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_calculation_completion(metric)
|
||||
#
|
||||
# # Schedule next calculation
|
||||
# self.schedule_next_calculation(metric)
|
||||
#
|
||||
# def end_metric_calculation(self, activation):
|
||||
# """End the metric calculation workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate calculation summary
|
||||
# self.generate_calculation_summary(process.metric_definition)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_calculation_start(self, metric):
|
||||
# """Notify metric calculation start"""
|
||||
# # This would notify relevant parties
|
||||
# pass
|
||||
#
|
||||
# def prepare_calculation_environment(self, metric):
|
||||
# """Prepare calculation environment"""
|
||||
# # This would prepare calculation environment
|
||||
# pass
|
||||
#
|
||||
# def collect_calculation_data(self, metric):
|
||||
# """Collect data for calculation"""
|
||||
# # This would collect data from configured sources
|
||||
# return {'status': 'collected', 'records': 1000}
|
||||
#
|
||||
# def store_calculation_data(self, metric, data):
|
||||
# """Store calculation data"""
|
||||
# # This would store calculation data
|
||||
# pass
|
||||
#
|
||||
# def validate_data_quality(self, metric):
|
||||
# """Validate data quality"""
|
||||
# # This would validate data quality
|
||||
# return {'quality_score': 95, 'issues': []}
|
||||
#
|
||||
# def store_quality_results(self, metric, results):
|
||||
# """Store quality validation results"""
|
||||
# # This would store quality results
|
||||
# pass
|
||||
#
|
||||
# def check_metric_thresholds(self, metric):
|
||||
# """Check metric thresholds"""
|
||||
# # This would check threshold violations
|
||||
# return {'violations': [], 'status': 'normal'}
|
||||
#
|
||||
# def store_threshold_results(self, metric, results):
|
||||
# """Store threshold check results"""
|
||||
# # This would store threshold results
|
||||
# pass
|
||||
#
|
||||
# def send_threshold_alerts(self, metric):
|
||||
# """Send threshold violation alerts"""
|
||||
# # This would send alerts for threshold violations
|
||||
# pass
|
||||
#
|
||||
# def notify_calculation_completion(self, metric):
|
||||
# """Notify calculation completion"""
|
||||
# # This would notify completion
|
||||
# pass
|
||||
#
|
||||
# def schedule_next_calculation(self, metric):
|
||||
# """Schedule next metric calculation"""
|
||||
# # Schedule next calculation based on aggregation period
|
||||
# if metric.aggregation_period == 'HOURLY':
|
||||
# countdown = 3600
|
||||
# elif metric.aggregation_period == 'DAILY':
|
||||
# countdown = 86400
|
||||
# else:
|
||||
# countdown = 3600 # Default to hourly
|
||||
#
|
||||
# calculate_metric.apply_async(
|
||||
# args=[metric.metric_id],
|
||||
# countdown=countdown
|
||||
# )
|
||||
#
|
||||
# def generate_calculation_summary(self, metric):
|
||||
# """Generate calculation summary"""
|
||||
# # This would generate calculation summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def schedule_report_execution(report_id):
|
||||
# """Background task to schedule report execution"""
|
||||
# try:
|
||||
# report = Report.objects.get(report_id=report_id)
|
||||
#
|
||||
# # Create report execution
|
||||
# execution = ReportExecution.objects.create(
|
||||
# report=report,
|
||||
# execution_type='SCHEDULED',
|
||||
# status='PENDING'
|
||||
# )
|
||||
#
|
||||
# # Start report generation workflow
|
||||
# # This would start the report generation workflow
|
||||
#
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def refresh_dashboard(dashboard_id):
|
||||
# """Background task to refresh dashboard data"""
|
||||
# try:
|
||||
# dashboard = Dashboard.objects.get(dashboard_id=dashboard_id)
|
||||
#
|
||||
# # Refresh all dashboard widgets
|
||||
# for widget in dashboard.widgets.all():
|
||||
# refresh_widget_data(widget)
|
||||
#
|
||||
# # Schedule next refresh
|
||||
# refresh_dashboard.apply_async(
|
||||
# args=[dashboard_id],
|
||||
# countdown=dashboard.refresh_interval
|
||||
# )
|
||||
#
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def calculate_metric(metric_id):
|
||||
# """Background task to calculate metric values"""
|
||||
# try:
|
||||
# metric = MetricDefinition.objects.get(metric_id=metric_id)
|
||||
#
|
||||
# # Start metric calculation workflow
|
||||
# # This would start the metric calculation workflow
|
||||
#
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def cleanup_old_reports():
|
||||
# """Background task to cleanup old report files"""
|
||||
# try:
|
||||
# # This would cleanup old report files
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def generate_analytics_summary():
|
||||
# """Background task to generate analytics summary"""
|
||||
# try:
|
||||
# # This would generate periodic analytics summary
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# def refresh_widget_data(widget):
|
||||
# """Helper function to refresh widget data"""
|
||||
# # This would refresh widget data
|
||||
# pass
|
||||
#
|
||||
@ -396,3 +396,724 @@ class MetricDefinitionForm(forms.ModelForm):
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
# from django import forms
|
||||
# from django.core.exceptions import ValidationError
|
||||
# from django.utils import timezone
|
||||
# from crispy_forms.helper import FormHelper
|
||||
# from crispy_forms.layout import Layout, Fieldset, Submit, Row, Column, HTML, Div
|
||||
# from crispy_forms.bootstrap import FormActions
|
||||
# import json
|
||||
#
|
||||
# from .models import Dashboard, DashboardWidget, DataSource, Report, MetricDefinition
|
||||
# from core.models import Tenant
|
||||
#
|
||||
#
|
||||
# class ReportGenerationForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for report generation configuration
|
||||
# """
|
||||
# parameters = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 4})
|
||||
# )
|
||||
# output_format = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('pdf', 'PDF'),
|
||||
# ('excel', 'Excel'),
|
||||
# ('csv', 'CSV'),
|
||||
# ('json', 'JSON')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# schedule_execution = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# execution_time = forms.DateTimeField(
|
||||
# required=False,
|
||||
# widget=forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'})
|
||||
# )
|
||||
# email_recipients = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = Report
|
||||
# fields = [
|
||||
# 'name', 'description', 'report_type', 'data_sources',
|
||||
# 'parameters', 'output_format', 'schedule_execution',
|
||||
# 'execution_time', 'email_recipients'
|
||||
# ]
|
||||
# widgets = {
|
||||
# 'name': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
# 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
# 'report_type': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'data_sources': forms.CheckboxSelectMultiple(attrs={'class': 'form-check-input'})
|
||||
# }
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# tenant = kwargs.pop('tenant', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# if tenant:
|
||||
# self.fields['data_sources'].queryset = DataSource.objects.filter(tenant=tenant)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Report Configuration',
|
||||
# Row(
|
||||
# Column('name', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('report_type', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'description',
|
||||
# 'data_sources',
|
||||
# 'parameters'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Output Settings',
|
||||
# Row(
|
||||
# Column('output_format', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('email_recipients', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# )
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Scheduling',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'schedule_execution',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_schedule_execution">Schedule for later execution</label>'),
|
||||
# HTML('</div>'),
|
||||
# 'execution_time'
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Generate Report', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'analytics:report_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean_parameters(self):
|
||||
# parameters = self.cleaned_data.get('parameters')
|
||||
# if parameters:
|
||||
# try:
|
||||
# json.loads(parameters)
|
||||
# except json.JSONDecodeError:
|
||||
# raise ValidationError('Parameters must be valid JSON.')
|
||||
# return parameters
|
||||
#
|
||||
# def clean(self):
|
||||
# cleaned_data = super().clean()
|
||||
# schedule_execution = cleaned_data.get('schedule_execution')
|
||||
# execution_time = cleaned_data.get('execution_time')
|
||||
#
|
||||
# if schedule_execution and not execution_time:
|
||||
# raise ValidationError('Execution time is required when scheduling execution.')
|
||||
#
|
||||
# return cleaned_data
|
||||
#
|
||||
#
|
||||
# class DashboardCreationForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for dashboard creation
|
||||
# """
|
||||
# layout_template = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('grid_2x2', '2x2 Grid'),
|
||||
# ('grid_3x3', '3x3 Grid'),
|
||||
# ('sidebar_main', 'Sidebar + Main'),
|
||||
# ('top_bottom', 'Top + Bottom'),
|
||||
# ('custom', 'Custom Layout')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# copy_from_dashboard = forms.ModelChoiceField(
|
||||
# queryset=None,
|
||||
# required=False,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = Dashboard
|
||||
# fields = [
|
||||
# 'name', 'description', 'dashboard_type', 'layout_template',
|
||||
# 'refresh_interval', 'is_public', 'allowed_roles',
|
||||
# 'copy_from_dashboard'
|
||||
# ]
|
||||
# widgets = {
|
||||
# 'name': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
# 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
# 'dashboard_type': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'refresh_interval': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||
# 'is_public': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
# 'allowed_roles': forms.Textarea(attrs={'class': 'form-control', 'rows': 2})
|
||||
# }
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# tenant = kwargs.pop('tenant', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# if tenant:
|
||||
# self.fields['copy_from_dashboard'].queryset = Dashboard.objects.filter(
|
||||
# tenant=tenant,
|
||||
# is_active=True
|
||||
# )
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Dashboard Information',
|
||||
# Row(
|
||||
# Column('name', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('dashboard_type', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'description'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Layout Configuration',
|
||||
# Row(
|
||||
# Column('layout_template', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('refresh_interval', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'copy_from_dashboard'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Access Control',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'is_public',
|
||||
# HTML('<label class="form-check-label" for="id_is_public">Make dashboard public</label>'),
|
||||
# HTML('</div>'),
|
||||
# 'allowed_roles'
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Create Dashboard', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'analytics:dashboard_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class DataSourceConfigurationForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for data source configuration
|
||||
# """
|
||||
# test_connection = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = DataSource
|
||||
# fields = [
|
||||
# 'name', 'description', 'source_type', 'connection_config',
|
||||
# 'query_config', 'refresh_interval', 'is_active',
|
||||
# 'test_connection'
|
||||
# ]
|
||||
# widgets = {
|
||||
# 'name': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
# 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
# 'source_type': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'connection_config': forms.Textarea(attrs={'class': 'form-control', 'rows': 5}),
|
||||
# 'query_config': forms.Textarea(attrs={'class': 'form-control', 'rows': 5}),
|
||||
# 'refresh_interval': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||
# 'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# }
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Data Source Information',
|
||||
# Row(
|
||||
# Column('name', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('source_type', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'description'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Connection Configuration',
|
||||
# 'connection_config',
|
||||
# HTML('<small class="form-text text-muted">Enter connection details in JSON format</small>')
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Query Configuration',
|
||||
# 'query_config',
|
||||
# HTML('<small class="form-text text-muted">Enter query configuration in JSON format</small>')
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Settings',
|
||||
# Row(
|
||||
# Column('refresh_interval', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'is_active',
|
||||
# HTML('<label class="form-check-label" for="id_is_active">Active</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'test_connection',
|
||||
# HTML('<label class="form-check-label" for="id_test_connection">Test connection after saving</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Save Data Source', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'analytics:data_source_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean_connection_config(self):
|
||||
# config = self.cleaned_data.get('connection_config')
|
||||
# if config:
|
||||
# try:
|
||||
# json.loads(config)
|
||||
# except json.JSONDecodeError:
|
||||
# raise ValidationError('Connection configuration must be valid JSON.')
|
||||
# return config
|
||||
#
|
||||
# def clean_query_config(self):
|
||||
# config = self.cleaned_data.get('query_config')
|
||||
# if config:
|
||||
# try:
|
||||
# json.loads(config)
|
||||
# except json.JSONDecodeError:
|
||||
# raise ValidationError('Query configuration must be valid JSON.')
|
||||
# return config
|
||||
#
|
||||
#
|
||||
# class MetricCalculationForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for metric calculation configuration
|
||||
# """
|
||||
# calculate_now = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# date_range_start = forms.DateField(
|
||||
# required=False,
|
||||
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||||
# )
|
||||
# date_range_end = forms.DateField(
|
||||
# required=False,
|
||||
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = MetricDefinition
|
||||
# fields = [
|
||||
# 'name', 'description', 'metric_type', 'data_source',
|
||||
# 'calculation_config', 'aggregation_period', 'target_value',
|
||||
# 'warning_threshold', 'critical_threshold', 'unit_of_measure',
|
||||
# 'calculate_now', 'date_range_start', 'date_range_end'
|
||||
# ]
|
||||
# widgets = {
|
||||
# 'name': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
# 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
# 'metric_type': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'data_source': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'calculation_config': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
|
||||
# 'aggregation_period': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'target_value': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||
# 'warning_threshold': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||
# 'critical_threshold': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||
# 'unit_of_measure': forms.TextInput(attrs={'class': 'form-control'})
|
||||
# }
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# tenant = kwargs.pop('tenant', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# if tenant:
|
||||
# self.fields['data_source'].queryset = DataSource.objects.filter(tenant=tenant)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Metric Definition',
|
||||
# Row(
|
||||
# Column('name', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('metric_type', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'description',
|
||||
# Row(
|
||||
# Column('data_source', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('aggregation_period', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'calculation_config'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Thresholds and Targets',
|
||||
# Row(
|
||||
# Column('target_value', css_class='form-group col-md-4 mb-0'),
|
||||
# Column('warning_threshold', css_class='form-group col-md-4 mb-0'),
|
||||
# Column('critical_threshold', css_class='form-group col-md-4 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'unit_of_measure'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Calculation Options',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'calculate_now',
|
||||
# HTML('<label class="form-check-label" for="id_calculate_now">Calculate metric immediately</label>'),
|
||||
# HTML('</div>'),
|
||||
# Row(
|
||||
# Column('date_range_start', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('date_range_end', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# )
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Save Metric', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'analytics:metric_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean_calculation_config(self):
|
||||
# config = self.cleaned_data.get('calculation_config')
|
||||
# if config:
|
||||
# try:
|
||||
# json.loads(config)
|
||||
# except json.JSONDecodeError:
|
||||
# raise ValidationError('Calculation configuration must be valid JSON.')
|
||||
# return config
|
||||
#
|
||||
#
|
||||
# class QualityAssuranceForm(forms.Form):
|
||||
# """
|
||||
# Form for quality assurance configuration
|
||||
# """
|
||||
# check_data_accuracy = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# validate_calculations = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# verify_data_sources = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# check_performance = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# generate_qa_report = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# qa_threshold = forms.DecimalField(
|
||||
# max_digits=5,
|
||||
# decimal_places=2,
|
||||
# initial=95.0,
|
||||
# widget=forms.NumberInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Quality Assurance Checks',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'check_data_accuracy',
|
||||
# HTML('<label class="form-check-label" for="id_check_data_accuracy">Check Data Accuracy</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'validate_calculations',
|
||||
# HTML('<label class="form-check-label" for="id_validate_calculations">Validate Calculations</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'verify_data_sources',
|
||||
# HTML('<label class="form-check-label" for="id_verify_data_sources">Verify Data Sources</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'check_performance',
|
||||
# HTML('<label class="form-check-label" for="id_check_performance">Check Performance</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'generate_qa_report',
|
||||
# HTML('<label class="form-check-label" for="id_generate_qa_report">Generate QA Report</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Quality Threshold',
|
||||
# 'qa_threshold',
|
||||
# HTML('<small class="form-text text-muted">Minimum quality score percentage (0-100)</small>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Start Quality Check', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'analytics:dashboard\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class DistributionForm(forms.Form):
|
||||
# """
|
||||
# Form for report distribution configuration
|
||||
# """
|
||||
# distribution_method = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('email', 'Email'),
|
||||
# ('download', 'Download Link'),
|
||||
# ('ftp', 'FTP Upload'),
|
||||
# ('api', 'API Endpoint'),
|
||||
# ('dashboard', 'Dashboard Publication')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# recipients = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 4})
|
||||
# )
|
||||
# subject = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# message = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 4})
|
||||
# )
|
||||
# schedule_distribution = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# distribution_time = forms.DateTimeField(
|
||||
# required=False,
|
||||
# widget=forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Distribution Method',
|
||||
# 'distribution_method',
|
||||
# 'recipients',
|
||||
# HTML('<small class="form-text text-muted">Enter email addresses separated by commas</small>')
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Message Content',
|
||||
# 'subject',
|
||||
# 'message'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Scheduling',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'schedule_distribution',
|
||||
# HTML('<label class="form-check-label" for="id_schedule_distribution">Schedule distribution</label>'),
|
||||
# HTML('</div>'),
|
||||
# 'distribution_time'
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Distribute Report', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'analytics:report_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean(self):
|
||||
# cleaned_data = super().clean()
|
||||
# distribution_method = cleaned_data.get('distribution_method')
|
||||
# recipients = cleaned_data.get('recipients')
|
||||
# schedule_distribution = cleaned_data.get('schedule_distribution')
|
||||
# distribution_time = cleaned_data.get('distribution_time')
|
||||
#
|
||||
# if distribution_method == 'email' and not recipients:
|
||||
# raise ValidationError('Recipients are required for email distribution.')
|
||||
#
|
||||
# if schedule_distribution and not distribution_time:
|
||||
# raise ValidationError('Distribution time is required when scheduling distribution.')
|
||||
#
|
||||
# return cleaned_data
|
||||
#
|
||||
#
|
||||
# class VisualizationForm(forms.Form):
|
||||
# """
|
||||
# Form for visualization configuration
|
||||
# """
|
||||
# chart_type = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('line', 'Line Chart'),
|
||||
# ('bar', 'Bar Chart'),
|
||||
# ('pie', 'Pie Chart'),
|
||||
# ('scatter', 'Scatter Plot'),
|
||||
# ('heatmap', 'Heat Map'),
|
||||
# ('gauge', 'Gauge'),
|
||||
# ('table', 'Data Table')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# title = forms.CharField(
|
||||
# required=True,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# x_axis_label = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# y_axis_label = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# color_scheme = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('default', 'Default'),
|
||||
# ('blue', 'Blue Theme'),
|
||||
# ('green', 'Green Theme'),
|
||||
# ('red', 'Red Theme'),
|
||||
# ('purple', 'Purple Theme'),
|
||||
# ('custom', 'Custom Colors')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# show_legend = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# show_grid = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Chart Configuration',
|
||||
# Row(
|
||||
# Column('chart_type', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('color_scheme', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'title'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Axis Labels',
|
||||
# Row(
|
||||
# Column('x_axis_label', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('y_axis_label', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# )
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Display Options',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'show_legend',
|
||||
# HTML('<label class="form-check-label" for="id_show_legend">Show Legend</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'show_grid',
|
||||
# HTML('<label class="form-check-label" for="id_show_grid">Show Grid</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Create Visualization', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'analytics:dashboard\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class AnalysisForm(forms.Form):
|
||||
# """
|
||||
# Form for data analysis configuration
|
||||
# """
|
||||
# analysis_type = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('descriptive', 'Descriptive Analysis'),
|
||||
# ('trend', 'Trend Analysis'),
|
||||
# ('correlation', 'Correlation Analysis'),
|
||||
# ('regression', 'Regression Analysis'),
|
||||
# ('forecasting', 'Forecasting'),
|
||||
# ('anomaly', 'Anomaly Detection')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# data_source = forms.ModelChoiceField(
|
||||
# queryset=None,
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# date_range_start = forms.DateField(
|
||||
# required=True,
|
||||
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||||
# )
|
||||
# date_range_end = forms.DateField(
|
||||
# required=True,
|
||||
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||||
# )
|
||||
# confidence_level = forms.DecimalField(
|
||||
# max_digits=5,
|
||||
# decimal_places=2,
|
||||
# initial=95.0,
|
||||
# widget=forms.NumberInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# generate_insights = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# tenant = kwargs.pop('tenant', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# if tenant:
|
||||
# self.fields['data_source'].queryset = DataSource.objects.filter(tenant=tenant)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Analysis Configuration',
|
||||
# Row(
|
||||
# Column('analysis_type', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('data_source', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# Row(
|
||||
# Column('date_range_start', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('date_range_end', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'confidence_level'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Options',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'generate_insights',
|
||||
# HTML('<label class="form-check-label" for="id_generate_insights">Generate Insights</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Start Analysis', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'analytics:dashboard\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
|
||||
@ -3157,4 +3157,731 @@ def report_list(request):
|
||||
# 'count': len(results)
|
||||
# }
|
||||
#
|
||||
# return render(request, 'analytics/partials/search_results.html', context)
|
||||
# return render(request, 'analytics/partials/search_results.html', context)
|
||||
|
||||
|
||||
# from django.shortcuts import render, redirect, get_object_or_404
|
||||
# from django.contrib.auth.decorators import login_required, permission_required
|
||||
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||
# from django.contrib import messages
|
||||
# from django.views.generic import (
|
||||
# CreateView, UpdateView, DeleteView, DetailView, ListView, FormView
|
||||
# )
|
||||
# from django.urls import reverse_lazy, reverse
|
||||
# from django.http import JsonResponse, HttpResponse
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
# from django.conf import settings
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# import json
|
||||
#
|
||||
# from .models import Dashboard, DashboardWidget, DataSource, Report, MetricDefinition, ReportExecution
|
||||
# from .forms import (
|
||||
# ReportGenerationForm, DashboardCreationForm, DataSourceConfigurationForm,
|
||||
# MetricCalculationForm, QualityAssuranceForm, DistributionForm,
|
||||
# VisualizationForm, AnalysisForm
|
||||
# )
|
||||
# from .flows import ReportGenerationFlow, DashboardManagementFlow, MetricCalculationFlow
|
||||
#
|
||||
#
|
||||
# class ReportGenerationView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
||||
# """
|
||||
# View for report generation workflow
|
||||
# """
|
||||
# model = Report
|
||||
# form_class = ReportGenerationForm
|
||||
# template_name = 'analytics/report_generation.html'
|
||||
# permission_required = 'analytics.can_generate_reports'
|
||||
# flow_class = ReportGenerationFlow
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['tenant'] = self.request.user.tenant
|
||||
# return kwargs
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# with transaction.atomic():
|
||||
# # Create report
|
||||
# report = form.save(commit=False)
|
||||
# report.tenant = self.request.user.tenant
|
||||
# report.created_by = self.request.user
|
||||
# report.save()
|
||||
#
|
||||
# # Save many-to-many relationships
|
||||
# form.save_m2m()
|
||||
#
|
||||
# # Start report generation workflow
|
||||
# process = self.flow_class.start.run(
|
||||
# report=report,
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Report generation initiated for "{report.name}". '
|
||||
# f'You will be notified when the report is ready.'
|
||||
# )
|
||||
#
|
||||
# return redirect('analytics:report_detail', pk=report.pk)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Generate Report'
|
||||
# context['breadcrumbs'] = [
|
||||
# {'name': 'Home', 'url': reverse('core:dashboard')},
|
||||
# {'name': 'Analytics', 'url': reverse('analytics:dashboard')},
|
||||
# {'name': 'Reports', 'url': reverse('analytics:report_list')},
|
||||
# {'name': 'Generate Report', 'url': ''}
|
||||
# ]
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class DashboardCreationView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
||||
# """
|
||||
# View for dashboard creation workflow
|
||||
# """
|
||||
# model = Dashboard
|
||||
# form_class = DashboardCreationForm
|
||||
# template_name = 'analytics/dashboard_creation.html'
|
||||
# permission_required = 'analytics.can_create_dashboards'
|
||||
# flow_class = DashboardManagementFlow
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['tenant'] = self.request.user.tenant
|
||||
# return kwargs
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# with transaction.atomic():
|
||||
# # Create dashboard
|
||||
# dashboard = form.save(commit=False)
|
||||
# dashboard.tenant = self.request.user.tenant
|
||||
# dashboard.created_by = self.request.user
|
||||
# dashboard.save()
|
||||
#
|
||||
# # Start dashboard creation workflow
|
||||
# process = self.flow_class.start.run(
|
||||
# dashboard=dashboard,
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Dashboard "{dashboard.name}" created successfully. '
|
||||
# f'You can now add widgets and configure the layout.'
|
||||
# )
|
||||
#
|
||||
# return redirect('analytics:dashboard_detail', pk=dashboard.pk)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Create Dashboard'
|
||||
# context['breadcrumbs'] = [
|
||||
# {'name': 'Home', 'url': reverse('core:dashboard')},
|
||||
# {'name': 'Analytics', 'url': reverse('analytics:dashboard')},
|
||||
# {'name': 'Dashboards', 'url': reverse('analytics:dashboard_list')},
|
||||
# {'name': 'Create Dashboard', 'url': ''}
|
||||
# ]
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class DataSourceConfigurationView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||||
# """
|
||||
# View for data source configuration
|
||||
# """
|
||||
# model = DataSource
|
||||
# form_class = DataSourceConfigurationForm
|
||||
# template_name = 'analytics/data_source_configuration.html'
|
||||
# permission_required = 'analytics.can_configure_data_sources'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('analytics:data_source_detail', kwargs={'pk': self.object.pk})
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# data_source = form.save(commit=False)
|
||||
# data_source.tenant = self.request.user.tenant
|
||||
# data_source.created_by = self.request.user
|
||||
# data_source.save()
|
||||
#
|
||||
# # Test connection if requested
|
||||
# if form.cleaned_data.get('test_connection'):
|
||||
# connection_result = self.test_data_source_connection(data_source)
|
||||
# if connection_result['success']:
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Data source "{data_source.name}" configured successfully. Connection test passed.'
|
||||
# )
|
||||
# else:
|
||||
# messages.warning(
|
||||
# self.request,
|
||||
# f'Data source "{data_source.name}" configured, but connection test failed: {connection_result["error"]}'
|
||||
# )
|
||||
# else:
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Data source "{data_source.name}" configured successfully.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def test_data_source_connection(self, data_source):
|
||||
# """Test data source connection"""
|
||||
# try:
|
||||
# # Implement connection testing logic based on source type
|
||||
# if data_source.source_type == 'database':
|
||||
# return self.test_database_connection(data_source)
|
||||
# elif data_source.source_type == 'api':
|
||||
# return self.test_api_connection(data_source)
|
||||
# elif data_source.source_type == 'file':
|
||||
# return self.test_file_connection(data_source)
|
||||
# else:
|
||||
# return {'success': True}
|
||||
# except Exception as e:
|
||||
# return {'success': False, 'error': str(e)}
|
||||
#
|
||||
# def test_database_connection(self, data_source):
|
||||
# """Test database connection"""
|
||||
# # Implement database connection testing
|
||||
# return {'success': True}
|
||||
#
|
||||
# def test_api_connection(self, data_source):
|
||||
# """Test API connection"""
|
||||
# # Implement API connection testing
|
||||
# return {'success': True}
|
||||
#
|
||||
# def test_file_connection(self, data_source):
|
||||
# """Test file connection"""
|
||||
# # Implement file connection testing
|
||||
# return {'success': True}
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Configure Data Source'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class MetricCalculationView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
||||
# """
|
||||
# View for metric calculation workflow
|
||||
# """
|
||||
# model = MetricDefinition
|
||||
# form_class = MetricCalculationForm
|
||||
# template_name = 'analytics/metric_calculation.html'
|
||||
# permission_required = 'analytics.can_calculate_metrics'
|
||||
# flow_class = MetricCalculationFlow
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['tenant'] = self.request.user.tenant
|
||||
# return kwargs
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# with transaction.atomic():
|
||||
# # Create metric definition
|
||||
# metric = form.save(commit=False)
|
||||
# metric.tenant = self.request.user.tenant
|
||||
# metric.created_by = self.request.user
|
||||
# metric.save()
|
||||
#
|
||||
# # Start metric calculation workflow
|
||||
# process = self.flow_class.start.run(
|
||||
# metric=metric,
|
||||
# calculate_now=form.cleaned_data.get('calculate_now', False),
|
||||
# date_range_start=form.cleaned_data.get('date_range_start'),
|
||||
# date_range_end=form.cleaned_data.get('date_range_end'),
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# if form.cleaned_data.get('calculate_now'):
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Metric "{metric.name}" created and calculation initiated. '
|
||||
# f'Results will be available shortly.'
|
||||
# )
|
||||
# else:
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Metric "{metric.name}" created successfully.'
|
||||
# )
|
||||
#
|
||||
# return redirect('analytics:metric_detail', pk=metric.pk)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Calculate Metric'
|
||||
# context['breadcrumbs'] = [
|
||||
# {'name': 'Home', 'url': reverse('core:dashboard')},
|
||||
# {'name': 'Analytics', 'url': reverse('analytics:dashboard')},
|
||||
# {'name': 'Metrics', 'url': reverse('analytics:metric_list')},
|
||||
# {'name': 'Calculate Metric', 'url': ''}
|
||||
# ]
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class QualityAssuranceView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||
# """
|
||||
# View for quality assurance configuration
|
||||
# """
|
||||
# form_class = QualityAssuranceForm
|
||||
# template_name = 'analytics/quality_assurance.html'
|
||||
# permission_required = 'analytics.can_perform_qa'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('analytics:dashboard')
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# qa_config = form.cleaned_data
|
||||
#
|
||||
# # Start quality assurance process
|
||||
# self.start_quality_assurance(qa_config)
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# 'Quality assurance process initiated. You will receive a notification when complete.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def start_quality_assurance(self, config):
|
||||
# """Start quality assurance process"""
|
||||
# # This would start the QA process
|
||||
# pass
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Quality Assurance'
|
||||
# context['qa_metrics'] = self.get_qa_metrics()
|
||||
# return context
|
||||
#
|
||||
# def get_qa_metrics(self):
|
||||
# """Get quality assurance metrics"""
|
||||
# return {
|
||||
# 'data_accuracy': 98.5,
|
||||
# 'calculation_accuracy': 99.2,
|
||||
# 'source_reliability': 97.8,
|
||||
# 'performance_score': 95.6
|
||||
# }
|
||||
#
|
||||
#
|
||||
# class DistributionView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||
# """
|
||||
# View for report distribution
|
||||
# """
|
||||
# form_class = DistributionForm
|
||||
# template_name = 'analytics/distribution.html'
|
||||
# permission_required = 'analytics.can_distribute_reports'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('analytics:report_detail', kwargs={'pk': self.kwargs['pk']})
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# report = get_object_or_404(Report, pk=self.kwargs['pk'])
|
||||
# distribution_config = form.cleaned_data
|
||||
#
|
||||
# # Start distribution process
|
||||
# self.distribute_report(report, distribution_config)
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Report "{report.name}" distribution initiated.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def distribute_report(self, report, config):
|
||||
# """Distribute report based on configuration"""
|
||||
# method = config['distribution_method']
|
||||
#
|
||||
# if method == 'email':
|
||||
# self.distribute_via_email(report, config)
|
||||
# elif method == 'download':
|
||||
# self.create_download_link(report, config)
|
||||
# elif method == 'ftp':
|
||||
# self.upload_to_ftp(report, config)
|
||||
# elif method == 'api':
|
||||
# self.publish_to_api(report, config)
|
||||
# elif method == 'dashboard':
|
||||
# self.publish_to_dashboard(report, config)
|
||||
#
|
||||
# def distribute_via_email(self, report, config):
|
||||
# """Distribute report via email"""
|
||||
# recipients = [email.strip() for email in config['recipients'].split(',')]
|
||||
#
|
||||
# send_mail(
|
||||
# subject=config.get('subject', f'Report: {report.name}'),
|
||||
# message=config.get('message', f'Please find the attached report: {report.name}'),
|
||||
# from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
# recipient_list=recipients,
|
||||
# fail_silently=False
|
||||
# )
|
||||
#
|
||||
# def create_download_link(self, report, config):
|
||||
# """Create download link for report"""
|
||||
# # Implement download link creation
|
||||
# pass
|
||||
#
|
||||
# def upload_to_ftp(self, report, config):
|
||||
# """Upload report to FTP server"""
|
||||
# # Implement FTP upload
|
||||
# pass
|
||||
#
|
||||
# def publish_to_api(self, report, config):
|
||||
# """Publish report to API endpoint"""
|
||||
# # Implement API publication
|
||||
# pass
|
||||
#
|
||||
# def publish_to_dashboard(self, report, config):
|
||||
# """Publish report to dashboard"""
|
||||
# # Implement dashboard publication
|
||||
# pass
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['report'] = get_object_or_404(Report, pk=self.kwargs['pk'])
|
||||
# context['title'] = 'Distribute Report'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class VisualizationView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||
# """
|
||||
# View for visualization configuration
|
||||
# """
|
||||
# form_class = VisualizationForm
|
||||
# template_name = 'analytics/visualization.html'
|
||||
# permission_required = 'analytics.can_create_visualizations'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('analytics:dashboard_detail', kwargs={'pk': self.kwargs['pk']})
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# dashboard = get_object_or_404(Dashboard, pk=self.kwargs['pk'])
|
||||
# visualization_config = form.cleaned_data
|
||||
#
|
||||
# # Create visualization widget
|
||||
# widget = self.create_visualization_widget(dashboard, visualization_config)
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Visualization "{visualization_config["title"]}" added to dashboard.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def create_visualization_widget(self, dashboard, config):
|
||||
# """Create visualization widget"""
|
||||
# widget = DashboardWidget.objects.create(
|
||||
# dashboard=dashboard,
|
||||
# widget_type='chart',
|
||||
# title=config['title'],
|
||||
# configuration=config,
|
||||
# position_x=0,
|
||||
# position_y=0,
|
||||
# width=6,
|
||||
# height=4,
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
# return widget
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['dashboard'] = get_object_or_404(Dashboard, pk=self.kwargs['pk'])
|
||||
# context['title'] = 'Create Visualization'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class AnalysisView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||
# """
|
||||
# View for data analysis
|
||||
# """
|
||||
# form_class = AnalysisForm
|
||||
# template_name = 'analytics/analysis.html'
|
||||
# permission_required = 'analytics.can_perform_analysis'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('analytics:dashboard')
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['tenant'] = self.request.user.tenant
|
||||
# return kwargs
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# analysis_config = form.cleaned_data
|
||||
#
|
||||
# # Start analysis process
|
||||
# analysis_result = self.perform_analysis(analysis_config)
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# 'Data analysis completed successfully. Results are available in the dashboard.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def perform_analysis(self, config):
|
||||
# """Perform data analysis"""
|
||||
# analysis_type = config['analysis_type']
|
||||
#
|
||||
# if analysis_type == 'descriptive':
|
||||
# return self.descriptive_analysis(config)
|
||||
# elif analysis_type == 'trend':
|
||||
# return self.trend_analysis(config)
|
||||
# elif analysis_type == 'correlation':
|
||||
# return self.correlation_analysis(config)
|
||||
# elif analysis_type == 'regression':
|
||||
# return self.regression_analysis(config)
|
||||
# elif analysis_type == 'forecasting':
|
||||
# return self.forecasting_analysis(config)
|
||||
# elif analysis_type == 'anomaly':
|
||||
# return self.anomaly_detection(config)
|
||||
#
|
||||
# def descriptive_analysis(self, config):
|
||||
# """Perform descriptive analysis"""
|
||||
# # Implement descriptive analysis
|
||||
# return {}
|
||||
#
|
||||
# def trend_analysis(self, config):
|
||||
# """Perform trend analysis"""
|
||||
# # Implement trend analysis
|
||||
# return {}
|
||||
#
|
||||
# def correlation_analysis(self, config):
|
||||
# """Perform correlation analysis"""
|
||||
# # Implement correlation analysis
|
||||
# return {}
|
||||
#
|
||||
# def regression_analysis(self, config):
|
||||
# """Perform regression analysis"""
|
||||
# # Implement regression analysis
|
||||
# return {}
|
||||
#
|
||||
# def forecasting_analysis(self, config):
|
||||
# """Perform forecasting analysis"""
|
||||
# # Implement forecasting analysis
|
||||
# return {}
|
||||
#
|
||||
# def anomaly_detection(self, config):
|
||||
# """Perform anomaly detection"""
|
||||
# # Implement anomaly detection
|
||||
# return {}
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Data Analysis'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class DashboardListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
# """
|
||||
# View for listing dashboards
|
||||
# """
|
||||
# model = Dashboard
|
||||
# template_name = 'analytics/dashboard_list.html'
|
||||
# context_object_name = 'dashboards'
|
||||
# permission_required = 'analytics.view_dashboard'
|
||||
# paginate_by = 20
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# queryset = Dashboard.objects.filter(tenant=self.request.user.tenant)
|
||||
#
|
||||
# # Apply filters
|
||||
# search = self.request.GET.get('search')
|
||||
# if search:
|
||||
# queryset = queryset.filter(name__icontains=search)
|
||||
#
|
||||
# dashboard_type = self.request.GET.get('type')
|
||||
# if dashboard_type:
|
||||
# queryset = queryset.filter(dashboard_type=dashboard_type)
|
||||
#
|
||||
# return queryset.order_by('-created_at')
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Dashboards'
|
||||
# context['search'] = self.request.GET.get('search', '')
|
||||
# context['selected_type'] = self.request.GET.get('type', '')
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class DashboardDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||
# """
|
||||
# View for dashboard details
|
||||
# """
|
||||
# model = Dashboard
|
||||
# template_name = 'analytics/dashboard_detail.html'
|
||||
# context_object_name = 'dashboard'
|
||||
# permission_required = 'analytics.view_dashboard'
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# return Dashboard.objects.filter(tenant=self.request.user.tenant)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# dashboard = self.object
|
||||
# context['title'] = dashboard.name
|
||||
# context['widgets'] = dashboard.widgets.filter(is_active=True).order_by('position_y', 'position_x')
|
||||
# context['can_edit'] = self.request.user.has_perm('analytics.change_dashboard')
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class ReportListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
# """
|
||||
# View for listing reports
|
||||
# """
|
||||
# model = Report
|
||||
# template_name = 'analytics/report_list.html'
|
||||
# context_object_name = 'reports'
|
||||
# permission_required = 'analytics.view_report'
|
||||
# paginate_by = 20
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# queryset = Report.objects.filter(tenant=self.request.user.tenant)
|
||||
#
|
||||
# # Apply filters
|
||||
# search = self.request.GET.get('search')
|
||||
# if search:
|
||||
# queryset = queryset.filter(name__icontains=search)
|
||||
#
|
||||
# report_type = self.request.GET.get('type')
|
||||
# if report_type:
|
||||
# queryset = queryset.filter(report_type=report_type)
|
||||
#
|
||||
# return queryset.order_by('-created_at')
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Reports'
|
||||
# context['search'] = self.request.GET.get('search', '')
|
||||
# context['selected_type'] = self.request.GET.get('type', '')
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class ReportDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||
# """
|
||||
# View for report details
|
||||
# """
|
||||
# model = Report
|
||||
# template_name = 'analytics/report_detail.html'
|
||||
# context_object_name = 'report'
|
||||
# permission_required = 'analytics.view_report'
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# return Report.objects.filter(tenant=self.request.user.tenant)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# report = self.object
|
||||
# context['title'] = report.name
|
||||
# context['executions'] = report.executions.order_by('-created_at')[:10]
|
||||
# context['can_generate'] = self.request.user.has_perm('analytics.can_generate_reports')
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# # AJAX Views
|
||||
# @login_required
|
||||
# @permission_required('analytics.can_test_data_sources')
|
||||
# def test_data_source_ajax(request, data_source_id):
|
||||
# """AJAX view to test data source connection"""
|
||||
# if request.method == 'POST':
|
||||
# try:
|
||||
# data_source = DataSource.objects.get(
|
||||
# id=data_source_id,
|
||||
# tenant=request.user.tenant
|
||||
# )
|
||||
#
|
||||
# # Test connection
|
||||
# result = test_data_source_connection(data_source)
|
||||
#
|
||||
# return JsonResponse({
|
||||
# 'success': result['success'],
|
||||
# 'message': 'Connection successful' if result['success'] else result.get('error', 'Connection failed')
|
||||
# })
|
||||
# except DataSource.DoesNotExist:
|
||||
# return JsonResponse({
|
||||
# 'success': False,
|
||||
# 'message': 'Data source not found.'
|
||||
# })
|
||||
#
|
||||
# return JsonResponse({'success': False, 'message': 'Invalid request.'})
|
||||
#
|
||||
#
|
||||
# @login_required
|
||||
# @permission_required('analytics.can_calculate_metrics')
|
||||
# def calculate_metric_ajax(request, metric_id):
|
||||
# """AJAX view to calculate metric"""
|
||||
# if request.method == 'POST':
|
||||
# try:
|
||||
# metric = MetricDefinition.objects.get(
|
||||
# id=metric_id,
|
||||
# tenant=request.user.tenant
|
||||
# )
|
||||
#
|
||||
# # Start metric calculation
|
||||
# process = MetricCalculationFlow.start.run(
|
||||
# metric=metric,
|
||||
# calculate_now=True,
|
||||
# created_by=request.user
|
||||
# )
|
||||
#
|
||||
# return JsonResponse({
|
||||
# 'success': True,
|
||||
# 'message': 'Metric calculation initiated.'
|
||||
# })
|
||||
# except MetricDefinition.DoesNotExist:
|
||||
# return JsonResponse({
|
||||
# 'success': False,
|
||||
# 'message': 'Metric not found.'
|
||||
# })
|
||||
#
|
||||
# return JsonResponse({'success': False, 'message': 'Invalid request.'})
|
||||
#
|
||||
#
|
||||
# @login_required
|
||||
# def dashboard_data_ajax(request, dashboard_id):
|
||||
# """AJAX view to get dashboard data"""
|
||||
# try:
|
||||
# dashboard = Dashboard.objects.get(
|
||||
# id=dashboard_id,
|
||||
# tenant=request.user.tenant
|
||||
# )
|
||||
#
|
||||
# widgets_data = []
|
||||
# for widget in dashboard.widgets.filter(is_active=True):
|
||||
# widget_data = {
|
||||
# 'id': widget.id,
|
||||
# 'title': widget.title,
|
||||
# 'type': widget.widget_type,
|
||||
# 'position': {
|
||||
# 'x': widget.position_x,
|
||||
# 'y': widget.position_y,
|
||||
# 'width': widget.width,
|
||||
# 'height': widget.height
|
||||
# },
|
||||
# 'data': widget.get_data() # This would be implemented in the model
|
||||
# }
|
||||
# widgets_data.append(widget_data)
|
||||
#
|
||||
# return JsonResponse({
|
||||
# 'success': True,
|
||||
# 'dashboard': {
|
||||
# 'id': dashboard.id,
|
||||
# 'name': dashboard.name,
|
||||
# 'widgets': widgets_data
|
||||
# }
|
||||
# })
|
||||
# except Dashboard.DoesNotExist:
|
||||
# return JsonResponse({
|
||||
# 'success': False,
|
||||
# 'message': 'Dashboard not found.'
|
||||
# })
|
||||
#
|
||||
#
|
||||
# def test_data_source_connection(data_source):
|
||||
# """Test data source connection"""
|
||||
# try:
|
||||
# # Implement connection testing logic
|
||||
# return {'success': True}
|
||||
# except Exception as e:
|
||||
# return {'success': False, 'error': str(e)}
|
||||
#
|
||||
|
||||
BIN
appointments/__pycache__/flows.cpython-312.pyc
Normal file
BIN
appointments/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
871
appointments/flows.py
Normal file
871
appointments/flows.py
Normal file
@ -0,0 +1,871 @@
|
||||
# """
|
||||
# Viewflow workflows for appointments app.
|
||||
# Provides appointment scheduling, confirmation, and queue management workflows.
|
||||
# """
|
||||
#
|
||||
# from viewflow import Flow, lock
|
||||
# from viewflow.base import this, flow_func
|
||||
# from viewflow.contrib import celery
|
||||
# from viewflow.decorators import flow_view
|
||||
# from viewflow.fields import CharField, ModelField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.models import Process, Task
|
||||
# from django.contrib.auth.models import User
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import AppointmentRequest, SlotAvailability, WaitingQueue, QueueEntry
|
||||
# from .views import (
|
||||
# AppointmentRequestView, AvailabilityCheckView, AppointmentSchedulingView,
|
||||
# AppointmentConfirmationView, ReminderView, CheckInView, QueueManagementView,
|
||||
# TelemedicineSetupView, AppointmentCompletionView, ReschedulingView,
|
||||
# CancellationView, NoShowHandlingView
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class AppointmentSchedulingProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for appointment scheduling
|
||||
# """
|
||||
# appointment_request = ModelField(AppointmentRequest, help_text='Associated appointment request')
|
||||
#
|
||||
# # Process status tracking
|
||||
# request_submitted = models.BooleanField(default=False)
|
||||
# availability_checked = models.BooleanField(default=False)
|
||||
# appointment_scheduled = models.BooleanField(default=False)
|
||||
# confirmation_sent = models.BooleanField(default=False)
|
||||
# reminders_scheduled = models.BooleanField(default=False)
|
||||
# patient_checked_in = models.BooleanField(default=False)
|
||||
# appointment_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Appointment Scheduling Process'
|
||||
# verbose_name_plural = 'Appointment Scheduling Processes'
|
||||
#
|
||||
#
|
||||
# class AppointmentSchedulingFlow(Flow):
|
||||
# """
|
||||
# Appointment Scheduling Workflow
|
||||
#
|
||||
# This flow manages the complete appointment lifecycle from
|
||||
# request through scheduling, confirmation, and completion.
|
||||
# """
|
||||
#
|
||||
# process_class = AppointmentSchedulingProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_appointment_scheduling)
|
||||
# .Next(this.submit_request)
|
||||
# )
|
||||
#
|
||||
# submit_request = (
|
||||
# flow_view(AppointmentRequestView)
|
||||
# .Permission('appointments.can_submit_requests')
|
||||
# .Next(this.check_availability)
|
||||
# )
|
||||
#
|
||||
# check_availability = (
|
||||
# flow_view(AvailabilityCheckView)
|
||||
# .Permission('appointments.can_check_availability')
|
||||
# .Next(this.schedule_appointment)
|
||||
# )
|
||||
#
|
||||
# schedule_appointment = (
|
||||
# flow_view(AppointmentSchedulingView)
|
||||
# .Permission('appointments.can_schedule_appointments')
|
||||
# .Next(this.send_confirmation)
|
||||
# )
|
||||
#
|
||||
# send_confirmation = (
|
||||
# flow_view(AppointmentConfirmationView)
|
||||
# .Permission('appointments.can_send_confirmations')
|
||||
# .Next(this.schedule_reminders)
|
||||
# )
|
||||
#
|
||||
# schedule_reminders = (
|
||||
# flow_func(this.setup_reminders)
|
||||
# .Next(this.check_in_patient)
|
||||
# )
|
||||
#
|
||||
# check_in_patient = (
|
||||
# flow_view(CheckInView)
|
||||
# .Permission('appointments.can_check_in_patients')
|
||||
# .Next(this.complete_appointment)
|
||||
# )
|
||||
#
|
||||
# complete_appointment = (
|
||||
# flow_func(this.finalize_appointment)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_appointment_scheduling)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_appointment_scheduling(self, activation):
|
||||
# """Initialize the appointment scheduling process"""
|
||||
# process = activation.process
|
||||
# appointment = process.appointment_request
|
||||
#
|
||||
# # Update appointment status
|
||||
# appointment.status = 'REQUESTED'
|
||||
# appointment.save()
|
||||
#
|
||||
# # Send notification to scheduling staff
|
||||
# self.notify_scheduling_staff(appointment)
|
||||
#
|
||||
# # Check for urgent appointments
|
||||
# if appointment.priority in ['HIGH', 'URGENT'] or appointment.urgency_score >= 8:
|
||||
# self.notify_urgent_appointment(appointment)
|
||||
#
|
||||
# def setup_reminders(self, activation):
|
||||
# """Setup appointment reminders"""
|
||||
# process = activation.process
|
||||
# appointment = process.appointment_request
|
||||
#
|
||||
# # Mark reminders as scheduled
|
||||
# process.reminders_scheduled = True
|
||||
# process.save()
|
||||
#
|
||||
# # Schedule reminder tasks
|
||||
# self.schedule_appointment_reminders(appointment)
|
||||
#
|
||||
# # Send immediate confirmation if telemedicine
|
||||
# if appointment.is_telemedicine:
|
||||
# self.setup_telemedicine_meeting(appointment)
|
||||
#
|
||||
# def finalize_appointment(self, activation):
|
||||
# """Finalize the appointment process"""
|
||||
# process = activation.process
|
||||
# appointment = process.appointment_request
|
||||
#
|
||||
# # Update appointment status
|
||||
# appointment.status = 'COMPLETED'
|
||||
# appointment.completed_at = timezone.now()
|
||||
# appointment.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.appointment_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_appointment_completion(appointment)
|
||||
#
|
||||
# # Update provider schedule
|
||||
# self.update_provider_schedule(appointment)
|
||||
#
|
||||
# # Generate follow-up recommendations
|
||||
# self.generate_follow_up_recommendations(appointment)
|
||||
#
|
||||
# def end_appointment_scheduling(self, activation):
|
||||
# """End the appointment scheduling workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate appointment summary
|
||||
# self.generate_appointment_summary(process.appointment_request)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_scheduling_staff(self, appointment):
|
||||
# """Notify scheduling staff of new appointment request"""
|
||||
# from django.contrib.auth.models import Group
|
||||
#
|
||||
# scheduling_staff = User.objects.filter(
|
||||
# groups__name='Scheduling Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in scheduling_staff:
|
||||
# send_mail(
|
||||
# subject=f'New Appointment Request: {appointment.patient.get_full_name()}',
|
||||
# message=f'New {appointment.get_appointment_type_display()} appointment request for {appointment.specialty}.',
|
||||
# from_email='scheduling@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_urgent_appointment(self, appointment):
|
||||
# """Notify of urgent appointment request"""
|
||||
# scheduling_managers = User.objects.filter(
|
||||
# groups__name='Scheduling Managers'
|
||||
# )
|
||||
#
|
||||
# for manager in scheduling_managers:
|
||||
# send_mail(
|
||||
# subject=f'URGENT Appointment Request: {appointment.patient.get_full_name()}',
|
||||
# message=f'{appointment.get_priority_display()} appointment request requires immediate attention.',
|
||||
# from_email='scheduling@hospital.com',
|
||||
# recipient_list=[manager.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def schedule_appointment_reminders(self, appointment):
|
||||
# """Schedule appointment reminder tasks"""
|
||||
# if appointment.scheduled_datetime:
|
||||
# # Schedule 24-hour reminder
|
||||
# reminder_24h = appointment.scheduled_datetime - timedelta(hours=24)
|
||||
# if reminder_24h > timezone.now():
|
||||
# send_appointment_reminder.apply_async(
|
||||
# args=[appointment.id, '24_hour'],
|
||||
# eta=reminder_24h
|
||||
# )
|
||||
#
|
||||
# # Schedule 2-hour reminder
|
||||
# reminder_2h = appointment.scheduled_datetime - timedelta(hours=2)
|
||||
# if reminder_2h > timezone.now():
|
||||
# send_appointment_reminder.apply_async(
|
||||
# args=[appointment.id, '2_hour'],
|
||||
# eta=reminder_2h
|
||||
# )
|
||||
#
|
||||
# def setup_telemedicine_meeting(self, appointment):
|
||||
# """Setup telemedicine meeting details"""
|
||||
# # This would integrate with telemedicine platforms
|
||||
# pass
|
||||
#
|
||||
# def notify_appointment_completion(self, appointment):
|
||||
# """Notify appointment completion"""
|
||||
# # Notify patient
|
||||
# if appointment.patient.email:
|
||||
# send_mail(
|
||||
# subject='Appointment Completed',
|
||||
# message=f'Your appointment with {appointment.provider.get_full_name()} has been completed.',
|
||||
# from_email='appointments@hospital.com',
|
||||
# recipient_list=[appointment.patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def update_provider_schedule(self, appointment):
|
||||
# """Update provider schedule after appointment"""
|
||||
# # This would update provider availability
|
||||
# pass
|
||||
#
|
||||
# def generate_follow_up_recommendations(self, appointment):
|
||||
# """Generate follow-up appointment recommendations"""
|
||||
# # This would analyze appointment and suggest follow-ups
|
||||
# pass
|
||||
#
|
||||
# def generate_appointment_summary(self, appointment):
|
||||
# """Generate appointment summary"""
|
||||
# # This would generate appointment summary report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class AppointmentConfirmationProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for appointment confirmation
|
||||
# """
|
||||
# appointment_request = ModelField(AppointmentRequest, help_text='Associated appointment request')
|
||||
#
|
||||
# # Process status tracking
|
||||
# confirmation_requested = models.BooleanField(default=False)
|
||||
# patient_contacted = models.BooleanField(default=False)
|
||||
# confirmation_received = models.BooleanField(default=False)
|
||||
# details_updated = models.BooleanField(default=False)
|
||||
# confirmation_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Appointment Confirmation Process'
|
||||
# verbose_name_plural = 'Appointment Confirmation Processes'
|
||||
#
|
||||
#
|
||||
# class AppointmentConfirmationFlow(Flow):
|
||||
# """
|
||||
# Appointment Confirmation Workflow
|
||||
#
|
||||
# This flow manages appointment confirmation including patient
|
||||
# contact, confirmation receipt, and detail updates.
|
||||
# """
|
||||
#
|
||||
# process_class = AppointmentConfirmationProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_confirmation)
|
||||
# .Next(this.request_confirmation)
|
||||
# )
|
||||
#
|
||||
# request_confirmation = (
|
||||
# flow_func(this.send_confirmation_request)
|
||||
# .Next(this.contact_patient)
|
||||
# )
|
||||
#
|
||||
# contact_patient = (
|
||||
# flow_view(PatientContactView)
|
||||
# .Permission('appointments.can_contact_patients')
|
||||
# .Next(this.receive_confirmation)
|
||||
# )
|
||||
#
|
||||
# receive_confirmation = (
|
||||
# flow_view(ConfirmationReceiptView)
|
||||
# .Permission('appointments.can_receive_confirmations')
|
||||
# .Next(this.update_details)
|
||||
# )
|
||||
#
|
||||
# update_details = (
|
||||
# flow_view(DetailUpdateView)
|
||||
# .Permission('appointments.can_update_details')
|
||||
# .Next(this.complete_confirmation)
|
||||
# )
|
||||
#
|
||||
# complete_confirmation = (
|
||||
# flow_func(this.finalize_confirmation)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_confirmation)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_confirmation(self, activation):
|
||||
# """Initialize the confirmation process"""
|
||||
# process = activation.process
|
||||
# appointment = process.appointment_request
|
||||
#
|
||||
# # Send confirmation request
|
||||
# self.send_confirmation_request(appointment)
|
||||
#
|
||||
# def send_confirmation_request(self, activation):
|
||||
# """Send confirmation request to patient"""
|
||||
# process = activation.process
|
||||
# appointment = process.appointment_request
|
||||
#
|
||||
# # Mark confirmation requested
|
||||
# process.confirmation_requested = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send confirmation request via preferred method
|
||||
# self.send_confirmation_via_preferred_method(appointment)
|
||||
#
|
||||
# def finalize_confirmation(self, activation):
|
||||
# """Finalize the confirmation process"""
|
||||
# process = activation.process
|
||||
# appointment = process.appointment_request
|
||||
#
|
||||
# # Update appointment status
|
||||
# appointment.status = 'CONFIRMED'
|
||||
# appointment.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.confirmation_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send confirmation completion notification
|
||||
# self.notify_confirmation_completion(appointment)
|
||||
#
|
||||
# def end_confirmation(self, activation):
|
||||
# """End the confirmation workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Log confirmation completion
|
||||
# self.log_confirmation_completion(process.appointment_request)
|
||||
#
|
||||
# # Helper methods
|
||||
# def send_confirmation_via_preferred_method(self, appointment):
|
||||
# """Send confirmation via patient's preferred method"""
|
||||
# preferences = appointment.reminder_preferences
|
||||
#
|
||||
# if preferences.get('email', True) and appointment.patient.email:
|
||||
# self.send_email_confirmation(appointment)
|
||||
#
|
||||
# if preferences.get('sms', False) and appointment.patient.phone:
|
||||
# self.send_sms_confirmation(appointment)
|
||||
#
|
||||
# if preferences.get('phone', False):
|
||||
# self.schedule_phone_confirmation(appointment)
|
||||
#
|
||||
# def send_email_confirmation(self, appointment):
|
||||
# """Send email confirmation"""
|
||||
# send_mail(
|
||||
# subject='Appointment Confirmation Required',
|
||||
# message=f'Please confirm your appointment on {appointment.scheduled_datetime}.',
|
||||
# from_email='appointments@hospital.com',
|
||||
# recipient_list=[appointment.patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def send_sms_confirmation(self, appointment):
|
||||
# """Send SMS confirmation"""
|
||||
# # This would integrate with SMS service
|
||||
# pass
|
||||
#
|
||||
# def schedule_phone_confirmation(self, appointment):
|
||||
# """Schedule phone confirmation call"""
|
||||
# # This would schedule a phone call task
|
||||
# pass
|
||||
#
|
||||
# def notify_confirmation_completion(self, appointment):
|
||||
# """Notify confirmation completion"""
|
||||
# # Notify scheduling staff
|
||||
# scheduling_staff = User.objects.filter(
|
||||
# groups__name='Scheduling Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in scheduling_staff:
|
||||
# send_mail(
|
||||
# subject=f'Appointment Confirmed: {appointment.patient.get_full_name()}',
|
||||
# message=f'Patient has confirmed appointment for {appointment.scheduled_datetime}.',
|
||||
# from_email='appointments@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def log_confirmation_completion(self, appointment):
|
||||
# """Log confirmation completion"""
|
||||
# # This would log confirmation details
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class QueueManagementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for queue management
|
||||
# """
|
||||
# queue_entry = ModelField(QueueEntry, help_text='Associated queue entry')
|
||||
#
|
||||
# # Process status tracking
|
||||
# patient_queued = models.BooleanField(default=False)
|
||||
# position_assigned = models.BooleanField(default=False)
|
||||
# wait_time_estimated = models.BooleanField(default=False)
|
||||
# patient_called = models.BooleanField(default=False)
|
||||
# service_started = models.BooleanField(default=False)
|
||||
# service_completed = models.BooleanField(default=False)
|
||||
# queue_exited = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Queue Management Process'
|
||||
# verbose_name_plural = 'Queue Management Processes'
|
||||
#
|
||||
#
|
||||
# class QueueManagementFlow(Flow):
|
||||
# """
|
||||
# Queue Management Workflow
|
||||
#
|
||||
# This flow manages patient flow through waiting queues
|
||||
# including position assignment and service coordination.
|
||||
# """
|
||||
#
|
||||
# process_class = QueueManagementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_queue_management)
|
||||
# .Next(this.queue_patient)
|
||||
# )
|
||||
#
|
||||
# queue_patient = (
|
||||
# flow_view(PatientQueuingView)
|
||||
# .Permission('appointments.can_queue_patients')
|
||||
# .Next(this.assign_position)
|
||||
# )
|
||||
#
|
||||
# assign_position = (
|
||||
# flow_func(this.calculate_queue_position)
|
||||
# .Next(this.estimate_wait_time)
|
||||
# )
|
||||
#
|
||||
# estimate_wait_time = (
|
||||
# flow_func(this.calculate_wait_time)
|
||||
# .Next(this.call_patient)
|
||||
# )
|
||||
#
|
||||
# call_patient = (
|
||||
# flow_view(PatientCallingView)
|
||||
# .Permission('appointments.can_call_patients')
|
||||
# .Next(this.start_service)
|
||||
# )
|
||||
#
|
||||
# start_service = (
|
||||
# flow_view(ServiceStartView)
|
||||
# .Permission('appointments.can_start_service')
|
||||
# .Next(this.complete_service)
|
||||
# )
|
||||
#
|
||||
# complete_service = (
|
||||
# flow_view(ServiceCompletionView)
|
||||
# .Permission('appointments.can_complete_service')
|
||||
# .Next(this.exit_queue)
|
||||
# )
|
||||
#
|
||||
# exit_queue = (
|
||||
# flow_func(this.finalize_queue_management)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_queue_management)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_queue_management(self, activation):
|
||||
# """Initialize the queue management process"""
|
||||
# process = activation.process
|
||||
# entry = process.queue_entry
|
||||
#
|
||||
# # Update entry status
|
||||
# entry.status = 'WAITING'
|
||||
# entry.save()
|
||||
#
|
||||
# # Send queue notification
|
||||
# self.notify_queue_entry(entry)
|
||||
#
|
||||
# def calculate_queue_position(self, activation):
|
||||
# """Calculate and assign queue position"""
|
||||
# process = activation.process
|
||||
# entry = process.queue_entry
|
||||
#
|
||||
# # Calculate position based on priority and arrival time
|
||||
# position = self.determine_queue_position(entry)
|
||||
# entry.queue_position = position
|
||||
# entry.save()
|
||||
#
|
||||
# # Mark position assigned
|
||||
# process.position_assigned = True
|
||||
# process.save()
|
||||
#
|
||||
# # Update other queue positions
|
||||
# self.update_queue_positions(entry.queue)
|
||||
#
|
||||
# def calculate_wait_time(self, activation):
|
||||
# """Calculate estimated wait time"""
|
||||
# process = activation.process
|
||||
# entry = process.queue_entry
|
||||
#
|
||||
# # Calculate estimated service time
|
||||
# estimated_time = self.estimate_service_time(entry)
|
||||
# entry.estimated_service_time = estimated_time
|
||||
# entry.save()
|
||||
#
|
||||
# # Mark wait time estimated
|
||||
# process.wait_time_estimated = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send wait time notification
|
||||
# self.notify_wait_time(entry)
|
||||
#
|
||||
# def finalize_queue_management(self, activation):
|
||||
# """Finalize the queue management process"""
|
||||
# process = activation.process
|
||||
# entry = process.queue_entry
|
||||
#
|
||||
# # Update entry status
|
||||
# entry.status = 'COMPLETED'
|
||||
# entry.served_at = timezone.now()
|
||||
# entry.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.queue_exited = True
|
||||
# process.save()
|
||||
#
|
||||
# # Update queue metrics
|
||||
# self.update_queue_metrics(entry)
|
||||
#
|
||||
# # Notify next patient
|
||||
# self.notify_next_patient(entry.queue)
|
||||
#
|
||||
# def end_queue_management(self, activation):
|
||||
# """End the queue management workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate queue summary
|
||||
# self.generate_queue_summary(process.queue_entry)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_queue_entry(self, entry):
|
||||
# """Notify patient of queue entry"""
|
||||
# if entry.patient.email:
|
||||
# send_mail(
|
||||
# subject='Added to Waiting Queue',
|
||||
# message=f'You have been added to the {entry.queue.name} queue.',
|
||||
# from_email='appointments@hospital.com',
|
||||
# recipient_list=[entry.patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def determine_queue_position(self, entry):
|
||||
# """Determine queue position based on priority"""
|
||||
# # This would implement priority-based positioning logic
|
||||
# return entry.queue.current_queue_size + 1
|
||||
#
|
||||
# def update_queue_positions(self, queue):
|
||||
# """Update positions for all entries in queue"""
|
||||
# entries = queue.queue_entries.filter(status='WAITING').order_by('priority_score', 'joined_at')
|
||||
# for i, entry in enumerate(entries, 1):
|
||||
# entry.queue_position = i
|
||||
# entry.save()
|
||||
#
|
||||
# def estimate_service_time(self, entry):
|
||||
# """Estimate service time for queue entry"""
|
||||
# queue = entry.queue
|
||||
# position = entry.queue_position
|
||||
# avg_service_time = queue.average_service_time_minutes
|
||||
#
|
||||
# estimated_minutes = position * avg_service_time
|
||||
# return timezone.now() + timedelta(minutes=estimated_minutes)
|
||||
#
|
||||
# def notify_wait_time(self, entry):
|
||||
# """Notify patient of estimated wait time"""
|
||||
# if entry.patient.email and entry.estimated_service_time:
|
||||
# wait_minutes = int((entry.estimated_service_time - timezone.now()).total_seconds() / 60)
|
||||
# send_mail(
|
||||
# subject='Queue Wait Time Update',
|
||||
# message=f'Your estimated wait time is {wait_minutes} minutes.',
|
||||
# from_email='appointments@hospital.com',
|
||||
# recipient_list=[entry.patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def update_queue_metrics(self, entry):
|
||||
# """Update queue performance metrics"""
|
||||
# # This would update queue analytics
|
||||
# pass
|
||||
#
|
||||
# def notify_next_patient(self, queue):
|
||||
# """Notify next patient in queue"""
|
||||
# next_entry = queue.queue_entries.filter(
|
||||
# status='WAITING'
|
||||
# ).order_by('queue_position').first()
|
||||
#
|
||||
# if next_entry:
|
||||
# self.notify_patient_ready(next_entry)
|
||||
#
|
||||
# def notify_patient_ready(self, entry):
|
||||
# """Notify patient they are ready to be called"""
|
||||
# if entry.patient.email:
|
||||
# send_mail(
|
||||
# subject='Ready for Appointment',
|
||||
# message='You will be called shortly for your appointment.',
|
||||
# from_email='appointments@hospital.com',
|
||||
# recipient_list=[entry.patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_queue_summary(self, entry):
|
||||
# """Generate queue management summary"""
|
||||
# # This would generate queue analytics
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class TelemedicineSetupProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for telemedicine setup
|
||||
# """
|
||||
# appointment_request = ModelField(AppointmentRequest, help_text='Associated appointment request')
|
||||
#
|
||||
# # Process status tracking
|
||||
# platform_selected = models.BooleanField(default=False)
|
||||
# meeting_created = models.BooleanField(default=False)
|
||||
# credentials_sent = models.BooleanField(default=False)
|
||||
# technical_check_completed = models.BooleanField(default=False)
|
||||
# meeting_ready = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Telemedicine Setup Process'
|
||||
# verbose_name_plural = 'Telemedicine Setup Processes'
|
||||
#
|
||||
#
|
||||
# class TelemedicineSetupFlow(Flow):
|
||||
# """
|
||||
# Telemedicine Setup Workflow
|
||||
#
|
||||
# This flow manages telemedicine appointment setup including
|
||||
# platform selection, meeting creation, and technical verification.
|
||||
# """
|
||||
#
|
||||
# process_class = TelemedicineSetupProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_telemedicine_setup)
|
||||
# .Next(this.select_platform)
|
||||
# )
|
||||
#
|
||||
# select_platform = (
|
||||
# flow_view(PlatformSelectionView)
|
||||
# .Permission('appointments.can_select_platforms')
|
||||
# .Next(this.create_meeting)
|
||||
# )
|
||||
#
|
||||
# create_meeting = (
|
||||
# flow_view(MeetingCreationView)
|
||||
# .Permission('appointments.can_create_meetings')
|
||||
# .Next(this.send_credentials)
|
||||
# )
|
||||
#
|
||||
# send_credentials = (
|
||||
# flow_view(CredentialSendingView)
|
||||
# .Permission('appointments.can_send_credentials')
|
||||
# .Next(this.technical_check)
|
||||
# )
|
||||
#
|
||||
# technical_check = (
|
||||
# flow_view(TechnicalCheckView)
|
||||
# .Permission('appointments.can_perform_technical_checks')
|
||||
# .Next(this.finalize_setup)
|
||||
# )
|
||||
#
|
||||
# finalize_setup = (
|
||||
# flow_func(this.complete_telemedicine_setup)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_telemedicine_setup)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_telemedicine_setup(self, activation):
|
||||
# """Initialize the telemedicine setup process"""
|
||||
# process = activation.process
|
||||
# appointment = process.appointment_request
|
||||
#
|
||||
# # Mark as telemedicine appointment
|
||||
# appointment.is_telemedicine = True
|
||||
# appointment.save()
|
||||
#
|
||||
# # Send setup notification
|
||||
# self.notify_telemedicine_setup(appointment)
|
||||
#
|
||||
# def complete_telemedicine_setup(self, activation):
|
||||
# """Finalize the telemedicine setup process"""
|
||||
# process = activation.process
|
||||
# appointment = process.appointment_request
|
||||
#
|
||||
# # Mark meeting as ready
|
||||
# process.meeting_ready = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send final meeting details
|
||||
# self.send_final_meeting_details(appointment)
|
||||
#
|
||||
# # Schedule pre-meeting reminder
|
||||
# self.schedule_pre_meeting_reminder(appointment)
|
||||
#
|
||||
# def end_telemedicine_setup(self, activation):
|
||||
# """End the telemedicine setup workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Log setup completion
|
||||
# self.log_telemedicine_setup(process.appointment_request)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_telemedicine_setup(self, appointment):
|
||||
# """Notify patient of telemedicine setup"""
|
||||
# if appointment.patient.email:
|
||||
# send_mail(
|
||||
# subject='Telemedicine Appointment Setup',
|
||||
# message='Your telemedicine appointment is being set up. You will receive meeting details shortly.',
|
||||
# from_email='telemedicine@hospital.com',
|
||||
# recipient_list=[appointment.patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def send_final_meeting_details(self, appointment):
|
||||
# """Send final meeting details to patient"""
|
||||
# if appointment.patient.email:
|
||||
# send_mail(
|
||||
# subject='Telemedicine Meeting Details',
|
||||
# message=f'Meeting URL: {appointment.meeting_url}\nMeeting ID: {appointment.meeting_id}',
|
||||
# from_email='telemedicine@hospital.com',
|
||||
# recipient_list=[appointment.patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def schedule_pre_meeting_reminder(self, appointment):
|
||||
# """Schedule pre-meeting reminder"""
|
||||
# if appointment.scheduled_datetime:
|
||||
# reminder_time = appointment.scheduled_datetime - timedelta(minutes=15)
|
||||
# if reminder_time > timezone.now():
|
||||
# send_telemedicine_reminder.apply_async(
|
||||
# args=[appointment.id],
|
||||
# eta=reminder_time
|
||||
# )
|
||||
#
|
||||
# def log_telemedicine_setup(self, appointment):
|
||||
# """Log telemedicine setup completion"""
|
||||
# # This would log setup details
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def send_appointment_reminder(appointment_id, reminder_type):
|
||||
# """Background task to send appointment reminders"""
|
||||
# try:
|
||||
# appointment = AppointmentRequest.objects.get(id=appointment_id)
|
||||
#
|
||||
# if reminder_type == '24_hour':
|
||||
# subject = 'Appointment Reminder - Tomorrow'
|
||||
# message = f'Reminder: You have an appointment tomorrow at {appointment.scheduled_datetime}.'
|
||||
# elif reminder_type == '2_hour':
|
||||
# subject = 'Appointment Reminder - 2 Hours'
|
||||
# message = f'Reminder: You have an appointment in 2 hours at {appointment.scheduled_datetime}.'
|
||||
#
|
||||
# if appointment.patient.email:
|
||||
# send_mail(
|
||||
# subject=subject,
|
||||
# message=message,
|
||||
# from_email='appointments@hospital.com',
|
||||
# recipient_list=[appointment.patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def send_telemedicine_reminder(appointment_id):
|
||||
# """Background task to send telemedicine pre-meeting reminder"""
|
||||
# try:
|
||||
# appointment = AppointmentRequest.objects.get(id=appointment_id)
|
||||
#
|
||||
# if appointment.patient.email:
|
||||
# send_mail(
|
||||
# subject='Telemedicine Meeting Starting Soon',
|
||||
# message=f'Your telemedicine appointment starts in 15 minutes. Meeting URL: {appointment.meeting_url}',
|
||||
# from_email='telemedicine@hospital.com',
|
||||
# recipient_list=[appointment.patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def auto_confirm_appointments():
|
||||
# """Background task to automatically confirm appointments"""
|
||||
# try:
|
||||
# # This would implement auto-confirmation logic
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def manage_no_shows():
|
||||
# """Background task to manage no-show appointments"""
|
||||
# try:
|
||||
# # This would identify and handle no-show appointments
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def optimize_schedules():
|
||||
# """Background task to optimize provider schedules"""
|
||||
# try:
|
||||
# # This would implement schedule optimization
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def update_queue_positions():
|
||||
# """Background task to update queue positions"""
|
||||
# try:
|
||||
# # This would update queue positions and wait times
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
@ -439,3 +439,751 @@ class SlotSearchForm(forms.Form):
|
||||
role__in=['PHYSICIAN', 'NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT']
|
||||
).order_by('last_name', 'first_name')
|
||||
|
||||
|
||||
# from django import forms
|
||||
# from django.core.exceptions import ValidationError
|
||||
# from django.utils import timezone
|
||||
# from django.contrib.auth.models import User
|
||||
# from crispy_forms.helper import FormHelper
|
||||
# from crispy_forms.layout import Layout, Fieldset, Submit, Row, Column, HTML, Div
|
||||
# from crispy_forms.bootstrap import FormActions
|
||||
# from datetime import datetime, timedelta
|
||||
#
|
||||
# from .models import Appointment, AppointmentConfirmation, Queue, TelemedicineSession
|
||||
# from patients.models import Patient
|
||||
# from core.models import Department
|
||||
#
|
||||
#
|
||||
# class AppointmentSchedulingForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for appointment scheduling
|
||||
# """
|
||||
# patient_search = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.TextInput(attrs={
|
||||
# 'class': 'form-control',
|
||||
# 'placeholder': 'Search patient by name, ID, or phone...',
|
||||
# 'data-toggle': 'patient-search'
|
||||
# })
|
||||
# )
|
||||
# preferred_time_1 = forms.TimeField(
|
||||
# required=False,
|
||||
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
|
||||
# )
|
||||
# preferred_time_2 = forms.TimeField(
|
||||
# required=False,
|
||||
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
|
||||
# )
|
||||
# preferred_time_3 = forms.TimeField(
|
||||
# required=False,
|
||||
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
|
||||
# )
|
||||
# special_instructions = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
||||
# )
|
||||
# send_confirmation = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# send_reminder = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = Appointment
|
||||
# fields = [
|
||||
# 'patient', 'provider', 'department', 'appointment_type',
|
||||
# 'appointment_date', 'appointment_time', 'duration',
|
||||
# 'urgency', 'reason', 'notes', 'special_instructions',
|
||||
# 'send_confirmation', 'send_reminder'
|
||||
# ]
|
||||
# widgets = {
|
||||
# 'patient': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'provider': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'department': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'appointment_type': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'appointment_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||
# 'appointment_time': forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'}),
|
||||
# 'duration': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||
# 'urgency': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'reason': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
# 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
||||
# }
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# tenant = kwargs.pop('tenant', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# if tenant:
|
||||
# self.fields['patient'].queryset = Patient.objects.filter(tenant=tenant)
|
||||
# self.fields['provider'].queryset = User.objects.filter(
|
||||
# tenant=tenant,
|
||||
# groups__name__in=['Doctors', 'Nurses', 'Specialists']
|
||||
# )
|
||||
# self.fields['department'].queryset = Department.objects.filter(tenant=tenant)
|
||||
#
|
||||
# # Set minimum date to today
|
||||
# self.fields['appointment_date'].widget.attrs['min'] = timezone.now().date().isoformat()
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Patient Information',
|
||||
# 'patient_search',
|
||||
# 'patient'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Appointment Details',
|
||||
# Row(
|
||||
# Column('provider', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('department', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# Row(
|
||||
# Column('appointment_type', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('urgency', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# Row(
|
||||
# Column('appointment_date', css_class='form-group col-md-4 mb-0'),
|
||||
# Column('appointment_time', css_class='form-group col-md-4 mb-0'),
|
||||
# Column('duration', css_class='form-group col-md-4 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'reason',
|
||||
# 'notes',
|
||||
# 'special_instructions'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Preferred Times (Alternative Options)',
|
||||
# Row(
|
||||
# Column('preferred_time_1', css_class='form-group col-md-4 mb-0'),
|
||||
# Column('preferred_time_2', css_class='form-group col-md-4 mb-0'),
|
||||
# Column('preferred_time_3', css_class='form-group col-md-4 mb-0'),
|
||||
# css_class='form-row'
|
||||
# )
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Notifications',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'send_confirmation',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_send_confirmation">Send appointment confirmation</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'send_reminder',
|
||||
# HTML('<label class="form-check-label" for="id_send_reminder">Send appointment reminder</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Schedule Appointment', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'appointments:appointment_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean(self):
|
||||
# cleaned_data = super().clean()
|
||||
# appointment_date = cleaned_data.get('appointment_date')
|
||||
# appointment_time = cleaned_data.get('appointment_time')
|
||||
# provider = cleaned_data.get('provider')
|
||||
#
|
||||
# if appointment_date and appointment_time:
|
||||
# appointment_datetime = datetime.combine(appointment_date, appointment_time)
|
||||
#
|
||||
# # Check if appointment is in the past
|
||||
# if appointment_datetime < timezone.now():
|
||||
# raise ValidationError('Appointment cannot be scheduled in the past.')
|
||||
#
|
||||
# # Check provider availability
|
||||
# if provider and self.check_provider_conflict(provider, appointment_datetime):
|
||||
# raise ValidationError('Provider is not available at the selected time.')
|
||||
#
|
||||
# return cleaned_data
|
||||
#
|
||||
# def check_provider_conflict(self, provider, appointment_datetime):
|
||||
# """Check if provider has conflicting appointments"""
|
||||
# duration = self.cleaned_data.get('duration', 30)
|
||||
# end_time = appointment_datetime + timedelta(minutes=duration)
|
||||
#
|
||||
# conflicts = Appointment.objects.filter(
|
||||
# provider=provider,
|
||||
# appointment_date=appointment_datetime.date(),
|
||||
# status__in=['scheduled', 'confirmed', 'in_progress']
|
||||
# ).exclude(id=self.instance.id if self.instance else None)
|
||||
#
|
||||
# for conflict in conflicts:
|
||||
# conflict_start = datetime.combine(conflict.appointment_date, conflict.appointment_time)
|
||||
# conflict_end = conflict_start + timedelta(minutes=conflict.duration)
|
||||
#
|
||||
# if (appointment_datetime < conflict_end and end_time > conflict_start):
|
||||
# return True
|
||||
#
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# class AppointmentConfirmationForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for appointment confirmation
|
||||
# """
|
||||
# confirmation_method = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('phone', 'Phone Call'),
|
||||
# ('email', 'Email'),
|
||||
# ('sms', 'SMS'),
|
||||
# ('in_person', 'In Person'),
|
||||
# ('online', 'Online Portal')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# contact_attempts = forms.IntegerField(
|
||||
# initial=1,
|
||||
# min_value=1,
|
||||
# max_value=5,
|
||||
# widget=forms.NumberInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# confirmation_notes = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
||||
# )
|
||||
# reschedule_requested = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# new_preferred_date = forms.DateField(
|
||||
# required=False,
|
||||
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||||
# )
|
||||
# new_preferred_time = forms.TimeField(
|
||||
# required=False,
|
||||
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = AppointmentConfirmation
|
||||
# fields = [
|
||||
# 'confirmation_method', 'contact_attempts', 'confirmation_notes',
|
||||
# 'reschedule_requested', 'new_preferred_date', 'new_preferred_time'
|
||||
# ]
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# appointment = kwargs.pop('appointment', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.appointment = appointment
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Confirmation Details',
|
||||
# Row(
|
||||
# Column('confirmation_method', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('contact_attempts', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'confirmation_notes'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Reschedule Request',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'reschedule_requested',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_reschedule_requested">Patient requested reschedule</label>'),
|
||||
# HTML('</div>'),
|
||||
# Row(
|
||||
# Column('new_preferred_date', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('new_preferred_time', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# )
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Confirm Appointment', css_class='btn btn-primary'),
|
||||
# HTML(
|
||||
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean(self):
|
||||
# cleaned_data = super().clean()
|
||||
# reschedule_requested = cleaned_data.get('reschedule_requested')
|
||||
# new_preferred_date = cleaned_data.get('new_preferred_date')
|
||||
# new_preferred_time = cleaned_data.get('new_preferred_time')
|
||||
#
|
||||
# if reschedule_requested:
|
||||
# if not new_preferred_date:
|
||||
# raise ValidationError('New preferred date is required when reschedule is requested.')
|
||||
# if not new_preferred_time:
|
||||
# raise ValidationError('New preferred time is required when reschedule is requested.')
|
||||
#
|
||||
# return cleaned_data
|
||||
#
|
||||
#
|
||||
# class QueueManagementForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for queue management
|
||||
# """
|
||||
# estimated_wait_time = forms.IntegerField(
|
||||
# required=False,
|
||||
# widget=forms.NumberInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# priority_adjustment = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('none', 'No Change'),
|
||||
# ('increase', 'Increase Priority'),
|
||||
# ('decrease', 'Decrease Priority')
|
||||
# ],
|
||||
# required=False,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# queue_notes = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
||||
# )
|
||||
# notify_patient = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = Queue
|
||||
# fields = [
|
||||
# 'queue_type', 'priority', 'estimated_wait_time',
|
||||
# 'priority_adjustment', 'queue_notes', 'notify_patient'
|
||||
# ]
|
||||
# widgets = {
|
||||
# 'queue_type': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'priority': forms.Select(attrs={'class': 'form-control'})
|
||||
# }
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Queue Configuration',
|
||||
# Row(
|
||||
# Column('queue_type', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('priority', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# Row(
|
||||
# Column('estimated_wait_time', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('priority_adjustment', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'queue_notes'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Notifications',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'notify_patient',
|
||||
# HTML('<label class="form-check-label" for="id_notify_patient">Notify patient of queue status</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Update Queue', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'appointments:queue_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class TelemedicineSetupForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for telemedicine session setup
|
||||
# """
|
||||
# platform = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('zoom', 'Zoom'),
|
||||
# ('teams', 'Microsoft Teams'),
|
||||
# ('webex', 'Cisco Webex'),
|
||||
# ('meet', 'Google Meet'),
|
||||
# ('custom', 'Custom Platform')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# test_connection = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# send_instructions = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# backup_phone = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# technical_support_contact = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# session_recording = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = TelemedicineSession
|
||||
# fields = [
|
||||
# 'platform', 'meeting_url', 'meeting_id', 'meeting_password',
|
||||
# 'test_connection', 'send_instructions', 'backup_phone',
|
||||
# 'technical_support_contact', 'session_recording'
|
||||
# ]
|
||||
# widgets = {
|
||||
# 'meeting_url': forms.URLInput(attrs={'class': 'form-control'}),
|
||||
# 'meeting_id': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
# 'meeting_password': forms.TextInput(attrs={'class': 'form-control'})
|
||||
# }
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# appointment = kwargs.pop('appointment', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.appointment = appointment
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Platform Configuration',
|
||||
# 'platform',
|
||||
# 'meeting_url',
|
||||
# Row(
|
||||
# Column('meeting_id', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('meeting_password', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# )
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Support Information',
|
||||
# Row(
|
||||
# Column('backup_phone', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('technical_support_contact', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# )
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Session Options',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'test_connection',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_test_connection">Test connection before appointment</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'send_instructions',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_send_instructions">Send setup instructions to patient</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'session_recording',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_session_recording">Record session (with consent)</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Setup Telemedicine', css_class='btn btn-primary'),
|
||||
# HTML(
|
||||
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean_meeting_url(self):
|
||||
# meeting_url = self.cleaned_data.get('meeting_url')
|
||||
# platform = self.cleaned_data.get('platform')
|
||||
#
|
||||
# if platform and meeting_url:
|
||||
# # Validate URL format based on platform
|
||||
# if platform == 'zoom' and 'zoom.us' not in meeting_url:
|
||||
# raise ValidationError('Invalid Zoom meeting URL format.')
|
||||
# elif platform == 'teams' and 'teams.microsoft.com' not in meeting_url:
|
||||
# raise ValidationError('Invalid Microsoft Teams meeting URL format.')
|
||||
# elif platform == 'webex' and 'webex.com' not in meeting_url:
|
||||
# raise ValidationError('Invalid Webex meeting URL format.')
|
||||
# elif platform == 'meet' and 'meet.google.com' not in meeting_url:
|
||||
# raise ValidationError('Invalid Google Meet URL format.')
|
||||
#
|
||||
# return meeting_url
|
||||
#
|
||||
#
|
||||
# class AppointmentRescheduleForm(forms.Form):
|
||||
# """
|
||||
# Form for appointment rescheduling
|
||||
# """
|
||||
# new_date = forms.DateField(
|
||||
# required=True,
|
||||
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||||
# )
|
||||
# new_time = forms.TimeField(
|
||||
# required=True,
|
||||
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
|
||||
# )
|
||||
# new_provider = forms.ModelChoiceField(
|
||||
# queryset=None,
|
||||
# required=False,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# reschedule_reason = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('patient_request', 'Patient Request'),
|
||||
# ('provider_unavailable', 'Provider Unavailable'),
|
||||
# ('emergency', 'Emergency'),
|
||||
# ('equipment_issue', 'Equipment Issue'),
|
||||
# ('other', 'Other')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# notes = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
||||
# )
|
||||
# notify_patient = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# tenant = kwargs.pop('tenant', None)
|
||||
# appointment = kwargs.pop('appointment', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# if tenant:
|
||||
# self.fields['new_provider'].queryset = User.objects.filter(
|
||||
# tenant=tenant,
|
||||
# groups__name__in=['Doctors', 'Nurses', 'Specialists']
|
||||
# )
|
||||
#
|
||||
# # Set minimum date to today
|
||||
# self.fields['new_date'].widget.attrs['min'] = timezone.now().date().isoformat()
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'New Appointment Details',
|
||||
# Row(
|
||||
# Column('new_date', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('new_time', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'new_provider'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Reschedule Information',
|
||||
# 'reschedule_reason',
|
||||
# 'notes'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Notifications',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'notify_patient',
|
||||
# HTML('<label class="form-check-label" for="id_notify_patient">Notify patient of reschedule</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Reschedule Appointment', css_class='btn btn-primary'),
|
||||
# HTML(
|
||||
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class AppointmentCancellationForm(forms.Form):
|
||||
# """
|
||||
# Form for appointment cancellation
|
||||
# """
|
||||
# cancellation_reason = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('patient_request', 'Patient Request'),
|
||||
# ('provider_unavailable', 'Provider Unavailable'),
|
||||
# ('patient_no_show', 'Patient No Show'),
|
||||
# ('emergency', 'Emergency'),
|
||||
# ('equipment_failure', 'Equipment Failure'),
|
||||
# ('other', 'Other')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# cancellation_notes = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
||||
# )
|
||||
# offer_reschedule = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# notify_patient = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# refund_required = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Cancellation Details',
|
||||
# 'cancellation_reason',
|
||||
# 'cancellation_notes'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Follow-up Actions',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'offer_reschedule',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_offer_reschedule">Offer to reschedule appointment</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'notify_patient',
|
||||
# HTML('<label class="form-check-label" for="id_notify_patient">Notify patient of cancellation</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'refund_required',
|
||||
# HTML('<label class="form-check-label" for="id_refund_required">Refund required</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Cancel Appointment', css_class='btn btn-danger'),
|
||||
# HTML(
|
||||
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Back</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class AppointmentCheckInForm(forms.Form):
|
||||
# """
|
||||
# Form for appointment check-in
|
||||
# """
|
||||
# arrival_time = forms.TimeField(
|
||||
# initial=timezone.now().time(),
|
||||
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
|
||||
# )
|
||||
# insurance_verified = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# copay_collected = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# forms_completed = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# vitals_required = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# special_needs = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 2})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Check-in Information',
|
||||
# 'arrival_time',
|
||||
# 'special_needs'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Pre-visit Tasks',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'insurance_verified',
|
||||
# HTML('<label class="form-check-label" for="id_insurance_verified">Insurance verified</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'copay_collected',
|
||||
# HTML('<label class="form-check-label" for="id_copay_collected">Copay collected</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'forms_completed',
|
||||
# HTML('<label class="form-check-label" for="id_forms_completed">Forms completed</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'vitals_required',
|
||||
# HTML('<label class="form-check-label" for="id_vitals_required">Vitals required</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Check In Patient', css_class='btn btn-primary'),
|
||||
# HTML(
|
||||
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class BulkAppointmentForm(forms.Form):
|
||||
# """
|
||||
# Form for bulk appointment operations
|
||||
# """
|
||||
# action = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('confirm', 'Confirm Appointments'),
|
||||
# ('reschedule', 'Reschedule Appointments'),
|
||||
# ('cancel', 'Cancel Appointments'),
|
||||
# ('send_reminders', 'Send Reminders')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# appointment_ids = forms.CharField(
|
||||
# widget=forms.HiddenInput()
|
||||
# )
|
||||
# bulk_date = forms.DateField(
|
||||
# required=False,
|
||||
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||||
# )
|
||||
# bulk_reason = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
||||
# )
|
||||
# notify_patients = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# 'appointment_ids',
|
||||
# Fieldset(
|
||||
# 'Bulk Operation',
|
||||
# 'action',
|
||||
# 'bulk_date',
|
||||
# 'bulk_reason'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Notifications',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'notify_patients',
|
||||
# HTML('<label class="form-check-label" for="id_notify_patients">Notify patients</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Execute Bulk Operation', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'appointments:appointment_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
|
||||
@ -3209,3 +3209,907 @@ def reschedule_appointment(request, appointment_id):
|
||||
# 'appointment': session.appointment
|
||||
# })
|
||||
#
|
||||
|
||||
|
||||
# from django.shortcuts import render, redirect, get_object_or_404
|
||||
# from django.contrib.auth.decorators import login_required, permission_required
|
||||
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||
# from django.contrib import messages
|
||||
# from django.views.generic import (
|
||||
# CreateView, UpdateView, DeleteView, DetailView, ListView, FormView
|
||||
# )
|
||||
# from django.urls import reverse_lazy, reverse
|
||||
# from django.http import JsonResponse, HttpResponse
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
# from django.conf import settings
|
||||
# from django.db.models import Q, Count
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from datetime import datetime, timedelta
|
||||
# import json
|
||||
#
|
||||
# from .models import Appointment, AppointmentConfirmation, Queue, TelemedicineSession
|
||||
# from .forms import (
|
||||
# AppointmentSchedulingForm, AppointmentConfirmationForm, QueueManagementForm,
|
||||
# TelemedicineSetupForm, AppointmentRescheduleForm, AppointmentCancellationForm,
|
||||
# AppointmentCheckInForm, BulkAppointmentForm
|
||||
# )
|
||||
# from .flows import AppointmentSchedulingFlow, AppointmentConfirmationFlow, QueueManagementFlow, TelemedicineSetupFlow
|
||||
# from patients.models import Patient
|
||||
#
|
||||
#
|
||||
# class AppointmentSchedulingView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
||||
# """
|
||||
# View for appointment scheduling workflow
|
||||
# """
|
||||
# model = Appointment
|
||||
# form_class = AppointmentSchedulingForm
|
||||
# template_name = 'appointments/appointment_scheduling.html'
|
||||
# permission_required = 'appointments.can_schedule_appointments'
|
||||
# flow_class = AppointmentSchedulingFlow
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['tenant'] = self.request.user.tenant
|
||||
# return kwargs
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# with transaction.atomic():
|
||||
# # Create appointment
|
||||
# appointment = form.save(commit=False)
|
||||
# appointment.tenant = self.request.user.tenant
|
||||
# appointment.scheduled_by = self.request.user
|
||||
# appointment.status = 'scheduled'
|
||||
# appointment.save()
|
||||
#
|
||||
# # Start appointment scheduling workflow
|
||||
# process = self.flow_class.start.run(
|
||||
# appointment=appointment,
|
||||
# send_confirmation=form.cleaned_data.get('send_confirmation', True),
|
||||
# send_reminder=form.cleaned_data.get('send_reminder', True),
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Appointment scheduled successfully for {appointment.patient.get_full_name()} '
|
||||
# f'on {appointment.appointment_date} at {appointment.appointment_time}.'
|
||||
# )
|
||||
#
|
||||
# return redirect('appointments:appointment_detail', pk=appointment.pk)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Schedule Appointment'
|
||||
# context['breadcrumbs'] = [
|
||||
# {'name': 'Home', 'url': reverse('core:dashboard')},
|
||||
# {'name': 'Appointments', 'url': reverse('appointments:appointment_list')},
|
||||
# {'name': 'Schedule Appointment', 'url': ''}
|
||||
# ]
|
||||
# context['available_slots'] = self.get_available_slots()
|
||||
# return context
|
||||
#
|
||||
# def get_available_slots(self):
|
||||
# """Get available appointment slots for the next 7 days"""
|
||||
# slots = []
|
||||
# today = timezone.now().date()
|
||||
#
|
||||
# for i in range(7):
|
||||
# date = today + timedelta(days=i)
|
||||
# day_slots = self.get_slots_for_date(date)
|
||||
# if day_slots:
|
||||
# slots.append({
|
||||
# 'date': date,
|
||||
# 'slots': day_slots
|
||||
# })
|
||||
#
|
||||
# return slots
|
||||
#
|
||||
# def get_slots_for_date(self, date):
|
||||
# """Get available slots for a specific date"""
|
||||
# # This would implement slot availability logic
|
||||
# return []
|
||||
#
|
||||
#
|
||||
# class AppointmentConfirmationView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
||||
# """
|
||||
# View for appointment confirmation workflow
|
||||
# """
|
||||
# model = AppointmentConfirmation
|
||||
# form_class = AppointmentConfirmationForm
|
||||
# template_name = 'appointments/appointment_confirmation.html'
|
||||
# permission_required = 'appointments.can_confirm_appointments'
|
||||
# flow_class = AppointmentConfirmationFlow
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
||||
# return kwargs
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# appointment = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
||||
#
|
||||
# with transaction.atomic():
|
||||
# # Create confirmation record
|
||||
# confirmation = form.save(commit=False)
|
||||
# confirmation.appointment = appointment
|
||||
# confirmation.confirmed_by = self.request.user
|
||||
# confirmation.confirmed_at = timezone.now()
|
||||
# confirmation.save()
|
||||
#
|
||||
# # Update appointment status
|
||||
# if form.cleaned_data.get('reschedule_requested'):
|
||||
# appointment.status = 'reschedule_requested'
|
||||
# appointment.save()
|
||||
#
|
||||
# # Start rescheduling process
|
||||
# messages.info(
|
||||
# self.request,
|
||||
# 'Patient requested reschedule. Please process the reschedule request.'
|
||||
# )
|
||||
# return redirect('appointments:appointment_reschedule', pk=appointment.pk)
|
||||
# else:
|
||||
# appointment.status = 'confirmed'
|
||||
# appointment.save()
|
||||
#
|
||||
# # Start confirmation workflow
|
||||
# process = self.flow_class.start.run(
|
||||
# appointment=appointment,
|
||||
# confirmation=confirmation,
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Appointment confirmed for {appointment.patient.get_full_name()}.'
|
||||
# )
|
||||
#
|
||||
# return redirect('appointments:appointment_detail', pk=appointment.pk)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
||||
# context['title'] = 'Confirm Appointment'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class QueueManagementView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
||||
# """
|
||||
# View for queue management workflow
|
||||
# """
|
||||
# model = Queue
|
||||
# form_class = QueueManagementForm
|
||||
# template_name = 'appointments/queue_management.html'
|
||||
# permission_required = 'appointments.can_manage_queue'
|
||||
# flow_class = QueueManagementFlow
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# appointment = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
||||
#
|
||||
# with transaction.atomic():
|
||||
# # Create or update queue entry
|
||||
# queue_entry, created = Queue.objects.get_or_create(
|
||||
# appointment=appointment,
|
||||
# defaults={
|
||||
# 'tenant': self.request.user.tenant,
|
||||
# 'patient': appointment.patient,
|
||||
# 'provider': appointment.provider,
|
||||
# 'department': appointment.department,
|
||||
# 'created_by': self.request.user
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# # Update queue entry with form data
|
||||
# for field in form.cleaned_data:
|
||||
# if hasattr(queue_entry, field):
|
||||
# setattr(queue_entry, field, form.cleaned_data[field])
|
||||
#
|
||||
# queue_entry.save()
|
||||
#
|
||||
# # Start queue management workflow
|
||||
# process = self.flow_class.start.run(
|
||||
# queue_entry=queue_entry,
|
||||
# appointment=appointment,
|
||||
# notify_patient=form.cleaned_data.get('notify_patient', True),
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Queue updated for {appointment.patient.get_full_name()}. '
|
||||
# f'Position: {queue_entry.position}, Wait time: {queue_entry.estimated_wait_time} minutes.'
|
||||
# )
|
||||
#
|
||||
# return redirect('appointments:queue_list')
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
||||
# context['title'] = 'Manage Queue'
|
||||
# context['current_queue'] = self.get_current_queue()
|
||||
# return context
|
||||
#
|
||||
# def get_current_queue(self):
|
||||
# """Get current queue status"""
|
||||
# return Queue.objects.filter(
|
||||
# tenant=self.request.user.tenant,
|
||||
# status='waiting'
|
||||
# ).order_by('priority', 'created_at')
|
||||
#
|
||||
#
|
||||
# class TelemedicineSetupView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
||||
# """
|
||||
# View for telemedicine setup workflow
|
||||
# """
|
||||
# model = TelemedicineSession
|
||||
# form_class = TelemedicineSetupForm
|
||||
# template_name = 'appointments/telemedicine_setup.html'
|
||||
# permission_required = 'appointments.can_setup_telemedicine'
|
||||
# flow_class = TelemedicineSetupFlow
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
||||
# return kwargs
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# appointment = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
||||
#
|
||||
# with transaction.atomic():
|
||||
# # Create telemedicine session
|
||||
# session = form.save(commit=False)
|
||||
# session.appointment = appointment
|
||||
# session.tenant = self.request.user.tenant
|
||||
# session.created_by = self.request.user
|
||||
# session.save()
|
||||
#
|
||||
# # Update appointment type
|
||||
# appointment.appointment_type = 'telemedicine'
|
||||
# appointment.save()
|
||||
#
|
||||
# # Start telemedicine setup workflow
|
||||
# process = self.flow_class.start.run(
|
||||
# session=session,
|
||||
# appointment=appointment,
|
||||
# test_connection=form.cleaned_data.get('test_connection', True),
|
||||
# send_instructions=form.cleaned_data.get('send_instructions', True),
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Telemedicine session setup completed for {appointment.patient.get_full_name()}. '
|
||||
# f'Meeting details have been sent to the patient.'
|
||||
# )
|
||||
#
|
||||
# return redirect('appointments:appointment_detail', pk=appointment.pk)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
||||
# context['title'] = 'Setup Telemedicine'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class AppointmentRescheduleView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||
# """
|
||||
# View for appointment rescheduling
|
||||
# """
|
||||
# form_class = AppointmentRescheduleForm
|
||||
# template_name = 'appointments/appointment_reschedule.html'
|
||||
# permission_required = 'appointments.can_reschedule_appointments'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('appointments:appointment_detail', kwargs={'pk': self.kwargs['pk']})
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['tenant'] = self.request.user.tenant
|
||||
# kwargs['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['pk'])
|
||||
# return kwargs
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# appointment = get_object_or_404(Appointment, pk=self.kwargs['pk'])
|
||||
#
|
||||
# with transaction.atomic():
|
||||
# # Store original appointment details
|
||||
# original_date = appointment.appointment_date
|
||||
# original_time = appointment.appointment_time
|
||||
# original_provider = appointment.provider
|
||||
#
|
||||
# # Update appointment
|
||||
# appointment.appointment_date = form.cleaned_data['new_date']
|
||||
# appointment.appointment_time = form.cleaned_data['new_time']
|
||||
# if form.cleaned_data.get('new_provider'):
|
||||
# appointment.provider = form.cleaned_data['new_provider']
|
||||
# appointment.status = 'rescheduled'
|
||||
# appointment.save()
|
||||
#
|
||||
# # Log reschedule
|
||||
# self.log_reschedule(appointment, original_date, original_time, original_provider, form.cleaned_data)
|
||||
#
|
||||
# # Send notifications
|
||||
# if form.cleaned_data.get('notify_patient'):
|
||||
# self.send_reschedule_notification(appointment, form.cleaned_data)
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Appointment rescheduled successfully. New date: {appointment.appointment_date}, '
|
||||
# f'New time: {appointment.appointment_time}.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def log_reschedule(self, appointment, original_date, original_time, original_provider, form_data):
|
||||
# """Log reschedule details"""
|
||||
# from core.models import AuditLogEntry
|
||||
# AuditLogEntry.objects.create(
|
||||
# tenant=appointment.tenant,
|
||||
# user=self.request.user,
|
||||
# event_type='APPOINTMENT_RESCHEDULE',
|
||||
# action='UPDATE',
|
||||
# object_type='Appointment',
|
||||
# object_id=str(appointment.id),
|
||||
# details={
|
||||
# 'original_date': original_date.isoformat(),
|
||||
# 'original_time': original_time.isoformat(),
|
||||
# 'original_provider': original_provider.get_full_name() if original_provider else None,
|
||||
# 'new_date': form_data['new_date'].isoformat(),
|
||||
# 'new_time': form_data['new_time'].isoformat(),
|
||||
# 'new_provider': form_data['new_provider'].get_full_name() if form_data.get('new_provider') else None,
|
||||
# 'reason': form_data['reschedule_reason'],
|
||||
# 'notes': form_data.get('notes', '')
|
||||
# },
|
||||
# ip_address=self.request.META.get('REMOTE_ADDR'),
|
||||
# user_agent=self.request.META.get('HTTP_USER_AGENT', '')
|
||||
# )
|
||||
#
|
||||
# def send_reschedule_notification(self, appointment, form_data):
|
||||
# """Send reschedule notification to patient"""
|
||||
# if appointment.patient.email:
|
||||
# send_mail(
|
||||
# subject='Appointment Rescheduled',
|
||||
# message=f'Your appointment has been rescheduled to {appointment.appointment_date} at {appointment.appointment_time}.',
|
||||
# from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
# recipient_list=[appointment.patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['pk'])
|
||||
# context['title'] = 'Reschedule Appointment'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class AppointmentCancellationView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||
# """
|
||||
# View for appointment cancellation
|
||||
# """
|
||||
# form_class = AppointmentCancellationForm
|
||||
# template_name = 'appointments/appointment_cancellation.html'
|
||||
# permission_required = 'appointments.can_cancel_appointments'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('appointments:appointment_list')
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# appointment = get_object_or_404(Appointment, pk=self.kwargs['pk'])
|
||||
#
|
||||
# with transaction.atomic():
|
||||
# # Update appointment status
|
||||
# appointment.status = 'cancelled'
|
||||
# appointment.cancelled_at = timezone.now()
|
||||
# appointment.cancelled_by = self.request.user
|
||||
# appointment.cancellation_reason = form.cleaned_data['cancellation_reason']
|
||||
# appointment.cancellation_notes = form.cleaned_data.get('cancellation_notes', '')
|
||||
# appointment.save()
|
||||
#
|
||||
# # Handle follow-up actions
|
||||
# if form.cleaned_data.get('offer_reschedule'):
|
||||
# self.offer_reschedule(appointment)
|
||||
#
|
||||
# if form.cleaned_data.get('notify_patient'):
|
||||
# self.send_cancellation_notification(appointment, form.cleaned_data)
|
||||
#
|
||||
# if form.cleaned_data.get('refund_required'):
|
||||
# self.process_refund(appointment)
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Appointment cancelled for {appointment.patient.get_full_name()}.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def offer_reschedule(self, appointment):
|
||||
# """Offer reschedule options to patient"""
|
||||
# # This would implement reschedule offering logic
|
||||
# pass
|
||||
#
|
||||
# def send_cancellation_notification(self, appointment, form_data):
|
||||
# """Send cancellation notification to patient"""
|
||||
# if appointment.patient.email:
|
||||
# send_mail(
|
||||
# subject='Appointment Cancelled',
|
||||
# message=f'Your appointment on {appointment.appointment_date} has been cancelled. Reason: {form_data["cancellation_reason"]}',
|
||||
# from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
# recipient_list=[appointment.patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def process_refund(self, appointment):
|
||||
# """Process refund if required"""
|
||||
# # This would implement refund processing logic
|
||||
# pass
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['pk'])
|
||||
# context['title'] = 'Cancel Appointment'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class AppointmentCheckInView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||
# """
|
||||
# View for appointment check-in
|
||||
# """
|
||||
# form_class = AppointmentCheckInForm
|
||||
# template_name = 'appointments/appointment_checkin.html'
|
||||
# permission_required = 'appointments.can_checkin_patients'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('appointments:appointment_detail', kwargs={'pk': self.kwargs['pk']})
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# appointment = get_object_or_404(Appointment, pk=self.kwargs['pk'])
|
||||
#
|
||||
# with transaction.atomic():
|
||||
# # Update appointment status
|
||||
# appointment.status = 'checked_in'
|
||||
# appointment.checked_in_at = timezone.now()
|
||||
# appointment.checked_in_by = self.request.user
|
||||
# appointment.arrival_time = form.cleaned_data['arrival_time']
|
||||
# appointment.save()
|
||||
#
|
||||
# # Create queue entry if needed
|
||||
# if not hasattr(appointment, 'queue_entry'):
|
||||
# Queue.objects.create(
|
||||
# appointment=appointment,
|
||||
# tenant=appointment.tenant,
|
||||
# patient=appointment.patient,
|
||||
# provider=appointment.provider,
|
||||
# department=appointment.department,
|
||||
# queue_type='check_in',
|
||||
# priority='normal',
|
||||
# status='waiting',
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# # Handle pre-visit tasks
|
||||
# self.process_checkin_tasks(appointment, form.cleaned_data)
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'{appointment.patient.get_full_name()} checked in successfully.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def process_checkin_tasks(self, appointment, form_data):
|
||||
# """Process check-in tasks"""
|
||||
# tasks = []
|
||||
#
|
||||
# if form_data.get('insurance_verified'):
|
||||
# tasks.append('Insurance verified')
|
||||
# if form_data.get('copay_collected'):
|
||||
# tasks.append('Copay collected')
|
||||
# if form_data.get('forms_completed'):
|
||||
# tasks.append('Forms completed')
|
||||
# if form_data.get('vitals_required'):
|
||||
# tasks.append('Vitals required')
|
||||
#
|
||||
# # Log completed tasks
|
||||
# if tasks:
|
||||
# from core.models import AuditLogEntry
|
||||
# AuditLogEntry.objects.create(
|
||||
# tenant=appointment.tenant,
|
||||
# user=self.request.user,
|
||||
# event_type='APPOINTMENT_CHECKIN',
|
||||
# action='UPDATE',
|
||||
# object_type='Appointment',
|
||||
# object_id=str(appointment.id),
|
||||
# details={
|
||||
# 'completed_tasks': tasks,
|
||||
# 'special_needs': form_data.get('special_needs', '')
|
||||
# },
|
||||
# ip_address=self.request.META.get('REMOTE_ADDR'),
|
||||
# user_agent=self.request.META.get('HTTP_USER_AGENT', '')
|
||||
# )
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['pk'])
|
||||
# context['title'] = 'Check In Patient'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class BulkAppointmentView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||
# """
|
||||
# View for bulk appointment operations
|
||||
# """
|
||||
# form_class = BulkAppointmentForm
|
||||
# template_name = 'appointments/bulk_appointment.html'
|
||||
# permission_required = 'appointments.can_bulk_manage_appointments'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('appointments:appointment_list')
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# appointment_ids = form.cleaned_data['appointment_ids'].split(',')
|
||||
# appointments = Appointment.objects.filter(
|
||||
# id__in=appointment_ids,
|
||||
# tenant=self.request.user.tenant
|
||||
# )
|
||||
#
|
||||
# action = form.cleaned_data['action']
|
||||
#
|
||||
# with transaction.atomic():
|
||||
# if action == 'confirm':
|
||||
# self.bulk_confirm(appointments, form.cleaned_data)
|
||||
# elif action == 'reschedule':
|
||||
# self.bulk_reschedule(appointments, form.cleaned_data)
|
||||
# elif action == 'cancel':
|
||||
# self.bulk_cancel(appointments, form.cleaned_data)
|
||||
# elif action == 'send_reminders':
|
||||
# self.bulk_send_reminders(appointments, form.cleaned_data)
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Bulk operation "{action}" completed for {appointments.count()} appointments.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def bulk_confirm(self, appointments, form_data):
|
||||
# """Bulk confirm appointments"""
|
||||
# for appointment in appointments:
|
||||
# appointment.status = 'confirmed'
|
||||
# appointment.save()
|
||||
#
|
||||
# if form_data.get('notify_patients'):
|
||||
# self.send_confirmation_notification(appointment)
|
||||
#
|
||||
# def bulk_reschedule(self, appointments, form_data):
|
||||
# """Bulk reschedule appointments"""
|
||||
# new_date = form_data.get('bulk_date')
|
||||
# if new_date:
|
||||
# for appointment in appointments:
|
||||
# appointment.appointment_date = new_date
|
||||
# appointment.status = 'rescheduled'
|
||||
# appointment.save()
|
||||
#
|
||||
# if form_data.get('notify_patients'):
|
||||
# self.send_reschedule_notification(appointment, form_data)
|
||||
#
|
||||
# def bulk_cancel(self, appointments, form_data):
|
||||
# """Bulk cancel appointments"""
|
||||
# for appointment in appointments:
|
||||
# appointment.status = 'cancelled'
|
||||
# appointment.cancelled_at = timezone.now()
|
||||
# appointment.cancelled_by = self.request.user
|
||||
# appointment.cancellation_reason = 'bulk_cancellation'
|
||||
# appointment.cancellation_notes = form_data.get('bulk_reason', '')
|
||||
# appointment.save()
|
||||
#
|
||||
# if form_data.get('notify_patients'):
|
||||
# self.send_cancellation_notification(appointment, form_data)
|
||||
#
|
||||
# def bulk_send_reminders(self, appointments, form_data):
|
||||
# """Bulk send reminders"""
|
||||
# for appointment in appointments:
|
||||
# if form_data.get('notify_patients'):
|
||||
# self.send_reminder_notification(appointment)
|
||||
#
|
||||
# def send_confirmation_notification(self, appointment):
|
||||
# """Send confirmation notification"""
|
||||
# if appointment.patient.email:
|
||||
# send_mail(
|
||||
# subject='Appointment Confirmed',
|
||||
# message=f'Your appointment on {appointment.appointment_date} at {appointment.appointment_time} has been confirmed.',
|
||||
# from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
# recipient_list=[appointment.patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def send_reminder_notification(self, appointment):
|
||||
# """Send reminder notification"""
|
||||
# if appointment.patient.email:
|
||||
# send_mail(
|
||||
# subject='Appointment Reminder',
|
||||
# message=f'Reminder: You have an appointment on {appointment.appointment_date} at {appointment.appointment_time}.',
|
||||
# from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
# recipient_list=[appointment.patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Bulk Appointment Operations'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class AppointmentListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
# """
|
||||
# View for listing appointments
|
||||
# """
|
||||
# model = Appointment
|
||||
# template_name = 'appointments/appointment_list.html'
|
||||
# context_object_name = 'appointments'
|
||||
# permission_required = 'appointments.view_appointment'
|
||||
# paginate_by = 25
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# queryset = Appointment.objects.filter(tenant=self.request.user.tenant)
|
||||
#
|
||||
# # Apply filters
|
||||
# search = self.request.GET.get('search')
|
||||
# if search:
|
||||
# queryset = queryset.filter(
|
||||
# Q(patient__first_name__icontains=search) |
|
||||
# Q(patient__last_name__icontains=search) |
|
||||
# Q(provider__first_name__icontains=search) |
|
||||
# Q(provider__last_name__icontains=search) |
|
||||
# Q(reason__icontains=search)
|
||||
# )
|
||||
#
|
||||
# status = self.request.GET.get('status')
|
||||
# if status:
|
||||
# queryset = queryset.filter(status=status)
|
||||
#
|
||||
# date_from = self.request.GET.get('date_from')
|
||||
# if date_from:
|
||||
# queryset = queryset.filter(appointment_date__gte=date_from)
|
||||
#
|
||||
# date_to = self.request.GET.get('date_to')
|
||||
# if date_to:
|
||||
# queryset = queryset.filter(appointment_date__lte=date_to)
|
||||
#
|
||||
# provider = self.request.GET.get('provider')
|
||||
# if provider:
|
||||
# queryset = queryset.filter(provider_id=provider)
|
||||
#
|
||||
# return queryset.order_by('appointment_date', 'appointment_time')
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Appointments'
|
||||
# context['providers'] = self.get_providers()
|
||||
# context['search'] = self.request.GET.get('search', '')
|
||||
# context['selected_status'] = self.request.GET.get('status', '')
|
||||
# context['selected_provider'] = self.request.GET.get('provider', '')
|
||||
# context['date_from'] = self.request.GET.get('date_from', '')
|
||||
# context['date_to'] = self.request.GET.get('date_to', '')
|
||||
# return context
|
||||
#
|
||||
# def get_providers(self):
|
||||
# """Get providers for filter"""
|
||||
# from django.contrib.auth.models import User
|
||||
# return User.objects.filter(
|
||||
# tenant=self.request.user.tenant,
|
||||
# groups__name__in=['Doctors', 'Nurses', 'Specialists']
|
||||
# ).distinct()
|
||||
#
|
||||
#
|
||||
# class AppointmentDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||
# """
|
||||
# View for appointment details
|
||||
# """
|
||||
# model = Appointment
|
||||
# template_name = 'appointments/appointment_detail.html'
|
||||
# context_object_name = 'appointment'
|
||||
# permission_required = 'appointments.view_appointment'
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# return Appointment.objects.filter(tenant=self.request.user.tenant)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# appointment = self.object
|
||||
# context['title'] = f'Appointment - {appointment.patient.get_full_name()}'
|
||||
# context['confirmation'] = getattr(appointment, 'confirmation', None)
|
||||
# context['queue_entry'] = getattr(appointment, 'queue_entry', None)
|
||||
# context['telemedicine_session'] = getattr(appointment, 'telemedicine_session', None)
|
||||
# context['can_edit'] = self.request.user.has_perm('appointments.change_appointment')
|
||||
# context['can_cancel'] = self.request.user.has_perm('appointments.can_cancel_appointments')
|
||||
# context['can_reschedule'] = self.request.user.has_perm('appointments.can_reschedule_appointments')
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class QueueListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
# """
|
||||
# View for listing queue entries
|
||||
# """
|
||||
# model = Queue
|
||||
# template_name = 'appointments/queue_list.html'
|
||||
# context_object_name = 'queue_entries'
|
||||
# permission_required = 'appointments.view_queue'
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# queryset = Queue.objects.filter(tenant=self.request.user.tenant)
|
||||
#
|
||||
# # Apply filters
|
||||
# status = self.request.GET.get('status', 'waiting')
|
||||
# if status:
|
||||
# queryset = queryset.filter(status=status)
|
||||
#
|
||||
# department = self.request.GET.get('department')
|
||||
# if department:
|
||||
# queryset = queryset.filter(department_id=department)
|
||||
#
|
||||
# return queryset.order_by('priority', 'created_at')
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Patient Queue'
|
||||
# context['departments'] = self.get_departments()
|
||||
# context['selected_status'] = self.request.GET.get('status', 'waiting')
|
||||
# context['selected_department'] = self.request.GET.get('department', '')
|
||||
# context['queue_stats'] = self.get_queue_stats()
|
||||
# return context
|
||||
#
|
||||
# def get_departments(self):
|
||||
# """Get departments for filter"""
|
||||
# from core.models import Department
|
||||
# return Department.objects.filter(tenant=self.request.user.tenant)
|
||||
#
|
||||
# def get_queue_stats(self):
|
||||
# """Get queue statistics"""
|
||||
# return {
|
||||
# 'total_waiting': Queue.objects.filter(
|
||||
# tenant=self.request.user.tenant,
|
||||
# status='waiting'
|
||||
# ).count(),
|
||||
# 'average_wait_time': 25, # Would be calculated
|
||||
# 'longest_wait': 45 # Would be calculated
|
||||
# }
|
||||
#
|
||||
#
|
||||
# # AJAX Views
|
||||
# @login_required
|
||||
# @permission_required('appointments.view_appointment')
|
||||
# def appointment_availability_ajax(request):
|
||||
# """AJAX view to check appointment availability"""
|
||||
# date = request.GET.get('date')
|
||||
# provider_id = request.GET.get('provider_id')
|
||||
#
|
||||
# if not date or not provider_id:
|
||||
# return JsonResponse({'success': False, 'message': 'Missing parameters'})
|
||||
#
|
||||
# try:
|
||||
# from django.contrib.auth.models import User
|
||||
# provider = User.objects.get(id=provider_id, tenant=request.user.tenant)
|
||||
# appointment_date = datetime.strptime(date, '%Y-%m-%d').date()
|
||||
#
|
||||
# # Get existing appointments for the date
|
||||
# existing_appointments = Appointment.objects.filter(
|
||||
# provider=provider,
|
||||
# appointment_date=appointment_date,
|
||||
# status__in=['scheduled', 'confirmed', 'in_progress']
|
||||
# )
|
||||
#
|
||||
# # Generate available slots
|
||||
# available_slots = generate_available_slots(appointment_date, existing_appointments)
|
||||
#
|
||||
# return JsonResponse({
|
||||
# 'success': True,
|
||||
# 'slots': available_slots
|
||||
# })
|
||||
# except Exception as e:
|
||||
# return JsonResponse({'success': False, 'message': str(e)})
|
||||
#
|
||||
#
|
||||
# @login_required
|
||||
# @permission_required('appointments.view_patient')
|
||||
# def patient_search_ajax(request):
|
||||
# """AJAX view for patient search"""
|
||||
# query = request.GET.get('q', '')
|
||||
# if len(query) < 2:
|
||||
# return JsonResponse({'patients': []})
|
||||
#
|
||||
# patients = Patient.objects.filter(
|
||||
# tenant=request.user.tenant
|
||||
# ).filter(
|
||||
# Q(first_name__icontains=query) |
|
||||
# Q(last_name__icontains=query) |
|
||||
# Q(patient_id__icontains=query) |
|
||||
# Q(phone_number__icontains=query)
|
||||
# )[:10]
|
||||
#
|
||||
# patient_data = [
|
||||
# {
|
||||
# 'id': patient.id,
|
||||
# 'name': patient.get_full_name(),
|
||||
# 'patient_id': patient.patient_id,
|
||||
# 'phone': patient.phone_number,
|
||||
# 'email': patient.email
|
||||
# }
|
||||
# for patient in patients
|
||||
# ]
|
||||
#
|
||||
# return JsonResponse({'patients': patient_data})
|
||||
#
|
||||
#
|
||||
# @login_required
|
||||
# @permission_required('appointments.can_manage_queue')
|
||||
# def update_queue_position_ajax(request):
|
||||
# """AJAX view to update queue position"""
|
||||
# if request.method == 'POST':
|
||||
# try:
|
||||
# data = json.loads(request.body)
|
||||
# queue_id = data.get('queue_id')
|
||||
# new_position = data.get('new_position')
|
||||
#
|
||||
# queue_entry = Queue.objects.get(
|
||||
# id=queue_id,
|
||||
# tenant=request.user.tenant
|
||||
# )
|
||||
#
|
||||
# queue_entry.position = new_position
|
||||
# queue_entry.save()
|
||||
#
|
||||
# return JsonResponse({
|
||||
# 'success': True,
|
||||
# 'message': 'Queue position updated successfully.'
|
||||
# })
|
||||
# except Queue.DoesNotExist:
|
||||
# return JsonResponse({
|
||||
# 'success': False,
|
||||
# 'message': 'Queue entry not found.'
|
||||
# })
|
||||
# except Exception as e:
|
||||
# return JsonResponse({
|
||||
# 'success': False,
|
||||
# 'message': str(e)
|
||||
# })
|
||||
#
|
||||
# return JsonResponse({'success': False, 'message': 'Invalid request.'})
|
||||
#
|
||||
#
|
||||
# def generate_available_slots(date, existing_appointments):
|
||||
# """Generate available appointment slots for a date"""
|
||||
# slots = []
|
||||
# start_time = datetime.strptime('09:00', '%H:%M').time()
|
||||
# end_time = datetime.strptime('17:00', '%H:%M').time()
|
||||
# slot_duration = 30 # minutes
|
||||
#
|
||||
# current_time = datetime.combine(date, start_time)
|
||||
# end_datetime = datetime.combine(date, end_time)
|
||||
#
|
||||
# while current_time < end_datetime:
|
||||
# slot_time = current_time.time()
|
||||
#
|
||||
# # Check if slot is available
|
||||
# is_available = True
|
||||
# for appointment in existing_appointments:
|
||||
# appointment_start = datetime.combine(date, appointment.appointment_time)
|
||||
# appointment_end = appointment_start + timedelta(minutes=appointment.duration)
|
||||
#
|
||||
# slot_start = current_time
|
||||
# slot_end = current_time + timedelta(minutes=slot_duration)
|
||||
#
|
||||
# if slot_start < appointment_end and slot_end > appointment_start:
|
||||
# is_available = False
|
||||
# break
|
||||
#
|
||||
# if is_available:
|
||||
# slots.append({
|
||||
# 'time': slot_time.strftime('%H:%M'),
|
||||
# 'available': True
|
||||
# })
|
||||
#
|
||||
# current_time += timedelta(minutes=slot_duration)
|
||||
#
|
||||
# return slots
|
||||
#
|
||||
|
||||
BIN
billing/__pycache__/flows.cpython-312.pyc
Normal file
BIN
billing/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1022
billing/flows.py
Normal file
1022
billing/flows.py
Normal file
File diff suppressed because it is too large
Load Diff
787
billing/forms.py
787
billing/forms.py
@ -786,3 +786,790 @@ class PaymentSearchForm(forms.Form):
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
# from django import forms
|
||||
# from django.core.exceptions import ValidationError
|
||||
# from django.utils import timezone
|
||||
# from django.contrib.auth.models import User
|
||||
# from crispy_forms.helper import FormHelper
|
||||
# from crispy_forms.layout import Layout, Fieldset, Submit, Row, Column, HTML, Div
|
||||
# from crispy_forms.bootstrap import FormActions
|
||||
# from decimal import Decimal
|
||||
# import json
|
||||
#
|
||||
# from .models import (
|
||||
# Bill, BillItem, InsuranceClaim, Payment, PaymentMethod,
|
||||
# InsuranceProvider, ClaimDenial, PaymentPlan
|
||||
# )
|
||||
# from patients.models import Patient
|
||||
#
|
||||
#
|
||||
# class MedicalBillingForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for medical billing creation
|
||||
# """
|
||||
# patient_search = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.TextInput(attrs={
|
||||
# 'class': 'form-control',
|
||||
# 'placeholder': 'Search patient by name, ID, or insurance...',
|
||||
# 'data-toggle': 'patient-search'
|
||||
# })
|
||||
# )
|
||||
# insurance_verification = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# auto_submit_primary = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# auto_submit_secondary = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# generate_patient_statement = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = Bill
|
||||
# fields = [
|
||||
# 'patient', 'encounter', 'bill_type', 'service_date',
|
||||
# 'diagnosis_codes', 'procedure_codes', 'provider',
|
||||
# 'facility', 'insurance_verification', 'auto_submit_primary',
|
||||
# 'auto_submit_secondary', 'generate_patient_statement'
|
||||
# ]
|
||||
# widgets = {
|
||||
# 'patient': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'encounter': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'bill_type': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'service_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||
# 'diagnosis_codes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
# 'procedure_codes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
# 'provider': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'facility': forms.Select(attrs={'class': 'form-control'})
|
||||
# }
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# tenant = kwargs.pop('tenant', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# if tenant:
|
||||
# self.fields['patient'].queryset = Patient.objects.filter(tenant=tenant)
|
||||
# self.fields['provider'].queryset = User.objects.filter(
|
||||
# tenant=tenant,
|
||||
# groups__name__in=['Doctors', 'Nurses', 'Specialists']
|
||||
# )
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Patient Information',
|
||||
# 'patient_search',
|
||||
# 'patient',
|
||||
# 'encounter'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Service Details',
|
||||
# Row(
|
||||
# Column('bill_type', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('service_date', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# Row(
|
||||
# Column('provider', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('facility', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'diagnosis_codes',
|
||||
# 'procedure_codes'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Billing Options',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'insurance_verification',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_insurance_verification">Verify insurance eligibility</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'auto_submit_primary',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_auto_submit_primary">Auto-submit to primary insurance</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'auto_submit_secondary',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_auto_submit_secondary">Auto-submit to secondary insurance</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'generate_patient_statement',
|
||||
# HTML(
|
||||
# '<label class="form-check-label" for="id_generate_patient_statement">Generate patient statement</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Create Bill', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'billing:bill_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean_diagnosis_codes(self):
|
||||
# codes = self.cleaned_data.get('diagnosis_codes')
|
||||
# if codes:
|
||||
# # Validate ICD-10 codes format
|
||||
# code_list = [code.strip() for code in codes.split(',')]
|
||||
# for code in code_list:
|
||||
# if not self.validate_icd10_code(code):
|
||||
# raise ValidationError(f'Invalid ICD-10 code format: {code}')
|
||||
# return codes
|
||||
#
|
||||
# def clean_procedure_codes(self):
|
||||
# codes = self.cleaned_data.get('procedure_codes')
|
||||
# if codes:
|
||||
# # Validate CPT codes format
|
||||
# code_list = [code.strip() for code in codes.split(',')]
|
||||
# for code in code_list:
|
||||
# if not self.validate_cpt_code(code):
|
||||
# raise ValidationError(f'Invalid CPT code format: {code}')
|
||||
# return codes
|
||||
#
|
||||
# def validate_icd10_code(self, code):
|
||||
# """Validate ICD-10 code format"""
|
||||
# # Basic ICD-10 validation (simplified)
|
||||
# return len(code) >= 3 and code[0].isalpha()
|
||||
#
|
||||
# def validate_cpt_code(self, code):
|
||||
# """Validate CPT code format"""
|
||||
# # Basic CPT validation (simplified)
|
||||
# return len(code) == 5 and code.isdigit()
|
||||
#
|
||||
#
|
||||
# class BillItemForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for bill item creation/editing
|
||||
# """
|
||||
# quantity = forms.DecimalField(
|
||||
# max_digits=10,
|
||||
# decimal_places=2,
|
||||
# initial=1.00,
|
||||
# widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
|
||||
# )
|
||||
# unit_price = forms.DecimalField(
|
||||
# max_digits=10,
|
||||
# decimal_places=2,
|
||||
# widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
|
||||
# )
|
||||
# discount_amount = forms.DecimalField(
|
||||
# max_digits=10,
|
||||
# decimal_places=2,
|
||||
# initial=0.00,
|
||||
# required=False,
|
||||
# widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = BillItem
|
||||
# fields = [
|
||||
# 'service_code', 'description', 'quantity', 'unit_price',
|
||||
# 'discount_amount', 'modifier_codes', 'revenue_code'
|
||||
# ]
|
||||
# widgets = {
|
||||
# 'service_code': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
# 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
|
||||
# 'modifier_codes': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
# 'revenue_code': forms.TextInput(attrs={'class': 'form-control'})
|
||||
# }
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Service Information',
|
||||
# Row(
|
||||
# Column('service_code', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('revenue_code', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'description',
|
||||
# 'modifier_codes'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Pricing',
|
||||
# Row(
|
||||
# Column('quantity', css_class='form-group col-md-4 mb-0'),
|
||||
# Column('unit_price', css_class='form-group col-md-4 mb-0'),
|
||||
# Column('discount_amount', css_class='form-group col-md-4 mb-0'),
|
||||
# css_class='form-row'
|
||||
# )
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Save Item', css_class='btn btn-primary'),
|
||||
# HTML('<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean(self):
|
||||
# cleaned_data = super().clean()
|
||||
# quantity = cleaned_data.get('quantity')
|
||||
# unit_price = cleaned_data.get('unit_price')
|
||||
# discount_amount = cleaned_data.get('discount_amount', Decimal('0.00'))
|
||||
#
|
||||
# if quantity and unit_price:
|
||||
# total_amount = quantity * unit_price
|
||||
# if discount_amount > total_amount:
|
||||
# raise ValidationError('Discount amount cannot exceed total amount.')
|
||||
#
|
||||
# return cleaned_data
|
||||
#
|
||||
#
|
||||
# class InsuranceClaimForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for insurance claim submission
|
||||
# """
|
||||
# claim_type = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('primary', 'Primary Insurance'),
|
||||
# ('secondary', 'Secondary Insurance'),
|
||||
# ('tertiary', 'Tertiary Insurance')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# submit_electronically = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# prior_authorization = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# referral_number = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = InsuranceClaim
|
||||
# fields = [
|
||||
# 'bill', 'insurance_provider', 'claim_type', 'policy_number',
|
||||
# 'group_number', 'subscriber_id', 'prior_authorization',
|
||||
# 'referral_number', 'submit_electronically'
|
||||
# ]
|
||||
# widgets = {
|
||||
# 'bill': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'insurance_provider': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'policy_number': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
# 'group_number': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
# 'subscriber_id': forms.TextInput(attrs={'class': 'form-control'})
|
||||
# }
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# tenant = kwargs.pop('tenant', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# if tenant:
|
||||
# self.fields['bill'].queryset = Bill.objects.filter(tenant=tenant)
|
||||
# self.fields['insurance_provider'].queryset = InsuranceProvider.objects.filter(tenant=tenant)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Claim Information',
|
||||
# Row(
|
||||
# Column('bill', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('claim_type', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'insurance_provider'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Insurance Details',
|
||||
# Row(
|
||||
# Column('policy_number', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('group_number', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'subscriber_id'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Authorization',
|
||||
# Row(
|
||||
# Column('prior_authorization', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('referral_number', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# )
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Submission Options',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'submit_electronically',
|
||||
# HTML('<label class="form-check-label" for="id_submit_electronically">Submit electronically</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Submit Claim', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'billing:claim_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class PaymentProcessingForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for payment processing
|
||||
# """
|
||||
# payment_amount = forms.DecimalField(
|
||||
# max_digits=10,
|
||||
# decimal_places=2,
|
||||
# widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
|
||||
# )
|
||||
# payment_date = forms.DateField(
|
||||
# initial=timezone.now().date(),
|
||||
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||||
# )
|
||||
# reference_number = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# notes = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
||||
# )
|
||||
# send_receipt = forms.BooleanField(
|
||||
# required=False,
|
||||
# initial=True,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = Payment
|
||||
# fields = [
|
||||
# 'bill', 'payment_method', 'payment_source', 'payment_amount',
|
||||
# 'payment_date', 'reference_number', 'notes', 'send_receipt'
|
||||
# ]
|
||||
# widgets = {
|
||||
# 'bill': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'payment_method': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'payment_source': forms.Select(attrs={'class': 'form-control'})
|
||||
# }
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# tenant = kwargs.pop('tenant', None)
|
||||
# bill = kwargs.pop('bill', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# if tenant:
|
||||
# self.fields['bill'].queryset = Bill.objects.filter(tenant=tenant)
|
||||
# self.fields['payment_method'].queryset = PaymentMethod.objects.filter(tenant=tenant)
|
||||
#
|
||||
# if bill:
|
||||
# self.fields['bill'].initial = bill
|
||||
# self.fields['payment_amount'].initial = bill.balance_due
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Payment Information',
|
||||
# Row(
|
||||
# Column('bill', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('payment_source', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# Row(
|
||||
# Column('payment_method', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('payment_amount', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# Row(
|
||||
# Column('payment_date', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('reference_number', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'notes'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Options',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'send_receipt',
|
||||
# HTML('<label class="form-check-label" for="id_send_receipt">Send payment receipt</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Process Payment', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'billing:payment_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean_payment_amount(self):
|
||||
# payment_amount = self.cleaned_data.get('payment_amount')
|
||||
# bill = self.cleaned_data.get('bill')
|
||||
#
|
||||
# if payment_amount and bill:
|
||||
# if payment_amount > bill.balance_due:
|
||||
# raise ValidationError('Payment amount cannot exceed balance due.')
|
||||
# if payment_amount <= 0:
|
||||
# raise ValidationError('Payment amount must be greater than zero.')
|
||||
#
|
||||
# return payment_amount
|
||||
#
|
||||
#
|
||||
# class DenialManagementForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for denial management
|
||||
# """
|
||||
# denial_reason = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('invalid_code', 'Invalid Procedure/Diagnosis Code'),
|
||||
# ('not_covered', 'Service Not Covered'),
|
||||
# ('prior_auth', 'Prior Authorization Required'),
|
||||
# ('duplicate', 'Duplicate Claim'),
|
||||
# ('incomplete', 'Incomplete Information'),
|
||||
# ('timely_filing', 'Timely Filing Limit Exceeded'),
|
||||
# ('other', 'Other')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# appeal_deadline = forms.DateField(
|
||||
# required=False,
|
||||
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||||
# )
|
||||
# corrective_action = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 4})
|
||||
# )
|
||||
# resubmit_claim = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
# file_appeal = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = ClaimDenial
|
||||
# fields = [
|
||||
# 'claim', 'denial_reason', 'denial_description', 'denied_amount',
|
||||
# 'appeal_deadline', 'corrective_action', 'resubmit_claim', 'file_appeal'
|
||||
# ]
|
||||
# widgets = {
|
||||
# 'claim': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'denial_description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
# 'denied_amount': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
|
||||
# }
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# tenant = kwargs.pop('tenant', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# if tenant:
|
||||
# self.fields['claim'].queryset = InsuranceClaim.objects.filter(tenant=tenant)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Denial Information',
|
||||
# 'claim',
|
||||
# Row(
|
||||
# Column('denial_reason', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('denied_amount', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'denial_description',
|
||||
# 'appeal_deadline'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Corrective Actions',
|
||||
# 'corrective_action',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'resubmit_claim',
|
||||
# HTML('<label class="form-check-label" for="id_resubmit_claim">Resubmit claim with corrections</label>'),
|
||||
# HTML('</div>'),
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'file_appeal',
|
||||
# HTML('<label class="form-check-label" for="id_file_appeal">File appeal</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Process Denial', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'billing:denial_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class PaymentPlanForm(forms.ModelForm):
|
||||
# """
|
||||
# Form for payment plan setup
|
||||
# """
|
||||
# plan_type = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('monthly', 'Monthly Payments'),
|
||||
# ('weekly', 'Weekly Payments'),
|
||||
# ('biweekly', 'Bi-weekly Payments'),
|
||||
# ('custom', 'Custom Schedule')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# number_of_payments = forms.IntegerField(
|
||||
# min_value=2,
|
||||
# max_value=60,
|
||||
# widget=forms.NumberInput(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# payment_amount = forms.DecimalField(
|
||||
# max_digits=10,
|
||||
# decimal_places=2,
|
||||
# widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
|
||||
# )
|
||||
# first_payment_date = forms.DateField(
|
||||
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||||
# )
|
||||
# auto_payment = forms.BooleanField(
|
||||
# required=False,
|
||||
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# model = PaymentPlan
|
||||
# fields = [
|
||||
# 'bill', 'plan_type', 'total_amount', 'number_of_payments',
|
||||
# 'payment_amount', 'first_payment_date', 'auto_payment'
|
||||
# ]
|
||||
# widgets = {
|
||||
# 'bill': forms.Select(attrs={'class': 'form-control'}),
|
||||
# 'total_amount': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
|
||||
# }
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# tenant = kwargs.pop('tenant', None)
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# if tenant:
|
||||
# self.fields['bill'].queryset = Bill.objects.filter(tenant=tenant, balance_due__gt=0)
|
||||
#
|
||||
# # Set minimum date to tomorrow
|
||||
# tomorrow = timezone.now().date() + timezone.timedelta(days=1)
|
||||
# self.fields['first_payment_date'].widget.attrs['min'] = tomorrow.isoformat()
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Payment Plan Details',
|
||||
# 'bill',
|
||||
# Row(
|
||||
# Column('plan_type', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('total_amount', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# Row(
|
||||
# Column('number_of_payments', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('payment_amount', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'first_payment_date'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Options',
|
||||
# HTML('<div class="form-check">'),
|
||||
# 'auto_payment',
|
||||
# HTML('<label class="form-check-label" for="id_auto_payment">Enable automatic payments</label>'),
|
||||
# HTML('</div>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Create Payment Plan', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'billing:payment_plan_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def clean(self):
|
||||
# cleaned_data = super().clean()
|
||||
# total_amount = cleaned_data.get('total_amount')
|
||||
# number_of_payments = cleaned_data.get('number_of_payments')
|
||||
# payment_amount = cleaned_data.get('payment_amount')
|
||||
#
|
||||
# if total_amount and number_of_payments and payment_amount:
|
||||
# calculated_total = payment_amount * number_of_payments
|
||||
# if abs(calculated_total - total_amount) > Decimal('0.01'):
|
||||
# raise ValidationError('Payment amount × number of payments must equal total amount.')
|
||||
#
|
||||
# return cleaned_data
|
||||
#
|
||||
#
|
||||
# class CollectionsForm(forms.Form):
|
||||
# """
|
||||
# Form for collections management
|
||||
# """
|
||||
# collection_action = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('letter_1', 'Send First Collection Letter'),
|
||||
# ('letter_2', 'Send Second Collection Letter'),
|
||||
# ('letter_3', 'Send Final Collection Letter'),
|
||||
# ('phone_call', 'Schedule Phone Call'),
|
||||
# ('external_agency', 'Send to External Agency'),
|
||||
# ('write_off', 'Write Off Balance')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# collection_notes = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 4})
|
||||
# )
|
||||
# follow_up_date = forms.DateField(
|
||||
# required=False,
|
||||
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||||
# )
|
||||
# write_off_reason = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('uncollectible', 'Uncollectible'),
|
||||
# ('patient_deceased', 'Patient Deceased'),
|
||||
# ('bankruptcy', 'Bankruptcy'),
|
||||
# ('small_balance', 'Small Balance'),
|
||||
# ('other', 'Other')
|
||||
# ],
|
||||
# required=False,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Collection Action',
|
||||
# 'collection_action',
|
||||
# 'collection_notes',
|
||||
# 'follow_up_date'
|
||||
# ),
|
||||
# Fieldset(
|
||||
# 'Write-off Details',
|
||||
# 'write_off_reason',
|
||||
# HTML('<small class="form-text text-muted">Required only for write-off actions</small>')
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Execute Collection Action', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'billing:collections_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class InsuranceVerificationForm(forms.Form):
|
||||
# """
|
||||
# Form for insurance verification
|
||||
# """
|
||||
# verification_type = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('eligibility', 'Eligibility Verification'),
|
||||
# ('benefits', 'Benefits Verification'),
|
||||
# ('authorization', 'Prior Authorization'),
|
||||
# ('referral', 'Referral Verification')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# service_date = forms.DateField(
|
||||
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||||
# )
|
||||
# procedure_codes = forms.CharField(
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
||||
# )
|
||||
# verification_notes = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 4})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# Fieldset(
|
||||
# 'Verification Details',
|
||||
# Row(
|
||||
# Column('verification_type', css_class='form-group col-md-6 mb-0'),
|
||||
# Column('service_date', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'procedure_codes',
|
||||
# 'verification_notes'
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Verify Insurance', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'billing:verification_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class BulkBillingForm(forms.Form):
|
||||
# """
|
||||
# Form for bulk billing operations
|
||||
# """
|
||||
# action = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('submit_claims', 'Submit Claims'),
|
||||
# ('generate_statements', 'Generate Patient Statements'),
|
||||
# ('send_reminders', 'Send Payment Reminders'),
|
||||
# ('update_status', 'Update Status'),
|
||||
# ('apply_adjustments', 'Apply Adjustments')
|
||||
# ],
|
||||
# required=True,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# bill_ids = forms.CharField(
|
||||
# widget=forms.HiddenInput()
|
||||
# )
|
||||
# new_status = forms.ChoiceField(
|
||||
# choices=[
|
||||
# ('pending', 'Pending'),
|
||||
# ('submitted', 'Submitted'),
|
||||
# ('paid', 'Paid'),
|
||||
# ('denied', 'Denied'),
|
||||
# ('cancelled', 'Cancelled')
|
||||
# ],
|
||||
# required=False,
|
||||
# widget=forms.Select(attrs={'class': 'form-control'})
|
||||
# )
|
||||
# adjustment_amount = forms.DecimalField(
|
||||
# max_digits=10,
|
||||
# decimal_places=2,
|
||||
# required=False,
|
||||
# widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
|
||||
# )
|
||||
# adjustment_reason = forms.CharField(
|
||||
# required=False,
|
||||
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
||||
# )
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
#
|
||||
# self.helper = FormHelper()
|
||||
# self.helper.layout = Layout(
|
||||
# 'bill_ids',
|
||||
# Fieldset(
|
||||
# 'Bulk Operation',
|
||||
# 'action',
|
||||
# 'new_status',
|
||||
# Row(
|
||||
# Column('adjustment_amount', css_class='form-group col-md-6 mb-0'),
|
||||
# css_class='form-row'
|
||||
# ),
|
||||
# 'adjustment_reason'
|
||||
# ),
|
||||
# FormActions(
|
||||
# Submit('submit', 'Execute Bulk Operation', css_class='btn btn-primary'),
|
||||
# HTML('<a href="{% url \'billing:bill_list\' %}" class="btn btn-secondary">Cancel</a>')
|
||||
# )
|
||||
# )
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
@ -14,6 +14,8 @@ urlpatterns = [
|
||||
# Medical Bills
|
||||
path('bills/', views.MedicalBillListView.as_view(), name='bill_list'),
|
||||
path('bills/<uuid:bill_id>/', views.MedicalBillDetailView.as_view(), name='bill_detail'),
|
||||
path('bills/<uuid:bill_id>/', views.bill_details_api, name='bill_details_api'),
|
||||
path('bills/<uuid:bill_id>/line-items', views.bill_line_items_api, name='bill_line_items_api'),
|
||||
path('bills/create/', views.MedicalBillCreateView.as_view(), name='bill_create'),
|
||||
path('bills/<uuid:bill_id>/edit/', views.MedicalBillUpdateView.as_view(), name='bill_update'),
|
||||
path('bills/<uuid:bill_id>/delete/', views.MedicalBillDeleteView.as_view(), name='bill_delete'),
|
||||
@ -40,14 +42,15 @@ urlpatterns = [
|
||||
path('bills/<uuid:bill_id>/payments/create/', views.PaymentCreateView.as_view(), name='bill_payment_create'),
|
||||
|
||||
# HTMX endpoints
|
||||
path('htmx/stats/', views.htmx_billing_stats, name='billing_stats'),
|
||||
path('htmx/bill-search/', views.htmx_bill_search, name='bill_search'),
|
||||
path('stats/bills/', views.billing_stats, name='billing_stats'),
|
||||
path('bill-search/', views.bill_search, name='bill_search'),
|
||||
|
||||
# Action endpoints
|
||||
path('bills/<uuid:bill_id>/submit/', views.submit_bill, name='submit_bill'),
|
||||
|
||||
# Export endpoints
|
||||
path('export/bills/', views.export_bills, name='export_bills'),
|
||||
path('export/claims/', views.export_claims, name='export_claims'),
|
||||
|
||||
# API endpoints
|
||||
# path('api/', include('billing.api.urls')),
|
||||
|
||||
697
billing/views.py
697
billing/views.py
@ -393,7 +393,7 @@ class InsuranceClaimDetailView(LoginRequiredMixin, DetailView):
|
||||
claim = self.get_object()
|
||||
|
||||
# Get related data
|
||||
context['status_updates'] = claim.claimstatusupdate_set.all().order_by('-update_date')
|
||||
# context['status_updates'] = claim.claimstatusupdate_set.all().order_by('-update_date')
|
||||
|
||||
return context
|
||||
|
||||
@ -426,8 +426,8 @@ class InsuranceClaimCreateView(LoginRequiredMixin, PermissionRequiredMixin, Crea
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
# Set medical bill and created_by
|
||||
bill_id = self.kwargs.get('bill_id')
|
||||
# Prefer URL bill_id; otherwise read from POST("medical_bill")
|
||||
bill_id = self.kwargs.get('bill_id') or self.request.POST.get('medical_bill')
|
||||
if bill_id:
|
||||
try:
|
||||
medical_bill = MedicalBill.objects.get(
|
||||
@ -438,22 +438,25 @@ class InsuranceClaimCreateView(LoginRequiredMixin, PermissionRequiredMixin, Crea
|
||||
except MedicalBill.DoesNotExist:
|
||||
messages.error(self.request, 'Medical bill not found.')
|
||||
return redirect('billing:bill_list')
|
||||
|
||||
else:
|
||||
messages.error(self.request, 'Please select a medical bill.')
|
||||
return redirect('billing:claim_create')
|
||||
|
||||
form.instance.created_by = self.request.user
|
||||
|
||||
# Generate claim number
|
||||
|
||||
if not form.instance.claim_number:
|
||||
form.instance.claim_number = form.instance.generate_claim_number()
|
||||
|
||||
|
||||
response = super().form_valid(form)
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
f'Insurance claim {self.object.claim_number} created successfully.'
|
||||
)
|
||||
|
||||
messages.success(self.request, f'Insurance claim {self.object.claim_number} created successfully.')
|
||||
return response
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
tenant = getattr(self.request, 'tenant', None)
|
||||
ctx['available_bills'] = MedicalBill.objects.filter(tenant=tenant).select_related('patient')
|
||||
return ctx
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('billing:claim_detail', kwargs={'claim_id': self.object.claim_id})
|
||||
|
||||
@ -631,7 +634,7 @@ class PaymentCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView)
|
||||
# return JsonResponse(stats)
|
||||
|
||||
@login_required
|
||||
def htmx_billing_stats(request):
|
||||
def billing_stats(request):
|
||||
"""
|
||||
HTMX view for billing statistics.
|
||||
"""
|
||||
@ -690,7 +693,30 @@ def htmx_billing_stats(request):
|
||||
|
||||
|
||||
@login_required
|
||||
def htmx_bill_search(request):
|
||||
def bill_details_api(request, bill_id):
|
||||
tenant = getattr(request, 'tenant', None)
|
||||
if not tenant:
|
||||
return JsonResponse({'error': 'No tenant found'}, status=400)
|
||||
|
||||
bill = get_object_or_404(
|
||||
MedicalBill.objects.select_related('patient', 'billing_provider'),
|
||||
bill_id=bill_id,
|
||||
tenant=tenant,
|
||||
)
|
||||
|
||||
data = {
|
||||
'patient_name': bill.patient.get_full_name() if bill.patient else '',
|
||||
'bill_number': bill.bill_number or '',
|
||||
'bill_date': bill.bill_date.isoformat() if bill.bill_date else '',
|
||||
'total_amount': str(bill.total_amount or 0),
|
||||
'service_date_from': bill.service_date_from.isoformat() if bill.service_date_from else '',
|
||||
'service_date_to': bill.service_date_to.isoformat() if bill.service_date_to else '',
|
||||
'billing_provider': bill.billing_provider.get_full_name() if bill.billing_provider else '',
|
||||
}
|
||||
return JsonResponse(data)
|
||||
|
||||
@login_required
|
||||
def bill_search(request):
|
||||
"""
|
||||
HTMX endpoint for bill search.
|
||||
"""
|
||||
@ -1391,6 +1417,145 @@ def payment_download(request, payment_id):
|
||||
messages.error(request, f'Error generating PDF: {str(e)}')
|
||||
return redirect('billing:payment_receipt', payment_id=payment_id)
|
||||
|
||||
|
||||
@login_required
|
||||
def export_claims(request):
|
||||
"""
|
||||
Export insurance claims to CSV.
|
||||
Supports optional filtering by 'claims' GET param: ?claims=ID1,ID2,ID3
|
||||
"""
|
||||
tenant = getattr(request, 'tenant', None)
|
||||
if not tenant:
|
||||
return HttpResponse('No tenant found', status=400)
|
||||
|
||||
# Base queryset
|
||||
qs = InsuranceClaim.objects.filter(tenant=tenant).select_related(
|
||||
'medical_bill__patient',
|
||||
'insurance_info',
|
||||
)
|
||||
|
||||
# Optional selection filter (comma-separated claim_ids)
|
||||
selected = request.GET.get('claims')
|
||||
if selected:
|
||||
claim_ids = [c.strip() for c in selected.split(',') if c.strip()]
|
||||
if claim_ids:
|
||||
qs = qs.filter(claim_id__in=claim_ids)
|
||||
|
||||
# Prepare CSV response
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename="insurance_claims.csv"'
|
||||
|
||||
writer = csv.writer(response)
|
||||
writer.writerow([
|
||||
'Claim Number',
|
||||
'Bill Number',
|
||||
'Patient Name',
|
||||
'Insurance Company',
|
||||
'Claim Type',
|
||||
'Service From',
|
||||
'Service To',
|
||||
'Billed Amount',
|
||||
'Status',
|
||||
])
|
||||
|
||||
for claim in qs:
|
||||
bill = getattr(claim, 'medical_bill', None)
|
||||
patient = getattr(bill, 'patient', None)
|
||||
|
||||
# Safely get nice display values
|
||||
insurance_company = getattr(getattr(claim, 'insurance_info', None), 'company', None)
|
||||
if not insurance_company:
|
||||
# Fallback to __str__ of insurance_info or empty
|
||||
insurance_company = str(getattr(claim, 'insurance_info', '')) or ''
|
||||
|
||||
claim_type = getattr(claim, 'get_claim_type_display', None)
|
||||
if callable(claim_type):
|
||||
claim_type = claim.get_claim_type_display()
|
||||
else:
|
||||
claim_type = getattr(claim, 'claim_type', '') or ''
|
||||
|
||||
status_val = ''
|
||||
get_status_display = getattr(claim, 'get_status_display', None)
|
||||
if callable(get_status_display):
|
||||
status_val = claim.get_status_display()
|
||||
else:
|
||||
# Fallback if no choices helper exists
|
||||
status_val = getattr(claim, 'status', '') or ''
|
||||
|
||||
writer.writerow([
|
||||
getattr(claim, 'claim_number', '') or '',
|
||||
getattr(bill, 'bill_number', '') if bill else '',
|
||||
patient.get_full_name() if patient else '',
|
||||
insurance_company,
|
||||
claim_type,
|
||||
claim.service_date_from.strftime('%Y-%m-%d') if getattr(claim, 'service_date_from', None) else '',
|
||||
claim.service_date_to.strftime('%Y-%m-%d') if getattr(claim, 'service_date_to', None) else '',
|
||||
str(getattr(claim, 'billed_amount', '')) or '0',
|
||||
status_val,
|
||||
])
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@login_required
|
||||
def bill_line_items_api(request, bill_id=None):
|
||||
"""
|
||||
Return line items for a medical bill as JSON.
|
||||
Supports:
|
||||
- /api/bills/<uuid:bill_id>/line-items/
|
||||
- /api/bills/line-items/?bill_id=<uuid>
|
||||
"""
|
||||
tenant = getattr(request, 'tenant', None)
|
||||
if not tenant:
|
||||
return JsonResponse({'success': False, 'error': 'No tenant found'}, status=400)
|
||||
|
||||
bill_id = bill_id or request.GET.get('bill_id')
|
||||
if not bill_id:
|
||||
return JsonResponse({'success': False, 'error': 'bill_id is required'}, status=400)
|
||||
|
||||
bill = get_object_or_404(
|
||||
MedicalBill.objects.select_related('patient').prefetch_related('billlineitem_set'),
|
||||
bill_id=bill_id,
|
||||
tenant=tenant,
|
||||
)
|
||||
|
||||
# Prefer per-item service date if your model has it; otherwise fall back
|
||||
bill_service_date = (
|
||||
bill.service_date_from.isoformat() if getattr(bill, 'service_date_from', None)
|
||||
else bill.bill_date.isoformat() if getattr(bill, 'bill_date', None)
|
||||
else ''
|
||||
)
|
||||
|
||||
items = []
|
||||
for li in bill.billlineitem_set.all():
|
||||
qty = getattr(li, 'quantity', 0) or 0
|
||||
price = getattr(li, 'unit_price', Decimal('0')) or Decimal('0')
|
||||
# If your BillLineItem has service_date, use it; otherwise default
|
||||
li_service_date = getattr(li, 'service_date', None)
|
||||
if li_service_date:
|
||||
li_service_date = li_service_date.isoformat()
|
||||
else:
|
||||
li_service_date = bill_service_date
|
||||
|
||||
items.append({
|
||||
'id': getattr(li, 'id', None),
|
||||
'service_code': getattr(li, 'service_code', '') or '',
|
||||
'description': getattr(li, 'description', '') or '',
|
||||
'quantity': qty,
|
||||
'unit_price': str(price),
|
||||
'service_date': li_service_date,
|
||||
'total': str(price * Decimal(qty)),
|
||||
})
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'bill_id': str(bill.bill_id),
|
||||
'patient_name': bill.patient.get_full_name() if bill.patient else '',
|
||||
'line_items': items,
|
||||
})
|
||||
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
# """
|
||||
@ -2159,3 +2324,503 @@ payment_list = PaymentListView.as_view()
|
||||
#
|
||||
#
|
||||
#
|
||||
# from django.shortcuts import render, redirect, get_object_or_404
|
||||
# from django.contrib.auth.decorators import login_required, permission_required
|
||||
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||
# from django.contrib import messages
|
||||
# from django.views.generic import (
|
||||
# CreateView, UpdateView, DeleteView, DetailView, ListView, FormView
|
||||
# )
|
||||
# from django.urls import reverse_lazy, reverse
|
||||
# from django.http import JsonResponse, HttpResponse
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
# from django.conf import settings
|
||||
# from django.db.models import Q, Sum, Count
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from decimal import Decimal
|
||||
# import json
|
||||
#
|
||||
# from .models import (
|
||||
# Bill, BillItem, InsuranceClaim, Payment, PaymentMethod,
|
||||
# InsuranceProvider, ClaimDenial, PaymentPlan
|
||||
# )
|
||||
# from .forms import (
|
||||
# MedicalBillingForm, BillItemForm, InsuranceClaimForm, PaymentProcessingForm,
|
||||
# DenialManagementForm, PaymentPlanForm, CollectionsForm,
|
||||
# InsuranceVerificationForm, BulkBillingForm
|
||||
# )
|
||||
# from .flows import MedicalBillingFlow, InsuranceClaimFlow, PaymentProcessingFlow, DenialManagementFlow, CollectionsFlow
|
||||
# from patients.models import Patient
|
||||
#
|
||||
#
|
||||
# class MedicalBillingView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
||||
# """
|
||||
# View for medical billing workflow
|
||||
# """
|
||||
# model = Bill
|
||||
# form_class = MedicalBillingForm
|
||||
# template_name = 'billing/medical_billing.html'
|
||||
# permission_required = 'billing.can_create_bills'
|
||||
# flow_class = MedicalBillingFlow
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['tenant'] = self.request.user.tenant
|
||||
# return kwargs
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# with transaction.atomic():
|
||||
# # Create bill
|
||||
# bill = form.save(commit=False)
|
||||
# bill.tenant = self.request.user.tenant
|
||||
# bill.created_by = self.request.user
|
||||
# bill.status = 'draft'
|
||||
# bill.save()
|
||||
#
|
||||
# # Start medical billing workflow
|
||||
# process = self.flow_class.start.run(
|
||||
# bill=bill,
|
||||
# insurance_verification=form.cleaned_data.get('insurance_verification', True),
|
||||
# auto_submit_primary=form.cleaned_data.get('auto_submit_primary', True),
|
||||
# auto_submit_secondary=form.cleaned_data.get('auto_submit_secondary', False),
|
||||
# generate_patient_statement=form.cleaned_data.get('generate_patient_statement', True),
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Medical bill created successfully for {bill.patient.get_full_name()}. '
|
||||
# f'Billing workflow initiated.'
|
||||
# )
|
||||
#
|
||||
# return redirect('billing:bill_detail', pk=bill.pk)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Create Medical Bill'
|
||||
# context['breadcrumbs'] = [
|
||||
# {'name': 'Home', 'url': reverse('core:dashboard')},
|
||||
# {'name': 'Billing', 'url': reverse('billing:dashboard')},
|
||||
# {'name': 'Bills', 'url': reverse('billing:bill_list')},
|
||||
# {'name': 'Create Bill', 'url': ''}
|
||||
# ]
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class BillItemView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||||
# """
|
||||
# View for bill item creation/editing
|
||||
# """
|
||||
# model = BillItem
|
||||
# form_class = BillItemForm
|
||||
# template_name = 'billing/bill_item_form.html'
|
||||
# permission_required = 'billing.can_edit_bill_items'
|
||||
#
|
||||
# def get_success_url(self):
|
||||
# return reverse('billing:bill_detail', kwargs={'pk': self.kwargs['bill_id']})
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# bill = get_object_or_404(Bill, pk=self.kwargs['bill_id'])
|
||||
#
|
||||
# with transaction.atomic():
|
||||
# # Create bill item
|
||||
# item = form.save(commit=False)
|
||||
# item.bill = bill
|
||||
# item.tenant = bill.tenant
|
||||
# item.save()
|
||||
#
|
||||
# # Recalculate bill totals
|
||||
# self.recalculate_bill_totals(bill)
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Bill item "{item.description}" added successfully.'
|
||||
# )
|
||||
#
|
||||
# return super().form_valid(form)
|
||||
#
|
||||
# def recalculate_bill_totals(self, bill):
|
||||
# """Recalculate bill totals"""
|
||||
# items = bill.items.all()
|
||||
# subtotal = sum(item.total_amount for item in items)
|
||||
#
|
||||
# bill.subtotal = subtotal
|
||||
# bill.total_amount = subtotal + bill.tax_amount
|
||||
# bill.balance_due = bill.total_amount - bill.paid_amount
|
||||
# bill.save()
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['bill'] = get_object_or_404(Bill, pk=self.kwargs['bill_id'])
|
||||
# context['title'] = 'Add Bill Item'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class InsuranceClaimView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
||||
# """
|
||||
# View for insurance claim submission workflow
|
||||
# """
|
||||
# model = InsuranceClaim
|
||||
# form_class = InsuranceClaimForm
|
||||
# template_name = 'billing/insurance_claim.html'
|
||||
# permission_required = 'billing.can_submit_claims'
|
||||
# flow_class = InsuranceClaimFlow
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['tenant'] = self.request.user.tenant
|
||||
# return kwargs
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# with transaction.atomic():
|
||||
# # Create insurance claim
|
||||
# claim = form.save(commit=False)
|
||||
# claim.tenant = self.request.user.tenant
|
||||
# claim.submitted_by = self.request.user
|
||||
# claim.status = 'pending'
|
||||
# claim.save()
|
||||
#
|
||||
# # Start insurance claim workflow
|
||||
# process = self.flow_class.start.run(
|
||||
# claim=claim,
|
||||
# submit_electronically=form.cleaned_data.get('submit_electronically', True),
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Insurance claim submitted successfully. Claim ID: {claim.claim_number}'
|
||||
# )
|
||||
#
|
||||
# return redirect('billing:claim_detail', pk=claim.pk)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Submit Insurance Claim'
|
||||
# context['breadcrumbs'] = [
|
||||
# {'name': 'Home', 'url': reverse('core:dashboard')},
|
||||
# {'name': 'Billing', 'url': reverse('billing:dashboard')},
|
||||
# {'name': 'Claims', 'url': reverse('billing:claim_list')},
|
||||
# {'name': 'Submit Claim', 'url': ''}
|
||||
# ]
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class PaymentProcessingView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
||||
# """
|
||||
# View for payment processing workflow
|
||||
# """
|
||||
# model = Payment
|
||||
# form_class = PaymentProcessingForm
|
||||
# template_name = 'billing/payment_processing.html'
|
||||
# permission_required = 'billing.can_process_payments'
|
||||
# flow_class = PaymentProcessingFlow
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['tenant'] = self.request.user.tenant
|
||||
#
|
||||
# # Pre-populate bill if provided
|
||||
# bill_id = self.kwargs.get('bill_id')
|
||||
# if bill_id:
|
||||
# kwargs['bill'] = get_object_or_404(Bill, pk=bill_id)
|
||||
#
|
||||
# return kwargs
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# with transaction.atomic():
|
||||
# # Create payment
|
||||
# payment = form.save(commit=False)
|
||||
# payment.tenant = self.request.user.tenant
|
||||
# payment.processed_by = self.request.user
|
||||
# payment.status = 'pending'
|
||||
# payment.save()
|
||||
#
|
||||
# # Update bill balance
|
||||
# bill = payment.bill
|
||||
# bill.paid_amount += payment.payment_amount
|
||||
# bill.balance_due = bill.total_amount - bill.paid_amount
|
||||
#
|
||||
# if bill.balance_due <= 0:
|
||||
# bill.status = 'paid'
|
||||
# else:
|
||||
# bill.status = 'partial_payment'
|
||||
#
|
||||
# bill.save()
|
||||
#
|
||||
# # Start payment processing workflow
|
||||
# process = self.flow_class.start.run(
|
||||
# payment=payment,
|
||||
# send_receipt=form.cleaned_data.get('send_receipt', True),
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Payment of ${payment.payment_amount} processed successfully. '
|
||||
# f'Remaining balance: ${bill.balance_due}'
|
||||
# )
|
||||
#
|
||||
# return redirect('billing:payment_detail', pk=payment.pk)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Process Payment'
|
||||
#
|
||||
# bill_id = self.kwargs.get('bill_id')
|
||||
# if bill_id:
|
||||
# context['bill'] = get_object_or_404(Bill, pk=bill_id)
|
||||
#
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class DenialManagementView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
||||
# """
|
||||
# View for denial management workflow
|
||||
# """
|
||||
# model = ClaimDenial
|
||||
# form_class = DenialManagementForm
|
||||
# template_name = 'billing/denial_management.html'
|
||||
# permission_required = 'billing.can_manage_denials'
|
||||
# flow_class = DenialManagementFlow
|
||||
#
|
||||
# def get_form_kwargs(self):
|
||||
# kwargs = super().get_form_kwargs()
|
||||
# kwargs['tenant'] = self.request.user.tenant
|
||||
# return kwargs
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# with transaction.atomic():
|
||||
# # Create denial record
|
||||
# denial = form.save(commit=False)
|
||||
# denial.tenant = self.request.user.tenant
|
||||
# denial.processed_by = self.request.user
|
||||
# denial.save()
|
||||
#
|
||||
# # Update claim status
|
||||
# claim = denial.claim
|
||||
# claim.status = 'denied'
|
||||
# claim.save()
|
||||
#
|
||||
# # Start denial management workflow
|
||||
# process = self.flow_class.start.run(
|
||||
# denial=denial,
|
||||
# resubmit_claim=form.cleaned_data.get('resubmit_claim', False),
|
||||
# file_appeal=form.cleaned_data.get('file_appeal', False),
|
||||
# created_by=self.request.user
|
||||
# )
|
||||
#
|
||||
# messages.success(
|
||||
# self.request,
|
||||
# f'Denial processed for claim {claim.claim_number}. '
|
||||
# f'Workflow initiated for corrective actions.'
|
||||
# )
|
||||
#
|
||||
# return redirect('billing:denial_detail', pk=denial.pk)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Manage Claim Denial'
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# class BillListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
# """
|
||||
# View for listing bills
|
||||
# """
|
||||
# model = Bill
|
||||
# template_name = 'billing/bill_list.html'
|
||||
# context_object_name = 'bills'
|
||||
# permission_required = 'billing.view_bill'
|
||||
# paginate_by = 25
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# queryset = Bill.objects.filter(tenant=self.request.user.tenant)
|
||||
#
|
||||
# # Apply filters
|
||||
# search = self.request.GET.get('search')
|
||||
# if search:
|
||||
# queryset = queryset.filter(
|
||||
# Q(patient__first_name__icontains=search) |
|
||||
# Q(patient__last_name__icontains=search) |
|
||||
# Q(bill_number__icontains=search)
|
||||
# )
|
||||
#
|
||||
# status = self.request.GET.get('status')
|
||||
# if status:
|
||||
# queryset = queryset.filter(status=status)
|
||||
#
|
||||
# date_from = self.request.GET.get('date_from')
|
||||
# if date_from:
|
||||
# queryset = queryset.filter(service_date__gte=date_from)
|
||||
#
|
||||
# date_to = self.request.GET.get('date_to')
|
||||
# if date_to:
|
||||
# queryset = queryset.filter(service_date__lte=date_to)
|
||||
#
|
||||
# return queryset.order_by('-created_at')
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = 'Bills'
|
||||
# context['search'] = self.request.GET.get('search', '')
|
||||
# context['selected_status'] = self.request.GET.get('status', '')
|
||||
# context['date_from'] = self.request.GET.get('date_from', '')
|
||||
# context['date_to'] = self.request.GET.get('date_to', '')
|
||||
# context['billing_stats'] = self.get_billing_stats()
|
||||
# return context
|
||||
#
|
||||
# def get_billing_stats(self):
|
||||
# """Get billing statistics"""
|
||||
# bills = Bill.objects.filter(tenant=self.request.user.tenant)
|
||||
# return {
|
||||
# 'total_bills': bills.count(),
|
||||
# 'total_amount': bills.aggregate(Sum('total_amount'))['total_amount__sum'] or 0,
|
||||
# 'total_paid': bills.aggregate(Sum('paid_amount'))['paid_amount__sum'] or 0,
|
||||
# 'total_outstanding': bills.aggregate(Sum('balance_due'))['balance_due__sum'] or 0
|
||||
# }
|
||||
#
|
||||
#
|
||||
# class BillDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||
# """
|
||||
# View for bill details
|
||||
# """
|
||||
# model = Bill
|
||||
# template_name = 'billing/bill_detail.html'
|
||||
# context_object_name = 'bill'
|
||||
# permission_required = 'billing.view_bill'
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# return Bill.objects.filter(tenant=self.request.user.tenant)
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# bill = self.object
|
||||
# context['title'] = f'Bill {bill.bill_number}'
|
||||
# context['items'] = bill.items.all()
|
||||
# context['claims'] = bill.claims.all()
|
||||
# context['payments'] = bill.payments.all()
|
||||
# context['payment_plan'] = getattr(bill, 'payment_plan', None)
|
||||
# context['can_edit'] = self.request.user.has_perm('billing.change_bill')
|
||||
# context['can_process_payment'] = self.request.user.has_perm('billing.can_process_payments')
|
||||
# return context
|
||||
#
|
||||
#
|
||||
# # AJAX Views
|
||||
# @login_required
|
||||
# @permission_required('billing.view_patient')
|
||||
# def patient_billing_search_ajax(request):
|
||||
# """AJAX view for patient billing search"""
|
||||
# query = request.GET.get('q', '')
|
||||
# if len(query) < 2:
|
||||
# return JsonResponse({'patients': []})
|
||||
#
|
||||
# patients = Patient.objects.filter(
|
||||
# tenant=request.user.tenant
|
||||
# ).filter(
|
||||
# Q(first_name__icontains=query) |
|
||||
# Q(last_name__icontains=query) |
|
||||
# Q(patient_id__icontains=query) |
|
||||
# Q(insurance_id__icontains=query)
|
||||
# )[:10]
|
||||
#
|
||||
# patient_data = [
|
||||
# {
|
||||
# 'id': patient.id,
|
||||
# 'name': patient.get_full_name(),
|
||||
# 'patient_id': patient.patient_id,
|
||||
# 'insurance': patient.primary_insurance.name if patient.primary_insurance else 'No Insurance',
|
||||
# 'outstanding_balance': str(patient.get_outstanding_balance())
|
||||
# }
|
||||
# for patient in patients
|
||||
# ]
|
||||
#
|
||||
# return JsonResponse({'patients': patient_data})
|
||||
#
|
||||
#
|
||||
# @login_required
|
||||
# @permission_required('billing.can_calculate_totals')
|
||||
# def calculate_bill_totals_ajax(request):
|
||||
# """AJAX view to calculate bill totals"""
|
||||
# if request.method == 'POST':
|
||||
# try:
|
||||
# data = json.loads(request.body)
|
||||
# items = data.get('items', [])
|
||||
#
|
||||
# subtotal = Decimal('0.00')
|
||||
# for item in items:
|
||||
# quantity = Decimal(str(item.get('quantity', 1)))
|
||||
# unit_price = Decimal(str(item.get('unit_price', 0)))
|
||||
# discount = Decimal(str(item.get('discount', 0)))
|
||||
#
|
||||
# item_total = (quantity * unit_price) - discount
|
||||
# subtotal += item_total
|
||||
#
|
||||
# tax_rate = Decimal('0.08') # 8% tax rate (configurable)
|
||||
# tax_amount = subtotal * tax_rate
|
||||
# total_amount = subtotal + tax_amount
|
||||
#
|
||||
# return JsonResponse({
|
||||
# 'success': True,
|
||||
# 'subtotal': str(subtotal),
|
||||
# 'tax_amount': str(tax_amount),
|
||||
# 'total_amount': str(total_amount)
|
||||
# })
|
||||
# except Exception as e:
|
||||
# return JsonResponse({
|
||||
# 'success': False,
|
||||
# 'error': str(e)
|
||||
# })
|
||||
#
|
||||
# return JsonResponse({'success': False, 'message': 'Invalid request.'})
|
||||
#
|
||||
#
|
||||
# @login_required
|
||||
# @permission_required('billing.can_verify_insurance')
|
||||
# def verify_insurance_ajax(request, patient_id):
|
||||
# """AJAX view to verify insurance"""
|
||||
# if request.method == 'POST':
|
||||
# try:
|
||||
# patient = Patient.objects.get(
|
||||
# id=patient_id,
|
||||
# tenant=request.user.tenant
|
||||
# )
|
||||
#
|
||||
# # Perform insurance verification
|
||||
# verification_result = verify_patient_insurance(patient)
|
||||
#
|
||||
# return JsonResponse({
|
||||
# 'success': verification_result['success'],
|
||||
# 'data': verification_result
|
||||
# })
|
||||
# except Patient.DoesNotExist:
|
||||
# return JsonResponse({
|
||||
# 'success': False,
|
||||
# 'message': 'Patient not found.'
|
||||
# })
|
||||
#
|
||||
# return JsonResponse({'success': False, 'message': 'Invalid request.'})
|
||||
#
|
||||
#
|
||||
# def verify_patient_insurance(patient):
|
||||
# """Verify patient insurance"""
|
||||
# try:
|
||||
# # This would implement actual insurance verification
|
||||
# return {
|
||||
# 'success': True,
|
||||
# 'status': 'active',
|
||||
# 'coverage': 'full',
|
||||
# 'copay': 25.00,
|
||||
# 'deductible': 500.00,
|
||||
# 'deductible_met': 150.00,
|
||||
# 'out_of_pocket_max': 2000.00,
|
||||
# 'out_of_pocket_met': 300.00
|
||||
# }
|
||||
# except Exception as e:
|
||||
# return {
|
||||
# 'success': False,
|
||||
# 'error': str(e)
|
||||
# }
|
||||
#
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -3,7 +3,7 @@ from django.db import models
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from core.models import Department
|
||||
from hr.models import Department
|
||||
from patients.models import PatientProfile
|
||||
from accounts.models import User
|
||||
|
||||
|
||||
@ -9,25 +9,25 @@ urlpatterns = [
|
||||
|
||||
# Donor Management
|
||||
path('donors/', views.DonorListView.as_view(), name='donor_list'),
|
||||
path('donors/<int:donor_id>/', views.donor_detail, name='donor_detail'),
|
||||
path('donors/create/', views.donor_create, name='donor_create'),
|
||||
path('donors/<int:donor_id>/update/', views.donor_update, name='donor_update'),
|
||||
path('donors/<int:donor_id>/', views.DonorDetailView.as_view(), name='donor_detail'),
|
||||
path('donors/create/', views.DonorCreateView.as_view(), name='donor_create'),
|
||||
path('donors/<int:donor_id>/update/', views.DonorUpdateView.as_view(), name='donor_update'),
|
||||
path('donors/<int:donor_id>/eligibility/', views.donor_eligibility_check, name='donor_eligibility'),
|
||||
|
||||
# Blood Unit Management
|
||||
path('units/', views.blood_unit_list, name='blood_unit_list'),
|
||||
path('units/<int:unit_id>/', views.blood_unit_detail, name='blood_unit_detail'),
|
||||
path('units/create/', views.blood_unit_create, name='blood_unit_create'),
|
||||
path('units/create/<int:donor_id>/', views.blood_unit_create, name='blood_unit_create_for_donor'),
|
||||
path('units/', views.BloodUnitListView.as_view(), name='blood_unit_list'),
|
||||
path('units/<int:unit_id>/', views.BloodUnitDetailView.as_view(), name='blood_unit_detail'),
|
||||
path('units/create/', views.BloodUnitCreateView.as_view(), name='blood_unit_create'),
|
||||
path('units/create/<int:donor_id>/', views.BloodUnitCreateView.as_view(), name='blood_unit_create_for_donor'),
|
||||
|
||||
# Blood Testing
|
||||
path('units/<int:unit_id>/test/', views.blood_test_create, name='blood_test_create'),
|
||||
path('units/<int:unit_id>/crossmatch/<int:patient_id>/', views.crossmatch_create, name='crossmatch_create'),
|
||||
|
||||
# Blood Requests
|
||||
path('requests/', views.blood_request_list, name='blood_request_list'),
|
||||
path('requests/<int:request_id>/', views.blood_request_detail, name='blood_request_detail'),
|
||||
path('requests/create/', views.blood_request_create, name='blood_request_create'),
|
||||
path('requests/', views.BloodRequestListView.as_view(), name='blood_request_list'),
|
||||
path('requests/<int:request_id>/', views.BloodRequestDetailView.as_view(), name='blood_request_detail'),
|
||||
path('requests/create/', views.BloodRequestCreateView.as_view(), name='blood_request_create'),
|
||||
|
||||
# Blood Issue and Transfusion
|
||||
path('requests/<int:request_id>/issue/', views.blood_issue_create, name='blood_issue_create'),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.contrib import messages
|
||||
@ -11,7 +11,7 @@ from django.contrib.auth.models import User
|
||||
from datetime import timedelta
|
||||
import json
|
||||
|
||||
from django.views.generic import ListView
|
||||
from django.views.generic import ListView, CreateView, DetailView, DeleteView, UpdateView
|
||||
|
||||
from .models import (
|
||||
BloodGroup, Donor, BloodComponent, BloodUnit, BloodTest, CrossMatch,
|
||||
@ -52,9 +52,7 @@ def dashboard(request):
|
||||
context['blood_group_stats'] = blood_group_stats
|
||||
|
||||
# Recent activities
|
||||
context['recent_units'] = BloodUnit.objects.select_related(
|
||||
'donor', 'component', 'blood_group'
|
||||
).order_by('-collection_date')[:10]
|
||||
context['recent_units'] = BloodUnit.objects.select_related('donor', 'component', 'blood_group').order_by('-collection_date')
|
||||
|
||||
context['urgent_requests'] = BloodRequest.objects.filter(
|
||||
urgency='emergency', status__in=['pending', 'processing']
|
||||
@ -110,62 +108,65 @@ class DonorListView(LoginRequiredMixin, ListView):
|
||||
|
||||
|
||||
|
||||
@login_required
|
||||
def donor_detail(request, donor_id):
|
||||
"""Donor detail view with donation history"""
|
||||
donor = get_object_or_404(Donor, id=donor_id)
|
||||
class DonorDetailView(LoginRequiredMixin, DetailView):
|
||||
model = Donor
|
||||
template_name = 'blood_bank/donors/donor_detail.html'
|
||||
context_object_name = 'donor'
|
||||
pk_url_kwarg = 'donor_id'
|
||||
|
||||
# Get donation history
|
||||
blood_units = BloodUnit.objects.filter(donor=donor).select_related(
|
||||
'component', 'blood_group'
|
||||
).order_by('-collection_date')
|
||||
|
||||
context = {
|
||||
'donor': donor,
|
||||
'blood_units': blood_units,
|
||||
'total_donations': blood_units.count(),
|
||||
'last_donation': blood_units.first(),
|
||||
}
|
||||
|
||||
return render(request, 'blood_bank/donors/donor_detail.html', context)
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
donor = self.object
|
||||
blood_units = (
|
||||
BloodUnit.objects
|
||||
.filter(donor=donor)
|
||||
.select_related('component', 'blood_group')
|
||||
.order_by('-collection_date')
|
||||
)
|
||||
ctx.update({
|
||||
'blood_units': blood_units,
|
||||
'total_donations': blood_units.count(),
|
||||
'last_donation': blood_units.first(),
|
||||
})
|
||||
return ctx
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('blood_bank.add_donor')
|
||||
def donor_create(request):
|
||||
"""Create new donor"""
|
||||
if request.method == 'POST':
|
||||
form = DonorForm(request.POST)
|
||||
if form.is_valid():
|
||||
donor = form.save(commit=False)
|
||||
donor.created_by = request.user
|
||||
donor.save()
|
||||
messages.success(request, f'Donor {donor.donor_id} created successfully.')
|
||||
return redirect('blood_bank:donor_detail', donor_id=donor.id)
|
||||
else:
|
||||
form = DonorForm()
|
||||
class DonorCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||||
model = Donor
|
||||
form_class = DonorForm
|
||||
template_name = 'blood_bank/donors/donor_form.html'
|
||||
permission_required = 'blood_bank.add_donor'
|
||||
|
||||
return render(request, 'blood_bank/donors/donor_form.html', {'form': form, 'title': 'Add New Donor'})
|
||||
def form_valid(self, form):
|
||||
donor = form.save(commit=False)
|
||||
donor.created_by = self.request.user
|
||||
donor.save()
|
||||
messages.success(self.request, f'Donor {donor.donor_id} created successfully.')
|
||||
return redirect('blood_bank:donor_detail', donor_id=donor.id)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['title'] = 'Add New Donor'
|
||||
return ctx
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('blood_bank.change_donor')
|
||||
def donor_update(request, donor_id):
|
||||
"""Update donor information"""
|
||||
donor = get_object_or_404(Donor, id=donor_id)
|
||||
class DonorUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
||||
model = Donor
|
||||
form_class = DonorForm
|
||||
template_name = 'blood_bank/donors/donor_form.html'
|
||||
permission_required = 'blood_bank.change_donor'
|
||||
pk_url_kwarg = 'donor_id'
|
||||
context_object_name = 'donor'
|
||||
|
||||
if request.method == 'POST':
|
||||
form = DonorForm(request.POST, instance=donor)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, f'Donor {donor.donor_id} updated successfully.')
|
||||
return redirect('blood_bank:donor_detail', donor_id=donor.id)
|
||||
else:
|
||||
form = DonorForm(instance=donor)
|
||||
def form_valid(self, form):
|
||||
donor = form.save()
|
||||
messages.success(self.request, f'Donor {donor.donor_id} updated successfully.')
|
||||
return redirect('blood_bank:donor_detail', donor_id=donor.id)
|
||||
|
||||
return render(request, 'blood_bank/donors/donor_form.html', {
|
||||
'form': form, 'donor': donor, 'title': 'Update Donor'
|
||||
})
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['title'] = 'Update Donor'
|
||||
return ctx
|
||||
|
||||
|
||||
@login_required
|
||||
@ -192,167 +193,163 @@ def donor_eligibility_check(request, donor_id):
|
||||
return render(request, 'blood_bank/donors/donor_eligibility.html', context)
|
||||
|
||||
|
||||
# Blood Unit Management Views
|
||||
@login_required
|
||||
def blood_unit_list(request):
|
||||
"""List all blood units with filtering"""
|
||||
form = BloodInventorySearchForm(request.GET)
|
||||
blood_units = BloodUnit.objects.select_related(
|
||||
'donor', 'component', 'blood_group'
|
||||
).order_by('-collection_date')
|
||||
class BloodUnitListView(LoginRequiredMixin, ListView):
|
||||
model = BloodUnit
|
||||
template_name = 'blood_bank/units/blood_unit_list.html'
|
||||
context_object_name = 'blood_units' # you'll still get page_obj automatically
|
||||
paginate_by = 25
|
||||
|
||||
if form.is_valid():
|
||||
if form.cleaned_data['blood_group']:
|
||||
blood_units = blood_units.filter(blood_group=form.cleaned_data['blood_group'])
|
||||
def get_queryset(self):
|
||||
# base queryset
|
||||
qs = BloodUnit.objects.select_related('donor', 'component', 'blood_group') \
|
||||
.order_by('-collection_date')
|
||||
|
||||
if form.cleaned_data['component']:
|
||||
blood_units = blood_units.filter(component=form.cleaned_data['component'])
|
||||
# bind/validate the filter form
|
||||
self.form = BloodInventorySearchForm(self.request.GET)
|
||||
if self.form.is_valid():
|
||||
cd = self.form.cleaned_data
|
||||
|
||||
if form.cleaned_data['status']:
|
||||
blood_units = blood_units.filter(status=form.cleaned_data['status'])
|
||||
if cd.get('blood_group'):
|
||||
qs = qs.filter(blood_group=cd['blood_group'])
|
||||
|
||||
if form.cleaned_data['expiry_days']:
|
||||
expiry_date = timezone.now() + timedelta(days=form.cleaned_data['expiry_days'])
|
||||
blood_units = blood_units.filter(expiry_date__lte=expiry_date)
|
||||
if cd.get('component'):
|
||||
qs = qs.filter(component=cd['component'])
|
||||
|
||||
paginator = Paginator(blood_units, 25)
|
||||
page_number = request.GET.get('page')
|
||||
page_obj = paginator.get_page(page_number)
|
||||
if cd.get('status'):
|
||||
qs = qs.filter(status=cd['status'])
|
||||
|
||||
context = {
|
||||
'page_obj': page_obj,
|
||||
'form': form,
|
||||
}
|
||||
if cd.get('expiry_days') is not None:
|
||||
expiry_date = timezone.now() + timedelta(days=cd['expiry_days'])
|
||||
qs = qs.filter(expiry_date__lte=expiry_date)
|
||||
|
||||
return render(request, 'blood_bank/units/blood_unit_list.html', context)
|
||||
return qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
# expose the (bound) form to the template
|
||||
ctx['form'] = getattr(self, 'form', BloodInventorySearchForm(self.request.GET))
|
||||
return ctx
|
||||
|
||||
|
||||
@login_required
|
||||
def blood_unit_detail(request, unit_id):
|
||||
"""Blood unit detail view with test results"""
|
||||
blood_unit = get_object_or_404(BloodUnit, id=unit_id)
|
||||
class BloodUnitDetailView(LoginRequiredMixin, DetailView):
|
||||
model = BloodUnit
|
||||
template_name = 'blood_bank/units/blood_unit_detail.html'
|
||||
context_object_name = 'blood_unit'
|
||||
pk_url_kwarg = 'unit_id'
|
||||
|
||||
# Get test results
|
||||
tests = BloodTest.objects.filter(blood_unit=blood_unit).select_related('tested_by')
|
||||
crossmatches = CrossMatch.objects.filter(blood_unit=blood_unit).select_related(
|
||||
'recipient', 'tested_by'
|
||||
)
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
blood_unit = self.object
|
||||
|
||||
context = {
|
||||
'blood_unit': blood_unit,
|
||||
'tests': tests,
|
||||
'crossmatches': crossmatches,
|
||||
}
|
||||
|
||||
return render(request, 'blood_bank/units/blood_unit_detail.html', context)
|
||||
# related objects
|
||||
ctx['tests'] = BloodTest.objects.filter(blood_unit=blood_unit) \
|
||||
.select_related('tested_by')
|
||||
ctx['crossmatches'] = CrossMatch.objects.filter(blood_unit=blood_unit) \
|
||||
.select_related('recipient', 'tested_by')
|
||||
return ctx
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('blood_bank.add_bloodunit')
|
||||
def blood_unit_create(request, donor_id=None):
|
||||
"""Create new blood unit"""
|
||||
donor = None
|
||||
if donor_id:
|
||||
donor = get_object_or_404(Donor, id=donor_id)
|
||||
class BloodUnitCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||||
model = BloodUnit
|
||||
form_class = BloodUnitForm
|
||||
template_name = 'blood_bank/units/blood_unit_form.html'
|
||||
permission_required = 'blood_bank.add_bloodunit'
|
||||
|
||||
if request.method == 'POST':
|
||||
form = BloodUnitForm(request.POST)
|
||||
if form.is_valid():
|
||||
blood_unit = form.save(commit=False)
|
||||
blood_unit.collected_by = request.user
|
||||
blood_unit.save()
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
donor_id = self.kwargs.get('donor_id')
|
||||
if donor_id:
|
||||
donor = get_object_or_404(Donor, id=donor_id)
|
||||
initial['donor'] = donor
|
||||
initial['blood_group'] = donor.blood_group
|
||||
self.donor = donor # store for use in context
|
||||
return initial
|
||||
|
||||
# Update donor's last donation date and total donations
|
||||
if blood_unit.donor:
|
||||
blood_unit.donor.last_donation_date = blood_unit.collection_date
|
||||
blood_unit.donor.total_donations += 1
|
||||
blood_unit.donor.save()
|
||||
def form_valid(self, form):
|
||||
blood_unit = form.save(commit=False)
|
||||
blood_unit.collected_by = self.request.user
|
||||
blood_unit.save()
|
||||
|
||||
messages.success(request, f'Blood unit {blood_unit.unit_number} created successfully.')
|
||||
return redirect('blood_bank:blood_unit_detail', unit_id=blood_unit.id)
|
||||
else:
|
||||
initial_data = {}
|
||||
if donor:
|
||||
initial_data['donor'] = donor
|
||||
initial_data['blood_group'] = donor.blood_group
|
||||
form = BloodUnitForm(initial=initial_data)
|
||||
# Update donor’s donation stats
|
||||
if blood_unit.donor:
|
||||
blood_unit.donor.last_donation_date = blood_unit.collection_date
|
||||
blood_unit.donor.total_donations += 1
|
||||
blood_unit.donor.save()
|
||||
|
||||
return render(request, 'blood_bank/units/blood_unit_form.html', {
|
||||
'form': form, 'donor': donor, 'title': 'Register Blood Unit'
|
||||
})
|
||||
messages.success(self.request, f'Blood unit {blood_unit.unit_number} created successfully.')
|
||||
return redirect('blood_bank:blood_unit_detail', unit_id=blood_unit.id)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['donor'] = getattr(self, 'donor', None)
|
||||
context['title'] = 'Register Blood Unit'
|
||||
return context
|
||||
|
||||
|
||||
# Blood Request Management Views
|
||||
@login_required
|
||||
def blood_request_list(request):
|
||||
"""List all blood requests"""
|
||||
requests = BloodRequest.objects.select_related(
|
||||
'patient', 'requesting_department', 'requesting_physician', 'component_requested'
|
||||
).order_by('-request_date')
|
||||
class BloodRequestListView(LoginRequiredMixin, ListView):
|
||||
model = BloodRequest
|
||||
template_name = 'blood_bank/requests/blood_request_list.html'
|
||||
context_object_name = 'page_obj'
|
||||
paginate_by = 25
|
||||
|
||||
# Filter by status
|
||||
status_filter = request.GET.get('status')
|
||||
if status_filter:
|
||||
requests = requests.filter(status=status_filter)
|
||||
def get_queryset(self):
|
||||
qs = BloodRequest.objects.select_related(
|
||||
'patient', 'requesting_department', 'requesting_physician', 'component_requested'
|
||||
).order_by('-request_date')
|
||||
|
||||
# Filter by urgency
|
||||
urgency_filter = request.GET.get('urgency')
|
||||
if urgency_filter:
|
||||
requests = requests.filter(urgency=urgency_filter)
|
||||
status_filter = self.request.GET.get('status')
|
||||
urgency_filter = self.request.GET.get('urgency')
|
||||
|
||||
paginator = Paginator(requests, 25)
|
||||
page_number = request.GET.get('page')
|
||||
page_obj = paginator.get_page(page_number)
|
||||
if status_filter:
|
||||
qs = qs.filter(status=status_filter)
|
||||
if urgency_filter:
|
||||
qs = qs.filter(urgency=urgency_filter)
|
||||
|
||||
context = {
|
||||
'page_obj': page_obj,
|
||||
'status_choices': BloodRequest.STATUS_CHOICES,
|
||||
'urgency_choices': BloodRequest.URGENCY_CHOICES,
|
||||
'status_filter': status_filter,
|
||||
'urgency_filter': urgency_filter,
|
||||
}
|
||||
return qs
|
||||
|
||||
return render(request, 'blood_bank/requests/blood_request_list.html', context)
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['status_choices'] = BloodRequest.STATUS_CHOICES
|
||||
context['urgency_choices'] = BloodRequest.URGENCY_CHOICES
|
||||
context['status_filter'] = self.request.GET.get('status')
|
||||
context['urgency_filter'] = self.request.GET.get('urgency')
|
||||
return context
|
||||
|
||||
|
||||
@login_required
|
||||
def blood_request_detail(request, request_id):
|
||||
"""Blood request detail view"""
|
||||
blood_request = get_object_or_404(BloodRequest, id=request_id)
|
||||
class BloodRequestDetailView(LoginRequiredMixin, DetailView):
|
||||
model = BloodRequest
|
||||
pk_url_kwarg = 'request_id'
|
||||
template_name = 'blood_bank/requests/blood_request_detail.html'
|
||||
context_object_name = 'blood_request'
|
||||
|
||||
# Get issued units for this request
|
||||
issues = BloodIssue.objects.filter(blood_request=blood_request).select_related(
|
||||
'blood_unit', 'issued_by', 'issued_to'
|
||||
)
|
||||
|
||||
context = {
|
||||
'blood_request': blood_request,
|
||||
'issues': issues,
|
||||
}
|
||||
|
||||
return render(request, 'blood_bank/requests/blood_request_detail.html', context)
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['issues'] = BloodIssue.objects.filter(
|
||||
blood_request=self.object
|
||||
).select_related('blood_unit', 'issued_by', 'issued_to')
|
||||
return context
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('blood_bank.add_bloodrequest')
|
||||
def blood_request_create(request):
|
||||
"""Create new blood request"""
|
||||
if request.method == 'POST':
|
||||
form = BloodRequestForm(request.POST)
|
||||
if form.is_valid():
|
||||
blood_request = form.save(commit=False)
|
||||
blood_request.requesting_physician = request.user
|
||||
# Generate request number
|
||||
blood_request.request_number = f"BR{timezone.now().strftime('%Y%m%d')}{BloodRequest.objects.count() + 1:04d}"
|
||||
blood_request.save()
|
||||
messages.success(request, f'Blood request {blood_request.request_number} created successfully.')
|
||||
return redirect('blood_bank:blood_request_detail', request_id=blood_request.id)
|
||||
else:
|
||||
form = BloodRequestForm()
|
||||
class BloodRequestCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||||
model = BloodRequest
|
||||
form_class = BloodRequestForm
|
||||
template_name = 'blood_bank/requests/blood_request_form.html'
|
||||
permission_required = 'blood_bank.add_bloodrequest'
|
||||
|
||||
return render(request, 'blood_bank/requests/blood_request_form.html', {
|
||||
'form': form, 'title': 'Create Blood Request'
|
||||
})
|
||||
def form_valid(self, form):
|
||||
blood_request = form.save(commit=False)
|
||||
blood_request.requesting_physician = self.request.user
|
||||
# Generate request number
|
||||
blood_request.request_number = f"BR{timezone.now().strftime('%Y%m%d')}{BloodRequest.objects.count() + 1:04d}"
|
||||
blood_request.save()
|
||||
|
||||
messages.success(self.request, f'Blood request {blood_request.request_number} created successfully.')
|
||||
return redirect('blood_bank:blood_request_detail', request_id=blood_request.id)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['title'] = 'Create Blood Request'
|
||||
return context
|
||||
|
||||
|
||||
# Blood Issue and Transfusion Views
|
||||
|
||||
BIN
communications/__pycache__/flows.cpython-312.pyc
Normal file
BIN
communications/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
1046
communications/flows.py
Normal file
1046
communications/flows.py
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
BIN
core/__pycache__/flows.cpython-312.pyc
Normal file
BIN
core/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
849
core/flows.py
Normal file
849
core/flows.py
Normal file
@ -0,0 +1,849 @@
|
||||
# """
|
||||
# Viewflow workflows for core app.
|
||||
# Provides system administration, tenant management, and configuration workflows.
|
||||
# """
|
||||
#
|
||||
# from viewflow import Flow, lock
|
||||
# from viewflow.base import this, flow_func
|
||||
# from viewflow.contrib import celery
|
||||
# from viewflow.decorators import flow_view
|
||||
# from viewflow.fields import CharField, ModelField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.models import Process, Task
|
||||
# from django.contrib.auth.models import User
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import Tenant, Department, AuditLogEntry, SystemConfiguration, SystemNotification, IntegrationLog
|
||||
# from .views import (
|
||||
# TenantSetupView, DepartmentManagementView, ConfigurationView,
|
||||
# AuditReviewView, NotificationManagementView, IntegrationMonitoringView,
|
||||
# SystemMaintenanceView, BackupView, SecurityAuditView, ComplianceCheckView
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class TenantOnboardingProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for tenant onboarding
|
||||
# """
|
||||
# tenant = ModelField(Tenant, help_text='Associated tenant')
|
||||
#
|
||||
# # Process status tracking
|
||||
# tenant_created = models.BooleanField(default=False)
|
||||
# configuration_setup = models.BooleanField(default=False)
|
||||
# departments_created = models.BooleanField(default=False)
|
||||
# users_configured = models.BooleanField(default=False)
|
||||
# integrations_setup = models.BooleanField(default=False)
|
||||
# testing_completed = models.BooleanField(default=False)
|
||||
# training_provided = models.BooleanField(default=False)
|
||||
# onboarding_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Tenant Onboarding Process'
|
||||
# verbose_name_plural = 'Tenant Onboarding Processes'
|
||||
#
|
||||
#
|
||||
# class TenantOnboardingFlow(Flow):
|
||||
# """
|
||||
# Tenant Onboarding Workflow
|
||||
#
|
||||
# This flow manages complete tenant onboarding from initial
|
||||
# setup through configuration, testing, and go-live.
|
||||
# """
|
||||
#
|
||||
# process_class = TenantOnboardingProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_tenant_onboarding)
|
||||
# .Next(this.setup_tenant)
|
||||
# )
|
||||
#
|
||||
# setup_tenant = (
|
||||
# flow_view(TenantSetupView)
|
||||
# .Permission('core.can_setup_tenants')
|
||||
# .Next(this.configure_system)
|
||||
# )
|
||||
#
|
||||
# configure_system = (
|
||||
# flow_view(ConfigurationView)
|
||||
# .Permission('core.can_configure_system')
|
||||
# .Next(this.create_departments)
|
||||
# )
|
||||
#
|
||||
# create_departments = (
|
||||
# flow_view(DepartmentManagementView)
|
||||
# .Permission('core.can_manage_departments')
|
||||
# .Next(this.configure_users)
|
||||
# )
|
||||
#
|
||||
# configure_users = (
|
||||
# flow_func(this.setup_initial_users)
|
||||
# .Next(this.setup_integrations)
|
||||
# )
|
||||
#
|
||||
# setup_integrations = (
|
||||
# flow_func(this.configure_integrations)
|
||||
# .Next(this.complete_testing)
|
||||
# )
|
||||
#
|
||||
# complete_testing = (
|
||||
# flow_func(this.perform_system_testing)
|
||||
# .Next(this.provide_training)
|
||||
# )
|
||||
#
|
||||
# provide_training = (
|
||||
# flow_func(this.deliver_user_training)
|
||||
# .Next(this.finalize_onboarding)
|
||||
# )
|
||||
#
|
||||
# finalize_onboarding = (
|
||||
# flow_func(this.complete_tenant_onboarding)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_tenant_onboarding)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_tenant_onboarding(self, activation):
|
||||
# """Initialize the tenant onboarding process"""
|
||||
# process = activation.process
|
||||
# tenant = process.tenant
|
||||
#
|
||||
# # Send onboarding notification
|
||||
# self.notify_onboarding_start(tenant)
|
||||
#
|
||||
# # Create onboarding checklist
|
||||
# self.create_onboarding_checklist(tenant)
|
||||
#
|
||||
# # Set up audit logging
|
||||
# self.setup_audit_logging(tenant)
|
||||
#
|
||||
# def setup_initial_users(self, activation):
|
||||
# """Setup initial users for tenant"""
|
||||
# process = activation.process
|
||||
# tenant = process.tenant
|
||||
#
|
||||
# # Create initial admin users
|
||||
# self.create_admin_users(tenant)
|
||||
#
|
||||
# # Mark users configured
|
||||
# process.users_configured = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send user credentials
|
||||
# self.send_user_credentials(tenant)
|
||||
#
|
||||
# def configure_integrations(self, activation):
|
||||
# """Configure system integrations"""
|
||||
# process = activation.process
|
||||
# tenant = process.tenant
|
||||
#
|
||||
# # Setup default integrations
|
||||
# self.setup_default_integrations(tenant)
|
||||
#
|
||||
# # Mark integrations setup
|
||||
# process.integrations_setup = True
|
||||
# process.save()
|
||||
#
|
||||
# # Test integration connectivity
|
||||
# self.test_integration_connectivity(tenant)
|
||||
#
|
||||
# def perform_system_testing(self, activation):
|
||||
# """Perform comprehensive system testing"""
|
||||
# process = activation.process
|
||||
# tenant = process.tenant
|
||||
#
|
||||
# # Execute system tests
|
||||
# test_results = self.execute_system_tests(tenant)
|
||||
#
|
||||
# # Mark testing completed
|
||||
# process.testing_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store test results
|
||||
# self.store_test_results(tenant, test_results)
|
||||
#
|
||||
# def deliver_user_training(self, activation):
|
||||
# """Deliver user training"""
|
||||
# process = activation.process
|
||||
# tenant = process.tenant
|
||||
#
|
||||
# # Schedule training sessions
|
||||
# self.schedule_training_sessions(tenant)
|
||||
#
|
||||
# # Mark training provided
|
||||
# process.training_provided = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send training materials
|
||||
# self.send_training_materials(tenant)
|
||||
#
|
||||
# def complete_tenant_onboarding(self, activation):
|
||||
# """Complete the tenant onboarding process"""
|
||||
# process = activation.process
|
||||
# tenant = process.tenant
|
||||
#
|
||||
# # Activate tenant
|
||||
# tenant.is_active = True
|
||||
# tenant.save()
|
||||
#
|
||||
# # Mark onboarding completed
|
||||
# process.onboarding_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_onboarding_completion(tenant)
|
||||
#
|
||||
# # Schedule post-onboarding follow-up
|
||||
# self.schedule_followup(tenant)
|
||||
#
|
||||
# def end_tenant_onboarding(self, activation):
|
||||
# """End the tenant onboarding workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate onboarding summary
|
||||
# self.generate_onboarding_summary(process.tenant)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_onboarding_start(self, tenant):
|
||||
# """Notify onboarding start"""
|
||||
# admin_team = User.objects.filter(groups__name='System Administrators')
|
||||
# for admin in admin_team:
|
||||
# send_mail(
|
||||
# subject=f'Tenant Onboarding Started: {tenant.name}',
|
||||
# message=f'Onboarding process started for tenant "{tenant.name}".',
|
||||
# from_email='admin@hospital.com',
|
||||
# recipient_list=[admin.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_onboarding_checklist(self, tenant):
|
||||
# """Create onboarding checklist"""
|
||||
# # This would create a comprehensive onboarding checklist
|
||||
# pass
|
||||
#
|
||||
# def setup_audit_logging(self, tenant):
|
||||
# """Setup audit logging for tenant"""
|
||||
# # This would configure audit logging
|
||||
# pass
|
||||
#
|
||||
# def create_admin_users(self, tenant):
|
||||
# """Create initial admin users"""
|
||||
# # This would create initial admin users
|
||||
# pass
|
||||
#
|
||||
# def send_user_credentials(self, tenant):
|
||||
# """Send user credentials"""
|
||||
# # This would send initial user credentials
|
||||
# pass
|
||||
#
|
||||
# def setup_default_integrations(self, tenant):
|
||||
# """Setup default integrations"""
|
||||
# # This would configure default integrations
|
||||
# pass
|
||||
#
|
||||
# def test_integration_connectivity(self, tenant):
|
||||
# """Test integration connectivity"""
|
||||
# # This would test all integrations
|
||||
# pass
|
||||
#
|
||||
# def execute_system_tests(self, tenant):
|
||||
# """Execute comprehensive system tests"""
|
||||
# # This would run system tests
|
||||
# return {'status': 'passed', 'issues': []}
|
||||
#
|
||||
# def store_test_results(self, tenant, results):
|
||||
# """Store test results"""
|
||||
# # This would store test results
|
||||
# pass
|
||||
#
|
||||
# def schedule_training_sessions(self, tenant):
|
||||
# """Schedule training sessions"""
|
||||
# # This would schedule training
|
||||
# pass
|
||||
#
|
||||
# def send_training_materials(self, tenant):
|
||||
# """Send training materials"""
|
||||
# # This would send training materials
|
||||
# pass
|
||||
#
|
||||
# def notify_onboarding_completion(self, tenant):
|
||||
# """Notify onboarding completion"""
|
||||
# send_mail(
|
||||
# subject=f'Tenant Onboarding Complete: {tenant.name}',
|
||||
# message=f'Onboarding completed successfully for "{tenant.name}".',
|
||||
# from_email='admin@hospital.com',
|
||||
# recipient_list=[tenant.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def schedule_followup(self, tenant):
|
||||
# """Schedule post-onboarding follow-up"""
|
||||
# # Schedule follow-up task
|
||||
# tenant_followup.apply_async(
|
||||
# args=[tenant.tenant_id],
|
||||
# countdown=86400 * 7 # 7 days
|
||||
# )
|
||||
#
|
||||
# def generate_onboarding_summary(self, tenant):
|
||||
# """Generate onboarding summary"""
|
||||
# # This would generate onboarding summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class SystemMaintenanceProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for system maintenance
|
||||
# """
|
||||
# maintenance_type = models.CharField(max_length=50, help_text='Type of maintenance')
|
||||
#
|
||||
# # Process status tracking
|
||||
# maintenance_scheduled = models.BooleanField(default=False)
|
||||
# notifications_sent = models.BooleanField(default=False)
|
||||
# backup_completed = models.BooleanField(default=False)
|
||||
# maintenance_executed = models.BooleanField(default=False)
|
||||
# testing_completed = models.BooleanField(default=False)
|
||||
# system_restored = models.BooleanField(default=False)
|
||||
# maintenance_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'System Maintenance Process'
|
||||
# verbose_name_plural = 'System Maintenance Processes'
|
||||
#
|
||||
#
|
||||
# class SystemMaintenanceFlow(Flow):
|
||||
# """
|
||||
# System Maintenance Workflow
|
||||
#
|
||||
# This flow manages scheduled system maintenance including
|
||||
# notifications, backups, execution, and restoration.
|
||||
# """
|
||||
#
|
||||
# process_class = SystemMaintenanceProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_system_maintenance)
|
||||
# .Next(this.schedule_maintenance)
|
||||
# )
|
||||
#
|
||||
# schedule_maintenance = (
|
||||
# flow_view(SystemMaintenanceView)
|
||||
# .Permission('core.can_schedule_maintenance')
|
||||
# .Next(this.send_notifications)
|
||||
# )
|
||||
#
|
||||
# send_notifications = (
|
||||
# flow_func(this.notify_maintenance_window)
|
||||
# .Next(this.create_backup)
|
||||
# )
|
||||
#
|
||||
# create_backup = (
|
||||
# flow_view(BackupView)
|
||||
# .Permission('core.can_create_backups')
|
||||
# .Next(this.execute_maintenance)
|
||||
# )
|
||||
#
|
||||
# execute_maintenance = (
|
||||
# flow_func(this.perform_maintenance_tasks)
|
||||
# .Next(this.test_system)
|
||||
# )
|
||||
#
|
||||
# test_system = (
|
||||
# flow_func(this.perform_post_maintenance_testing)
|
||||
# .Next(this.restore_system)
|
||||
# )
|
||||
#
|
||||
# restore_system = (
|
||||
# flow_func(this.restore_system_services)
|
||||
# .Next(this.complete_maintenance)
|
||||
# )
|
||||
#
|
||||
# complete_maintenance = (
|
||||
# flow_func(this.finalize_system_maintenance)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_system_maintenance)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_system_maintenance(self, activation):
|
||||
# """Initialize the system maintenance process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Send maintenance start notification
|
||||
# self.notify_maintenance_start(process.maintenance_type)
|
||||
#
|
||||
# # Create maintenance checklist
|
||||
# self.create_maintenance_checklist(process.maintenance_type)
|
||||
#
|
||||
# def notify_maintenance_window(self, activation):
|
||||
# """Send maintenance window notifications"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Send notifications to all users
|
||||
# self.send_maintenance_notifications(process.maintenance_type)
|
||||
#
|
||||
# # Mark notifications sent
|
||||
# process.notifications_sent = True
|
||||
# process.save()
|
||||
#
|
||||
# # Create system notification
|
||||
# self.create_system_notification(process.maintenance_type)
|
||||
#
|
||||
# def perform_maintenance_tasks(self, activation):
|
||||
# """Perform maintenance tasks"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Execute maintenance tasks
|
||||
# self.execute_maintenance_procedures(process.maintenance_type)
|
||||
#
|
||||
# # Mark maintenance executed
|
||||
# process.maintenance_executed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Log maintenance activities
|
||||
# self.log_maintenance_activities(process.maintenance_type)
|
||||
#
|
||||
# def perform_post_maintenance_testing(self, activation):
|
||||
# """Perform post-maintenance testing"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Execute post-maintenance tests
|
||||
# test_results = self.execute_post_maintenance_tests()
|
||||
#
|
||||
# # Mark testing completed
|
||||
# process.testing_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store test results
|
||||
# self.store_maintenance_test_results(test_results)
|
||||
#
|
||||
# def restore_system_services(self, activation):
|
||||
# """Restore system services"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Restore all system services
|
||||
# self.restore_services()
|
||||
#
|
||||
# # Mark system restored
|
||||
# process.system_restored = True
|
||||
# process.save()
|
||||
#
|
||||
# # Verify service restoration
|
||||
# self.verify_service_restoration()
|
||||
#
|
||||
# def finalize_system_maintenance(self, activation):
|
||||
# """Finalize the system maintenance process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark maintenance completed
|
||||
# process.maintenance_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_maintenance_completion(process.maintenance_type)
|
||||
#
|
||||
# # Generate maintenance report
|
||||
# self.generate_maintenance_report(process.maintenance_type)
|
||||
#
|
||||
# def end_system_maintenance(self, activation):
|
||||
# """End the system maintenance workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Archive maintenance records
|
||||
# self.archive_maintenance_records(process.maintenance_type)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_maintenance_start(self, maintenance_type):
|
||||
# """Notify maintenance start"""
|
||||
# admin_team = User.objects.filter(groups__name='System Administrators')
|
||||
# for admin in admin_team:
|
||||
# send_mail(
|
||||
# subject=f'System Maintenance Started: {maintenance_type}',
|
||||
# message=f'System maintenance process started for {maintenance_type}.',
|
||||
# from_email='admin@hospital.com',
|
||||
# recipient_list=[admin.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_maintenance_checklist(self, maintenance_type):
|
||||
# """Create maintenance checklist"""
|
||||
# # This would create maintenance checklist
|
||||
# pass
|
||||
#
|
||||
# def send_maintenance_notifications(self, maintenance_type):
|
||||
# """Send maintenance notifications to all users"""
|
||||
# # This would send notifications to all users
|
||||
# pass
|
||||
#
|
||||
# def create_system_notification(self, maintenance_type):
|
||||
# """Create system-wide notification"""
|
||||
# SystemNotification.objects.create(
|
||||
# title='Scheduled System Maintenance',
|
||||
# message=f'System maintenance is scheduled for {maintenance_type}.',
|
||||
# notification_type='MAINTENANCE',
|
||||
# priority='HIGH',
|
||||
# target_audience='ALL_USERS',
|
||||
# is_active=True
|
||||
# )
|
||||
#
|
||||
# def execute_maintenance_procedures(self, maintenance_type):
|
||||
# """Execute maintenance procedures"""
|
||||
# # This would execute maintenance procedures
|
||||
# pass
|
||||
#
|
||||
# def log_maintenance_activities(self, maintenance_type):
|
||||
# """Log maintenance activities"""
|
||||
# # This would log all maintenance activities
|
||||
# pass
|
||||
#
|
||||
# def execute_post_maintenance_tests(self):
|
||||
# """Execute post-maintenance tests"""
|
||||
# # This would run post-maintenance tests
|
||||
# return {'status': 'passed', 'issues': []}
|
||||
#
|
||||
# def store_maintenance_test_results(self, results):
|
||||
# """Store maintenance test results"""
|
||||
# # This would store test results
|
||||
# pass
|
||||
#
|
||||
# def restore_services(self):
|
||||
# """Restore all system services"""
|
||||
# # This would restore system services
|
||||
# pass
|
||||
#
|
||||
# def verify_service_restoration(self):
|
||||
# """Verify service restoration"""
|
||||
# # This would verify all services are restored
|
||||
# pass
|
||||
#
|
||||
# def notify_maintenance_completion(self, maintenance_type):
|
||||
# """Notify maintenance completion"""
|
||||
# all_users = User.objects.filter(is_active=True)
|
||||
# for user in all_users:
|
||||
# if user.email:
|
||||
# send_mail(
|
||||
# subject='System Maintenance Complete',
|
||||
# message=f'System maintenance for {maintenance_type} has been completed.',
|
||||
# from_email='admin@hospital.com',
|
||||
# recipient_list=[user.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_maintenance_report(self, maintenance_type):
|
||||
# """Generate maintenance report"""
|
||||
# # This would generate comprehensive maintenance report
|
||||
# pass
|
||||
#
|
||||
# def archive_maintenance_records(self, maintenance_type):
|
||||
# """Archive maintenance records"""
|
||||
# # This would archive maintenance records
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class AuditManagementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for audit management
|
||||
# """
|
||||
# audit_type = models.CharField(max_length=50, help_text='Type of audit')
|
||||
#
|
||||
# # Process status tracking
|
||||
# audit_initiated = models.BooleanField(default=False)
|
||||
# scope_defined = models.BooleanField(default=False)
|
||||
# data_collected = models.BooleanField(default=False)
|
||||
# analysis_completed = models.BooleanField(default=False)
|
||||
# findings_documented = models.BooleanField(default=False)
|
||||
# report_generated = models.BooleanField(default=False)
|
||||
# audit_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Audit Management Process'
|
||||
# verbose_name_plural = 'Audit Management Processes'
|
||||
#
|
||||
#
|
||||
# class AuditManagementFlow(Flow):
|
||||
# """
|
||||
# Audit Management Workflow
|
||||
#
|
||||
# This flow manages system audits including security,
|
||||
# compliance, and operational audits.
|
||||
# """
|
||||
#
|
||||
# process_class = AuditManagementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_audit_management)
|
||||
# .Next(this.initiate_audit)
|
||||
# )
|
||||
#
|
||||
# initiate_audit = (
|
||||
# flow_func(this.setup_audit_scope)
|
||||
# .Next(this.define_scope)
|
||||
# )
|
||||
#
|
||||
# define_scope = (
|
||||
# flow_func(this.define_audit_scope)
|
||||
# .Next(this.collect_data)
|
||||
# )
|
||||
#
|
||||
# collect_data = (
|
||||
# flow_func(this.gather_audit_data)
|
||||
# .Next(this.analyze_data)
|
||||
# )
|
||||
#
|
||||
# analyze_data = (
|
||||
# flow_view(AuditReviewView)
|
||||
# .Permission('core.can_review_audits')
|
||||
# .Next(this.document_findings)
|
||||
# )
|
||||
#
|
||||
# document_findings = (
|
||||
# flow_func(this.document_audit_findings)
|
||||
# .Next(this.generate_report)
|
||||
# )
|
||||
#
|
||||
# generate_report = (
|
||||
# flow_func(this.create_audit_report)
|
||||
# .Next(this.complete_audit)
|
||||
# )
|
||||
#
|
||||
# complete_audit = (
|
||||
# flow_func(this.finalize_audit_management)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_audit_management)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_audit_management(self, activation):
|
||||
# """Initialize the audit management process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Send audit start notification
|
||||
# self.notify_audit_start(process.audit_type)
|
||||
#
|
||||
# # Create audit checklist
|
||||
# self.create_audit_checklist(process.audit_type)
|
||||
#
|
||||
# def setup_audit_scope(self, activation):
|
||||
# """Setup audit scope"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark audit initiated
|
||||
# process.audit_initiated = True
|
||||
# process.save()
|
||||
#
|
||||
# # Configure audit parameters
|
||||
# self.configure_audit_parameters(process.audit_type)
|
||||
#
|
||||
# def define_audit_scope(self, activation):
|
||||
# """Define audit scope and criteria"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Define audit scope
|
||||
# self.establish_audit_scope(process.audit_type)
|
||||
#
|
||||
# # Mark scope defined
|
||||
# process.scope_defined = True
|
||||
# process.save()
|
||||
#
|
||||
# def gather_audit_data(self, activation):
|
||||
# """Gather audit data"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Collect audit data
|
||||
# audit_data = self.collect_audit_data(process.audit_type)
|
||||
#
|
||||
# # Mark data collected
|
||||
# process.data_collected = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store audit data
|
||||
# self.store_audit_data(process.audit_type, audit_data)
|
||||
#
|
||||
# def document_audit_findings(self, activation):
|
||||
# """Document audit findings"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Document findings
|
||||
# findings = self.create_audit_findings(process.audit_type)
|
||||
#
|
||||
# # Mark findings documented
|
||||
# process.findings_documented = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store findings
|
||||
# self.store_audit_findings(process.audit_type, findings)
|
||||
#
|
||||
# def create_audit_report(self, activation):
|
||||
# """Create audit report"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate audit report
|
||||
# report = self.generate_audit_report(process.audit_type)
|
||||
#
|
||||
# # Mark report generated
|
||||
# process.report_generated = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store report
|
||||
# self.store_audit_report(process.audit_type, report)
|
||||
#
|
||||
# def finalize_audit_management(self, activation):
|
||||
# """Finalize the audit management process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark audit completed
|
||||
# process.audit_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_audit_completion(process.audit_type)
|
||||
#
|
||||
# # Schedule follow-up actions
|
||||
# self.schedule_audit_followup(process.audit_type)
|
||||
#
|
||||
# def end_audit_management(self, activation):
|
||||
# """End the audit management workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Archive audit records
|
||||
# self.archive_audit_records(process.audit_type)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_audit_start(self, audit_type):
|
||||
# """Notify audit start"""
|
||||
# audit_team = User.objects.filter(groups__name='Audit Team')
|
||||
# for auditor in audit_team:
|
||||
# send_mail(
|
||||
# subject=f'Audit Started: {audit_type}',
|
||||
# message=f'Audit process started for {audit_type}.',
|
||||
# from_email='audit@hospital.com',
|
||||
# recipient_list=[auditor.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_audit_checklist(self, audit_type):
|
||||
# """Create audit checklist"""
|
||||
# # This would create audit checklist
|
||||
# pass
|
||||
#
|
||||
# def configure_audit_parameters(self, audit_type):
|
||||
# """Configure audit parameters"""
|
||||
# # This would configure audit parameters
|
||||
# pass
|
||||
#
|
||||
# def establish_audit_scope(self, audit_type):
|
||||
# """Establish audit scope"""
|
||||
# # This would define audit scope
|
||||
# pass
|
||||
#
|
||||
# def collect_audit_data(self, audit_type):
|
||||
# """Collect audit data"""
|
||||
# # This would collect audit data
|
||||
# return {'status': 'collected', 'records': 1000}
|
||||
#
|
||||
# def store_audit_data(self, audit_type, data):
|
||||
# """Store audit data"""
|
||||
# # This would store audit data
|
||||
# pass
|
||||
#
|
||||
# def create_audit_findings(self, audit_type):
|
||||
# """Create audit findings"""
|
||||
# # This would create audit findings
|
||||
# return {'findings': [], 'recommendations': []}
|
||||
#
|
||||
# def store_audit_findings(self, audit_type, findings):
|
||||
# """Store audit findings"""
|
||||
# # This would store findings
|
||||
# pass
|
||||
#
|
||||
# def generate_audit_report(self, audit_type):
|
||||
# """Generate audit report"""
|
||||
# # This would generate comprehensive audit report
|
||||
# return {'report_path': '/audits/report.pdf'}
|
||||
#
|
||||
# def store_audit_report(self, audit_type, report):
|
||||
# """Store audit report"""
|
||||
# # This would store audit report
|
||||
# pass
|
||||
#
|
||||
# def notify_audit_completion(self, audit_type):
|
||||
# """Notify audit completion"""
|
||||
# # This would notify relevant parties
|
||||
# pass
|
||||
#
|
||||
# def schedule_audit_followup(self, audit_type):
|
||||
# """Schedule audit follow-up"""
|
||||
# # Schedule follow-up task
|
||||
# audit_followup.apply_async(
|
||||
# args=[audit_type],
|
||||
# countdown=86400 * 30 # 30 days
|
||||
# )
|
||||
#
|
||||
# def archive_audit_records(self, audit_type):
|
||||
# """Archive audit records"""
|
||||
# # This would archive audit records
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def tenant_followup(tenant_id):
|
||||
# """Background task for tenant follow-up"""
|
||||
# try:
|
||||
# tenant = Tenant.objects.get(tenant_id=tenant_id)
|
||||
#
|
||||
# # Perform follow-up activities
|
||||
# # This would perform post-onboarding follow-up
|
||||
#
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def system_health_check():
|
||||
# """Background task for system health monitoring"""
|
||||
# try:
|
||||
# # This would perform system health checks
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def audit_followup(audit_type):
|
||||
# """Background task for audit follow-up"""
|
||||
# try:
|
||||
# # This would perform audit follow-up activities
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def cleanup_audit_logs():
|
||||
# """Background task to cleanup old audit logs"""
|
||||
# try:
|
||||
# # This would cleanup old audit logs
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def generate_system_reports():
|
||||
# """Background task to generate system reports"""
|
||||
# try:
|
||||
# # This would generate periodic system reports
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
@ -90,7 +90,7 @@ class Tenant(models.Model):
|
||||
)
|
||||
country = models.CharField(
|
||||
max_length=100,
|
||||
default='United States',
|
||||
default='Saudi Arabia',
|
||||
help_text='Country'
|
||||
)
|
||||
|
||||
@ -150,7 +150,7 @@ class Tenant(models.Model):
|
||||
)
|
||||
currency = models.CharField(
|
||||
max_length=3,
|
||||
default='USD',
|
||||
default='SAR',
|
||||
help_text='Organization currency code'
|
||||
)
|
||||
|
||||
@ -854,7 +854,6 @@ class IntegrationLog(models.Model):
|
||||
|
||||
|
||||
|
||||
|
||||
class Department(models.Model):
|
||||
"""
|
||||
Hospital department model for organizational structure.
|
||||
@ -890,10 +889,10 @@ class Department(models.Model):
|
||||
tenant = models.ForeignKey(
|
||||
Tenant,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='departments',
|
||||
related_name='core_departments',
|
||||
help_text='Organization tenant'
|
||||
)
|
||||
|
||||
|
||||
# Department Information
|
||||
department_id = models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
@ -914,14 +913,14 @@ class Department(models.Model):
|
||||
null=True,
|
||||
help_text='Department description'
|
||||
)
|
||||
|
||||
|
||||
# Department Classification
|
||||
department_type = models.CharField(
|
||||
max_length=30,
|
||||
choices=DEPARTMENT_TYPE_CHOICES,
|
||||
help_text='Type of department'
|
||||
)
|
||||
|
||||
|
||||
# Organizational Structure
|
||||
parent_department = models.ForeignKey(
|
||||
'self',
|
||||
@ -931,7 +930,7 @@ class Department(models.Model):
|
||||
related_name='sub_departments',
|
||||
help_text='Parent department (for hierarchical structure)'
|
||||
)
|
||||
|
||||
|
||||
# Management
|
||||
department_head = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
@ -941,7 +940,7 @@ class Department(models.Model):
|
||||
related_name='headed_departments',
|
||||
help_text='Department head/manager'
|
||||
)
|
||||
|
||||
|
||||
# Contact Information
|
||||
phone = models.CharField(
|
||||
max_length=20,
|
||||
@ -960,7 +959,7 @@ class Department(models.Model):
|
||||
null=True,
|
||||
help_text='Department email'
|
||||
)
|
||||
|
||||
|
||||
# Location
|
||||
building = models.CharField(
|
||||
max_length=50,
|
||||
@ -986,7 +985,7 @@ class Department(models.Model):
|
||||
null=True,
|
||||
help_text='Room numbers (e.g., 101-110, 201A-205C)'
|
||||
)
|
||||
|
||||
|
||||
# Operational Information
|
||||
is_active = models.BooleanField(
|
||||
default=True,
|
||||
@ -1001,7 +1000,7 @@ class Department(models.Model):
|
||||
blank=True,
|
||||
help_text='Operating hours by day of week'
|
||||
)
|
||||
|
||||
|
||||
# Budget and Cost Center
|
||||
cost_center_code = models.CharField(
|
||||
max_length=20,
|
||||
@ -1015,7 +1014,7 @@ class Department(models.Model):
|
||||
null=True,
|
||||
help_text='Budget code'
|
||||
)
|
||||
|
||||
|
||||
# Staffing
|
||||
authorized_positions = models.PositiveIntegerField(
|
||||
default=0,
|
||||
@ -1025,7 +1024,7 @@ class Department(models.Model):
|
||||
default=0,
|
||||
help_text='Current number of staff members'
|
||||
)
|
||||
|
||||
|
||||
# Quality and Compliance
|
||||
accreditation_required = models.BooleanField(
|
||||
default=False,
|
||||
@ -1047,7 +1046,7 @@ class Department(models.Model):
|
||||
null=True,
|
||||
help_text='Next scheduled inspection date'
|
||||
)
|
||||
|
||||
|
||||
# Metadata
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
@ -1059,7 +1058,7 @@ class Department(models.Model):
|
||||
related_name='created_departments',
|
||||
help_text='User who created the department'
|
||||
)
|
||||
|
||||
|
||||
class Meta:
|
||||
db_table = 'core_department'
|
||||
verbose_name = 'Department'
|
||||
@ -1072,24 +1071,24 @@ class Department(models.Model):
|
||||
models.Index(fields=['parent_department']),
|
||||
]
|
||||
unique_together = ['tenant', 'code']
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.code})"
|
||||
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
"""Return full department name with parent if applicable"""
|
||||
if self.parent_department:
|
||||
return f"{self.parent_department.name} - {self.name}"
|
||||
return self.name
|
||||
|
||||
|
||||
@property
|
||||
def staffing_percentage(self):
|
||||
"""Calculate current staffing percentage"""
|
||||
if self.authorized_positions > 0:
|
||||
return (self.current_staff_count / self.authorized_positions) * 100
|
||||
return 0
|
||||
|
||||
|
||||
def get_all_sub_departments(self):
|
||||
"""Get all sub-departments recursively"""
|
||||
sub_departments = []
|
||||
|
||||
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
BIN
emr/__pycache__/flows.cpython-312.pyc
Normal file
BIN
emr/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
929
emr/flows.py
Normal file
929
emr/flows.py
Normal file
@ -0,0 +1,929 @@
|
||||
# """
|
||||
# Viewflow workflows for EMR app.
|
||||
# Provides electronic medical record workflows for clinical documentation, care planning, and patient management.
|
||||
# """
|
||||
#
|
||||
# from viewflow import Flow, lock
|
||||
# from viewflow.base import this, flow_func
|
||||
# from viewflow.contrib import celery
|
||||
# from viewflow.decorators import flow_view
|
||||
# from viewflow.fields import CharField, ModelField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.models import Process, Task
|
||||
# from django.contrib.auth.models import User
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import Encounter, ClinicalNote, ProblemList, CarePlan, NoteTemplate
|
||||
# from .views import (
|
||||
# EncounterInitiationView, PatientAssessmentView, ClinicalDocumentationView,
|
||||
# ProblemIdentificationView, CarePlanDevelopmentView, TreatmentPlanningView,
|
||||
# ProgressMonitoringView, DischargePreparationView, QualityReviewView,
|
||||
# NoteCreationView, NoteReviewView, NoteSigningView
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class ClinicalEncounterProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for clinical encounters
|
||||
# """
|
||||
# encounter = ModelField(Encounter, help_text='Associated clinical encounter')
|
||||
#
|
||||
# # Process status tracking
|
||||
# encounter_initiated = models.BooleanField(default=False)
|
||||
# patient_assessed = models.BooleanField(default=False)
|
||||
# problems_identified = models.BooleanField(default=False)
|
||||
# care_plan_developed = models.BooleanField(default=False)
|
||||
# treatment_planned = models.BooleanField(default=False)
|
||||
# documentation_completed = models.BooleanField(default=False)
|
||||
# quality_reviewed = models.BooleanField(default=False)
|
||||
# encounter_finalized = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Clinical Encounter Process'
|
||||
# verbose_name_plural = 'Clinical Encounter Processes'
|
||||
#
|
||||
#
|
||||
# class ClinicalEncounterFlow(Flow):
|
||||
# """
|
||||
# Clinical Encounter Workflow
|
||||
#
|
||||
# This flow manages the complete clinical encounter process from
|
||||
# patient assessment through documentation and quality review.
|
||||
# """
|
||||
#
|
||||
# process_class = ClinicalEncounterProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_encounter)
|
||||
# .Next(this.initiate_encounter)
|
||||
# )
|
||||
#
|
||||
# initiate_encounter = (
|
||||
# flow_view(EncounterInitiationView)
|
||||
# .Permission('emr.can_initiate_encounters')
|
||||
# .Next(this.assess_patient)
|
||||
# )
|
||||
#
|
||||
# assess_patient = (
|
||||
# flow_view(PatientAssessmentView)
|
||||
# .Permission('emr.can_assess_patients')
|
||||
# .Next(this.parallel_clinical_work)
|
||||
# )
|
||||
#
|
||||
# parallel_clinical_work = (
|
||||
# flow_func(this.start_parallel_clinical_work)
|
||||
# .Next(this.identify_problems)
|
||||
# .Next(this.develop_care_plan)
|
||||
# .Next(this.plan_treatment)
|
||||
# )
|
||||
#
|
||||
# identify_problems = (
|
||||
# flow_view(ProblemIdentificationView)
|
||||
# .Permission('emr.can_identify_problems')
|
||||
# .Next(this.join_clinical_work)
|
||||
# )
|
||||
#
|
||||
# develop_care_plan = (
|
||||
# flow_view(CarePlanDevelopmentView)
|
||||
# .Permission('emr.can_develop_care_plans')
|
||||
# .Next(this.join_clinical_work)
|
||||
# )
|
||||
#
|
||||
# plan_treatment = (
|
||||
# flow_view(TreatmentPlanningView)
|
||||
# .Permission('emr.can_plan_treatment')
|
||||
# .Next(this.join_clinical_work)
|
||||
# )
|
||||
#
|
||||
# join_clinical_work = (
|
||||
# flow_func(this.join_parallel_clinical_work)
|
||||
# .Next(this.complete_documentation)
|
||||
# )
|
||||
#
|
||||
# complete_documentation = (
|
||||
# flow_view(ClinicalDocumentationView)
|
||||
# .Permission('emr.can_complete_documentation')
|
||||
# .Next(this.review_quality)
|
||||
# )
|
||||
#
|
||||
# review_quality = (
|
||||
# flow_view(QualityReviewView)
|
||||
# .Permission('emr.can_review_quality')
|
||||
# .Next(this.finalize_encounter)
|
||||
# )
|
||||
#
|
||||
# finalize_encounter = (
|
||||
# flow_func(this.complete_encounter)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_encounter)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_encounter(self, activation):
|
||||
# """Initialize the clinical encounter process"""
|
||||
# process = activation.process
|
||||
# encounter = process.encounter
|
||||
#
|
||||
# # Update encounter status
|
||||
# encounter.status = 'IN_PROGRESS'
|
||||
# encounter.save()
|
||||
#
|
||||
# # Send notification to clinical staff
|
||||
# self.notify_clinical_staff(encounter)
|
||||
#
|
||||
# # Check for high-priority encounters
|
||||
# if encounter.encounter_type in ['EMERGENCY', 'URGENT_CARE']:
|
||||
# self.notify_priority_encounter(encounter)
|
||||
#
|
||||
# def start_parallel_clinical_work(self, activation):
|
||||
# """Start parallel clinical work tasks"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Create parallel clinical tasks
|
||||
# self.create_clinical_tasks(process.encounter)
|
||||
#
|
||||
# def join_parallel_clinical_work(self, activation):
|
||||
# """Wait for all clinical work to complete"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Check if all clinical work is completed
|
||||
# if (process.problems_identified and
|
||||
# process.care_plan_developed and
|
||||
# process.treatment_planned):
|
||||
#
|
||||
# # Proceed to documentation
|
||||
# self.notify_documentation_ready(process.encounter)
|
||||
#
|
||||
# def complete_encounter(self, activation):
|
||||
# """Finalize the clinical encounter process"""
|
||||
# process = activation.process
|
||||
# encounter = process.encounter
|
||||
#
|
||||
# # Update encounter status
|
||||
# encounter.status = 'COMPLETED'
|
||||
# encounter.end_datetime = timezone.now()
|
||||
# encounter.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.encounter_finalized = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_encounter_completion(encounter)
|
||||
#
|
||||
# # Update clinical metrics
|
||||
# self.update_clinical_metrics(encounter)
|
||||
#
|
||||
# # Schedule follow-up if needed
|
||||
# self.schedule_follow_up(encounter)
|
||||
#
|
||||
# def end_encounter(self, activation):
|
||||
# """End the clinical encounter workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate encounter summary report
|
||||
# self.generate_encounter_summary(process.encounter)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_clinical_staff(self, encounter):
|
||||
# """Notify clinical staff of new encounter"""
|
||||
# from django.contrib.auth.models import Group
|
||||
#
|
||||
# clinical_staff = User.objects.filter(
|
||||
# groups__name='Clinical Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in clinical_staff:
|
||||
# send_mail(
|
||||
# subject=f'New Clinical Encounter: {encounter.patient.get_full_name()}',
|
||||
# message=f'New {encounter.get_encounter_type_display()} encounter started.',
|
||||
# from_email='clinical@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_priority_encounter(self, encounter):
|
||||
# """Notify of priority encounter"""
|
||||
# supervisors = User.objects.filter(
|
||||
# groups__name='Clinical Supervisors'
|
||||
# )
|
||||
#
|
||||
# for supervisor in supervisors:
|
||||
# send_mail(
|
||||
# subject=f'PRIORITY Encounter: {encounter.patient.get_full_name()}',
|
||||
# message=f'{encounter.get_encounter_type_display()} encounter requires immediate attention.',
|
||||
# from_email='clinical@hospital.com',
|
||||
# recipient_list=[supervisor.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_clinical_tasks(self, encounter):
|
||||
# """Create clinical work tasks"""
|
||||
# # This would create tasks in a task management system
|
||||
# pass
|
||||
#
|
||||
# def notify_documentation_ready(self, encounter):
|
||||
# """Notify that documentation is ready"""
|
||||
# if encounter.provider and encounter.provider.email:
|
||||
# send_mail(
|
||||
# subject=f'Documentation Ready: {encounter.patient.get_full_name()}',
|
||||
# message=f'Clinical work completed, ready for documentation.',
|
||||
# from_email='clinical@hospital.com',
|
||||
# recipient_list=[encounter.provider.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_encounter_completion(self, encounter):
|
||||
# """Notify encounter completion"""
|
||||
# # Notify care team
|
||||
# care_team = encounter.care_team.all()
|
||||
# for member in care_team:
|
||||
# if member.email:
|
||||
# send_mail(
|
||||
# subject=f'Encounter Completed: {encounter.patient.get_full_name()}',
|
||||
# message=f'Clinical encounter has been completed and documented.',
|
||||
# from_email='clinical@hospital.com',
|
||||
# recipient_list=[member.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def update_clinical_metrics(self, encounter):
|
||||
# """Update clinical quality metrics"""
|
||||
# # This would update clinical performance metrics
|
||||
# pass
|
||||
#
|
||||
# def schedule_follow_up(self, encounter):
|
||||
# """Schedule follow-up appointments if needed"""
|
||||
# # This would schedule follow-up appointments
|
||||
# pass
|
||||
#
|
||||
# def generate_encounter_summary(self, encounter):
|
||||
# """Generate encounter summary report"""
|
||||
# # This would generate a comprehensive encounter report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class ClinicalDocumentationProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for clinical documentation
|
||||
# """
|
||||
# clinical_note = ModelField(ClinicalNote, help_text='Associated clinical note')
|
||||
#
|
||||
# # Process status tracking
|
||||
# note_initiated = models.BooleanField(default=False)
|
||||
# content_drafted = models.BooleanField(default=False)
|
||||
# clinical_review_completed = models.BooleanField(default=False)
|
||||
# quality_check_completed = models.BooleanField(default=False)
|
||||
# note_signed = models.BooleanField(default=False)
|
||||
# note_finalized = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Clinical Documentation Process'
|
||||
# verbose_name_plural = 'Clinical Documentation Processes'
|
||||
#
|
||||
#
|
||||
# class ClinicalDocumentationFlow(Flow):
|
||||
# """
|
||||
# Clinical Documentation Workflow
|
||||
#
|
||||
# This flow manages clinical note creation, review, and finalization
|
||||
# with quality checks and electronic signatures.
|
||||
# """
|
||||
#
|
||||
# process_class = ClinicalDocumentationProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_documentation)
|
||||
# .Next(this.create_note)
|
||||
# )
|
||||
#
|
||||
# create_note = (
|
||||
# flow_view(NoteCreationView)
|
||||
# .Permission('emr.can_create_notes')
|
||||
# .Next(this.draft_content)
|
||||
# )
|
||||
#
|
||||
# draft_content = (
|
||||
# flow_view(NoteDraftingView)
|
||||
# .Permission('emr.can_draft_notes')
|
||||
# .Next(this.review_note)
|
||||
# )
|
||||
#
|
||||
# review_note = (
|
||||
# flow_view(NoteReviewView)
|
||||
# .Permission('emr.can_review_notes')
|
||||
# .Next(this.quality_check)
|
||||
# )
|
||||
#
|
||||
# quality_check = (
|
||||
# flow_view(NoteQualityCheckView)
|
||||
# .Permission('emr.can_quality_check_notes')
|
||||
# .Next(this.sign_note)
|
||||
# )
|
||||
#
|
||||
# sign_note = (
|
||||
# flow_view(NoteSigningView)
|
||||
# .Permission('emr.can_sign_notes')
|
||||
# .Next(this.finalize_note)
|
||||
# )
|
||||
#
|
||||
# finalize_note = (
|
||||
# flow_func(this.complete_documentation)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_documentation)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_documentation(self, activation):
|
||||
# """Initialize the documentation process"""
|
||||
# process = activation.process
|
||||
# note = process.clinical_note
|
||||
#
|
||||
# # Update note status
|
||||
# note.status = 'DRAFT'
|
||||
# note.save()
|
||||
#
|
||||
# # Send notification to documenting provider
|
||||
# self.notify_documentation_required(note)
|
||||
#
|
||||
# def complete_documentation(self, activation):
|
||||
# """Finalize the documentation process"""
|
||||
# process = activation.process
|
||||
# note = process.clinical_note
|
||||
#
|
||||
# # Update note status
|
||||
# note.status = 'FINAL'
|
||||
# note.finalized_datetime = timezone.now()
|
||||
# note.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.note_finalized = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_documentation_completion(note)
|
||||
#
|
||||
# # Update documentation metrics
|
||||
# self.update_documentation_metrics(note)
|
||||
#
|
||||
# def end_documentation(self, activation):
|
||||
# """End the documentation workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate documentation summary
|
||||
# self.generate_documentation_summary(process.clinical_note)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_documentation_required(self, note):
|
||||
# """Notify provider that documentation is required"""
|
||||
# if note.author and note.author.email:
|
||||
# send_mail(
|
||||
# subject=f'Documentation Required: {note.patient.get_full_name()}',
|
||||
# message=f'{note.get_note_type_display()} requires completion.',
|
||||
# from_email='documentation@hospital.com',
|
||||
# recipient_list=[note.author.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_documentation_completion(self, note):
|
||||
# """Notify documentation completion"""
|
||||
# # Notify care team
|
||||
# if note.encounter:
|
||||
# care_team = note.encounter.care_team.all()
|
||||
# for member in care_team:
|
||||
# if member.email:
|
||||
# send_mail(
|
||||
# subject=f'Documentation Complete: {note.patient.get_full_name()}',
|
||||
# message=f'{note.get_note_type_display()} has been completed and signed.',
|
||||
# from_email='documentation@hospital.com',
|
||||
# recipient_list=[member.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def update_documentation_metrics(self, note):
|
||||
# """Update documentation quality metrics"""
|
||||
# # This would update documentation performance metrics
|
||||
# pass
|
||||
#
|
||||
# def generate_documentation_summary(self, note):
|
||||
# """Generate documentation summary"""
|
||||
# # This would generate documentation summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class CarePlanManagementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for care plan management
|
||||
# """
|
||||
# care_plan = ModelField(CarePlan, help_text='Associated care plan')
|
||||
#
|
||||
# # Process status tracking
|
||||
# plan_initiated = models.BooleanField(default=False)
|
||||
# assessment_completed = models.BooleanField(default=False)
|
||||
# goals_established = models.BooleanField(default=False)
|
||||
# interventions_planned = models.BooleanField(default=False)
|
||||
# team_assigned = models.BooleanField(default=False)
|
||||
# plan_approved = models.BooleanField(default=False)
|
||||
# implementation_started = models.BooleanField(default=False)
|
||||
# progress_monitored = models.BooleanField(default=False)
|
||||
# plan_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Care Plan Management Process'
|
||||
# verbose_name_plural = 'Care Plan Management Processes'
|
||||
#
|
||||
#
|
||||
# class CarePlanManagementFlow(Flow):
|
||||
# """
|
||||
# Care Plan Management Workflow
|
||||
#
|
||||
# This flow manages care plan development, approval, implementation,
|
||||
# and monitoring with multidisciplinary team coordination.
|
||||
# """
|
||||
#
|
||||
# process_class = CarePlanManagementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_care_planning)
|
||||
# .Next(this.initiate_plan)
|
||||
# )
|
||||
#
|
||||
# initiate_plan = (
|
||||
# flow_view(CarePlanInitiationView)
|
||||
# .Permission('emr.can_initiate_care_plans')
|
||||
# .Next(this.complete_assessment)
|
||||
# )
|
||||
#
|
||||
# complete_assessment = (
|
||||
# flow_view(CarePlanAssessmentView)
|
||||
# .Permission('emr.can_assess_care_plans')
|
||||
# .Next(this.establish_goals)
|
||||
# )
|
||||
#
|
||||
# establish_goals = (
|
||||
# flow_view(GoalEstablishmentView)
|
||||
# .Permission('emr.can_establish_goals')
|
||||
# .Next(this.plan_interventions)
|
||||
# )
|
||||
#
|
||||
# plan_interventions = (
|
||||
# flow_view(InterventionPlanningView)
|
||||
# .Permission('emr.can_plan_interventions')
|
||||
# .Next(this.assign_care_team)
|
||||
# )
|
||||
#
|
||||
# assign_care_team = (
|
||||
# flow_view(CareTeamAssignmentView)
|
||||
# .Permission('emr.can_assign_care_team')
|
||||
# .Next(this.approve_plan)
|
||||
# )
|
||||
#
|
||||
# approve_plan = (
|
||||
# flow_view(CarePlanApprovalView)
|
||||
# .Permission('emr.can_approve_care_plans')
|
||||
# .Next(this.implement_plan)
|
||||
# )
|
||||
#
|
||||
# implement_plan = (
|
||||
# flow_func(this.start_implementation)
|
||||
# .Next(this.monitor_progress)
|
||||
# )
|
||||
#
|
||||
# monitor_progress = (
|
||||
# flow_view(ProgressMonitoringView)
|
||||
# .Permission('emr.can_monitor_progress')
|
||||
# .Next(this.complete_plan)
|
||||
# )
|
||||
#
|
||||
# complete_plan = (
|
||||
# flow_func(this.finalize_care_plan)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_care_planning)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_care_planning(self, activation):
|
||||
# """Initialize the care planning process"""
|
||||
# process = activation.process
|
||||
# plan = process.care_plan
|
||||
#
|
||||
# # Update plan status
|
||||
# plan.status = 'DRAFT'
|
||||
# plan.save()
|
||||
#
|
||||
# # Send notification to care team
|
||||
# self.notify_care_planning_start(plan)
|
||||
#
|
||||
# def start_implementation(self, activation):
|
||||
# """Start care plan implementation"""
|
||||
# process = activation.process
|
||||
# plan = process.care_plan
|
||||
#
|
||||
# # Update plan status
|
||||
# plan.status = 'ACTIVE'
|
||||
# plan.save()
|
||||
#
|
||||
# # Mark implementation started
|
||||
# process.implementation_started = True
|
||||
# process.save()
|
||||
#
|
||||
# # Notify care team of implementation
|
||||
# self.notify_implementation_start(plan)
|
||||
#
|
||||
# # Schedule monitoring activities
|
||||
# self.schedule_monitoring(plan)
|
||||
#
|
||||
# def finalize_care_plan(self, activation):
|
||||
# """Finalize the care plan process"""
|
||||
# process = activation.process
|
||||
# plan = process.care_plan
|
||||
#
|
||||
# # Update plan status based on completion
|
||||
# if plan.completion_percentage >= 100:
|
||||
# plan.status = 'COMPLETED'
|
||||
# else:
|
||||
# plan.status = 'ON_HOLD'
|
||||
#
|
||||
# plan.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.plan_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_care_plan_completion(plan)
|
||||
#
|
||||
# # Generate outcomes report
|
||||
# self.generate_outcomes_report(plan)
|
||||
#
|
||||
# def end_care_planning(self, activation):
|
||||
# """End the care planning workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate care plan summary
|
||||
# self.generate_care_plan_summary(process.care_plan)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_care_planning_start(self, plan):
|
||||
# """Notify care team of care planning start"""
|
||||
# care_team = plan.care_team.all()
|
||||
# for member in care_team:
|
||||
# if member.email:
|
||||
# send_mail(
|
||||
# subject=f'Care Plan Development: {plan.patient.get_full_name()}',
|
||||
# message=f'Care plan development has started for {plan.title}.',
|
||||
# from_email='careplanning@hospital.com',
|
||||
# recipient_list=[member.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_implementation_start(self, plan):
|
||||
# """Notify care team of implementation start"""
|
||||
# care_team = plan.care_team.all()
|
||||
# for member in care_team:
|
||||
# if member.email:
|
||||
# send_mail(
|
||||
# subject=f'Care Plan Implementation: {plan.patient.get_full_name()}',
|
||||
# message=f'Care plan implementation has started for {plan.title}.',
|
||||
# from_email='careplanning@hospital.com',
|
||||
# recipient_list=[member.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def schedule_monitoring(self, plan):
|
||||
# """Schedule monitoring activities"""
|
||||
# # This would schedule monitoring tasks
|
||||
# pass
|
||||
#
|
||||
# def notify_care_plan_completion(self, plan):
|
||||
# """Notify care plan completion"""
|
||||
# # Notify patient if email available
|
||||
# if plan.patient.email:
|
||||
# send_mail(
|
||||
# subject='Care Plan Completed',
|
||||
# message=f'Your care plan "{plan.title}" has been completed.',
|
||||
# from_email='careplanning@hospital.com',
|
||||
# recipient_list=[plan.patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_outcomes_report(self, plan):
|
||||
# """Generate care plan outcomes report"""
|
||||
# # This would generate outcomes report
|
||||
# pass
|
||||
#
|
||||
# def generate_care_plan_summary(self, plan):
|
||||
# """Generate care plan summary"""
|
||||
# # This would generate care plan summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class ProblemManagementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for problem management
|
||||
# """
|
||||
# problem = ModelField(ProblemList, help_text='Associated problem')
|
||||
#
|
||||
# # Process status tracking
|
||||
# problem_identified = models.BooleanField(default=False)
|
||||
# problem_assessed = models.BooleanField(default=False)
|
||||
# treatment_planned = models.BooleanField(default=False)
|
||||
# interventions_implemented = models.BooleanField(default=False)
|
||||
# progress_monitored = models.BooleanField(default=False)
|
||||
# problem_resolved = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Problem Management Process'
|
||||
# verbose_name_plural = 'Problem Management Processes'
|
||||
#
|
||||
#
|
||||
# class ProblemManagementFlow(Flow):
|
||||
# """
|
||||
# Problem Management Workflow
|
||||
#
|
||||
# This flow manages clinical problem identification, assessment,
|
||||
# treatment planning, and resolution tracking.
|
||||
# """
|
||||
#
|
||||
# process_class = ProblemManagementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_problem_management)
|
||||
# .Next(this.identify_problem)
|
||||
# )
|
||||
#
|
||||
# identify_problem = (
|
||||
# flow_view(ProblemIdentificationView)
|
||||
# .Permission('emr.can_identify_problems')
|
||||
# .Next(this.assess_problem)
|
||||
# )
|
||||
#
|
||||
# assess_problem = (
|
||||
# flow_view(ProblemAssessmentView)
|
||||
# .Permission('emr.can_assess_problems')
|
||||
# .Next(this.plan_treatment)
|
||||
# )
|
||||
#
|
||||
# plan_treatment = (
|
||||
# flow_view(TreatmentPlanningView)
|
||||
# .Permission('emr.can_plan_treatment')
|
||||
# .Next(this.implement_interventions)
|
||||
# )
|
||||
#
|
||||
# implement_interventions = (
|
||||
# flow_view(InterventionImplementationView)
|
||||
# .Permission('emr.can_implement_interventions')
|
||||
# .Next(this.monitor_progress)
|
||||
# )
|
||||
#
|
||||
# monitor_progress = (
|
||||
# flow_view(ProgressMonitoringView)
|
||||
# .Permission('emr.can_monitor_progress')
|
||||
# .Next(this.resolve_problem)
|
||||
# )
|
||||
#
|
||||
# resolve_problem = (
|
||||
# flow_func(this.complete_problem_management)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_problem_management)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_problem_management(self, activation):
|
||||
# """Initialize the problem management process"""
|
||||
# process = activation.process
|
||||
# problem = process.problem
|
||||
#
|
||||
# # Update problem status
|
||||
# problem.status = 'ACTIVE'
|
||||
# problem.save()
|
||||
#
|
||||
# # Send notification to care team
|
||||
# self.notify_problem_identified(problem)
|
||||
#
|
||||
# def complete_problem_management(self, activation):
|
||||
# """Finalize the problem management process"""
|
||||
# process = activation.process
|
||||
# problem = process.problem
|
||||
#
|
||||
# # Update problem status
|
||||
# if problem.resolution_date:
|
||||
# problem.status = 'RESOLVED'
|
||||
# else:
|
||||
# problem.status = 'ONGOING'
|
||||
#
|
||||
# problem.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.problem_resolved = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_problem_resolution(problem)
|
||||
#
|
||||
# def end_problem_management(self, activation):
|
||||
# """End the problem management workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate problem summary
|
||||
# self.generate_problem_summary(process.problem)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_problem_identified(self, problem):
|
||||
# """Notify care team of problem identification"""
|
||||
# # This would notify the care team
|
||||
# pass
|
||||
#
|
||||
# def notify_problem_resolution(self, problem):
|
||||
# """Notify problem resolution"""
|
||||
# # This would notify relevant parties
|
||||
# pass
|
||||
#
|
||||
# def generate_problem_summary(self, problem):
|
||||
# """Generate problem management summary"""
|
||||
# # This would generate problem summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class QualityAssuranceProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for clinical quality assurance
|
||||
# """
|
||||
# encounter_id = CharField(max_length=50, help_text='Encounter identifier')
|
||||
# review_type = CharField(max_length=20, help_text='Type of quality review')
|
||||
#
|
||||
# # Process status tracking
|
||||
# review_initiated = models.BooleanField(default=False)
|
||||
# documentation_reviewed = models.BooleanField(default=False)
|
||||
# clinical_indicators_checked = models.BooleanField(default=False)
|
||||
# compliance_verified = models.BooleanField(default=False)
|
||||
# feedback_provided = models.BooleanField(default=False)
|
||||
# quality_review_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Quality Assurance Process'
|
||||
# verbose_name_plural = 'Quality Assurance Processes'
|
||||
#
|
||||
#
|
||||
# class QualityAssuranceFlow(Flow):
|
||||
# """
|
||||
# Clinical Quality Assurance Workflow
|
||||
#
|
||||
# This flow manages clinical quality reviews including documentation
|
||||
# quality, clinical indicators, and compliance verification.
|
||||
# """
|
||||
#
|
||||
# process_class = QualityAssuranceProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_quality_review)
|
||||
# .Next(this.review_documentation)
|
||||
# )
|
||||
#
|
||||
# review_documentation = (
|
||||
# flow_view(DocumentationReviewView)
|
||||
# .Permission('emr.can_review_documentation')
|
||||
# .Next(this.check_clinical_indicators)
|
||||
# )
|
||||
#
|
||||
# check_clinical_indicators = (
|
||||
# flow_view(ClinicalIndicatorCheckView)
|
||||
# .Permission('emr.can_check_clinical_indicators')
|
||||
# .Next(this.verify_compliance)
|
||||
# )
|
||||
#
|
||||
# verify_compliance = (
|
||||
# flow_view(ComplianceVerificationView)
|
||||
# .Permission('emr.can_verify_compliance')
|
||||
# .Next(this.provide_feedback)
|
||||
# )
|
||||
#
|
||||
# provide_feedback = (
|
||||
# flow_view(QualityFeedbackView)
|
||||
# .Permission('emr.can_provide_quality_feedback')
|
||||
# .Next(this.complete_review)
|
||||
# )
|
||||
#
|
||||
# complete_review = (
|
||||
# flow_func(this.finalize_quality_review)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_quality_review)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_quality_review(self, activation):
|
||||
# """Initialize the quality review process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Notify quality staff
|
||||
# self.notify_quality_staff(process.encounter_id, process.review_type)
|
||||
#
|
||||
# def finalize_quality_review(self, activation):
|
||||
# """Finalize the quality review process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark review as completed
|
||||
# process.quality_review_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Generate quality report
|
||||
# self.generate_quality_report(process.encounter_id, process.review_type)
|
||||
#
|
||||
# def end_quality_review(self, activation):
|
||||
# """End the quality review workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Update quality metrics
|
||||
# self.update_quality_metrics(process.encounter_id, process.review_type)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_quality_staff(self, encounter_id, review_type):
|
||||
# """Notify quality staff"""
|
||||
# quality_staff = User.objects.filter(
|
||||
# groups__name='Quality Assurance'
|
||||
# )
|
||||
#
|
||||
# for staff in quality_staff:
|
||||
# send_mail(
|
||||
# subject=f'Quality Review Required: {encounter_id}',
|
||||
# message=f'{review_type} quality review required for encounter {encounter_id}.',
|
||||
# from_email='quality@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_quality_report(self, encounter_id, review_type):
|
||||
# """Generate quality report"""
|
||||
# # This would generate quality report
|
||||
# pass
|
||||
#
|
||||
# def update_quality_metrics(self, encounter_id, review_type):
|
||||
# """Update quality metrics"""
|
||||
# # This would update quality metrics
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def auto_generate_care_plans():
|
||||
# """Background task to automatically generate care plans"""
|
||||
# try:
|
||||
# # This would generate care plans based on problems
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def monitor_documentation_compliance():
|
||||
# """Background task to monitor documentation compliance"""
|
||||
# try:
|
||||
# # This would monitor documentation timeliness
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def generate_clinical_reports():
|
||||
# """Background task to generate clinical reports"""
|
||||
# try:
|
||||
# # This would generate clinical quality reports
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def auto_schedule_care_plan_reviews():
|
||||
# """Background task to schedule care plan reviews"""
|
||||
# try:
|
||||
# # This would schedule care plan reviews
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def identify_quality_indicators():
|
||||
# """Background task to identify quality indicators"""
|
||||
# try:
|
||||
# # This would identify quality improvement opportunities
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
@ -11,7 +11,7 @@ urlpatterns = [
|
||||
# Main views
|
||||
path('', views.EMRDashboardView.as_view(), name='dashboard'),
|
||||
path('encounters/', views.EncounterListView.as_view(), name='encounter_list'),
|
||||
path('encounters/<int:pk>/', views.EncounterDetailView.as_view(), name='encounter_detail'),
|
||||
path('encounters/<uuid:pk>/', views.EncounterDetailView.as_view(), name='encounter_detail'),
|
||||
# path('encounters/<int:pk>/update/', views.EncounterUpdateView.as_view(), name='encounter_update'),
|
||||
# path('encounters/<int:pk>/delete/', views.EncounterDeleteView.as_view(), name='encounter_delete'),
|
||||
path('encounters/create/', views.EncounterCreateView.as_view(), name='encounter_create'),
|
||||
|
||||
Binary file not shown.
@ -46,6 +46,8 @@ THIRD_PARTY_APPS = [
|
||||
'corsheaders',
|
||||
'django_extensions',
|
||||
'allauth',
|
||||
'viewflow',
|
||||
'viewflow.workflow',
|
||||
# 'allauth.socialaccount',
|
||||
]
|
||||
|
||||
|
||||
BIN
hr/__pycache__/flows.cpython-312.pyc
Normal file
BIN
hr/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
846
hr/flows.py
Normal file
846
hr/flows.py
Normal file
@ -0,0 +1,846 @@
|
||||
# """
|
||||
# Viewflow workflows for HR app.
|
||||
# Provides employee onboarding, performance review, and training workflows.
|
||||
# """
|
||||
#
|
||||
# from viewflow import Flow, lock
|
||||
# from viewflow.base import this, flow_func
|
||||
# from viewflow.contrib import celery
|
||||
# from viewflow.decorators import flow_view
|
||||
# from viewflow.fields import CharField, ModelField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.models import Process, Task
|
||||
# from django.contrib.auth.models import User
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import Employee, PerformanceReview, TrainingRecord
|
||||
# from .views import (
|
||||
# EmployeeOnboardingView, DocumentCollectionView, SystemAccessSetupView,
|
||||
# OrientationSchedulingView, PerformanceReviewInitiationView,
|
||||
# SelfAssessmentView, ManagerReviewView, ReviewMeetingView,
|
||||
# TrainingNeedsAssessmentView, TrainingSchedulingView, TrainingDeliveryView,
|
||||
# TrainingEvaluationView
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class EmployeeOnboardingProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for employee onboarding
|
||||
# """
|
||||
# employee = ModelField(Employee, help_text='Associated employee')
|
||||
#
|
||||
# # Process status tracking
|
||||
# employee_created = models.BooleanField(default=False)
|
||||
# documents_collected = models.BooleanField(default=False)
|
||||
# system_access_setup = models.BooleanField(default=False)
|
||||
# orientation_scheduled = models.BooleanField(default=False)
|
||||
# orientation_completed = models.BooleanField(default=False)
|
||||
# probationary_review_scheduled = models.BooleanField(default=False)
|
||||
# onboarding_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Employee Onboarding Process'
|
||||
# verbose_name_plural = 'Employee Onboarding Processes'
|
||||
#
|
||||
#
|
||||
# class EmployeeOnboardingFlow(Flow):
|
||||
# """
|
||||
# Employee Onboarding Workflow
|
||||
#
|
||||
# This flow manages the complete employee onboarding process from
|
||||
# initial setup through orientation and probationary period setup.
|
||||
# """
|
||||
#
|
||||
# process_class = EmployeeOnboardingProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_onboarding)
|
||||
# .Next(this.create_employee_record)
|
||||
# )
|
||||
#
|
||||
# create_employee_record = (
|
||||
# flow_view(EmployeeOnboardingView)
|
||||
# .Permission('hr.can_create_employees')
|
||||
# .Next(this.collect_documents)
|
||||
# )
|
||||
#
|
||||
# collect_documents = (
|
||||
# flow_view(DocumentCollectionView)
|
||||
# .Permission('hr.can_collect_documents')
|
||||
# .Next(this.setup_system_access)
|
||||
# )
|
||||
#
|
||||
# setup_system_access = (
|
||||
# flow_view(SystemAccessSetupView)
|
||||
# .Permission('hr.can_setup_system_access')
|
||||
# .Next(this.schedule_orientation)
|
||||
# )
|
||||
#
|
||||
# schedule_orientation = (
|
||||
# flow_view(OrientationSchedulingView)
|
||||
# .Permission('hr.can_schedule_orientation')
|
||||
# .Next(this.conduct_orientation)
|
||||
# )
|
||||
#
|
||||
# conduct_orientation = (
|
||||
# flow_view(OrientationDeliveryView)
|
||||
# .Permission('hr.can_conduct_orientation')
|
||||
# .Next(this.schedule_probationary_review)
|
||||
# )
|
||||
#
|
||||
# schedule_probationary_review = (
|
||||
# flow_func(this.setup_probationary_review)
|
||||
# .Next(this.finalize_onboarding)
|
||||
# )
|
||||
#
|
||||
# finalize_onboarding = (
|
||||
# flow_func(this.complete_onboarding)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_onboarding)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_onboarding(self, activation):
|
||||
# """Initialize the onboarding process"""
|
||||
# process = activation.process
|
||||
# employee = process.employee
|
||||
#
|
||||
# # Send welcome notification
|
||||
# self.send_welcome_notification(employee)
|
||||
#
|
||||
# # Notify HR staff
|
||||
# self.notify_hr_staff(employee)
|
||||
#
|
||||
# def setup_probationary_review(self, activation):
|
||||
# """Setup probationary review"""
|
||||
# process = activation.process
|
||||
# employee = process.employee
|
||||
#
|
||||
# # Schedule probationary review (typically 90 days)
|
||||
# review_date = timezone.now().date() + timezone.timedelta(days=90)
|
||||
#
|
||||
# # Create probationary review record
|
||||
# PerformanceReview.objects.create(
|
||||
# employee=employee,
|
||||
# review_period_start=employee.hire_date,
|
||||
# review_period_end=review_date,
|
||||
# review_date=review_date,
|
||||
# review_type='PROBATIONARY',
|
||||
# status='DRAFT'
|
||||
# )
|
||||
#
|
||||
# process.probationary_review_scheduled = True
|
||||
# process.save()
|
||||
#
|
||||
# # Notify manager
|
||||
# self.notify_manager_probationary_review(employee, review_date)
|
||||
#
|
||||
# def complete_onboarding(self, activation):
|
||||
# """Finalize the onboarding process"""
|
||||
# process = activation.process
|
||||
# employee = process.employee
|
||||
#
|
||||
# # Update employee status
|
||||
# employee.employment_status = 'ACTIVE'
|
||||
# employee.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.onboarding_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_onboarding_completion(employee)
|
||||
#
|
||||
# def end_onboarding(self, activation):
|
||||
# """End the onboarding workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate onboarding summary report
|
||||
# self.generate_onboarding_summary(process.employee)
|
||||
#
|
||||
# # Helper methods
|
||||
# def send_welcome_notification(self, employee):
|
||||
# """Send welcome notification to new employee"""
|
||||
# if employee.email:
|
||||
# send_mail(
|
||||
# subject=f'Welcome to {employee.tenant.name}!',
|
||||
# message=f'Welcome {employee.first_name}! Your onboarding process has begun.',
|
||||
# from_email='hr@hospital.com',
|
||||
# recipient_list=[employee.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_hr_staff(self, employee):
|
||||
# """Notify HR staff of new employee onboarding"""
|
||||
# from django.contrib.auth.models import Group
|
||||
#
|
||||
# hr_staff = User.objects.filter(
|
||||
# groups__name='HR Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in hr_staff:
|
||||
# send_mail(
|
||||
# subject=f'New Employee Onboarding: {employee.get_full_name()}',
|
||||
# message=f'New employee {employee.get_full_name()} onboarding has started.',
|
||||
# from_email='hr@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_manager_probationary_review(self, employee, review_date):
|
||||
# """Notify manager of upcoming probationary review"""
|
||||
# if employee.manager and employee.manager.email:
|
||||
# send_mail(
|
||||
# subject=f'Probationary Review Scheduled: {employee.get_full_name()}',
|
||||
# message=f'Probationary review scheduled for {review_date}.',
|
||||
# from_email='hr@hospital.com',
|
||||
# recipient_list=[employee.manager.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_onboarding_completion(self, employee):
|
||||
# """Notify relevant parties of onboarding completion"""
|
||||
# # Notify employee
|
||||
# if employee.email:
|
||||
# send_mail(
|
||||
# subject='Onboarding Complete',
|
||||
# message=f'Congratulations {employee.first_name}! Your onboarding is complete.',
|
||||
# from_email='hr@hospital.com',
|
||||
# recipient_list=[employee.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# # Notify manager
|
||||
# if employee.manager and employee.manager.email:
|
||||
# send_mail(
|
||||
# subject=f'Employee Onboarding Complete: {employee.get_full_name()}',
|
||||
# message=f'{employee.get_full_name()} has completed onboarding.',
|
||||
# from_email='hr@hospital.com',
|
||||
# recipient_list=[employee.manager.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_onboarding_summary(self, employee):
|
||||
# """Generate onboarding summary report"""
|
||||
# # This would generate a comprehensive onboarding report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class PerformanceReviewProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for performance reviews
|
||||
# """
|
||||
# performance_review = ModelField(PerformanceReview, help_text='Associated performance review')
|
||||
#
|
||||
# # Process status tracking
|
||||
# review_initiated = models.BooleanField(default=False)
|
||||
# self_assessment_completed = models.BooleanField(default=False)
|
||||
# manager_review_completed = models.BooleanField(default=False)
|
||||
# review_meeting_held = models.BooleanField(default=False)
|
||||
# employee_acknowledged = models.BooleanField(default=False)
|
||||
# development_plan_created = models.BooleanField(default=False)
|
||||
# review_finalized = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Performance Review Process'
|
||||
# verbose_name_plural = 'Performance Review Processes'
|
||||
#
|
||||
#
|
||||
# class PerformanceReviewFlow(Flow):
|
||||
# """
|
||||
# Performance Review Workflow
|
||||
#
|
||||
# This flow manages the complete performance review process including
|
||||
# self-assessment, manager review, meeting, and development planning.
|
||||
# """
|
||||
#
|
||||
# process_class = PerformanceReviewProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_performance_review)
|
||||
# .Next(this.initiate_review)
|
||||
# )
|
||||
#
|
||||
# initiate_review = (
|
||||
# flow_view(PerformanceReviewInitiationView)
|
||||
# .Permission('hr.can_initiate_reviews')
|
||||
# .Next(this.employee_self_assessment)
|
||||
# )
|
||||
#
|
||||
# employee_self_assessment = (
|
||||
# flow_view(SelfAssessmentView)
|
||||
# .Permission('hr.can_complete_self_assessment')
|
||||
# .Next(this.manager_review)
|
||||
# )
|
||||
#
|
||||
# manager_review = (
|
||||
# flow_view(ManagerReviewView)
|
||||
# .Permission('hr.can_conduct_manager_review')
|
||||
# .Next(this.review_meeting)
|
||||
# )
|
||||
#
|
||||
# review_meeting = (
|
||||
# flow_view(ReviewMeetingView)
|
||||
# .Permission('hr.can_conduct_review_meeting')
|
||||
# .Next(this.employee_acknowledgment)
|
||||
# )
|
||||
#
|
||||
# employee_acknowledgment = (
|
||||
# flow_view(EmployeeAcknowledgmentView)
|
||||
# .Permission('hr.can_acknowledge_review')
|
||||
# .Next(this.create_development_plan)
|
||||
# )
|
||||
#
|
||||
# create_development_plan = (
|
||||
# flow_view(DevelopmentPlanView)
|
||||
# .Permission('hr.can_create_development_plan')
|
||||
# .Next(this.finalize_review)
|
||||
# )
|
||||
#
|
||||
# finalize_review = (
|
||||
# flow_func(this.complete_performance_review)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_performance_review)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_performance_review(self, activation):
|
||||
# """Initialize the performance review process"""
|
||||
# process = activation.process
|
||||
# review = process.performance_review
|
||||
#
|
||||
# # Update review status
|
||||
# review.status = 'IN_PROGRESS'
|
||||
# review.save()
|
||||
#
|
||||
# # Notify employee and manager
|
||||
# self.notify_review_start(review)
|
||||
#
|
||||
# def complete_performance_review(self, activation):
|
||||
# """Finalize the performance review process"""
|
||||
# process = activation.process
|
||||
# review = process.performance_review
|
||||
#
|
||||
# # Update review status
|
||||
# review.status = 'COMPLETED'
|
||||
# review.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.review_finalized = True
|
||||
# process.save()
|
||||
#
|
||||
# # Schedule next review
|
||||
# self.schedule_next_review(review)
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_review_completion(review)
|
||||
#
|
||||
# def end_performance_review(self, activation):
|
||||
# """End the performance review workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate review summary report
|
||||
# self.generate_review_summary(process.performance_review)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_review_start(self, review):
|
||||
# """Notify employee and manager of review start"""
|
||||
# # Notify employee
|
||||
# if review.employee.email:
|
||||
# send_mail(
|
||||
# subject='Performance Review Started',
|
||||
# message=f'Your {review.get_review_type_display()} has been initiated.',
|
||||
# from_email='hr@hospital.com',
|
||||
# recipient_list=[review.employee.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# # Notify manager
|
||||
# if review.employee.manager and review.employee.manager.email:
|
||||
# send_mail(
|
||||
# subject=f'Performance Review: {review.employee.get_full_name()}',
|
||||
# message=f'Performance review for {review.employee.get_full_name()} has started.',
|
||||
# from_email='hr@hospital.com',
|
||||
# recipient_list=[review.employee.manager.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def schedule_next_review(self, review):
|
||||
# """Schedule next performance review"""
|
||||
# if review.review_type == 'ANNUAL':
|
||||
# # Schedule next annual review
|
||||
# next_review_date = review.review_date + timezone.timedelta(days=365)
|
||||
#
|
||||
# PerformanceReview.objects.create(
|
||||
# employee=review.employee,
|
||||
# review_period_start=review.review_period_end + timezone.timedelta(days=1),
|
||||
# review_period_end=next_review_date,
|
||||
# review_date=next_review_date,
|
||||
# review_type='ANNUAL',
|
||||
# status='DRAFT'
|
||||
# )
|
||||
#
|
||||
# def notify_review_completion(self, review):
|
||||
# """Notify relevant parties of review completion"""
|
||||
# # Notify employee
|
||||
# if review.employee.email:
|
||||
# send_mail(
|
||||
# subject='Performance Review Complete',
|
||||
# message=f'Your {review.get_review_type_display()} has been completed.',
|
||||
# from_email='hr@hospital.com',
|
||||
# recipient_list=[review.employee.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# # Notify HR
|
||||
# hr_staff = User.objects.filter(groups__name='HR Staff')
|
||||
# for staff in hr_staff:
|
||||
# send_mail(
|
||||
# subject=f'Performance Review Complete: {review.employee.get_full_name()}',
|
||||
# message=f'Performance review for {review.employee.get_full_name()} has been completed.',
|
||||
# from_email='hr@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_review_summary(self, review):
|
||||
# """Generate performance review summary report"""
|
||||
# # This would generate a comprehensive review report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class TrainingProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for training programs
|
||||
# """
|
||||
# training_record = ModelField(TrainingRecord, help_text='Associated training record')
|
||||
#
|
||||
# # Process status tracking
|
||||
# training_needs_assessed = models.BooleanField(default=False)
|
||||
# training_scheduled = models.BooleanField(default=False)
|
||||
# training_delivered = models.BooleanField(default=False)
|
||||
# training_evaluated = models.BooleanField(default=False)
|
||||
# certification_issued = models.BooleanField(default=False)
|
||||
# follow_up_scheduled = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Training Process'
|
||||
# verbose_name_plural = 'Training Processes'
|
||||
#
|
||||
#
|
||||
# class TrainingFlow(Flow):
|
||||
# """
|
||||
# Training Workflow
|
||||
#
|
||||
# This flow manages training programs including needs assessment,
|
||||
# scheduling, delivery, evaluation, and certification.
|
||||
# """
|
||||
#
|
||||
# process_class = TrainingProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_training)
|
||||
# .Next(this.assess_training_needs)
|
||||
# )
|
||||
#
|
||||
# assess_training_needs = (
|
||||
# flow_view(TrainingNeedsAssessmentView)
|
||||
# .Permission('hr.can_assess_training_needs')
|
||||
# .Next(this.schedule_training)
|
||||
# )
|
||||
#
|
||||
# schedule_training = (
|
||||
# flow_view(TrainingSchedulingView)
|
||||
# .Permission('hr.can_schedule_training')
|
||||
# .Next(this.deliver_training)
|
||||
# )
|
||||
#
|
||||
# deliver_training = (
|
||||
# flow_view(TrainingDeliveryView)
|
||||
# .Permission('hr.can_deliver_training')
|
||||
# .Next(this.evaluate_training)
|
||||
# )
|
||||
#
|
||||
# evaluate_training = (
|
||||
# flow_view(TrainingEvaluationView)
|
||||
# .Permission('hr.can_evaluate_training')
|
||||
# .Next(this.issue_certification)
|
||||
# )
|
||||
#
|
||||
# issue_certification = (
|
||||
# flow_func(this.process_certification)
|
||||
# .Next(this.schedule_follow_up)
|
||||
# )
|
||||
#
|
||||
# schedule_follow_up = (
|
||||
# flow_func(this.setup_follow_up)
|
||||
# .Next(this.finalize_training)
|
||||
# )
|
||||
#
|
||||
# finalize_training = (
|
||||
# flow_func(this.complete_training)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_training)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_training(self, activation):
|
||||
# """Initialize the training process"""
|
||||
# process = activation.process
|
||||
# training = process.training_record
|
||||
#
|
||||
# # Update training status
|
||||
# training.status = 'SCHEDULED'
|
||||
# training.save()
|
||||
#
|
||||
# # Notify employee and manager
|
||||
# self.notify_training_start(training)
|
||||
#
|
||||
# def process_certification(self, activation):
|
||||
# """Process certification if training passed"""
|
||||
# process = activation.process
|
||||
# training = process.training_record
|
||||
#
|
||||
# if training.passed and training.training_type == 'CERTIFICATION':
|
||||
# # Generate certificate number
|
||||
# training.certificate_number = self.generate_certificate_number()
|
||||
# training.save()
|
||||
#
|
||||
# process.certification_issued = True
|
||||
# process.save()
|
||||
#
|
||||
# # Notify employee of certification
|
||||
# self.notify_certification_issued(training)
|
||||
#
|
||||
# def setup_follow_up(self, activation):
|
||||
# """Setup follow-up training if needed"""
|
||||
# process = activation.process
|
||||
# training = process.training_record
|
||||
#
|
||||
# # Schedule follow-up based on training type
|
||||
# if training.training_type in ['CERTIFICATION', 'MANDATORY']:
|
||||
# if training.expiry_date:
|
||||
# # Schedule renewal training
|
||||
# renewal_date = training.expiry_date - timezone.timedelta(days=30)
|
||||
# self.schedule_renewal_training(training, renewal_date)
|
||||
#
|
||||
# process.follow_up_scheduled = True
|
||||
# process.save()
|
||||
#
|
||||
# def complete_training(self, activation):
|
||||
# """Finalize the training process"""
|
||||
# process = activation.process
|
||||
# training = process.training_record
|
||||
#
|
||||
# # Update training status
|
||||
# if training.passed:
|
||||
# training.status = 'COMPLETED'
|
||||
# else:
|
||||
# training.status = 'FAILED'
|
||||
#
|
||||
# training.completion_date = timezone.now().date()
|
||||
# training.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_training_completion(training)
|
||||
#
|
||||
# def end_training(self, activation):
|
||||
# """End the training workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate training summary report
|
||||
# self.generate_training_summary(process.training_record)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_training_start(self, training):
|
||||
# """Notify employee and manager of training start"""
|
||||
# # Notify employee
|
||||
# if training.employee.email:
|
||||
# send_mail(
|
||||
# subject=f'Training Scheduled: {training.training_name}',
|
||||
# message=f'Your training "{training.training_name}" has been scheduled for {training.training_date}.',
|
||||
# from_email='hr@hospital.com',
|
||||
# recipient_list=[training.employee.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# # Notify manager
|
||||
# if training.employee.manager and training.employee.manager.email:
|
||||
# send_mail(
|
||||
# subject=f'Employee Training: {training.employee.get_full_name()}',
|
||||
# message=f'{training.employee.get_full_name()} has training scheduled: {training.training_name}.',
|
||||
# from_email='hr@hospital.com',
|
||||
# recipient_list=[training.employee.manager.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_certificate_number(self):
|
||||
# """Generate unique certificate number"""
|
||||
# from django.utils.crypto import get_random_string
|
||||
# return f"CERT{timezone.now().strftime('%Y%m%d')}{get_random_string(4, '0123456789')}"
|
||||
#
|
||||
# def notify_certification_issued(self, training):
|
||||
# """Notify employee of certification"""
|
||||
# if training.employee.email:
|
||||
# send_mail(
|
||||
# subject=f'Certification Issued: {training.training_name}',
|
||||
# message=f'Congratulations! You have been certified in {training.training_name}. Certificate #: {training.certificate_number}',
|
||||
# from_email='hr@hospital.com',
|
||||
# recipient_list=[training.employee.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def schedule_renewal_training(self, training, renewal_date):
|
||||
# """Schedule renewal training"""
|
||||
# TrainingRecord.objects.create(
|
||||
# employee=training.employee,
|
||||
# training_name=f"{training.training_name} - Renewal",
|
||||
# training_type=training.training_type,
|
||||
# training_date=renewal_date,
|
||||
# status='SCHEDULED'
|
||||
# )
|
||||
#
|
||||
# def notify_training_completion(self, training):
|
||||
# """Notify relevant parties of training completion"""
|
||||
# # Notify employee
|
||||
# if training.employee.email:
|
||||
# status_text = "completed successfully" if training.passed else "not passed"
|
||||
# send_mail(
|
||||
# subject=f'Training {status_text.title()}: {training.training_name}',
|
||||
# message=f'Your training "{training.training_name}" has been {status_text}.',
|
||||
# from_email='hr@hospital.com',
|
||||
# recipient_list=[training.employee.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_training_summary(self, training):
|
||||
# """Generate training summary report"""
|
||||
# # This would generate a comprehensive training report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class ComplianceTrackingProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for compliance tracking
|
||||
# """
|
||||
# employee_id = CharField(max_length=50, help_text='Employee identifier')
|
||||
# compliance_type = CharField(max_length=20, help_text='Type of compliance')
|
||||
#
|
||||
# # Process status tracking
|
||||
# compliance_checked = models.BooleanField(default=False)
|
||||
# deficiencies_identified = models.BooleanField(default=False)
|
||||
# corrective_actions_planned = models.BooleanField(default=False)
|
||||
# actions_implemented = models.BooleanField(default=False)
|
||||
# compliance_verified = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Compliance Tracking Process'
|
||||
# verbose_name_plural = 'Compliance Tracking Processes'
|
||||
#
|
||||
#
|
||||
# class ComplianceTrackingFlow(Flow):
|
||||
# """
|
||||
# Compliance Tracking Workflow
|
||||
#
|
||||
# This flow manages compliance tracking including monitoring,
|
||||
# deficiency identification, and corrective actions.
|
||||
# """
|
||||
#
|
||||
# process_class = ComplianceTrackingProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_compliance_tracking)
|
||||
# .Next(this.check_compliance)
|
||||
# )
|
||||
#
|
||||
# check_compliance = (
|
||||
# flow_func(this.monitor_compliance)
|
||||
# .Next(this.identify_deficiencies)
|
||||
# )
|
||||
#
|
||||
# identify_deficiencies = (
|
||||
# flow_func(this.assess_deficiencies)
|
||||
# .Next(this.plan_corrective_actions)
|
||||
# )
|
||||
#
|
||||
# plan_corrective_actions = (
|
||||
# flow_view(CorrectiveActionPlanningView)
|
||||
# .Permission('hr.can_plan_corrective_actions')
|
||||
# .Next(this.implement_actions)
|
||||
# )
|
||||
#
|
||||
# implement_actions = (
|
||||
# flow_view(CorrectiveActionImplementationView)
|
||||
# .Permission('hr.can_implement_corrective_actions')
|
||||
# .Next(this.verify_compliance)
|
||||
# )
|
||||
#
|
||||
# verify_compliance = (
|
||||
# flow_func(this.validate_compliance)
|
||||
# .Next(this.finalize_compliance)
|
||||
# )
|
||||
#
|
||||
# finalize_compliance = (
|
||||
# flow_func(this.complete_compliance_tracking)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_compliance_tracking)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_compliance_tracking(self, activation):
|
||||
# """Initialize the compliance tracking process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Log compliance check initiation
|
||||
# self.log_compliance_check(process.employee_id, process.compliance_type)
|
||||
#
|
||||
# def monitor_compliance(self, activation):
|
||||
# """Monitor employee compliance"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Check compliance status
|
||||
# compliance_status = self.check_employee_compliance(process.employee_id, process.compliance_type)
|
||||
#
|
||||
# process.compliance_checked = True
|
||||
# process.save()
|
||||
#
|
||||
# if not compliance_status:
|
||||
# # Alert of compliance issues
|
||||
# self.alert_compliance_issues(process.employee_id, process.compliance_type)
|
||||
#
|
||||
# def assess_deficiencies(self, activation):
|
||||
# """Assess compliance deficiencies"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Identify specific deficiencies
|
||||
# deficiencies = self.identify_compliance_deficiencies(process.employee_id, process.compliance_type)
|
||||
#
|
||||
# if deficiencies:
|
||||
# process.deficiencies_identified = True
|
||||
# process.save()
|
||||
#
|
||||
# # Notify relevant parties
|
||||
# self.notify_compliance_deficiencies(process.employee_id, deficiencies)
|
||||
#
|
||||
# def validate_compliance(self, activation):
|
||||
# """Validate compliance after corrective actions"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Re-check compliance
|
||||
# compliance_status = self.check_employee_compliance(process.employee_id, process.compliance_type)
|
||||
#
|
||||
# if compliance_status:
|
||||
# process.compliance_verified = True
|
||||
# process.save()
|
||||
# else:
|
||||
# # Escalate if still non-compliant
|
||||
# self.escalate_compliance_issue(process.employee_id, process.compliance_type)
|
||||
#
|
||||
# def complete_compliance_tracking(self, activation):
|
||||
# """Finalize the compliance tracking process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate compliance report
|
||||
# self.generate_compliance_report(process.employee_id, process.compliance_type)
|
||||
#
|
||||
# def end_compliance_tracking(self, activation):
|
||||
# """End the compliance tracking workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Archive compliance tracking
|
||||
# self.archive_compliance_tracking(process.employee_id, process.compliance_type)
|
||||
#
|
||||
# # Helper methods
|
||||
# def log_compliance_check(self, employee_id, compliance_type):
|
||||
# """Log compliance check"""
|
||||
# # This would log the compliance check
|
||||
# pass
|
||||
#
|
||||
# def check_employee_compliance(self, employee_id, compliance_type):
|
||||
# """Check employee compliance status"""
|
||||
# # This would check compliance status
|
||||
# return True
|
||||
#
|
||||
# def alert_compliance_issues(self, employee_id, compliance_type):
|
||||
# """Alert of compliance issues"""
|
||||
# # This would send compliance alerts
|
||||
# pass
|
||||
#
|
||||
# def identify_compliance_deficiencies(self, employee_id, compliance_type):
|
||||
# """Identify specific compliance deficiencies"""
|
||||
# # This would identify deficiencies
|
||||
# return []
|
||||
#
|
||||
# def notify_compliance_deficiencies(self, employee_id, deficiencies):
|
||||
# """Notify of compliance deficiencies"""
|
||||
# # This would send deficiency notifications
|
||||
# pass
|
||||
#
|
||||
# def escalate_compliance_issue(self, employee_id, compliance_type):
|
||||
# """Escalate compliance issue"""
|
||||
# # This would escalate the issue
|
||||
# pass
|
||||
#
|
||||
# def generate_compliance_report(self, employee_id, compliance_type):
|
||||
# """Generate compliance report"""
|
||||
# # This would generate compliance report
|
||||
# pass
|
||||
#
|
||||
# def archive_compliance_tracking(self, employee_id, compliance_type):
|
||||
# """Archive compliance tracking"""
|
||||
# # This would archive the tracking
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def auto_schedule_performance_reviews():
|
||||
# """Background task to automatically schedule performance reviews"""
|
||||
# try:
|
||||
# # This would schedule upcoming reviews
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def monitor_training_expiry():
|
||||
# """Background task to monitor training and certification expiry"""
|
||||
# try:
|
||||
# # This would monitor expiring certifications
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def generate_hr_compliance_report():
|
||||
# """Background task to generate HR compliance reports"""
|
||||
# try:
|
||||
# # This would generate compliance reports
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def auto_assign_mandatory_training():
|
||||
# """Background task to automatically assign mandatory training"""
|
||||
# try:
|
||||
# # This would assign mandatory training
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
18
hr/migrations/0002_trainingrecord_is_certified.py
Normal file
18
hr/migrations/0002_trainingrecord_is_certified.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.4 on 2025-09-07 14:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("hr", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="trainingrecord",
|
||||
name="is_certified",
|
||||
field=models.BooleanField(default=False, help_text="Training is certified"),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
700
hr/models.py
700
hr/models.py
@ -11,6 +11,8 @@ from django.conf import settings
|
||||
from datetime import timedelta, datetime, date, time
|
||||
from decimal import Decimal
|
||||
import json
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
||||
|
||||
class Employee(models.Model):
|
||||
@ -157,7 +159,13 @@ class Employee(models.Model):
|
||||
null=True,
|
||||
help_text='Country'
|
||||
)
|
||||
|
||||
national_id = models.CharField(
|
||||
max_length=10,
|
||||
blank=True,
|
||||
null=True,
|
||||
unique=True,
|
||||
help_text='National ID'
|
||||
)
|
||||
# Personal Details
|
||||
date_of_birth = models.DateField(
|
||||
blank=True,
|
||||
@ -391,12 +399,19 @@ class Department(models.Model):
|
||||
"""
|
||||
Department model for organizational structure.
|
||||
"""
|
||||
|
||||
DEPARTMENT_TYPE_CHOICES = [
|
||||
('CLINICAL', 'Clinical'),
|
||||
('ADMINISTRATIVE', 'Administrative'),
|
||||
('SUPPORT', 'Support'),
|
||||
('ANCILLARY', 'Ancillary'),
|
||||
('EXECUTIVE', 'Executive'),
|
||||
]
|
||||
|
||||
# Tenant relationship
|
||||
tenant = models.ForeignKey(
|
||||
'core.Tenant',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='hr_departments',
|
||||
related_name='departments',
|
||||
help_text='Organization tenant'
|
||||
)
|
||||
|
||||
@ -409,7 +424,7 @@ class Department(models.Model):
|
||||
)
|
||||
department_code = models.CharField(
|
||||
max_length=20,
|
||||
help_text='Department code'
|
||||
help_text='Department code (e.g., CARD, EMER, SURG)'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
@ -424,13 +439,7 @@ class Department(models.Model):
|
||||
# Department Type
|
||||
department_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('CLINICAL', 'Clinical'),
|
||||
('ADMINISTRATIVE', 'Administrative'),
|
||||
('SUPPORT', 'Support'),
|
||||
('ANCILLARY', 'Ancillary'),
|
||||
('EXECUTIVE', 'Executive'),
|
||||
],
|
||||
choices=DEPARTMENT_TYPE_CHOICES,
|
||||
help_text='Department type'
|
||||
)
|
||||
|
||||
@ -440,7 +449,7 @@ class Department(models.Model):
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='child_departments',
|
||||
related_name='sub_departments',
|
||||
help_text='Parent department'
|
||||
)
|
||||
|
||||
@ -453,6 +462,24 @@ class Department(models.Model):
|
||||
related_name='headed_departments',
|
||||
help_text='Department head'
|
||||
)
|
||||
# Contact Information
|
||||
phone = models.CharField(
|
||||
max_length=20,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Department phone number'
|
||||
)
|
||||
extension = models.CharField(
|
||||
max_length=10,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Phone extension'
|
||||
)
|
||||
email = models.EmailField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Department email'
|
||||
)
|
||||
|
||||
# Budget Information
|
||||
annual_budget = models.DecimalField(
|
||||
@ -468,7 +495,10 @@ class Department(models.Model):
|
||||
null=True,
|
||||
help_text='Cost center code'
|
||||
)
|
||||
|
||||
authorized_positions = models.PositiveIntegerField(
|
||||
default=0,
|
||||
help_text='Number of authorized positions'
|
||||
)
|
||||
# Location Information
|
||||
location = models.CharField(
|
||||
max_length=100,
|
||||
@ -477,11 +507,42 @@ class Department(models.Model):
|
||||
help_text='Department location'
|
||||
)
|
||||
|
||||
# Department Status
|
||||
# Operational Information
|
||||
is_active = models.BooleanField(
|
||||
default=True,
|
||||
help_text='Department is active'
|
||||
)
|
||||
is_24_hour = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Department operates 24 hours'
|
||||
)
|
||||
operating_hours = models.JSONField(
|
||||
default=dict,
|
||||
blank=True,
|
||||
help_text='Operating hours by day of week'
|
||||
)
|
||||
|
||||
# Quality and Compliance
|
||||
accreditation_required = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Department requires special accreditation'
|
||||
)
|
||||
accreditation_body = models.CharField(
|
||||
max_length=100,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Accrediting body (e.g., Joint Commission, CAP)'
|
||||
)
|
||||
last_inspection_date = models.DateField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Last inspection date'
|
||||
)
|
||||
next_inspection_date = models.DateField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Next scheduled inspection date'
|
||||
)
|
||||
|
||||
# Notes
|
||||
notes = models.TextField(
|
||||
@ -517,7 +578,14 @@ class Department(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.department_code} - {self.name}"
|
||||
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
"""Return full department name with parent if applicable"""
|
||||
if self.parent_department:
|
||||
return f"{self.parent_department.name} - {self.name}"
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def employee_count(self):
|
||||
"""
|
||||
@ -532,12 +600,33 @@ class Department(models.Model):
|
||||
"""
|
||||
return sum(emp.fte_percentage for emp in self.employees.filter(employment_status='ACTIVE')) / 100
|
||||
|
||||
@property
|
||||
def staffing_percentage(self):
|
||||
"""Calculate current staffing percentage"""
|
||||
if self.authorized_positions > 0:
|
||||
return (self.employee_count / self.authorized_positions) * 100
|
||||
return 0
|
||||
|
||||
def get_all_sub_departments(self):
|
||||
"""Get all sub-departments recursively"""
|
||||
sub_departments = []
|
||||
for sub_dept in self.sub_departments.all():
|
||||
sub_departments.append(sub_dept)
|
||||
sub_departments.extend(sub_dept.get_all_sub_departments())
|
||||
return sub_departments
|
||||
|
||||
|
||||
class Schedule(models.Model):
|
||||
"""
|
||||
Schedule model for employee work schedules.
|
||||
"""
|
||||
|
||||
SCHEDULE_TYPE_CHOICES = [
|
||||
('REGULAR', 'Regular Schedule'),
|
||||
('ROTATING', 'Rotating Schedule'),
|
||||
('FLEXIBLE', 'Flexible Schedule'),
|
||||
('ON_CALL', 'On-Call Schedule'),
|
||||
('TEMPORARY', 'Temporary Schedule'),
|
||||
]
|
||||
# Employee relationship
|
||||
employee = models.ForeignKey(
|
||||
Employee,
|
||||
@ -566,13 +655,7 @@ class Schedule(models.Model):
|
||||
# Schedule Type
|
||||
schedule_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('REGULAR', 'Regular Schedule'),
|
||||
('ROTATING', 'Rotating Schedule'),
|
||||
('FLEXIBLE', 'Flexible Schedule'),
|
||||
('ON_CALL', 'On-Call Schedule'),
|
||||
('TEMPORARY', 'Temporary Schedule'),
|
||||
],
|
||||
choices=SCHEDULE_TYPE_CHOICES,
|
||||
help_text='Schedule type'
|
||||
)
|
||||
|
||||
@ -668,7 +751,23 @@ class ScheduleAssignment(models.Model):
|
||||
"""
|
||||
Schedule assignment model for specific shift assignments.
|
||||
"""
|
||||
|
||||
SHIFT_TYPE_CHOICES = [
|
||||
('DAY', 'Day Shift'),
|
||||
('EVENING', 'Evening Shift'),
|
||||
('NIGHT', 'Night Shift'),
|
||||
('WEEKEND', 'Weekend Shift'),
|
||||
('HOLIDAY', 'Holiday Shift'),
|
||||
('ON_CALL', 'On-Call'),
|
||||
('OVERTIME', 'Overtime'),
|
||||
]
|
||||
STATUS_CHOICES = [
|
||||
('SCHEDULED', 'Scheduled'),
|
||||
('CONFIRMED', 'Confirmed'),
|
||||
('COMPLETED', 'Completed'),
|
||||
('CANCELLED', 'Cancelled'),
|
||||
('NO_SHOW', 'No Show'),
|
||||
]
|
||||
|
||||
# Schedule relationship
|
||||
schedule = models.ForeignKey(
|
||||
Schedule,
|
||||
@ -699,15 +798,7 @@ class ScheduleAssignment(models.Model):
|
||||
# Shift Information
|
||||
shift_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('DAY', 'Day Shift'),
|
||||
('EVENING', 'Evening Shift'),
|
||||
('NIGHT', 'Night Shift'),
|
||||
('WEEKEND', 'Weekend Shift'),
|
||||
('HOLIDAY', 'Holiday Shift'),
|
||||
('ON_CALL', 'On-Call'),
|
||||
('OVERTIME', 'Overtime'),
|
||||
],
|
||||
choices=SHIFT_TYPE_CHOICES,
|
||||
help_text='Shift type'
|
||||
)
|
||||
|
||||
@ -730,13 +821,7 @@ class ScheduleAssignment(models.Model):
|
||||
# Assignment Status
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('SCHEDULED', 'Scheduled'),
|
||||
('CONFIRMED', 'Confirmed'),
|
||||
('COMPLETED', 'Completed'),
|
||||
('CANCELLED', 'Cancelled'),
|
||||
('NO_SHOW', 'No Show'),
|
||||
],
|
||||
choices=STATUS_CHOICES,
|
||||
default='SCHEDULED',
|
||||
help_text='Assignment status'
|
||||
)
|
||||
@ -812,7 +897,25 @@ class TimeEntry(models.Model):
|
||||
"""
|
||||
Time entry model for tracking actual work hours.
|
||||
"""
|
||||
|
||||
ENTRY_TYPE_CHOICES = [
|
||||
('REGULAR', 'Regular Time'),
|
||||
('OVERTIME', 'Overtime'),
|
||||
('HOLIDAY', 'Holiday'),
|
||||
('VACATION', 'Vacation'),
|
||||
('SICK', 'Sick Leave'),
|
||||
('PERSONAL', 'Personal Time'),
|
||||
('BEREAVEMENT', 'Bereavement'),
|
||||
('JURY_DUTY', 'Jury Duty'),
|
||||
('TRAINING', 'Training'),
|
||||
]
|
||||
STATUS_CHOICES = [
|
||||
('DRAFT', 'Draft'),
|
||||
('SUBMITTED', 'Submitted'),
|
||||
('APPROVED', 'Approved'),
|
||||
('REJECTED', 'Rejected'),
|
||||
('PAID', 'Paid'),
|
||||
]
|
||||
|
||||
# Employee relationship
|
||||
employee = models.ForeignKey(
|
||||
Employee,
|
||||
@ -889,17 +992,7 @@ class TimeEntry(models.Model):
|
||||
# Entry Type
|
||||
entry_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('REGULAR', 'Regular Time'),
|
||||
('OVERTIME', 'Overtime'),
|
||||
('HOLIDAY', 'Holiday'),
|
||||
('VACATION', 'Vacation'),
|
||||
('SICK', 'Sick Leave'),
|
||||
('PERSONAL', 'Personal Time'),
|
||||
('BEREAVEMENT', 'Bereavement'),
|
||||
('JURY_DUTY', 'Jury Duty'),
|
||||
('TRAINING', 'Training'),
|
||||
],
|
||||
choices=ENTRY_TYPE_CHOICES,
|
||||
default='REGULAR',
|
||||
help_text='Entry type'
|
||||
)
|
||||
@ -938,13 +1031,7 @@ class TimeEntry(models.Model):
|
||||
# Entry Status
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('DRAFT', 'Draft'),
|
||||
('SUBMITTED', 'Submitted'),
|
||||
('APPROVED', 'Approved'),
|
||||
('REJECTED', 'Rejected'),
|
||||
('PAID', 'Paid'),
|
||||
],
|
||||
choices=STATUS_CHOICES,
|
||||
default='DRAFT',
|
||||
help_text='Entry status'
|
||||
)
|
||||
@ -1024,7 +1111,21 @@ class PerformanceReview(models.Model):
|
||||
"""
|
||||
Performance review model for employee evaluations.
|
||||
"""
|
||||
|
||||
REVIEW_TYPE_CHOICES = [
|
||||
('ANNUAL', 'Annual Review'),
|
||||
('PROBATIONARY', 'Probationary Review'),
|
||||
('MID_YEAR', 'Mid-Year Review'),
|
||||
('PROJECT', 'Project Review'),
|
||||
('DISCIPLINARY', 'Disciplinary Review'),
|
||||
('PROMOTION', 'Promotion Review'),
|
||||
]
|
||||
STATUS_CHOICES = [
|
||||
('DRAFT', 'Draft'),
|
||||
('IN_PROGRESS', 'In Progress'),
|
||||
('COMPLETED', 'Completed'),
|
||||
('ACKNOWLEDGED', 'Acknowledged by Employee'),
|
||||
('DISPUTED', 'Disputed'),
|
||||
]
|
||||
# Employee relationship
|
||||
employee = models.ForeignKey(
|
||||
Employee,
|
||||
@ -1055,14 +1156,7 @@ class PerformanceReview(models.Model):
|
||||
# Review Type
|
||||
review_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('ANNUAL', 'Annual Review'),
|
||||
('PROBATIONARY', 'Probationary Review'),
|
||||
('MID_YEAR', 'Mid-Year Review'),
|
||||
('PROJECT', 'Project Review'),
|
||||
('DISCIPLINARY', 'Disciplinary Review'),
|
||||
('PROMOTION', 'Promotion Review'),
|
||||
],
|
||||
choices=REVIEW_TYPE_CHOICES,
|
||||
help_text='Review type'
|
||||
)
|
||||
|
||||
@ -1146,13 +1240,7 @@ class PerformanceReview(models.Model):
|
||||
# Review Status
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('DRAFT', 'Draft'),
|
||||
('IN_PROGRESS', 'In Progress'),
|
||||
('COMPLETED', 'Completed'),
|
||||
('ACKNOWLEDGED', 'Acknowledged by Employee'),
|
||||
('DISPUTED', 'Disputed'),
|
||||
],
|
||||
choices=STATUS_CHOICES,
|
||||
default='DRAFT',
|
||||
help_text='Review status'
|
||||
)
|
||||
@ -1204,7 +1292,26 @@ class TrainingRecord(models.Model):
|
||||
"""
|
||||
Training record model for employee training and certifications.
|
||||
"""
|
||||
|
||||
TRAINING_TYPE_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'),
|
||||
]
|
||||
STATUS_CHOICES = [
|
||||
('SCHEDULED', 'Scheduled'),
|
||||
('IN_PROGRESS', 'In Progress'),
|
||||
('COMPLETED', 'Completed'),
|
||||
('CANCELLED', 'Cancelled'),
|
||||
('NO_SHOW', 'No Show'),
|
||||
('FAILED', 'Failed'),
|
||||
]
|
||||
# Employee relationship
|
||||
employee = models.ForeignKey(
|
||||
Employee,
|
||||
@ -1229,25 +1336,14 @@ class TrainingRecord(models.Model):
|
||||
null=True,
|
||||
help_text='Training description'
|
||||
)
|
||||
|
||||
|
||||
# Training Type
|
||||
training_type = models.CharField(
|
||||
max_length=20,
|
||||
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'),
|
||||
],
|
||||
choices=TRAINING_TYPE_CHOICES,
|
||||
help_text='Training type'
|
||||
)
|
||||
|
||||
|
||||
# Training Provider
|
||||
training_provider = models.CharField(
|
||||
max_length=200,
|
||||
@ -1261,7 +1357,7 @@ class TrainingRecord(models.Model):
|
||||
null=True,
|
||||
help_text='Instructor name'
|
||||
)
|
||||
|
||||
|
||||
# Training Dates
|
||||
training_date = models.DateField(
|
||||
help_text='Training date'
|
||||
@ -1294,14 +1390,7 @@ class TrainingRecord(models.Model):
|
||||
# Training Status
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('SCHEDULED', 'Scheduled'),
|
||||
('IN_PROGRESS', 'In Progress'),
|
||||
('COMPLETED', 'Completed'),
|
||||
('CANCELLED', 'Cancelled'),
|
||||
('NO_SHOW', 'No Show'),
|
||||
('FAILED', 'Failed'),
|
||||
],
|
||||
choices=STATUS_CHOICES,
|
||||
default='SCHEDULED',
|
||||
help_text='Training status'
|
||||
)
|
||||
@ -1318,7 +1407,10 @@ class TrainingRecord(models.Model):
|
||||
default=False,
|
||||
help_text='Training passed'
|
||||
)
|
||||
|
||||
is_certified = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Training is certified'
|
||||
)
|
||||
# Certification Information
|
||||
certificate_number = models.CharField(
|
||||
max_length=50,
|
||||
@ -1409,3 +1501,411 @@ class TrainingRecord(models.Model):
|
||||
return (self.expiry_date - date.today()).days <= 30
|
||||
return False
|
||||
|
||||
|
||||
# class TrainingPrograms(models.Model):
|
||||
# PROGRAM_TYPE_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'),
|
||||
# ]
|
||||
#
|
||||
# # Multi-tenancy
|
||||
# tenant = models.ForeignKey(
|
||||
# 'core.Tenant', on_delete=models.CASCADE, related_name='training_programs'
|
||||
# )
|
||||
#
|
||||
# program_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
|
||||
# name = models.CharField(max_length=200)
|
||||
# description = models.TextField(blank=True, null=True)
|
||||
# program_type = models.CharField(max_length=20, choices=PROGRAM_TYPE_CHOICES)
|
||||
#
|
||||
# # Provider/Instructor at the program (defaults; sessions can override)
|
||||
# program_provider = models.CharField(max_length=200, blank=True, null=True)
|
||||
# default_instructor = models.ForeignKey(
|
||||
# Employee, on_delete=models.SET_NULL, null=True, blank=True,
|
||||
# related_name='default_instructor_programs',
|
||||
# help_text='Default instructor; sessions may override'
|
||||
# )
|
||||
#
|
||||
# # Optional “program window” (e.g., for long initiatives)
|
||||
# start_date = models.DateField(help_text='Program start date', blank=True, null=True)
|
||||
# end_date = models.DateField(help_text='Program end date', blank=True, null=True)
|
||||
#
|
||||
# duration_hours = models.DecimalField(max_digits=5, decimal_places=2,
|
||||
# default=Decimal('0.00'))
|
||||
# cost = models.DecimalField(max_digits=10, decimal_places=2,
|
||||
# default=Decimal('0.00'))
|
||||
# is_certified = models.BooleanField(default=False)
|
||||
#
|
||||
# # Renewal/expiry policy (applies if is_certified)
|
||||
# validity_days = models.PositiveIntegerField(
|
||||
# blank=True, null=True,
|
||||
# help_text='Days certificate is valid from completion (e.g., 365).'
|
||||
# )
|
||||
# notify_before_days = models.PositiveIntegerField(
|
||||
# blank=True, null=True,
|
||||
# help_text='Days before expiry to flag for renewal.'
|
||||
# )
|
||||
#
|
||||
# # Metadata
|
||||
# created_at = models.DateTimeField(auto_now_add=True)
|
||||
# updated_at = models.DateTimeField(auto_now=True)
|
||||
# created_by = models.ForeignKey(
|
||||
# settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
|
||||
# null=True, blank=True, related_name='created_training_programs'
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# db_table = 'hr_training_program'
|
||||
# ordering = ['name']
|
||||
# unique_together = [('tenant', 'name')]
|
||||
# indexes = [
|
||||
# models.Index(fields=['tenant', 'program_type']),
|
||||
# models.Index(fields=['tenant', 'is_certified']),
|
||||
# ]
|
||||
#
|
||||
# def clean(self):
|
||||
# if self.start_date and self.end_date and self.end_date < self.start_date:
|
||||
# raise ValidationError(_('Program end_date cannot be before start_date.'))
|
||||
# if self.is_certified and not self.validity_days:
|
||||
# # Not hard error—could be open-ended—but warn as best practice.
|
||||
# pass
|
||||
#
|
||||
# def __str__(self):
|
||||
# return f'{self.name} ({self.get_program_type_display()})'
|
||||
#
|
||||
#
|
||||
# class ProgramModule(models.Model):
|
||||
# """Optional content structure for a program."""
|
||||
# program = models.ForeignKey(
|
||||
# TrainingPrograms, on_delete=models.CASCADE, related_name='modules'
|
||||
# )
|
||||
# title = models.CharField(max_length=200)
|
||||
# order = models.PositiveIntegerField(default=1)
|
||||
# hours = models.DecimalField(max_digits=5, decimal_places=2,
|
||||
# default=Decimal('0.00'))
|
||||
#
|
||||
# class Meta:
|
||||
# db_table = 'hr_training_program_module'
|
||||
# ordering = ['program', 'order']
|
||||
# unique_together = [('program', 'order')]
|
||||
# indexes = [models.Index(fields=['program', 'order'])]
|
||||
#
|
||||
# def __str__(self):
|
||||
# return f'{self.program.name} · {self.order}. {self.title}'
|
||||
#
|
||||
#
|
||||
# class ProgramPrerequisite(models.Model):
|
||||
# """A program may require completion of other program(s)."""
|
||||
# program = models.ForeignKey(
|
||||
# TrainingPrograms, on_delete=models.CASCADE, related_name='prerequisites'
|
||||
# )
|
||||
# required_program = models.ForeignKey(
|
||||
# TrainingPrograms, on_delete=models.CASCADE, related_name='unlocking_programs'
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# db_table = 'hr_training_program_prerequisite'
|
||||
# unique_together = [('program', 'required_program')]
|
||||
#
|
||||
# def clean(self):
|
||||
# if self.program_id == self.required_program_id:
|
||||
# raise ValidationError(_('Program cannot require itself.'))
|
||||
#
|
||||
#
|
||||
# class TrainingSession(models.Model):
|
||||
# """
|
||||
# A scheduled run of a program (cohort/class).
|
||||
# """
|
||||
# DELIVERY_CHOICES = [
|
||||
# ('IN_PERSON', 'In-person'),
|
||||
# ('VIRTUAL', 'Virtual'),
|
||||
# ('HYBRID', 'Hybrid'),
|
||||
# ('SELF_PACED', 'Self-paced'),
|
||||
# ]
|
||||
#
|
||||
# tenant = models.ForeignKey(
|
||||
# Tenant, on_delete=models.CASCADE, related_name='training_sessions'
|
||||
# )
|
||||
# session_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
|
||||
# program = models.ForeignKey(
|
||||
# TrainingPrograms, on_delete=models.CASCADE, related_name='sessions'
|
||||
# )
|
||||
# title = models.CharField(
|
||||
# max_length=200, blank=True, null=True,
|
||||
# help_text='Optional run title; falls back to program name'
|
||||
# )
|
||||
# instructor = models.ForeignKey(
|
||||
# Employee, on_delete=models.SET_NULL, null=True, blank=True,
|
||||
# related_name='instructed_sessions'
|
||||
# )
|
||||
# delivery_method = models.CharField(max_length=12, choices=DELIVERY_CHOICES, default='IN_PERSON')
|
||||
#
|
||||
# # Schedule
|
||||
# start_at = models.DateTimeField()
|
||||
# end_at = models.DateTimeField()
|
||||
# location = models.CharField(max_length=200, blank=True, null=True)
|
||||
# capacity = models.PositiveIntegerField(default=0)
|
||||
#
|
||||
# # Overrides
|
||||
# cost_override = models.DecimalField(max_digits=10, decimal_places=2,
|
||||
# blank=True, null=True)
|
||||
# hours_override = models.DecimalField(max_digits=5, decimal_places=2,
|
||||
# blank=True, null=True)
|
||||
#
|
||||
# # Metadata
|
||||
# created_at = models.DateTimeField(auto_now_add=True)
|
||||
# created_by = models.ForeignKey(
|
||||
# settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
|
||||
# null=True, blank=True, related_name='created_training_sessions'
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# db_table = 'hr_training_session'
|
||||
# ordering = ['-start_at']
|
||||
# indexes = [
|
||||
# models.Index(fields=['tenant', 'start_at']),
|
||||
# models.Index(fields=['tenant', 'program']),
|
||||
# ]
|
||||
# constraints = [
|
||||
# models.CheckConstraint(
|
||||
# check=models.Q(end_at__gt=models.F('start_at')),
|
||||
# name='session_end_after_start'
|
||||
# ),
|
||||
# ]
|
||||
#
|
||||
# def __str__(self):
|
||||
# return self.title or f'{self.program.name} @ {self.start_at:%Y-%m-%d}'
|
||||
#
|
||||
#
|
||||
# class TrainingRecord(models.Model):
|
||||
# """
|
||||
# Enrollment/participation record (renamed semantic, kept class name).
|
||||
# Each row = an employee participating in a specific session of a program.
|
||||
# """
|
||||
# STATUS_CHOICES = [
|
||||
# ('SCHEDULED', 'Scheduled'),
|
||||
# ('IN_PROGRESS', 'In Progress'),
|
||||
# ('COMPLETED', 'Completed'),
|
||||
# ('CANCELLED', 'Cancelled'),
|
||||
# ('NO_SHOW', 'No Show'),
|
||||
# ('FAILED', 'Failed'),
|
||||
# ('WAITLISTED', 'Waitlisted'),
|
||||
# ]
|
||||
#
|
||||
# tenant = models.ForeignKey(
|
||||
# Tenant, on_delete=models.CASCADE, related_name='training_records'
|
||||
# )
|
||||
# record_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
|
||||
#
|
||||
# # Core links
|
||||
# employee = models.ForeignKey(
|
||||
# Employee, on_delete=models.CASCADE, related_name='training_records'
|
||||
# )
|
||||
# program = models.ForeignKey(
|
||||
# TrainingPrograms, on_delete=models.PROTECT, related_name='training_records'
|
||||
# )
|
||||
# session = models.ForeignKey(
|
||||
# TrainingSession, on_delete=models.PROTECT, related_name='enrollments',
|
||||
# help_text='The specific run the employee is enrolled in.'
|
||||
# )
|
||||
#
|
||||
# # Timeline
|
||||
# enrolled_at = models.DateTimeField(auto_now_add=True)
|
||||
# started_at = models.DateTimeField(blank=True, null=True)
|
||||
# completion_date = models.DateField(blank=True, null=True)
|
||||
#
|
||||
# # Outcomes
|
||||
# status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='SCHEDULED')
|
||||
# credits_earned = models.DecimalField(max_digits=5, decimal_places=2,
|
||||
# default=Decimal('0.00'))
|
||||
# score = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True)
|
||||
# passed = models.BooleanField(default=False)
|
||||
#
|
||||
# # Notes/Cost
|
||||
# notes = models.TextField(blank=True, null=True)
|
||||
# cost_paid = models.DecimalField(max_digits=10, decimal_places=2,
|
||||
# blank=True, null=True)
|
||||
#
|
||||
# created_at = models.DateTimeField(auto_now_add=True)
|
||||
# updated_at = models.DateTimeField(auto_now=True)
|
||||
# created_by = models.ForeignKey(
|
||||
# settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
|
||||
# null=True, blank=True, related_name='created_training_records'
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# db_table = 'hr_training_record'
|
||||
# verbose_name = 'Training Enrollment'
|
||||
# verbose_name_plural = 'Training Enrollments'
|
||||
# ordering = ['-enrolled_at']
|
||||
# unique_together = [('employee', 'session')]
|
||||
# indexes = [
|
||||
# models.Index(fields=['tenant', 'employee']),
|
||||
# models.Index(fields=['tenant', 'program']),
|
||||
# models.Index(fields=['tenant', 'session']),
|
||||
# models.Index(fields=['tenant', 'status']),
|
||||
# models.Index(fields=['tenant', 'completion_date']),
|
||||
# ]
|
||||
#
|
||||
# def clean(self):
|
||||
# # Tenancy alignment
|
||||
# if self.program and self.tenant_id != self.program.tenant_id:
|
||||
# raise ValidationError(_('Tenant mismatch between record and program.'))
|
||||
# if self.session and self.tenant_id != self.session.tenant_id:
|
||||
# raise ValidationError(_('Tenant mismatch between record and session.'))
|
||||
# if self.employee and self.tenant_id != self.employee.tenant_id:
|
||||
# raise ValidationError(_('Tenant mismatch between record and employee.'))
|
||||
#
|
||||
# # Prevent enrolling into sessions of a different program (shouldn’t happen)
|
||||
# if self.session and self.program and self.session.program_id != self.program_id:
|
||||
# raise ValidationError(_('Session does not belong to the selected program.'))
|
||||
#
|
||||
# if self.completion_date and self.status not in ('COMPLETED', 'FAILED'):
|
||||
# raise ValidationError(_('Completion date requires status COMPLETED or FAILED.'))
|
||||
#
|
||||
# def __str__(self):
|
||||
# return f'{self.employee} → {self.program.name} ({self.get_status_display()})'
|
||||
#
|
||||
# # Helper properties
|
||||
# @property
|
||||
# def hours(self):
|
||||
# return self.session.hours_override or self.program.duration_hours
|
||||
#
|
||||
# @property
|
||||
# def effective_cost(self):
|
||||
# return self.cost_paid if self.cost_paid is not None else \
|
||||
# (self.session.cost_override if self.session.cost_override is not None
|
||||
# else self.program.cost)
|
||||
#
|
||||
# @property
|
||||
# def eligible_for_certificate(self):
|
||||
# return self.status == 'COMPLETED' and self.passed and self.program.is_certified
|
||||
#
|
||||
#
|
||||
# class SessionAttendance(models.Model):
|
||||
# """
|
||||
# Optional check-in/out per participant per session (or per day if multi-day).
|
||||
# If you want per-day granularity, add a "session_day" field.
|
||||
# """
|
||||
# ATTENDANCE_STATUS = [
|
||||
# ('PRESENT', 'Present'),
|
||||
# ('LATE', 'Late'),
|
||||
# ('ABSENT', 'Absent'),
|
||||
# ('EXCUSED', 'Excused'),
|
||||
# ]
|
||||
#
|
||||
# enrollment = models.ForeignKey(
|
||||
# TrainingRecord, on_delete=models.CASCADE, related_name='attendance'
|
||||
# )
|
||||
# checked_in_at = models.DateTimeField(blank=True, null=True)
|
||||
# checked_out_at = models.DateTimeField(blank=True, null=True)
|
||||
# status = models.CharField(max_length=10, choices=ATTENDANCE_STATUS, default='PRESENT')
|
||||
# notes = models.CharField(max_length=255, blank=True, null=True)
|
||||
#
|
||||
# class Meta:
|
||||
# db_table = 'hr_training_attendance'
|
||||
# ordering = ['enrollment_id', 'checked_in_at']
|
||||
# indexes = [models.Index(fields=['enrollment'])]
|
||||
#
|
||||
#
|
||||
# class SessionAssessment(models.Model):
|
||||
# """
|
||||
# Optional evaluation (quiz/exam) tied to an enrollment.
|
||||
# """
|
||||
# enrollment = models.ForeignKey(
|
||||
# TrainingRecord, on_delete=models.CASCADE, related_name='assessments'
|
||||
# )
|
||||
# name = models.CharField(max_length=200)
|
||||
# max_score = models.DecimalField(max_digits=7, decimal_places=2, default=100)
|
||||
# score = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True)
|
||||
# passed = models.BooleanField(default=False)
|
||||
# taken_at = models.DateTimeField(blank=True, null=True)
|
||||
#
|
||||
# class Meta:
|
||||
# db_table = 'hr_training_assessment'
|
||||
# ordering = ['-taken_at']
|
||||
# indexes = [models.Index(fields=['enrollment'])]
|
||||
#
|
||||
#
|
||||
# class TrainingCertificates(models.Model):
|
||||
# """
|
||||
# Issued certificates on completion.
|
||||
# Usually tied to a program and the enrollment that produced it.
|
||||
# """
|
||||
# tenant = models.ForeignKey(
|
||||
# Tenant, on_delete=models.CASCADE, related_name='training_certificates'
|
||||
# )
|
||||
# certificate_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
|
||||
#
|
||||
# program = models.ForeignKey(
|
||||
# TrainingPrograms, on_delete=models.PROTECT, related_name='certificates'
|
||||
# )
|
||||
# employee = models.ForeignKey(
|
||||
# Employee, on_delete=models.CASCADE, related_name='training_certificates'
|
||||
# )
|
||||
# enrollment = models.OneToOneField(
|
||||
# TrainingRecord, on_delete=models.CASCADE, related_name='certificate',
|
||||
# help_text='The enrollment that generated this certificate.'
|
||||
# )
|
||||
#
|
||||
# certificate_name = models.CharField(max_length=200)
|
||||
# certificate_number = models.CharField(max_length=50, blank=True, null=True)
|
||||
# certification_body = models.CharField(max_length=200, blank=True, null=True)
|
||||
# issued_date = models.DateField(auto_now_add=True)
|
||||
# expiry_date = models.DateField(blank=True, null=True)
|
||||
# file = models.FileField(upload_to='certificates/', blank=True, null=True)
|
||||
#
|
||||
# created_at = models.DateTimeField(auto_now_add=True)
|
||||
# created_by = models.ForeignKey(
|
||||
# settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
|
||||
# null=True, blank=True, related_name='created_training_certificates'
|
||||
# )
|
||||
#
|
||||
# class Meta:
|
||||
# db_table = 'hr_training_certificate'
|
||||
# ordering = ['-issued_date']
|
||||
# unique_together = [('employee', 'program', 'enrollment')]
|
||||
# indexes = [
|
||||
# models.Index(fields=['tenant', 'employee']),
|
||||
# models.Index(fields=['tenant', 'program']),
|
||||
# models.Index(fields=['tenant', 'expiry_date']),
|
||||
# models.Index(fields=['certificate_number']),
|
||||
# ]
|
||||
#
|
||||
# def clean(self):
|
||||
# # tenancy alignment
|
||||
# if self.program and self.tenant_id != self.program.tenant_id:
|
||||
# raise ValidationError(_('Tenant mismatch between certificate and program.'))
|
||||
# if self.employee and self.tenant_id != self.employee.tenant_id:
|
||||
# raise ValidationError(_('Tenant mismatch between certificate and employee.'))
|
||||
# if self.enrollment and self.tenant_id != self.enrollment.tenant_id:
|
||||
# raise ValidationError(_('Tenant mismatch between certificate and enrollment.'))
|
||||
# if self.enrollment and self.enrollment.program_id != self.program_id:
|
||||
# raise ValidationError(_('Enrollment does not belong to this program.'))
|
||||
#
|
||||
# def __str__(self):
|
||||
# return f'{self.certificate_name} - {self.employee}'
|
||||
#
|
||||
# @property
|
||||
# def is_expired(self):
|
||||
# return bool(self.expiry_date and self.expiry_date < date.today())
|
||||
#
|
||||
# @property
|
||||
# def days_to_expiry(self):
|
||||
# return (self.expiry_date - date.today()).days if self.expiry_date else None
|
||||
#
|
||||
# @classmethod
|
||||
# def compute_expiry(cls, program: TrainingPrograms, issued_on: date) -> date | None:
|
||||
# if program.is_certified and program.validity_days:
|
||||
# return issued_on + timedelta(days=program.validity_days)
|
||||
# return None
|
||||
|
||||
|
||||
|
||||
@ -65,10 +65,12 @@ urlpatterns = [
|
||||
path('reviews/<int:pk>/', views.PerformanceReviewDetailView.as_view(), name='performance_review_detail'),
|
||||
path('reviews/<int:pk>/update/', views.PerformanceReviewUpdateView.as_view(), name='performance_review_update'),
|
||||
path('reviews/<int:pk>/delete/', views.PerformanceReviewDeleteView.as_view(), name='performance_review_delete'),
|
||||
path('reviews/<int:review_id>/complete/', views.complete_performance_review, name='complete_performance_review'),
|
||||
|
||||
# ============================================================================
|
||||
# TRAINING RECORD URLS (FULL CRUD - Operational Data)
|
||||
# ============================================================================
|
||||
path('training-management', views.TrainingManagementView.as_view(), name='training_management'),
|
||||
path('training/', views.TrainingRecordListView.as_view(), name='training_record_list'),
|
||||
path('training/create/', views.TrainingRecordCreateView.as_view(), name='training_record_create'),
|
||||
path('training/<int:pk>/', views.TrainingRecordDetailView.as_view(), name='training_record_detail'),
|
||||
@ -88,6 +90,7 @@ urlpatterns = [
|
||||
path('clock-out/', views.clock_out, name='clock_out'),
|
||||
path('time-entries/<int:entry_id>/approve/', views.approve_time_entry, name='approve_time_entry'),
|
||||
path('schedules/<int:schedule_id>/publish/', views.publish_schedule, name='publish_schedule'),
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# API ENDPOINTS
|
||||
|
||||
324
hr/views.py
324
hr/views.py
@ -28,10 +28,6 @@ from .forms import (
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# DASHBOARD AND OVERVIEW VIEWS
|
||||
# ============================================================================
|
||||
|
||||
class HRDashboardView(LoginRequiredMixin, TemplateView):
|
||||
"""
|
||||
Main HR dashboard with comprehensive statistics and recent activity.
|
||||
@ -92,10 +88,6 @@ class HRDashboardView(LoginRequiredMixin, TemplateView):
|
||||
return context
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# EMPLOYEE VIEWS (FULL CRUD - Master Data)
|
||||
# ============================================================================
|
||||
|
||||
class EmployeeListView(LoginRequiredMixin, ListView):
|
||||
"""
|
||||
List all employees with filtering and search capabilities.
|
||||
@ -236,7 +228,7 @@ class EmployeeDeleteView(LoginRequiredMixin, DeleteView):
|
||||
Soft delete an employee record (healthcare compliance).
|
||||
"""
|
||||
model = Employee
|
||||
template_name = 'hr/employee_confirm_delete.html'
|
||||
template_name = 'hr/employees/employee_confirm_delete.html'
|
||||
success_url = reverse_lazy('hr:employee_list')
|
||||
|
||||
def get_queryset(self):
|
||||
@ -253,16 +245,12 @@ class EmployeeDeleteView(LoginRequiredMixin, DeleteView):
|
||||
return redirect(self.success_url)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# DEPARTMENT VIEWS (FULL CRUD - Master Data)
|
||||
# ============================================================================
|
||||
|
||||
class DepartmentListView(LoginRequiredMixin, ListView):
|
||||
"""
|
||||
List all departments with employee counts.
|
||||
"""
|
||||
model = Department
|
||||
template_name = 'hr/department_list.html'
|
||||
template_name = 'hr/departments/department_list.html'
|
||||
context_object_name = 'departments'
|
||||
paginate_by = 20
|
||||
|
||||
@ -286,7 +274,7 @@ class DepartmentDetailView(LoginRequiredMixin, DetailView):
|
||||
Display detailed information about a specific department.
|
||||
"""
|
||||
model = Department
|
||||
template_name = 'hr/department_detail.html'
|
||||
template_name = 'hr/departments/department_detail.html'
|
||||
context_object_name = 'department'
|
||||
|
||||
def get_queryset(self):
|
||||
@ -316,7 +304,7 @@ class DepartmentCreateView(LoginRequiredMixin, CreateView):
|
||||
"""
|
||||
model = Department
|
||||
form_class = DepartmentForm
|
||||
template_name = 'hr/department_form.html'
|
||||
template_name = 'hr/departments/department_form.html'
|
||||
success_url = reverse_lazy('hr:department_list')
|
||||
|
||||
def form_valid(self, form):
|
||||
@ -336,7 +324,7 @@ class DepartmentUpdateView(LoginRequiredMixin, UpdateView):
|
||||
"""
|
||||
model = Department
|
||||
form_class = DepartmentForm
|
||||
template_name = 'hr/department_form.html'
|
||||
template_name = 'hr/departments/department_form.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return Department.objects.filter(tenant=self.request.user.tenant)
|
||||
@ -359,7 +347,7 @@ class DepartmentDeleteView(LoginRequiredMixin, DeleteView):
|
||||
Delete a department (only if no employees assigned).
|
||||
"""
|
||||
model = Department
|
||||
template_name = 'hr/department_confirm_delete.html'
|
||||
template_name = 'hr/departments/department_confirm_delete.html'
|
||||
success_url = reverse_lazy('hr:department_list')
|
||||
|
||||
def get_queryset(self):
|
||||
@ -377,16 +365,12 @@ class DepartmentDeleteView(LoginRequiredMixin, DeleteView):
|
||||
return super().delete(request, *args, **kwargs)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# SCHEDULE VIEWS (LIMITED CRUD - Operational Data)
|
||||
# ============================================================================
|
||||
|
||||
class ScheduleListView(LoginRequiredMixin, ListView):
|
||||
"""
|
||||
List all schedules with filtering capabilities.
|
||||
"""
|
||||
model = Schedule
|
||||
template_name = 'hr/schedule_list.html'
|
||||
template_name = 'hr/schedules/schedule_list.html'
|
||||
context_object_name = 'schedules'
|
||||
paginate_by = 20
|
||||
|
||||
@ -414,7 +398,7 @@ class ScheduleDetailView(LoginRequiredMixin, DetailView):
|
||||
Display detailed information about a specific schedule.
|
||||
"""
|
||||
model = Schedule
|
||||
template_name = 'hr/schedule_detail.html'
|
||||
template_name = 'hr/schedules/schedule_detail.html'
|
||||
context_object_name = 'schedule'
|
||||
|
||||
def get_queryset(self):
|
||||
@ -438,7 +422,7 @@ class ScheduleCreateView(LoginRequiredMixin, CreateView):
|
||||
"""
|
||||
model = Schedule
|
||||
form_class = ScheduleForm
|
||||
template_name = 'hr/schedule_form.html'
|
||||
template_name = 'hr/schedules/schedule_form.html'
|
||||
success_url = reverse_lazy('hr:schedule_list')
|
||||
|
||||
def form_valid(self, form):
|
||||
@ -458,7 +442,7 @@ class ScheduleUpdateView(LoginRequiredMixin, UpdateView):
|
||||
"""
|
||||
model = Schedule
|
||||
form_class = ScheduleForm
|
||||
template_name = 'hr/schedule_form.html'
|
||||
template_name = 'hr/schedules/schedule_form.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return Schedule.objects.filter(employee__tenant=self.request.user.tenant)
|
||||
@ -476,16 +460,12 @@ class ScheduleUpdateView(LoginRequiredMixin, UpdateView):
|
||||
return kwargs
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# SCHEDULE ASSIGNMENT VIEWS (LIMITED CRUD - Operational Data)
|
||||
# ============================================================================
|
||||
|
||||
class ScheduleAssignmentListView(LoginRequiredMixin, ListView):
|
||||
"""
|
||||
List all schedule assignments with filtering capabilities.
|
||||
"""
|
||||
model = ScheduleAssignment
|
||||
template_name = 'hr/schedule_assignment_list.html'
|
||||
template_name = 'hr/assignments/schedule_assignment_list.html'
|
||||
context_object_name = 'assignments'
|
||||
paginate_by = 20
|
||||
|
||||
@ -521,7 +501,7 @@ class ScheduleAssignmentCreateView(LoginRequiredMixin, CreateView):
|
||||
"""
|
||||
model = ScheduleAssignment
|
||||
form_class = ScheduleAssignmentForm
|
||||
template_name = 'hr/schedule_assignment_form.html'
|
||||
template_name = 'hr/assignments/schedule_assignment_form.html'
|
||||
success_url = reverse_lazy('hr:schedule_assignment_list')
|
||||
|
||||
def form_valid(self, form):
|
||||
@ -540,7 +520,7 @@ class ScheduleAssignmentUpdateView(LoginRequiredMixin, UpdateView):
|
||||
"""
|
||||
model = ScheduleAssignment
|
||||
form_class = ScheduleAssignmentForm
|
||||
template_name = 'hr/schedule_assignment_form.html'
|
||||
template_name = 'hr/assignments/schedule_assignment_form.html'
|
||||
success_url = reverse_lazy('hr:schedule_assignment_list')
|
||||
|
||||
def get_queryset(self):
|
||||
@ -556,16 +536,12 @@ class ScheduleAssignmentUpdateView(LoginRequiredMixin, UpdateView):
|
||||
return kwargs
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# TIME ENTRY VIEWS (RESTRICTED CRUD - Operational Data)
|
||||
# ============================================================================
|
||||
|
||||
class TimeEntryListView(LoginRequiredMixin, ListView):
|
||||
"""
|
||||
List time entries with filtering capabilities.
|
||||
"""
|
||||
model = TimeEntry
|
||||
template_name = 'hr/time_entry_list.html'
|
||||
template_name = 'hr/time_entries/time_entry_list.html'
|
||||
context_object_name = 'time_entries'
|
||||
paginate_by = 20
|
||||
|
||||
@ -600,7 +576,7 @@ class TimeEntryDetailView(LoginRequiredMixin, DetailView):
|
||||
Display detailed information about a specific time entry.
|
||||
"""
|
||||
model = TimeEntry
|
||||
template_name = 'hr/time_entry_detail.html'
|
||||
template_name = 'hr/time_entries/time_entry_detail.html'
|
||||
context_object_name = 'time_entry'
|
||||
|
||||
def get_queryset(self):
|
||||
@ -613,7 +589,7 @@ class TimeEntryCreateView(LoginRequiredMixin, CreateView):
|
||||
"""
|
||||
model = TimeEntry
|
||||
form_class = TimeEntryForm
|
||||
template_name = 'hr/time_entry_form.html'
|
||||
template_name = 'hr/time_entries/time_entry_form.html'
|
||||
success_url = reverse_lazy('hr:time_entry_list')
|
||||
|
||||
def form_valid(self, form):
|
||||
@ -632,7 +608,7 @@ class TimeEntryUpdateView(LoginRequiredMixin, UpdateView):
|
||||
"""
|
||||
model = TimeEntry
|
||||
form_class = TimeEntryForm
|
||||
template_name = 'hr/time_entry_form.html'
|
||||
template_name = 'hr/time_entries/time_entry_form.html'
|
||||
success_url = reverse_lazy('hr:time_entry_list')
|
||||
|
||||
def get_queryset(self):
|
||||
@ -653,16 +629,12 @@ class TimeEntryUpdateView(LoginRequiredMixin, UpdateView):
|
||||
return kwargs
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# PERFORMANCE REVIEW VIEWS (FULL CRUD - Operational Data)
|
||||
# ============================================================================
|
||||
|
||||
class PerformanceReviewListView(LoginRequiredMixin, ListView):
|
||||
"""
|
||||
List performance reviews with filtering capabilities.
|
||||
"""
|
||||
model = PerformanceReview
|
||||
template_name = 'hr/performance_review_list.html'
|
||||
template_name = 'hr/reviews/performance_review_list.html'
|
||||
context_object_name = 'reviews'
|
||||
paginate_by = 20
|
||||
|
||||
@ -694,11 +666,80 @@ class PerformanceReviewDetailView(LoginRequiredMixin, DetailView):
|
||||
Display detailed information about a specific performance review.
|
||||
"""
|
||||
model = PerformanceReview
|
||||
template_name = 'hr/performance_review_detail.html'
|
||||
template_name = 'hr/reviews/performance_review_detail.html'
|
||||
context_object_name = 'review'
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
return PerformanceReview.objects.filter(employee__tenant=self.request.user.tenant)
|
||||
# Eager load employee + department + supervisor to reduce queries
|
||||
return (PerformanceReview.objects
|
||||
.select_related('employee', 'employee__department', 'employee__supervisor', 'reviewer')
|
||||
.filter(employee__tenant=self.request.user.tenant))
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
queryset = queryset or self.get_queryset()
|
||||
return get_object_or_404(queryset, pk=self.kwargs.get('pk') or self.kwargs.get('id'))
|
||||
|
||||
@staticmethod
|
||||
def _split_lines(text):
|
||||
if not text:
|
||||
return []
|
||||
parts = []
|
||||
for line in str(text).replace('\r', '').split('\n'):
|
||||
for piece in line.split(';'):
|
||||
piece = piece.strip()
|
||||
if piece:
|
||||
parts.append(piece)
|
||||
return parts
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
review = ctx['review']
|
||||
|
||||
# Build categories from competency_ratings JSON: { "Teamwork": 4.0, ... }
|
||||
# Make a list of dicts the template can iterate through.
|
||||
categories = []
|
||||
ratings = review.competency_ratings or {}
|
||||
# Keep a stable order (by key) to avoid chart jitter
|
||||
for name in sorted(ratings.keys()):
|
||||
try:
|
||||
score = float(ratings[name])
|
||||
except (TypeError, ValueError):
|
||||
score = 0.0
|
||||
categories.append({'name': name, 'score': score, 'comments': ''})
|
||||
|
||||
# Previous reviews (same employee, exclude current)
|
||||
previous_reviews = (
|
||||
PerformanceReview.objects
|
||||
.filter(employee=review.employee)
|
||||
.exclude(pk=review.pk)
|
||||
.order_by('-review_date')[:5]
|
||||
.select_related('employee')
|
||||
)
|
||||
|
||||
# Strengths / AFI lists for bullet rendering
|
||||
strengths_list = self._split_lines(review.strengths)
|
||||
afi_list = self._split_lines(review.areas_for_improvement)
|
||||
|
||||
# Goals blocks as lists for nicer display
|
||||
goals_achieved_list = self._split_lines(review.goals_achieved)
|
||||
goals_not_achieved_list = self._split_lines(review.goals_not_achieved)
|
||||
future_goals_list = self._split_lines(review.future_goals)
|
||||
|
||||
ctx.update({
|
||||
'categories': categories,
|
||||
'previous_reviews': previous_reviews,
|
||||
'review_strengths_list': strengths_list,
|
||||
'review_afi_list': afi_list,
|
||||
'goals_achieved_list': goals_achieved_list,
|
||||
'goals_not_achieved_list': goals_not_achieved_list,
|
||||
'future_goals_list': future_goals_list,
|
||||
})
|
||||
|
||||
# convenience on the review object (template already expects these list props sometimes)
|
||||
setattr(review, 'strengths_list', strengths_list)
|
||||
setattr(review, 'areas_for_improvement_list', afi_list)
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class PerformanceReviewCreateView(LoginRequiredMixin, CreateView):
|
||||
@ -707,7 +748,7 @@ class PerformanceReviewCreateView(LoginRequiredMixin, CreateView):
|
||||
"""
|
||||
model = PerformanceReview
|
||||
form_class = PerformanceReviewForm
|
||||
template_name = 'hr/performance_review_form.html'
|
||||
template_name = 'hr/reviews/performance_review_form.html'
|
||||
success_url = reverse_lazy('hr:performance_review_list')
|
||||
|
||||
def form_valid(self, form):
|
||||
@ -727,7 +768,7 @@ class PerformanceReviewUpdateView(LoginRequiredMixin, UpdateView):
|
||||
"""
|
||||
model = PerformanceReview
|
||||
form_class = PerformanceReviewForm
|
||||
template_name = 'hr/performance_review_form.html'
|
||||
template_name = 'hr/reviews/performance_review_form.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return PerformanceReview.objects.filter(employee__tenant=self.request.user.tenant)
|
||||
@ -750,7 +791,7 @@ class PerformanceReviewDeleteView(LoginRequiredMixin, DeleteView):
|
||||
Delete a performance review (only if not finalized).
|
||||
"""
|
||||
model = PerformanceReview
|
||||
template_name = 'hr/performance_review_confirm_delete.html'
|
||||
template_name = 'hr/reviews/performance_review_confirm_delete.html'
|
||||
success_url = reverse_lazy('hr:performance_review_list')
|
||||
|
||||
def get_queryset(self):
|
||||
@ -768,39 +809,128 @@ class PerformanceReviewDeleteView(LoginRequiredMixin, DeleteView):
|
||||
return super().delete(request, *args, **kwargs)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# TRAINING RECORD VIEWS (FULL CRUD - Operational Data)
|
||||
# ============================================================================
|
||||
class TrainingManagementView(LoginRequiredMixin, ListView):
|
||||
model = TrainingRecord
|
||||
template_name = 'hr/training/training_management.html'
|
||||
context_object_name = 'training_records'
|
||||
paginate_by = 20
|
||||
|
||||
def get_queryset(self):
|
||||
qs = (TrainingRecord.objects
|
||||
.filter(employee__tenant=self.request.user.tenant)
|
||||
.select_related('employee', 'employee__department')
|
||||
.order_by('-training_date', '-completion_date'))
|
||||
# optional GET filters (works with the template’s inputs)
|
||||
if emp := self.request.GET.get('employee'):
|
||||
qs = qs.filter(employee_id=emp)
|
||||
if ttype := self.request.GET.get('training_type'):
|
||||
qs = qs.filter(training_type=ttype)
|
||||
if status := self.request.GET.get('status'):
|
||||
qs = qs.filter(status=status)
|
||||
return qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
tenant = self.request.user.tenant
|
||||
today = timezone.now().date()
|
||||
|
||||
base = TrainingRecord.objects.filter(employee__tenant=tenant)
|
||||
|
||||
total_records = base.count()
|
||||
completed_trainings = base.filter(status='COMPLETED').count()
|
||||
pending_trainings = base.filter(status__in=['SCHEDULED', 'IN_PROGRESS']).count()
|
||||
overdue_trainings = base.filter(expiry_date__lt=today, expiry_date__isnull=False).exclude(
|
||||
status='COMPLETED').count()
|
||||
|
||||
# compliance rate = “not expired” among trainings that have an expiry_date
|
||||
with_expiry = base.filter(expiry_date__isnull=False)
|
||||
valid_now = with_expiry.filter(expiry_date__gte=today).count()
|
||||
compliance_rate = round((valid_now / with_expiry.count()) * 100, 1) if with_expiry.exists() else 100.0
|
||||
|
||||
# expiring soon = within 30 days
|
||||
expiring_soon_count = with_expiry.filter(expiry_date__gte=today,
|
||||
expiry_date__lte=today + timedelta(days=30)).count()
|
||||
|
||||
# department compliance (simple example: percent of non-expired per dept among those with expiry)
|
||||
dept_rows = []
|
||||
departments = Department.objects.filter(tenant=tenant).order_by('name')
|
||||
for d in departments:
|
||||
d_qs = with_expiry.filter(employee__department=d)
|
||||
if not d_qs.exists():
|
||||
rate = 100
|
||||
else:
|
||||
ok = d_qs.filter(expiry_date__gte=today).count()
|
||||
rate = round((ok / d_qs.count()) * 100)
|
||||
color = 'success' if rate >= 90 else 'warning' if rate >= 70 else 'danger'
|
||||
dept_rows.append({'name': d.name, 'compliance_rate': rate, 'compliance_color': color})
|
||||
|
||||
# “compliance alerts” demo (overdue/expiring soon)
|
||||
alerts = []
|
||||
for tr in base.select_related('employee'):
|
||||
if tr.expiry_date:
|
||||
if tr.expiry_date < today:
|
||||
alerts.append({
|
||||
'id': tr.id,
|
||||
'employee': tr.employee,
|
||||
'requirement': tr.training_name,
|
||||
'due_date': tr.expiry_date,
|
||||
'priority_color': 'danger',
|
||||
'urgency_color': 'danger',
|
||||
'get_priority_display': 'Overdue',
|
||||
})
|
||||
elif today <= tr.expiry_date <= today + timedelta(days=30):
|
||||
alerts.append({
|
||||
'id': tr.id,
|
||||
'employee': tr.employee,
|
||||
'requirement': tr.training_name,
|
||||
'due_date': tr.expiry_date,
|
||||
'priority_color': 'warning',
|
||||
'urgency_color': 'warning',
|
||||
'get_priority_display': 'Expiring Soon',
|
||||
})
|
||||
|
||||
ctx.update({
|
||||
'total_records': total_records,
|
||||
'completed_trainings': completed_trainings,
|
||||
'pending_trainings': pending_trainings,
|
||||
'overdue_trainings': overdue_trainings,
|
||||
'departments': departments,
|
||||
'compliance_rate': compliance_rate,
|
||||
'expiring_soon_count': expiring_soon_count,
|
||||
'department_compliance': dept_rows,
|
||||
'compliance_alerts': alerts,
|
||||
})
|
||||
return ctx
|
||||
|
||||
class TrainingRecordListView(LoginRequiredMixin, ListView):
|
||||
"""
|
||||
List training records with filtering capabilities.
|
||||
"""
|
||||
List training records with filtering capabilities.
|
||||
"""
|
||||
model = TrainingRecord
|
||||
template_name = 'hr/training_record_list.html'
|
||||
template_name = 'hr/training/training_record_list.html'
|
||||
context_object_name = 'training_records'
|
||||
paginate_by = 20
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = TrainingRecord.objects.filter(
|
||||
employee__tenant=self.request.user.tenant
|
||||
).select_related('employee')
|
||||
|
||||
|
||||
# Filter by employee
|
||||
employee = self.request.GET.get('employee')
|
||||
if employee:
|
||||
queryset = queryset.filter(employee_id=employee)
|
||||
|
||||
|
||||
# Filter by training type
|
||||
training_type = self.request.GET.get('training_type')
|
||||
if training_type:
|
||||
queryset = queryset.filter(training_type=training_type)
|
||||
|
||||
|
||||
# Filter by completion status
|
||||
status = self.request.GET.get('status')
|
||||
if status:
|
||||
queryset = queryset.filter(status=status)
|
||||
|
||||
|
||||
return queryset.order_by('-completion_date')
|
||||
|
||||
|
||||
@ -809,8 +939,8 @@ class TrainingRecordDetailView(LoginRequiredMixin, DetailView):
|
||||
Display detailed information about a specific training record.
|
||||
"""
|
||||
model = TrainingRecord
|
||||
template_name = 'hr/training_record_detail.html'
|
||||
context_object_name = 'training_record'
|
||||
template_name = 'hr/training/training_record_detail.html'
|
||||
context_object_name = 'record'
|
||||
|
||||
def get_queryset(self):
|
||||
return TrainingRecord.objects.filter(employee__tenant=self.request.user.tenant)
|
||||
@ -822,7 +952,7 @@ class TrainingRecordCreateView(LoginRequiredMixin, CreateView):
|
||||
"""
|
||||
model = TrainingRecord
|
||||
form_class = TrainingRecordForm
|
||||
template_name = 'hr/training_record_form.html'
|
||||
template_name = 'hr/training/training_record_form.html'
|
||||
success_url = reverse_lazy('hr:training_record_list')
|
||||
|
||||
def form_valid(self, form):
|
||||
@ -842,7 +972,7 @@ class TrainingRecordUpdateView(LoginRequiredMixin, UpdateView):
|
||||
"""
|
||||
model = TrainingRecord
|
||||
form_class = TrainingRecordForm
|
||||
template_name = 'hr/training_record_form.html'
|
||||
template_name = 'hr/training/training_record_form.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return TrainingRecord.objects.filter(employee__tenant=self.request.user.tenant)
|
||||
@ -865,7 +995,7 @@ class TrainingRecordDeleteView(LoginRequiredMixin, DeleteView):
|
||||
Delete a training record.
|
||||
"""
|
||||
model = TrainingRecord
|
||||
template_name = 'hr/training_record_confirm_delete.html'
|
||||
template_name = 'hr/training/training_record_confirm_delete.html'
|
||||
success_url = reverse_lazy('hr:training_record_list')
|
||||
|
||||
def get_queryset(self):
|
||||
@ -876,9 +1006,15 @@ class TrainingRecordDeleteView(LoginRequiredMixin, DeleteView):
|
||||
return super().delete(request, *args, **kwargs)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# HTMX VIEWS FOR REAL-TIME UPDATES
|
||||
# ============================================================================
|
||||
@login_required
|
||||
def complete_performance_review(request, review_id):
|
||||
review = get_object_or_404(PerformanceReview, pk=review_id)
|
||||
review.status = 'COMPLETED'
|
||||
review.completed_by = request.user
|
||||
review.save()
|
||||
|
||||
messages.success(request, 'Performance review completed successfully.')
|
||||
return redirect('hr:performance_review_detail', pk=review.pk)
|
||||
|
||||
@login_required
|
||||
def hr_stats(request):
|
||||
@ -961,10 +1097,6 @@ def attendance_summary(request):
|
||||
return render(request, 'hr/partials/attendance_summary.html', context)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# ACTION VIEWS FOR WORKFLOW OPERATIONS
|
||||
# ============================================================================
|
||||
|
||||
@login_required
|
||||
def clock_in(request):
|
||||
"""
|
||||
@ -1093,7 +1225,7 @@ def approve_time_entry(request, entry_id):
|
||||
next_url = request.POST.get('next', reverse('hr:time_entry_detail', kwargs={'pk': time_entry.id}))
|
||||
return redirect(next_url)
|
||||
|
||||
return render(request, 'hr/time_entry_approve.html', {
|
||||
return render(request, 'hr/time_entries/time_entry_approve.html', {
|
||||
'time_entry': time_entry
|
||||
})
|
||||
|
||||
@ -1123,15 +1255,11 @@ def publish_schedule(request, schedule_id):
|
||||
messages.success(request, 'Schedule published successfully.')
|
||||
return redirect('hr:schedule_detail', pk=schedule.id)
|
||||
|
||||
return render(request, 'hr/schedule_publish.html', {
|
||||
return render(request, 'hr/schedules/schedule_publish.html', {
|
||||
'schedule': schedule
|
||||
})
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# API ENDPOINTS
|
||||
# ============================================================================
|
||||
|
||||
@login_required
|
||||
def api_employee_list(request):
|
||||
"""
|
||||
@ -1155,6 +1283,40 @@ def api_department_list(request):
|
||||
|
||||
return JsonResponse({'departments': list(departments)})
|
||||
|
||||
|
||||
# Query patterns to use if needed
|
||||
# # All upcoming sessions for a tenant (next 30 days)
|
||||
# TrainingSession.objects.filter(
|
||||
# tenant=request.user.tenant, start_at__gte=timezone.now(),
|
||||
# start_at__lte=timezone.now() + timedelta(days=30)
|
||||
# ).select_related('program', 'instructor')
|
||||
#
|
||||
# # Employees due for renewal in 30 days
|
||||
# TrainingCertificates.objects.filter(
|
||||
# tenant=request.user.tenant,
|
||||
# expiry_date__lte=date.today() + timedelta(days=30),
|
||||
# expiry_date__gte=date.today()
|
||||
# ).select_related('employee', 'program')
|
||||
#
|
||||
# # Enroll an employee, respecting capacity
|
||||
# session = TrainingSession.objects.select_for_update().get(pk=session_pk, tenant=tenant)
|
||||
# if session.capacity and session.enrollments.count() >= session.capacity:
|
||||
# status = 'WAITLISTED'
|
||||
# else:
|
||||
# status = 'SCHEDULED'
|
||||
# enrollment = TrainingRecord.objects.create(
|
||||
# tenant=tenant, employee=emp, program=session.program, session=session,
|
||||
# status=status, created_by=request.user
|
||||
# )
|
||||
#
|
||||
# # Mark completion + pass (auto-certificate will fire via signal)
|
||||
# enrollment.status = 'COMPLETED'
|
||||
# enrollment.passed = True
|
||||
# enrollment.completion_date = date.today()
|
||||
# enrollment.credits_earned = enrollment.hours
|
||||
# enrollment.save()
|
||||
|
||||
|
||||
#
|
||||
# from django.shortcuts import render, redirect, get_object_or_404
|
||||
# from django.views.generic import (
|
||||
|
||||
BIN
inpatients/__pycache__/flows.cpython-312.pyc
Normal file
BIN
inpatients/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
757
inpatients/flows.py
Normal file
757
inpatients/flows.py
Normal file
@ -0,0 +1,757 @@
|
||||
# """
|
||||
# Viewflow workflows for inpatients app.
|
||||
# Provides admission, transfer, and discharge workflows.
|
||||
# """
|
||||
#
|
||||
# from viewflow import Flow, lock
|
||||
# from viewflow.base import this, flow_func
|
||||
# from viewflow.contrib import celery
|
||||
# from viewflow.decorators import flow_view
|
||||
# from viewflow.fields import CharField, ModelField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.models import Process, Task
|
||||
# from django.contrib.auth.models import User
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import Admission, Transfer, DischargeSummary, Ward, Bed
|
||||
# from .views import (
|
||||
# AdmissionRegistrationView, BedAssignmentView, MedicalAssessmentView,
|
||||
# NursingAssessmentView, AdmissionOrdersView, TransferRequestView,
|
||||
# TransferApprovalView, TransferExecutionView, DischargeInitiationView,
|
||||
# DischargePlanningView, DischargeExecutionView
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class AdmissionProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for patient admissions
|
||||
# """
|
||||
# admission = ModelField(Admission, help_text='Associated admission record')
|
||||
#
|
||||
# # Process status tracking
|
||||
# registration_completed = models.BooleanField(default=False)
|
||||
# bed_assigned = models.BooleanField(default=False)
|
||||
# medical_assessment_completed = models.BooleanField(default=False)
|
||||
# nursing_assessment_completed = models.BooleanField(default=False)
|
||||
# orders_written = models.BooleanField(default=False)
|
||||
# admission_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Admission Process'
|
||||
# verbose_name_plural = 'Admission Processes'
|
||||
#
|
||||
#
|
||||
# class AdmissionFlow(Flow):
|
||||
# """
|
||||
# Hospital Patient Admission Workflow
|
||||
#
|
||||
# This flow manages the complete patient admission process from
|
||||
# initial registration through final admission completion.
|
||||
# """
|
||||
#
|
||||
# process_class = AdmissionProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_admission)
|
||||
# .Next(this.patient_registration)
|
||||
# )
|
||||
#
|
||||
# patient_registration = (
|
||||
# flow_view(AdmissionRegistrationView)
|
||||
# .Permission('inpatients.can_register_admissions')
|
||||
# .Next(this.check_bed_availability)
|
||||
# )
|
||||
#
|
||||
# check_bed_availability = (
|
||||
# flow_func(this.check_beds)
|
||||
# .Next(this.bed_assignment)
|
||||
# )
|
||||
#
|
||||
# bed_assignment = (
|
||||
# flow_view(BedAssignmentView)
|
||||
# .Permission('inpatients.can_assign_beds')
|
||||
# .Next(this.parallel_assessments)
|
||||
# )
|
||||
#
|
||||
# parallel_assessments = (
|
||||
# flow_func(this.start_parallel_assessments)
|
||||
# .Next(this.medical_assessment)
|
||||
# .Next(this.nursing_assessment)
|
||||
# )
|
||||
#
|
||||
# medical_assessment = (
|
||||
# flow_view(MedicalAssessmentView)
|
||||
# .Permission('inpatients.can_perform_medical_assessment')
|
||||
# .Next(this.join_assessments)
|
||||
# )
|
||||
#
|
||||
# nursing_assessment = (
|
||||
# flow_view(NursingAssessmentView)
|
||||
# .Permission('inpatients.can_perform_nursing_assessment')
|
||||
# .Next(this.join_assessments)
|
||||
# )
|
||||
#
|
||||
# join_assessments = (
|
||||
# flow_func(this.join_parallel_assessments)
|
||||
# .Next(this.write_admission_orders)
|
||||
# )
|
||||
#
|
||||
# write_admission_orders = (
|
||||
# flow_view(AdmissionOrdersView)
|
||||
# .Permission('inpatients.can_write_orders')
|
||||
# .Next(this.finalize_admission)
|
||||
# )
|
||||
#
|
||||
# finalize_admission = (
|
||||
# flow_func(this.complete_admission)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_admission)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_admission(self, activation):
|
||||
# """Initialize the admission process"""
|
||||
# process = activation.process
|
||||
# admission = process.admission
|
||||
#
|
||||
# # Update admission status
|
||||
# admission.status = 'PENDING'
|
||||
# admission.save()
|
||||
#
|
||||
# # Send notification to registration staff
|
||||
# self.notify_registration_staff(admission)
|
||||
#
|
||||
# def check_beds(self, activation):
|
||||
# """Check bed availability for the patient"""
|
||||
# process = activation.process
|
||||
# admission = process.admission
|
||||
#
|
||||
# # Check if beds are available in the requested ward
|
||||
# available_beds = Bed.objects.filter(
|
||||
# ward=admission.current_ward,
|
||||
# status='AVAILABLE'
|
||||
# ).count()
|
||||
#
|
||||
# if available_beds == 0:
|
||||
# # Handle no beds available - could trigger waiting list
|
||||
# admission.admission_notes += f"\nNo beds available in {admission.current_ward.name} at {timezone.now()}"
|
||||
# admission.save()
|
||||
#
|
||||
# # Notify bed management
|
||||
# self.notify_bed_shortage(admission)
|
||||
#
|
||||
# def start_parallel_assessments(self, activation):
|
||||
# """Start parallel medical and nursing assessments"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Create assessment tasks
|
||||
# self.create_assessment_tasks(process)
|
||||
#
|
||||
# def join_parallel_assessments(self, activation):
|
||||
# """Wait for both assessments to complete"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Check if both assessments are completed
|
||||
# if (process.medical_assessment_completed and
|
||||
# process.nursing_assessment_completed):
|
||||
#
|
||||
# # Notify physician to write orders
|
||||
# self.notify_physician_for_orders(process.admission)
|
||||
#
|
||||
# def complete_admission(self, activation):
|
||||
# """Finalize the admission process"""
|
||||
# process = activation.process
|
||||
# admission = process.admission
|
||||
#
|
||||
# # Update admission status
|
||||
# admission.status = 'ADMITTED'
|
||||
# admission.save()
|
||||
#
|
||||
# # Update bed status
|
||||
# if admission.current_bed:
|
||||
# admission.current_bed.status = 'OCCUPIED'
|
||||
# admission.current_bed.current_patient = admission.patient
|
||||
# admission.current_bed.current_admission = admission
|
||||
# admission.current_bed.occupied_since = timezone.now()
|
||||
# admission.current_bed.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.admission_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_admission_complete(admission)
|
||||
#
|
||||
# def end_admission(self, activation):
|
||||
# """End the admission workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate admission summary report
|
||||
# self.generate_admission_summary(process.admission)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_registration_staff(self, admission):
|
||||
# """Notify registration staff of new admission"""
|
||||
# from django.contrib.auth.models import Group
|
||||
#
|
||||
# registration_staff = User.objects.filter(
|
||||
# groups__name='Registration Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in registration_staff:
|
||||
# send_mail(
|
||||
# subject=f'New Admission: {admission.patient.get_full_name()}',
|
||||
# message=f'A new {admission.get_admission_type_display()} admission has been initiated.',
|
||||
# from_email='admissions@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_bed_shortage(self, admission):
|
||||
# """Notify bed management of shortage"""
|
||||
# from django.contrib.auth.models import Group
|
||||
#
|
||||
# bed_managers = User.objects.filter(
|
||||
# groups__name='Bed Management'
|
||||
# )
|
||||
#
|
||||
# for manager in bed_managers:
|
||||
# send_mail(
|
||||
# subject=f'Bed Shortage Alert - {admission.current_ward.name}',
|
||||
# message=f'No beds available for admission of {admission.patient.get_full_name()}.',
|
||||
# from_email='admissions@hospital.com',
|
||||
# recipient_list=[manager.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_assessment_tasks(self, process):
|
||||
# """Create assessment tasks for medical and nursing staff"""
|
||||
# # This would create tasks in a task management system
|
||||
# pass
|
||||
#
|
||||
# def notify_physician_for_orders(self, admission):
|
||||
# """Notify attending physician to write orders"""
|
||||
# if admission.attending_physician and admission.attending_physician.email:
|
||||
# send_mail(
|
||||
# subject=f'Admission Orders Required: {admission.patient.get_full_name()}',
|
||||
# message=f'Please write admission orders. Both assessments are complete.',
|
||||
# from_email='admissions@hospital.com',
|
||||
# recipient_list=[admission.attending_physician.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_admission_complete(self, admission):
|
||||
# """Notify relevant staff that admission is complete"""
|
||||
# # Notify nursing staff
|
||||
# nursing_staff = User.objects.filter(
|
||||
# groups__name='Nursing Staff',
|
||||
# profile__department=admission.current_ward
|
||||
# )
|
||||
#
|
||||
# for nurse in nursing_staff:
|
||||
# send_mail(
|
||||
# subject=f'New Patient Admitted: {admission.patient.get_full_name()}',
|
||||
# message=f'Patient has been admitted to {admission.current_bed.room_number if admission.current_bed else "TBD"}.',
|
||||
# from_email='admissions@hospital.com',
|
||||
# recipient_list=[nurse.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_admission_summary(self, admission):
|
||||
# """Generate admission summary report"""
|
||||
# # This would generate a comprehensive admission report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class TransferProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for patient transfers
|
||||
# """
|
||||
# transfer = ModelField(Transfer, help_text='Associated transfer record')
|
||||
#
|
||||
# # Process status tracking
|
||||
# request_approved = models.BooleanField(default=False)
|
||||
# bed_prepared = models.BooleanField(default=False)
|
||||
# transport_arranged = models.BooleanField(default=False)
|
||||
# handoff_completed = models.BooleanField(default=False)
|
||||
# transfer_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Transfer Process'
|
||||
# verbose_name_plural = 'Transfer Processes'
|
||||
#
|
||||
#
|
||||
# class TransferFlow(Flow):
|
||||
# """
|
||||
# Hospital Patient Transfer Workflow
|
||||
#
|
||||
# This flow manages patient transfers between wards, beds, or units.
|
||||
# """
|
||||
#
|
||||
# process_class = TransferProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_transfer)
|
||||
# .Next(this.transfer_request)
|
||||
# )
|
||||
#
|
||||
# transfer_request = (
|
||||
# flow_view(TransferRequestView)
|
||||
# .Permission('inpatients.can_request_transfers')
|
||||
# .Next(this.transfer_approval)
|
||||
# )
|
||||
#
|
||||
# transfer_approval = (
|
||||
# flow_view(TransferApprovalView)
|
||||
# .Permission('inpatients.can_approve_transfers')
|
||||
# .Next(this.prepare_destination)
|
||||
# )
|
||||
#
|
||||
# prepare_destination = (
|
||||
# flow_func(this.prepare_bed)
|
||||
# .Next(this.arrange_transport)
|
||||
# )
|
||||
#
|
||||
# arrange_transport = (
|
||||
# flow_func(this.schedule_transport)
|
||||
# .Next(this.execute_transfer)
|
||||
# )
|
||||
#
|
||||
# execute_transfer = (
|
||||
# flow_view(TransferExecutionView)
|
||||
# .Permission('inpatients.can_execute_transfers')
|
||||
# .Next(this.complete_transfer)
|
||||
# )
|
||||
#
|
||||
# complete_transfer = (
|
||||
# flow_func(this.finalize_transfer)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_transfer)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_transfer(self, activation):
|
||||
# """Initialize the transfer process"""
|
||||
# process = activation.process
|
||||
# transfer = process.transfer
|
||||
#
|
||||
# # Update transfer status
|
||||
# transfer.status = 'REQUESTED'
|
||||
# transfer.save()
|
||||
#
|
||||
# # Notify receiving ward
|
||||
# self.notify_receiving_ward(transfer)
|
||||
#
|
||||
# def prepare_bed(self, activation):
|
||||
# """Prepare destination bed"""
|
||||
# process = activation.process
|
||||
# transfer = process.transfer
|
||||
#
|
||||
# if transfer.to_bed:
|
||||
# # Reserve the destination bed
|
||||
# transfer.to_bed.status = 'RESERVED'
|
||||
# transfer.to_bed.reserved_until = timezone.now() + timezone.timedelta(hours=2)
|
||||
# transfer.to_bed.save()
|
||||
#
|
||||
# process.bed_prepared = True
|
||||
# process.save()
|
||||
#
|
||||
# def schedule_transport(self, activation):
|
||||
# """Schedule patient transport"""
|
||||
# process = activation.process
|
||||
# transfer = process.transfer
|
||||
#
|
||||
# # Auto-schedule transport based on priority
|
||||
# if transfer.priority in ['EMERGENT', 'STAT']:
|
||||
# transfer.scheduled_datetime = timezone.now() + timezone.timedelta(minutes=15)
|
||||
# elif transfer.priority == 'URGENT':
|
||||
# transfer.scheduled_datetime = timezone.now() + timezone.timedelta(hours=1)
|
||||
# else:
|
||||
# transfer.scheduled_datetime = timezone.now() + timezone.timedelta(hours=4)
|
||||
#
|
||||
# transfer.save()
|
||||
#
|
||||
# process.transport_arranged = True
|
||||
# process.save()
|
||||
#
|
||||
# # Notify transport team
|
||||
# self.notify_transport_team(transfer)
|
||||
#
|
||||
# def finalize_transfer(self, activation):
|
||||
# """Finalize the transfer process"""
|
||||
# process = activation.process
|
||||
# transfer = process.transfer
|
||||
# admission = transfer.admission
|
||||
#
|
||||
# # Update admission location
|
||||
# admission.current_ward = transfer.to_ward
|
||||
# admission.current_bed = transfer.to_bed
|
||||
# admission.save()
|
||||
#
|
||||
# # Update bed statuses
|
||||
# if transfer.from_bed:
|
||||
# transfer.from_bed.status = 'AVAILABLE'
|
||||
# transfer.from_bed.current_patient = None
|
||||
# transfer.from_bed.current_admission = None
|
||||
# transfer.from_bed.occupied_since = None
|
||||
# transfer.from_bed.save()
|
||||
#
|
||||
# if transfer.to_bed:
|
||||
# transfer.to_bed.status = 'OCCUPIED'
|
||||
# transfer.to_bed.current_patient = transfer.patient
|
||||
# transfer.to_bed.current_admission = admission
|
||||
# transfer.to_bed.occupied_since = timezone.now()
|
||||
# transfer.to_bed.save()
|
||||
#
|
||||
# # Update transfer status
|
||||
# transfer.status = 'COMPLETED'
|
||||
# transfer.actual_datetime = timezone.now()
|
||||
# transfer.save()
|
||||
#
|
||||
# process.transfer_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_transfer_complete(transfer)
|
||||
#
|
||||
# def end_transfer(self, activation):
|
||||
# """End the transfer workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate transfer summary
|
||||
# self.generate_transfer_summary(process.transfer)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_receiving_ward(self, transfer):
|
||||
# """Notify receiving ward of incoming transfer"""
|
||||
# receiving_staff = User.objects.filter(
|
||||
# groups__name='Nursing Staff',
|
||||
# profile__department=transfer.to_ward
|
||||
# )
|
||||
#
|
||||
# for staff in receiving_staff:
|
||||
# send_mail(
|
||||
# subject=f'Incoming Transfer: {transfer.patient.get_full_name()}',
|
||||
# message=f'Patient transfer requested from {transfer.from_ward.name}.',
|
||||
# from_email='transfers@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_transport_team(self, transfer):
|
||||
# """Notify transport team of scheduled transfer"""
|
||||
# transport_staff = User.objects.filter(
|
||||
# groups__name='Transport Team'
|
||||
# )
|
||||
#
|
||||
# for staff in transport_staff:
|
||||
# send_mail(
|
||||
# subject=f'Transport Scheduled: {transfer.patient.get_full_name()}',
|
||||
# message=f'Transfer scheduled for {transfer.scheduled_datetime}.',
|
||||
# from_email='transport@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_transfer_complete(self, transfer):
|
||||
# """Notify relevant staff that transfer is complete"""
|
||||
# # Notify both sending and receiving wards
|
||||
# all_staff = User.objects.filter(
|
||||
# groups__name='Nursing Staff',
|
||||
# profile__department__in=[transfer.from_ward, transfer.to_ward]
|
||||
# )
|
||||
#
|
||||
# for staff in all_staff:
|
||||
# send_mail(
|
||||
# subject=f'Transfer Complete: {transfer.patient.get_full_name()}',
|
||||
# message=f'Patient transfer from {transfer.from_ward.name} to {transfer.to_ward.name} completed.',
|
||||
# from_email='transfers@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_transfer_summary(self, transfer):
|
||||
# """Generate transfer summary report"""
|
||||
# # This would generate a comprehensive transfer report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class DischargeProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for patient discharges
|
||||
# """
|
||||
# admission = ModelField(Admission, help_text='Associated admission record')
|
||||
# discharge_summary = ModelField(DischargeSummary, null=True, blank=True,
|
||||
# help_text='Associated discharge summary')
|
||||
#
|
||||
# # Process status tracking
|
||||
# discharge_planning_started = models.BooleanField(default=False)
|
||||
# discharge_orders_written = models.BooleanField(default=False)
|
||||
# discharge_summary_completed = models.BooleanField(default=False)
|
||||
# patient_education_completed = models.BooleanField(default=False)
|
||||
# medications_reconciled = models.BooleanField(default=False)
|
||||
# follow_up_scheduled = models.BooleanField(default=False)
|
||||
# discharge_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Discharge Process'
|
||||
# verbose_name_plural = 'Discharge Processes'
|
||||
#
|
||||
#
|
||||
# class DischargeFlow(Flow):
|
||||
# """
|
||||
# Hospital Patient Discharge Workflow
|
||||
#
|
||||
# This flow manages the complete patient discharge process from
|
||||
# discharge planning through final discharge completion.
|
||||
# """
|
||||
#
|
||||
# process_class = DischargeProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_discharge)
|
||||
# .Next(this.discharge_planning)
|
||||
# )
|
||||
#
|
||||
# discharge_planning = (
|
||||
# flow_view(DischargePlanningView)
|
||||
# .Permission('inpatients.can_plan_discharges')
|
||||
# .Next(this.write_discharge_orders)
|
||||
# )
|
||||
#
|
||||
# write_discharge_orders = (
|
||||
# flow_view(DischargeOrdersView)
|
||||
# .Permission('inpatients.can_write_discharge_orders')
|
||||
# .Next(this.parallel_discharge_tasks)
|
||||
# )
|
||||
#
|
||||
# parallel_discharge_tasks = (
|
||||
# flow_func(this.start_parallel_discharge_tasks)
|
||||
# .Next(this.complete_discharge_summary)
|
||||
# .Next(this.patient_education)
|
||||
# .Next(this.medication_reconciliation)
|
||||
# .Next(this.schedule_follow_up)
|
||||
# )
|
||||
#
|
||||
# complete_discharge_summary = (
|
||||
# flow_view(DischargeDocumentationView)
|
||||
# .Permission('inpatients.can_complete_discharge_summary')
|
||||
# .Next(this.join_discharge_tasks)
|
||||
# )
|
||||
#
|
||||
# patient_education = (
|
||||
# flow_view(PatientEducationView)
|
||||
# .Permission('inpatients.can_provide_patient_education')
|
||||
# .Next(this.join_discharge_tasks)
|
||||
# )
|
||||
#
|
||||
# medication_reconciliation = (
|
||||
# flow_view(MedicationReconciliationView)
|
||||
# .Permission('inpatients.can_reconcile_medications')
|
||||
# .Next(this.join_discharge_tasks)
|
||||
# )
|
||||
#
|
||||
# schedule_follow_up = (
|
||||
# flow_view(FollowUpSchedulingView)
|
||||
# .Permission('inpatients.can_schedule_follow_up')
|
||||
# .Next(this.join_discharge_tasks)
|
||||
# )
|
||||
#
|
||||
# join_discharge_tasks = (
|
||||
# flow_func(this.join_parallel_discharge_tasks)
|
||||
# .Next(this.execute_discharge)
|
||||
# )
|
||||
#
|
||||
# execute_discharge = (
|
||||
# flow_view(DischargeExecutionView)
|
||||
# .Permission('inpatients.can_execute_discharges')
|
||||
# .Next(this.finalize_discharge)
|
||||
# )
|
||||
#
|
||||
# finalize_discharge = (
|
||||
# flow_func(this.complete_discharge)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_discharge)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_discharge(self, activation):
|
||||
# """Initialize the discharge process"""
|
||||
# process = activation.process
|
||||
# admission = process.admission
|
||||
#
|
||||
# # Update admission discharge planning status
|
||||
# admission.discharge_planning_started = True
|
||||
# admission.save()
|
||||
#
|
||||
# # Notify discharge planning team
|
||||
# self.notify_discharge_planning_team(admission)
|
||||
#
|
||||
# def start_parallel_discharge_tasks(self, activation):
|
||||
# """Start parallel discharge preparation tasks"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Create discharge preparation tasks
|
||||
# self.create_discharge_tasks(process)
|
||||
#
|
||||
# def join_parallel_discharge_tasks(self, activation):
|
||||
# """Wait for all discharge tasks to complete"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Check if all discharge tasks are completed
|
||||
# if (process.discharge_summary_completed and
|
||||
# process.patient_education_completed and
|
||||
# process.medications_reconciled and
|
||||
# process.follow_up_scheduled):
|
||||
#
|
||||
# # Notify that patient is ready for discharge
|
||||
# self.notify_ready_for_discharge(process.admission)
|
||||
#
|
||||
# def complete_discharge(self, activation):
|
||||
# """Finalize the discharge process"""
|
||||
# process = activation.process
|
||||
# admission = process.admission
|
||||
#
|
||||
# # Update admission status
|
||||
# admission.status = 'DISCHARGED'
|
||||
# admission.discharge_datetime = timezone.now()
|
||||
# admission.save()
|
||||
#
|
||||
# # Update bed status
|
||||
# if admission.current_bed:
|
||||
# admission.current_bed.status = 'CLEANING'
|
||||
# admission.current_bed.current_patient = None
|
||||
# admission.current_bed.current_admission = None
|
||||
# admission.current_bed.occupied_since = None
|
||||
# admission.current_bed.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.discharge_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_discharge_complete(admission)
|
||||
#
|
||||
# def end_discharge(self, activation):
|
||||
# """End the discharge workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate discharge summary report
|
||||
# self.generate_final_discharge_report(process.admission)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_discharge_planning_team(self, admission):
|
||||
# """Notify discharge planning team"""
|
||||
# discharge_planners = User.objects.filter(
|
||||
# groups__name='Discharge Planning'
|
||||
# )
|
||||
#
|
||||
# for planner in discharge_planners:
|
||||
# send_mail(
|
||||
# subject=f'Discharge Planning Required: {admission.patient.get_full_name()}',
|
||||
# message=f'Discharge planning has been initiated.',
|
||||
# from_email='discharge@hospital.com',
|
||||
# recipient_list=[planner.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_discharge_tasks(self, process):
|
||||
# """Create discharge preparation tasks"""
|
||||
# # This would create tasks in a task management system
|
||||
# pass
|
||||
#
|
||||
# def notify_ready_for_discharge(self, admission):
|
||||
# """Notify that patient is ready for discharge"""
|
||||
# if admission.attending_physician and admission.attending_physician.email:
|
||||
# send_mail(
|
||||
# subject=f'Patient Ready for Discharge: {admission.patient.get_full_name()}',
|
||||
# message=f'All discharge preparations are complete.',
|
||||
# from_email='discharge@hospital.com',
|
||||
# recipient_list=[admission.attending_physician.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_discharge_complete(self, admission):
|
||||
# """Notify relevant staff that discharge is complete"""
|
||||
# # Notify nursing staff and housekeeping
|
||||
# relevant_staff = User.objects.filter(
|
||||
# groups__name__in=['Nursing Staff', 'Housekeeping'],
|
||||
# profile__department=admission.current_ward
|
||||
# )
|
||||
#
|
||||
# for staff in relevant_staff:
|
||||
# send_mail(
|
||||
# subject=f'Patient Discharged: {admission.patient.get_full_name()}',
|
||||
# message=f'Patient has been discharged from {admission.current_bed.room_number if admission.current_bed else "ward"}.',
|
||||
# from_email='discharge@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_final_discharge_report(self, admission):
|
||||
# """Generate final discharge report"""
|
||||
# # This would generate a comprehensive discharge report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def auto_assign_bed(admission_id, ward_id):
|
||||
# """Background task to automatically assign available bed"""
|
||||
# try:
|
||||
# admission = Admission.objects.get(id=admission_id)
|
||||
# ward = Ward.objects.get(id=ward_id)
|
||||
#
|
||||
# available_bed = Bed.objects.filter(
|
||||
# ward=ward,
|
||||
# status='AVAILABLE'
|
||||
# ).first()
|
||||
#
|
||||
# if available_bed:
|
||||
# admission.current_bed = available_bed
|
||||
# available_bed.status = 'RESERVED'
|
||||
# available_bed.save()
|
||||
# admission.save()
|
||||
# return True
|
||||
#
|
||||
# return False
|
||||
# except (Admission.DoesNotExist, Ward.DoesNotExist):
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def schedule_discharge_planning(admission_id, days_before_discharge=2):
|
||||
# """Background task to schedule discharge planning"""
|
||||
# try:
|
||||
# admission = Admission.objects.get(id=admission_id)
|
||||
#
|
||||
# if admission.estimated_length_of_stay:
|
||||
# planning_date = admission.admission_datetime + timezone.timedelta(
|
||||
# days=admission.estimated_length_of_stay - days_before_discharge
|
||||
# )
|
||||
#
|
||||
# # Schedule discharge planning
|
||||
# # This would integrate with a scheduling system
|
||||
# return True
|
||||
#
|
||||
# return False
|
||||
# except Admission.DoesNotExist:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def generate_encounter_number():
|
||||
# """Generate unique encounter number"""
|
||||
# from django.utils.crypto import get_random_string
|
||||
# return f"ENC{timezone.now().strftime('%Y%m%d')}{get_random_string(4, '0123456789')}"
|
||||
#
|
||||
BIN
integration/__pycache__/flows.cpython-312.pyc
Normal file
BIN
integration/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
781
integration/flows.py
Normal file
781
integration/flows.py
Normal file
@ -0,0 +1,781 @@
|
||||
# """
|
||||
# Viewflow workflows for integration app.
|
||||
# Provides external system integration, data synchronization, and API management workflows.
|
||||
# """
|
||||
#
|
||||
# from viewflow import Flow, lock
|
||||
# from viewflow.base import this, flow_func
|
||||
# from viewflow.contrib import celery
|
||||
# from viewflow.decorators import flow_view
|
||||
# from viewflow.fields import CharField, ModelField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.models import Process, Task
|
||||
# from django.contrib.auth.models import User
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import (
|
||||
# ExternalSystem, IntegrationEndpoint, DataMapping, SyncConfiguration,
|
||||
# IntegrationExecution, WebhookEndpoint, WebhookExecution, IntegrationLog
|
||||
# )
|
||||
# from .views import (
|
||||
# SystemSetupView, EndpointConfigurationView, DataMappingView,
|
||||
# SyncConfigurationView, TestConnectionView, MonitoringView,
|
||||
# WebhookManagementView, SecurityConfigurationView, PerformanceOptimizationView
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class SystemIntegrationProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for system integration
|
||||
# """
|
||||
# external_system = ModelField(ExternalSystem, help_text='Associated external system')
|
||||
#
|
||||
# # Process status tracking
|
||||
# system_registered = models.BooleanField(default=False)
|
||||
# connection_tested = models.BooleanField(default=False)
|
||||
# endpoints_configured = models.BooleanField(default=False)
|
||||
# data_mapping_created = models.BooleanField(default=False)
|
||||
# security_configured = models.BooleanField(default=False)
|
||||
# testing_completed = models.BooleanField(default=False)
|
||||
# monitoring_setup = models.BooleanField(default=False)
|
||||
# integration_activated = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'System Integration Process'
|
||||
# verbose_name_plural = 'System Integration Processes'
|
||||
#
|
||||
#
|
||||
# class SystemIntegrationFlow(Flow):
|
||||
# """
|
||||
# System Integration Workflow
|
||||
#
|
||||
# This flow manages complete external system integration including
|
||||
# setup, configuration, testing, and activation.
|
||||
# """
|
||||
#
|
||||
# process_class = SystemIntegrationProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_system_integration)
|
||||
# .Next(this.register_system)
|
||||
# )
|
||||
#
|
||||
# register_system = (
|
||||
# flow_view(SystemSetupView)
|
||||
# .Permission('integration.can_setup_systems')
|
||||
# .Next(this.test_connection)
|
||||
# )
|
||||
#
|
||||
# test_connection = (
|
||||
# flow_view(TestConnectionView)
|
||||
# .Permission('integration.can_test_connections')
|
||||
# .Next(this.configure_endpoints)
|
||||
# )
|
||||
#
|
||||
# configure_endpoints = (
|
||||
# flow_view(EndpointConfigurationView)
|
||||
# .Permission('integration.can_configure_endpoints')
|
||||
# .Next(this.create_data_mapping)
|
||||
# )
|
||||
#
|
||||
# create_data_mapping = (
|
||||
# flow_view(DataMappingView)
|
||||
# .Permission('integration.can_create_mappings')
|
||||
# .Next(this.configure_security)
|
||||
# )
|
||||
#
|
||||
# configure_security = (
|
||||
# flow_view(SecurityConfigurationView)
|
||||
# .Permission('integration.can_configure_security')
|
||||
# .Next(this.complete_testing)
|
||||
# )
|
||||
#
|
||||
# complete_testing = (
|
||||
# flow_func(this.perform_integration_testing)
|
||||
# .Next(this.setup_monitoring)
|
||||
# )
|
||||
#
|
||||
# setup_monitoring = (
|
||||
# flow_view(MonitoringView)
|
||||
# .Permission('integration.can_setup_monitoring')
|
||||
# .Next(this.activate_integration)
|
||||
# )
|
||||
#
|
||||
# activate_integration = (
|
||||
# flow_func(this.activate_system_integration)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_system_integration)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_system_integration(self, activation):
|
||||
# """Initialize the system integration process"""
|
||||
# process = activation.process
|
||||
# system = process.external_system
|
||||
#
|
||||
# # Send integration notification
|
||||
# self.notify_integration_start(system)
|
||||
#
|
||||
# # Create integration checklist
|
||||
# self.create_integration_checklist(system)
|
||||
#
|
||||
# # Initialize integration logging
|
||||
# self.setup_integration_logging(system)
|
||||
#
|
||||
# def perform_integration_testing(self, activation):
|
||||
# """Perform comprehensive integration testing"""
|
||||
# process = activation.process
|
||||
# system = process.external_system
|
||||
#
|
||||
# # Execute integration tests
|
||||
# test_results = self.execute_integration_tests(system)
|
||||
#
|
||||
# # Mark testing completed
|
||||
# process.testing_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store test results
|
||||
# self.store_integration_test_results(system, test_results)
|
||||
#
|
||||
# # Validate test results
|
||||
# self.validate_test_results(system, test_results)
|
||||
#
|
||||
# def activate_system_integration(self, activation):
|
||||
# """Activate the system integration"""
|
||||
# process = activation.process
|
||||
# system = process.external_system
|
||||
#
|
||||
# # Activate system
|
||||
# system.is_active = True
|
||||
# system.save()
|
||||
#
|
||||
# # Mark integration activated
|
||||
# process.integration_activated = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send activation notifications
|
||||
# self.notify_integration_activation(system)
|
||||
#
|
||||
# # Schedule health checks
|
||||
# self.schedule_health_checks(system)
|
||||
#
|
||||
# # Start monitoring
|
||||
# self.start_integration_monitoring(system)
|
||||
#
|
||||
# def end_system_integration(self, activation):
|
||||
# """End the system integration workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate integration summary
|
||||
# self.generate_integration_summary(process.external_system)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_integration_start(self, system):
|
||||
# """Notify integration start"""
|
||||
# integration_team = User.objects.filter(groups__name='Integration Team')
|
||||
# for staff in integration_team:
|
||||
# send_mail(
|
||||
# subject=f'System Integration Started: {system.name}',
|
||||
# message=f'Integration process started for "{system.name}".',
|
||||
# from_email='integration@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_integration_checklist(self, system):
|
||||
# """Create integration checklist"""
|
||||
# # This would create integration checklist
|
||||
# pass
|
||||
#
|
||||
# def setup_integration_logging(self, system):
|
||||
# """Setup integration logging"""
|
||||
# IntegrationLog.objects.create(
|
||||
# external_system=system,
|
||||
# level='info',
|
||||
# category='system',
|
||||
# message=f'Integration process started for {system.name}'
|
||||
# )
|
||||
#
|
||||
# def execute_integration_tests(self, system):
|
||||
# """Execute comprehensive integration tests"""
|
||||
# # This would run integration tests
|
||||
# return {'status': 'passed', 'tests_run': 10, 'failures': 0}
|
||||
#
|
||||
# def store_integration_test_results(self, system, results):
|
||||
# """Store integration test results"""
|
||||
# # This would store test results
|
||||
# pass
|
||||
#
|
||||
# def validate_test_results(self, system, results):
|
||||
# """Validate integration test results"""
|
||||
# # This would validate test results
|
||||
# if results.get('failures', 0) > 0:
|
||||
# raise Exception('Integration tests failed')
|
||||
#
|
||||
# def notify_integration_activation(self, system):
|
||||
# """Notify integration activation"""
|
||||
# # Notify relevant teams
|
||||
# integration_team = User.objects.filter(groups__name='Integration Team')
|
||||
# for staff in integration_team:
|
||||
# send_mail(
|
||||
# subject=f'System Integration Activated: {system.name}',
|
||||
# message=f'Integration for "{system.name}" has been activated.',
|
||||
# from_email='integration@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def schedule_health_checks(self, system):
|
||||
# """Schedule periodic health checks"""
|
||||
# # Schedule health check task
|
||||
# system_health_check.apply_async(
|
||||
# args=[system.system_id],
|
||||
# countdown=300 # 5 minutes
|
||||
# )
|
||||
#
|
||||
# def start_integration_monitoring(self, system):
|
||||
# """Start integration monitoring"""
|
||||
# # This would start monitoring
|
||||
# pass
|
||||
#
|
||||
# def generate_integration_summary(self, system):
|
||||
# """Generate integration summary"""
|
||||
# # This would generate integration summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class DataSynchronizationProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for data synchronization
|
||||
# """
|
||||
# sync_configuration = ModelField(SyncConfiguration, help_text='Associated sync configuration')
|
||||
#
|
||||
# # Process status tracking
|
||||
# sync_initiated = models.BooleanField(default=False)
|
||||
# data_extracted = models.BooleanField(default=False)
|
||||
# data_transformed = models.BooleanField(default=False)
|
||||
# data_validated = models.BooleanField(default=False)
|
||||
# data_loaded = models.BooleanField(default=False)
|
||||
# conflicts_resolved = models.BooleanField(default=False)
|
||||
# sync_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Data Synchronization Process'
|
||||
# verbose_name_plural = 'Data Synchronization Processes'
|
||||
#
|
||||
#
|
||||
# class DataSynchronizationFlow(Flow):
|
||||
# """
|
||||
# Data Synchronization Workflow
|
||||
#
|
||||
# This flow manages automated data synchronization between
|
||||
# external systems and the hospital management system.
|
||||
# """
|
||||
#
|
||||
# process_class = DataSynchronizationProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_data_synchronization)
|
||||
# .Next(this.initiate_sync)
|
||||
# )
|
||||
#
|
||||
# initiate_sync = (
|
||||
# flow_func(this.setup_sync_process)
|
||||
# .Next(this.extract_data)
|
||||
# )
|
||||
#
|
||||
# extract_data = (
|
||||
# flow_func(this.perform_data_extraction)
|
||||
# .Next(this.transform_data)
|
||||
# )
|
||||
#
|
||||
# transform_data = (
|
||||
# flow_func(this.perform_data_transformation)
|
||||
# .Next(this.validate_data)
|
||||
# )
|
||||
#
|
||||
# validate_data = (
|
||||
# flow_func(this.perform_data_validation)
|
||||
# .Next(this.load_data)
|
||||
# )
|
||||
#
|
||||
# load_data = (
|
||||
# flow_func(this.perform_data_loading)
|
||||
# .Next(this.resolve_conflicts)
|
||||
# )
|
||||
#
|
||||
# resolve_conflicts = (
|
||||
# flow_func(this.handle_data_conflicts)
|
||||
# .Next(this.complete_sync)
|
||||
# )
|
||||
#
|
||||
# complete_sync = (
|
||||
# flow_func(this.finalize_data_synchronization)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_data_synchronization)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_data_synchronization(self, activation):
|
||||
# """Initialize the data synchronization process"""
|
||||
# process = activation.process
|
||||
# sync_config = process.sync_configuration
|
||||
#
|
||||
# # Send sync notification
|
||||
# self.notify_sync_start(sync_config)
|
||||
#
|
||||
# # Create sync execution record
|
||||
# self.create_sync_execution(sync_config)
|
||||
#
|
||||
# def setup_sync_process(self, activation):
|
||||
# """Setup synchronization process"""
|
||||
# process = activation.process
|
||||
# sync_config = process.sync_configuration
|
||||
#
|
||||
# # Mark sync initiated
|
||||
# process.sync_initiated = True
|
||||
# process.save()
|
||||
#
|
||||
# # Prepare sync environment
|
||||
# self.prepare_sync_environment(sync_config)
|
||||
#
|
||||
# def perform_data_extraction(self, activation):
|
||||
# """Extract data from source system"""
|
||||
# process = activation.process
|
||||
# sync_config = process.sync_configuration
|
||||
#
|
||||
# # Extract data
|
||||
# extracted_data = self.extract_source_data(sync_config)
|
||||
#
|
||||
# # Mark data extracted
|
||||
# process.data_extracted = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store extracted data
|
||||
# self.store_extracted_data(sync_config, extracted_data)
|
||||
#
|
||||
# def perform_data_transformation(self, activation):
|
||||
# """Transform data according to mapping rules"""
|
||||
# process = activation.process
|
||||
# sync_config = process.sync_configuration
|
||||
#
|
||||
# # Transform data
|
||||
# transformed_data = self.transform_sync_data(sync_config)
|
||||
#
|
||||
# # Mark data transformed
|
||||
# process.data_transformed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store transformed data
|
||||
# self.store_transformed_data(sync_config, transformed_data)
|
||||
#
|
||||
# def perform_data_validation(self, activation):
|
||||
# """Validate transformed data"""
|
||||
# process = activation.process
|
||||
# sync_config = process.sync_configuration
|
||||
#
|
||||
# # Validate data
|
||||
# validation_results = self.validate_sync_data(sync_config)
|
||||
#
|
||||
# # Mark data validated
|
||||
# process.data_validated = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store validation results
|
||||
# self.store_validation_results(sync_config, validation_results)
|
||||
#
|
||||
# def perform_data_loading(self, activation):
|
||||
# """Load data into target system"""
|
||||
# process = activation.process
|
||||
# sync_config = process.sync_configuration
|
||||
#
|
||||
# # Load data
|
||||
# loading_results = self.load_sync_data(sync_config)
|
||||
#
|
||||
# # Mark data loaded
|
||||
# process.data_loaded = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store loading results
|
||||
# self.store_loading_results(sync_config, loading_results)
|
||||
#
|
||||
# def handle_data_conflicts(self, activation):
|
||||
# """Handle data conflicts and duplicates"""
|
||||
# process = activation.process
|
||||
# sync_config = process.sync_configuration
|
||||
#
|
||||
# # Resolve conflicts
|
||||
# conflict_results = self.resolve_data_conflicts(sync_config)
|
||||
#
|
||||
# # Mark conflicts resolved
|
||||
# process.conflicts_resolved = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store conflict resolution results
|
||||
# self.store_conflict_results(sync_config, conflict_results)
|
||||
#
|
||||
# def finalize_data_synchronization(self, activation):
|
||||
# """Finalize the data synchronization process"""
|
||||
# process = activation.process
|
||||
# sync_config = process.sync_configuration
|
||||
#
|
||||
# # Mark sync completed
|
||||
# process.sync_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_sync_completion(sync_config)
|
||||
#
|
||||
# # Update sync statistics
|
||||
# self.update_sync_statistics(sync_config)
|
||||
#
|
||||
# # Schedule next sync if recurring
|
||||
# self.schedule_next_sync(sync_config)
|
||||
#
|
||||
# def end_data_synchronization(self, activation):
|
||||
# """End the data synchronization workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate sync summary
|
||||
# self.generate_sync_summary(process.sync_configuration)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_sync_start(self, sync_config):
|
||||
# """Notify sync start"""
|
||||
# # This would notify relevant parties
|
||||
# pass
|
||||
#
|
||||
# def create_sync_execution(self, sync_config):
|
||||
# """Create sync execution record"""
|
||||
# # This would create execution record
|
||||
# pass
|
||||
#
|
||||
# def prepare_sync_environment(self, sync_config):
|
||||
# """Prepare synchronization environment"""
|
||||
# # This would prepare sync environment
|
||||
# pass
|
||||
#
|
||||
# def extract_source_data(self, sync_config):
|
||||
# """Extract data from source system"""
|
||||
# # This would extract data from source
|
||||
# return {'status': 'extracted', 'records': 1000}
|
||||
#
|
||||
# def store_extracted_data(self, sync_config, data):
|
||||
# """Store extracted data"""
|
||||
# # This would store extracted data
|
||||
# pass
|
||||
#
|
||||
# def transform_sync_data(self, sync_config):
|
||||
# """Transform data according to mapping"""
|
||||
# # This would transform data
|
||||
# return {'status': 'transformed', 'records': 1000}
|
||||
#
|
||||
# def store_transformed_data(self, sync_config, data):
|
||||
# """Store transformed data"""
|
||||
# # This would store transformed data
|
||||
# pass
|
||||
#
|
||||
# def validate_sync_data(self, sync_config):
|
||||
# """Validate transformed data"""
|
||||
# # This would validate data
|
||||
# return {'status': 'valid', 'errors': []}
|
||||
#
|
||||
# def store_validation_results(self, sync_config, results):
|
||||
# """Store validation results"""
|
||||
# # This would store validation results
|
||||
# pass
|
||||
#
|
||||
# def load_sync_data(self, sync_config):
|
||||
# """Load data into target system"""
|
||||
# # This would load data
|
||||
# return {'status': 'loaded', 'records': 1000}
|
||||
#
|
||||
# def store_loading_results(self, sync_config, results):
|
||||
# """Store loading results"""
|
||||
# # This would store loading results
|
||||
# pass
|
||||
#
|
||||
# def resolve_data_conflicts(self, sync_config):
|
||||
# """Resolve data conflicts"""
|
||||
# # This would resolve conflicts
|
||||
# return {'status': 'resolved', 'conflicts': 0}
|
||||
#
|
||||
# def store_conflict_results(self, sync_config, results):
|
||||
# """Store conflict resolution results"""
|
||||
# # This would store conflict results
|
||||
# pass
|
||||
#
|
||||
# def notify_sync_completion(self, sync_config):
|
||||
# """Notify sync completion"""
|
||||
# # This would notify completion
|
||||
# pass
|
||||
#
|
||||
# def update_sync_statistics(self, sync_config):
|
||||
# """Update synchronization statistics"""
|
||||
# # This would update sync stats
|
||||
# pass
|
||||
#
|
||||
# def schedule_next_sync(self, sync_config):
|
||||
# """Schedule next synchronization"""
|
||||
# if sync_config.is_recurring:
|
||||
# # Schedule next sync
|
||||
# data_sync.apply_async(
|
||||
# args=[sync_config.sync_id],
|
||||
# countdown=sync_config.sync_interval_seconds
|
||||
# )
|
||||
#
|
||||
# def generate_sync_summary(self, sync_config):
|
||||
# """Generate synchronization summary"""
|
||||
# # This would generate sync summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class WebhookManagementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for webhook management
|
||||
# """
|
||||
# webhook_endpoint = ModelField(WebhookEndpoint, help_text='Associated webhook endpoint')
|
||||
#
|
||||
# # Process status tracking
|
||||
# webhook_created = models.BooleanField(default=False)
|
||||
# security_configured = models.BooleanField(default=False)
|
||||
# testing_completed = models.BooleanField(default=False)
|
||||
# monitoring_setup = models.BooleanField(default=False)
|
||||
# webhook_activated = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Webhook Management Process'
|
||||
# verbose_name_plural = 'Webhook Management Processes'
|
||||
#
|
||||
#
|
||||
# class WebhookManagementFlow(Flow):
|
||||
# """
|
||||
# Webhook Management Workflow
|
||||
#
|
||||
# This flow manages webhook endpoint creation, configuration,
|
||||
# testing, and activation for receiving external data.
|
||||
# """
|
||||
#
|
||||
# process_class = WebhookManagementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_webhook_management)
|
||||
# .Next(this.create_webhook)
|
||||
# )
|
||||
#
|
||||
# create_webhook = (
|
||||
# flow_view(WebhookManagementView)
|
||||
# .Permission('integration.can_manage_webhooks')
|
||||
# .Next(this.configure_security)
|
||||
# )
|
||||
#
|
||||
# configure_security = (
|
||||
# flow_func(this.setup_webhook_security)
|
||||
# .Next(this.test_webhook)
|
||||
# )
|
||||
#
|
||||
# test_webhook = (
|
||||
# flow_func(this.perform_webhook_testing)
|
||||
# .Next(this.setup_monitoring)
|
||||
# )
|
||||
#
|
||||
# setup_monitoring = (
|
||||
# flow_func(this.configure_webhook_monitoring)
|
||||
# .Next(this.activate_webhook)
|
||||
# )
|
||||
#
|
||||
# activate_webhook = (
|
||||
# flow_func(this.activate_webhook_endpoint)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_webhook_management)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_webhook_management(self, activation):
|
||||
# """Initialize the webhook management process"""
|
||||
# process = activation.process
|
||||
# webhook = process.webhook_endpoint
|
||||
#
|
||||
# # Send webhook creation notification
|
||||
# self.notify_webhook_creation(webhook)
|
||||
#
|
||||
# def setup_webhook_security(self, activation):
|
||||
# """Setup webhook security configuration"""
|
||||
# process = activation.process
|
||||
# webhook = process.webhook_endpoint
|
||||
#
|
||||
# # Configure security settings
|
||||
# self.configure_webhook_security(webhook)
|
||||
#
|
||||
# # Mark security configured
|
||||
# process.security_configured = True
|
||||
# process.save()
|
||||
#
|
||||
# def perform_webhook_testing(self, activation):
|
||||
# """Perform webhook testing"""
|
||||
# process = activation.process
|
||||
# webhook = process.webhook_endpoint
|
||||
#
|
||||
# # Test webhook functionality
|
||||
# test_results = self.test_webhook_functionality(webhook)
|
||||
#
|
||||
# # Mark testing completed
|
||||
# process.testing_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Store test results
|
||||
# self.store_webhook_test_results(webhook, test_results)
|
||||
#
|
||||
# def configure_webhook_monitoring(self, activation):
|
||||
# """Configure webhook monitoring"""
|
||||
# process = activation.process
|
||||
# webhook = process.webhook_endpoint
|
||||
#
|
||||
# # Setup monitoring
|
||||
# self.setup_webhook_monitoring(webhook)
|
||||
#
|
||||
# # Mark monitoring setup
|
||||
# process.monitoring_setup = True
|
||||
# process.save()
|
||||
#
|
||||
# def activate_webhook_endpoint(self, activation):
|
||||
# """Activate webhook endpoint"""
|
||||
# process = activation.process
|
||||
# webhook = process.webhook_endpoint
|
||||
#
|
||||
# # Activate webhook
|
||||
# webhook.is_active = True
|
||||
# webhook.save()
|
||||
#
|
||||
# # Mark webhook activated
|
||||
# process.webhook_activated = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send activation notifications
|
||||
# self.notify_webhook_activation(webhook)
|
||||
#
|
||||
# def end_webhook_management(self, activation):
|
||||
# """End the webhook management workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate webhook summary
|
||||
# self.generate_webhook_summary(process.webhook_endpoint)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_webhook_creation(self, webhook):
|
||||
# """Notify webhook creation"""
|
||||
# integration_team = User.objects.filter(groups__name='Integration Team')
|
||||
# for staff in integration_team:
|
||||
# send_mail(
|
||||
# subject=f'Webhook Created: {webhook.name}',
|
||||
# message=f'Webhook endpoint "{webhook.name}" has been created.',
|
||||
# from_email='integration@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def configure_webhook_security(self, webhook):
|
||||
# """Configure webhook security"""
|
||||
# # This would configure security settings
|
||||
# pass
|
||||
#
|
||||
# def test_webhook_functionality(self, webhook):
|
||||
# """Test webhook functionality"""
|
||||
# # This would test webhook
|
||||
# return {'status': 'passed', 'tests': 5}
|
||||
#
|
||||
# def store_webhook_test_results(self, webhook, results):
|
||||
# """Store webhook test results"""
|
||||
# # This would store test results
|
||||
# pass
|
||||
#
|
||||
# def setup_webhook_monitoring(self, webhook):
|
||||
# """Setup webhook monitoring"""
|
||||
# # This would setup monitoring
|
||||
# pass
|
||||
#
|
||||
# def notify_webhook_activation(self, webhook):
|
||||
# """Notify webhook activation"""
|
||||
# # This would notify activation
|
||||
# pass
|
||||
#
|
||||
# def generate_webhook_summary(self, webhook):
|
||||
# """Generate webhook summary"""
|
||||
# # This would generate webhook summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def system_health_check(system_id):
|
||||
# """Background task for system health monitoring"""
|
||||
# try:
|
||||
# system = ExternalSystem.objects.get(system_id=system_id)
|
||||
#
|
||||
# # Perform health check
|
||||
# # This would perform system health check
|
||||
#
|
||||
# # Schedule next health check
|
||||
# system_health_check.apply_async(
|
||||
# args=[system_id],
|
||||
# countdown=300 # 5 minutes
|
||||
# )
|
||||
#
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def data_sync(sync_id):
|
||||
# """Background task for data synchronization"""
|
||||
# try:
|
||||
# sync_config = SyncConfiguration.objects.get(sync_id=sync_id)
|
||||
#
|
||||
# # Start data synchronization workflow
|
||||
# # This would start the data sync workflow
|
||||
#
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def webhook_health_check():
|
||||
# """Background task for webhook health monitoring"""
|
||||
# try:
|
||||
# # This would monitor webhook health
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def integration_performance_monitoring():
|
||||
# """Background task for integration performance monitoring"""
|
||||
# try:
|
||||
# # This would monitor integration performance
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def cleanup_integration_logs():
|
||||
# """Background task to cleanup old integration logs"""
|
||||
# try:
|
||||
# # This would cleanup old logs
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
BIN
inventory/__pycache__/flows.cpython-312.pyc
Normal file
BIN
inventory/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
905
inventory/flows.py
Normal file
905
inventory/flows.py
Normal file
@ -0,0 +1,905 @@
|
||||
# """
|
||||
# Viewflow workflows for inventory app.
|
||||
# Provides inventory management, procurement, and supply chain workflows.
|
||||
# """
|
||||
#
|
||||
# from viewflow import Flow, lock
|
||||
# from viewflow.base import this, flow_func
|
||||
# from viewflow.contrib import celery
|
||||
# from viewflow.decorators import flow_view
|
||||
# from viewflow.fields import CharField, ModelField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.models import Process, Task
|
||||
# from django.contrib.auth.models import User
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import InventoryItem, PurchaseOrder, PurchaseOrderItem, Supplier, InventoryStock, InventoryLocation
|
||||
# from .views import (
|
||||
# PurchaseRequestView, VendorSelectionView, PurchaseOrderCreationView,
|
||||
# ApprovalView, OrderSubmissionView, ReceivingView, InspectionView,
|
||||
# StockUpdateView, InvoiceMatchingView, PaymentProcessingView,
|
||||
# StockReplenishmentView, StockTransferView, StockAdjustmentView,
|
||||
# CycleCountView, InventoryAuditView
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class ProcurementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for procurement
|
||||
# """
|
||||
# purchase_order = ModelField(PurchaseOrder, help_text='Associated purchase order')
|
||||
#
|
||||
# # Process status tracking
|
||||
# request_submitted = models.BooleanField(default=False)
|
||||
# vendor_selected = models.BooleanField(default=False)
|
||||
# order_created = models.BooleanField(default=False)
|
||||
# order_approved = models.BooleanField(default=False)
|
||||
# order_sent = models.BooleanField(default=False)
|
||||
# goods_received = models.BooleanField(default=False)
|
||||
# invoice_processed = models.BooleanField(default=False)
|
||||
# payment_completed = models.BooleanField(default=False)
|
||||
# procurement_closed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Procurement Process'
|
||||
# verbose_name_plural = 'Procurement Processes'
|
||||
#
|
||||
#
|
||||
# class ProcurementFlow(Flow):
|
||||
# """
|
||||
# Procurement Workflow
|
||||
#
|
||||
# This flow manages the complete procurement process from purchase
|
||||
# request through payment and order closure.
|
||||
# """
|
||||
#
|
||||
# process_class = ProcurementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_procurement)
|
||||
# .Next(this.submit_request)
|
||||
# )
|
||||
#
|
||||
# submit_request = (
|
||||
# flow_view(PurchaseRequestView)
|
||||
# .Permission('inventory.can_submit_purchase_requests')
|
||||
# .Next(this.select_vendor)
|
||||
# )
|
||||
#
|
||||
# select_vendor = (
|
||||
# flow_view(VendorSelectionView)
|
||||
# .Permission('inventory.can_select_vendors')
|
||||
# .Next(this.create_order)
|
||||
# )
|
||||
#
|
||||
# create_order = (
|
||||
# flow_view(PurchaseOrderCreationView)
|
||||
# .Permission('inventory.can_create_purchase_orders')
|
||||
# .Next(this.approve_order)
|
||||
# )
|
||||
#
|
||||
# approve_order = (
|
||||
# flow_view(ApprovalView)
|
||||
# .Permission('inventory.can_approve_purchase_orders')
|
||||
# .Next(this.send_order)
|
||||
# )
|
||||
#
|
||||
# send_order = (
|
||||
# flow_view(OrderSubmissionView)
|
||||
# .Permission('inventory.can_send_purchase_orders')
|
||||
# .Next(this.receive_goods)
|
||||
# )
|
||||
#
|
||||
# receive_goods = (
|
||||
# flow_view(ReceivingView)
|
||||
# .Permission('inventory.can_receive_goods')
|
||||
# .Next(this.inspect_goods)
|
||||
# )
|
||||
#
|
||||
# inspect_goods = (
|
||||
# flow_view(InspectionView)
|
||||
# .Permission('inventory.can_inspect_goods')
|
||||
# .Next(this.update_stock)
|
||||
# )
|
||||
#
|
||||
# update_stock = (
|
||||
# flow_view(StockUpdateView)
|
||||
# .Permission('inventory.can_update_stock')
|
||||
# .Next(this.process_invoice)
|
||||
# )
|
||||
#
|
||||
# process_invoice = (
|
||||
# flow_view(InvoiceMatchingView)
|
||||
# .Permission('inventory.can_process_invoices')
|
||||
# .Next(this.process_payment)
|
||||
# )
|
||||
#
|
||||
# process_payment = (
|
||||
# flow_view(PaymentProcessingView)
|
||||
# .Permission('inventory.can_process_payments')
|
||||
# .Next(this.close_order)
|
||||
# )
|
||||
#
|
||||
# close_order = (
|
||||
# flow_func(this.complete_procurement)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_procurement)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_procurement(self, activation):
|
||||
# """Initialize the procurement process"""
|
||||
# process = activation.process
|
||||
# order = process.purchase_order
|
||||
#
|
||||
# # Update order status
|
||||
# order.status = 'DRAFT'
|
||||
# order.save()
|
||||
#
|
||||
# # Send notification to procurement staff
|
||||
# self.notify_procurement_staff(order)
|
||||
#
|
||||
# # Check for urgent orders
|
||||
# if order.priority in ['HIGH', 'URGENT']:
|
||||
# self.notify_urgent_procurement(order)
|
||||
#
|
||||
# def complete_procurement(self, activation):
|
||||
# """Finalize the procurement process"""
|
||||
# process = activation.process
|
||||
# order = process.purchase_order
|
||||
#
|
||||
# # Update order status
|
||||
# order.status = 'CLOSED'
|
||||
# order.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.procurement_closed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_procurement_completion(order)
|
||||
#
|
||||
# # Update supplier performance metrics
|
||||
# self.update_supplier_performance(order)
|
||||
#
|
||||
# # Update procurement metrics
|
||||
# self.update_procurement_metrics(order)
|
||||
#
|
||||
# def end_procurement(self, activation):
|
||||
# """End the procurement workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate procurement summary report
|
||||
# self.generate_procurement_summary(process.purchase_order)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_procurement_staff(self, order):
|
||||
# """Notify procurement staff of new order"""
|
||||
# from django.contrib.auth.models import Group
|
||||
#
|
||||
# procurement_staff = User.objects.filter(
|
||||
# groups__name='Procurement Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in procurement_staff:
|
||||
# send_mail(
|
||||
# subject=f'New Purchase Order: {order.po_number}',
|
||||
# message=f'New purchase order for {order.supplier.name} requires processing.',
|
||||
# from_email='procurement@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_urgent_procurement(self, order):
|
||||
# """Notify of urgent procurement"""
|
||||
# procurement_managers = User.objects.filter(
|
||||
# groups__name='Procurement Managers'
|
||||
# )
|
||||
#
|
||||
# for manager in procurement_managers:
|
||||
# send_mail(
|
||||
# subject=f'URGENT Purchase Order: {order.po_number}',
|
||||
# message=f'{order.get_priority_display()} purchase order requires immediate attention.',
|
||||
# from_email='procurement@hospital.com',
|
||||
# recipient_list=[manager.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_procurement_completion(self, order):
|
||||
# """Notify procurement completion"""
|
||||
# # Notify requestor
|
||||
# if order.requested_by and order.requested_by.email:
|
||||
# send_mail(
|
||||
# subject=f'Purchase Order Complete: {order.po_number}',
|
||||
# message=f'Your purchase order has been completed and goods received.',
|
||||
# from_email='procurement@hospital.com',
|
||||
# recipient_list=[order.requested_by.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def update_supplier_performance(self, order):
|
||||
# """Update supplier performance metrics"""
|
||||
# supplier = order.supplier
|
||||
#
|
||||
# # Calculate on-time delivery
|
||||
# if order.actual_delivery_date and order.promised_delivery_date:
|
||||
# if order.actual_delivery_date <= order.promised_delivery_date:
|
||||
# # Update on-time delivery rate
|
||||
# pass
|
||||
#
|
||||
# # This would update comprehensive supplier metrics
|
||||
# pass
|
||||
#
|
||||
# def update_procurement_metrics(self, order):
|
||||
# """Update procurement performance metrics"""
|
||||
# # This would update procurement cycle time and other metrics
|
||||
# pass
|
||||
#
|
||||
# def generate_procurement_summary(self, order):
|
||||
# """Generate procurement summary report"""
|
||||
# # This would generate a comprehensive procurement report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class InventoryReplenishmentProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for inventory replenishment
|
||||
# """
|
||||
# inventory_item = ModelField(InventoryItem, help_text='Associated inventory item')
|
||||
#
|
||||
# # Process status tracking
|
||||
# reorder_triggered = models.BooleanField(default=False)
|
||||
# demand_analyzed = models.BooleanField(default=False)
|
||||
# quantity_calculated = models.BooleanField(default=False)
|
||||
# supplier_contacted = models.BooleanField(default=False)
|
||||
# order_placed = models.BooleanField(default=False)
|
||||
# delivery_scheduled = models.BooleanField(default=False)
|
||||
# stock_replenished = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Inventory Replenishment Process'
|
||||
# verbose_name_plural = 'Inventory Replenishment Processes'
|
||||
#
|
||||
#
|
||||
# class InventoryReplenishmentFlow(Flow):
|
||||
# """
|
||||
# Inventory Replenishment Workflow
|
||||
#
|
||||
# This flow manages automatic and manual inventory replenishment
|
||||
# including demand analysis and supplier coordination.
|
||||
# """
|
||||
#
|
||||
# process_class = InventoryReplenishmentProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_replenishment)
|
||||
# .Next(this.trigger_reorder)
|
||||
# )
|
||||
#
|
||||
# trigger_reorder = (
|
||||
# flow_func(this.check_reorder_point)
|
||||
# .Next(this.analyze_demand)
|
||||
# )
|
||||
#
|
||||
# analyze_demand = (
|
||||
# flow_view(DemandAnalysisView)
|
||||
# .Permission('inventory.can_analyze_demand')
|
||||
# .Next(this.calculate_quantity)
|
||||
# )
|
||||
#
|
||||
# calculate_quantity = (
|
||||
# flow_view(QuantityCalculationView)
|
||||
# .Permission('inventory.can_calculate_quantities')
|
||||
# .Next(this.contact_supplier)
|
||||
# )
|
||||
#
|
||||
# contact_supplier = (
|
||||
# flow_view(SupplierContactView)
|
||||
# .Permission('inventory.can_contact_suppliers')
|
||||
# .Next(this.place_order)
|
||||
# )
|
||||
#
|
||||
# place_order = (
|
||||
# flow_view(OrderPlacementView)
|
||||
# .Permission('inventory.can_place_orders')
|
||||
# .Next(this.schedule_delivery)
|
||||
# )
|
||||
#
|
||||
# schedule_delivery = (
|
||||
# flow_view(DeliverySchedulingView)
|
||||
# .Permission('inventory.can_schedule_deliveries')
|
||||
# .Next(this.replenish_stock)
|
||||
# )
|
||||
#
|
||||
# replenish_stock = (
|
||||
# flow_func(this.complete_replenishment)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_replenishment)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_replenishment(self, activation):
|
||||
# """Initialize the replenishment process"""
|
||||
# process = activation.process
|
||||
# item = process.inventory_item
|
||||
#
|
||||
# # Send notification to inventory staff
|
||||
# self.notify_replenishment_needed(item)
|
||||
#
|
||||
# def check_reorder_point(self, activation):
|
||||
# """Check if item has reached reorder point"""
|
||||
# process = activation.process
|
||||
# item = process.inventory_item
|
||||
#
|
||||
# # Check current stock levels
|
||||
# current_stock = item.total_stock
|
||||
#
|
||||
# if current_stock <= item.reorder_point:
|
||||
# process.reorder_triggered = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send urgent notification if below safety stock
|
||||
# if current_stock <= item.safety_stock:
|
||||
# self.notify_critical_stock(item)
|
||||
#
|
||||
# def complete_replenishment(self, activation):
|
||||
# """Finalize the replenishment process"""
|
||||
# process = activation.process
|
||||
# item = process.inventory_item
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.stock_replenished = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_replenishment_completion(item)
|
||||
#
|
||||
# # Update inventory metrics
|
||||
# self.update_inventory_metrics(item)
|
||||
#
|
||||
# def end_replenishment(self, activation):
|
||||
# """End the replenishment workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate replenishment summary
|
||||
# self.generate_replenishment_summary(process.inventory_item)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_replenishment_needed(self, item):
|
||||
# """Notify inventory staff of replenishment need"""
|
||||
# inventory_staff = User.objects.filter(
|
||||
# groups__name='Inventory Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in inventory_staff:
|
||||
# send_mail(
|
||||
# subject=f'Replenishment Needed: {item.item_name}',
|
||||
# message=f'Item {item.item_code} has reached reorder point.',
|
||||
# from_email='inventory@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_critical_stock(self, item):
|
||||
# """Notify of critical stock levels"""
|
||||
# inventory_managers = User.objects.filter(
|
||||
# groups__name='Inventory Managers'
|
||||
# )
|
||||
#
|
||||
# for manager in inventory_managers:
|
||||
# send_mail(
|
||||
# subject=f'CRITICAL STOCK: {item.item_name}',
|
||||
# message=f'Item {item.item_code} is below safety stock level.',
|
||||
# from_email='inventory@hospital.com',
|
||||
# recipient_list=[manager.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_replenishment_completion(self, item):
|
||||
# """Notify replenishment completion"""
|
||||
# # This would notify relevant staff
|
||||
# pass
|
||||
#
|
||||
# def update_inventory_metrics(self, item):
|
||||
# """Update inventory performance metrics"""
|
||||
# # This would update inventory turnover and other metrics
|
||||
# pass
|
||||
#
|
||||
# def generate_replenishment_summary(self, item):
|
||||
# """Generate replenishment summary"""
|
||||
# # This would generate replenishment summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class StockMovementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for stock movements
|
||||
# """
|
||||
# movement_type = CharField(max_length=20, help_text='Type of stock movement')
|
||||
# item_id = CharField(max_length=50, help_text='Item identifier')
|
||||
#
|
||||
# # Process status tracking
|
||||
# movement_initiated = models.BooleanField(default=False)
|
||||
# authorization_verified = models.BooleanField(default=False)
|
||||
# stock_reserved = models.BooleanField(default=False)
|
||||
# movement_executed = models.BooleanField(default=False)
|
||||
# documentation_completed = models.BooleanField(default=False)
|
||||
# movement_verified = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Stock Movement Process'
|
||||
# verbose_name_plural = 'Stock Movement Processes'
|
||||
#
|
||||
#
|
||||
# class StockMovementFlow(Flow):
|
||||
# """
|
||||
# Stock Movement Workflow
|
||||
#
|
||||
# This flow manages stock transfers, adjustments, and other
|
||||
# inventory movements with proper authorization and tracking.
|
||||
# """
|
||||
#
|
||||
# process_class = StockMovementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_movement)
|
||||
# .Next(this.initiate_movement)
|
||||
# )
|
||||
#
|
||||
# initiate_movement = (
|
||||
# flow_view(MovementInitiationView)
|
||||
# .Permission('inventory.can_initiate_movements')
|
||||
# .Next(this.verify_authorization)
|
||||
# )
|
||||
#
|
||||
# verify_authorization = (
|
||||
# flow_view(AuthorizationVerificationView)
|
||||
# .Permission('inventory.can_verify_authorization')
|
||||
# .Next(this.reserve_stock)
|
||||
# )
|
||||
#
|
||||
# reserve_stock = (
|
||||
# flow_view(StockReservationView)
|
||||
# .Permission('inventory.can_reserve_stock')
|
||||
# .Next(this.execute_movement)
|
||||
# )
|
||||
#
|
||||
# execute_movement = (
|
||||
# flow_view(MovementExecutionView)
|
||||
# .Permission('inventory.can_execute_movements')
|
||||
# .Next(this.complete_documentation)
|
||||
# )
|
||||
#
|
||||
# complete_documentation = (
|
||||
# flow_view(DocumentationView)
|
||||
# .Permission('inventory.can_complete_documentation')
|
||||
# .Next(this.verify_movement)
|
||||
# )
|
||||
#
|
||||
# verify_movement = (
|
||||
# flow_view(MovementVerificationView)
|
||||
# .Permission('inventory.can_verify_movements')
|
||||
# .Next(this.finalize_movement)
|
||||
# )
|
||||
#
|
||||
# finalize_movement = (
|
||||
# flow_func(this.complete_movement)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_movement)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_movement(self, activation):
|
||||
# """Initialize the stock movement process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Send notification to inventory staff
|
||||
# self.notify_movement_initiated(process.movement_type, process.item_id)
|
||||
#
|
||||
# def complete_movement(self, activation):
|
||||
# """Finalize the stock movement process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.movement_verified = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_movement_completion(process.movement_type, process.item_id)
|
||||
#
|
||||
# # Update inventory records
|
||||
# self.update_inventory_records(process.movement_type, process.item_id)
|
||||
#
|
||||
# def end_movement(self, activation):
|
||||
# """End the stock movement workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate movement summary
|
||||
# self.generate_movement_summary(process.movement_type, process.item_id)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_movement_initiated(self, movement_type, item_id):
|
||||
# """Notify inventory staff of movement initiation"""
|
||||
# inventory_staff = User.objects.filter(
|
||||
# groups__name='Inventory Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in inventory_staff:
|
||||
# send_mail(
|
||||
# subject=f'Stock Movement Initiated: {item_id}',
|
||||
# message=f'{movement_type} movement initiated for item {item_id}.',
|
||||
# from_email='inventory@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_movement_completion(self, movement_type, item_id):
|
||||
# """Notify movement completion"""
|
||||
# # This would notify relevant staff
|
||||
# pass
|
||||
#
|
||||
# def update_inventory_records(self, movement_type, item_id):
|
||||
# """Update inventory records"""
|
||||
# # This would update inventory records
|
||||
# pass
|
||||
#
|
||||
# def generate_movement_summary(self, movement_type, item_id):
|
||||
# """Generate movement summary"""
|
||||
# # This would generate movement summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class InventoryAuditProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for inventory audits
|
||||
# """
|
||||
# audit_type = CharField(max_length=20, help_text='Type of audit')
|
||||
# location_id = CharField(max_length=50, help_text='Location identifier')
|
||||
#
|
||||
# # Process status tracking
|
||||
# audit_scheduled = models.BooleanField(default=False)
|
||||
# audit_team_assigned = models.BooleanField(default=False)
|
||||
# physical_count_completed = models.BooleanField(default=False)
|
||||
# discrepancies_identified = models.BooleanField(default=False)
|
||||
# adjustments_made = models.BooleanField(default=False)
|
||||
# audit_report_generated = models.BooleanField(default=False)
|
||||
# audit_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Inventory Audit Process'
|
||||
# verbose_name_plural = 'Inventory Audit Processes'
|
||||
#
|
||||
#
|
||||
# class InventoryAuditFlow(Flow):
|
||||
# """
|
||||
# Inventory Audit Workflow
|
||||
#
|
||||
# This flow manages inventory audits including cycle counts,
|
||||
# physical inventories, and discrepancy resolution.
|
||||
# """
|
||||
#
|
||||
# process_class = InventoryAuditProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_audit)
|
||||
# .Next(this.schedule_audit)
|
||||
# )
|
||||
#
|
||||
# schedule_audit = (
|
||||
# flow_view(AuditSchedulingView)
|
||||
# .Permission('inventory.can_schedule_audits')
|
||||
# .Next(this.assign_team)
|
||||
# )
|
||||
#
|
||||
# assign_team = (
|
||||
# flow_view(TeamAssignmentView)
|
||||
# .Permission('inventory.can_assign_audit_teams')
|
||||
# .Next(this.conduct_count)
|
||||
# )
|
||||
#
|
||||
# conduct_count = (
|
||||
# flow_view(CycleCountView)
|
||||
# .Permission('inventory.can_conduct_counts')
|
||||
# .Next(this.identify_discrepancies)
|
||||
# )
|
||||
#
|
||||
# identify_discrepancies = (
|
||||
# flow_func(this.analyze_discrepancies)
|
||||
# .Next(this.make_adjustments)
|
||||
# )
|
||||
#
|
||||
# make_adjustments = (
|
||||
# flow_view(InventoryAdjustmentView)
|
||||
# .Permission('inventory.can_make_adjustments')
|
||||
# .Next(this.generate_report)
|
||||
# )
|
||||
#
|
||||
# generate_report = (
|
||||
# flow_view(AuditReportView)
|
||||
# .Permission('inventory.can_generate_audit_reports')
|
||||
# .Next(this.complete_audit)
|
||||
# )
|
||||
#
|
||||
# complete_audit = (
|
||||
# flow_func(this.finalize_audit)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_audit)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_audit(self, activation):
|
||||
# """Initialize the audit process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Send notification to audit team
|
||||
# self.notify_audit_scheduled(process.audit_type, process.location_id)
|
||||
#
|
||||
# def analyze_discrepancies(self, activation):
|
||||
# """Analyze inventory discrepancies"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Check for discrepancies
|
||||
# discrepancies = self.check_discrepancies(process.location_id)
|
||||
#
|
||||
# if discrepancies:
|
||||
# process.discrepancies_identified = True
|
||||
# process.save()
|
||||
#
|
||||
# # Alert audit supervisor
|
||||
# self.alert_audit_supervisor(process.location_id, discrepancies)
|
||||
#
|
||||
# def finalize_audit(self, activation):
|
||||
# """Finalize the audit process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark audit as completed
|
||||
# process.audit_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_audit_completion(process.audit_type, process.location_id)
|
||||
#
|
||||
# # Update audit metrics
|
||||
# self.update_audit_metrics(process.audit_type, process.location_id)
|
||||
#
|
||||
# def end_audit(self, activation):
|
||||
# """End the audit workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate audit summary
|
||||
# self.generate_audit_summary(process.audit_type, process.location_id)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_audit_scheduled(self, audit_type, location_id):
|
||||
# """Notify audit team of scheduled audit"""
|
||||
# audit_staff = User.objects.filter(
|
||||
# groups__name='Audit Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in audit_staff:
|
||||
# send_mail(
|
||||
# subject=f'Audit Scheduled: {location_id}',
|
||||
# message=f'{audit_type} audit scheduled for location {location_id}.',
|
||||
# from_email='audit@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def check_discrepancies(self, location_id):
|
||||
# """Check for inventory discrepancies"""
|
||||
# # This would implement discrepancy checking logic
|
||||
# return []
|
||||
#
|
||||
# def alert_audit_supervisor(self, location_id, discrepancies):
|
||||
# """Alert audit supervisor of discrepancies"""
|
||||
# supervisors = User.objects.filter(
|
||||
# groups__name='Audit Supervisors'
|
||||
# )
|
||||
#
|
||||
# for supervisor in supervisors:
|
||||
# send_mail(
|
||||
# subject=f'Audit Discrepancies Found: {location_id}',
|
||||
# message=f'Inventory discrepancies identified during audit.',
|
||||
# from_email='audit@hospital.com',
|
||||
# recipient_list=[supervisor.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_audit_completion(self, audit_type, location_id):
|
||||
# """Notify audit completion"""
|
||||
# # This would notify relevant parties
|
||||
# pass
|
||||
#
|
||||
# def update_audit_metrics(self, audit_type, location_id):
|
||||
# """Update audit metrics"""
|
||||
# # This would update audit performance metrics
|
||||
# pass
|
||||
#
|
||||
# def generate_audit_summary(self, audit_type, location_id):
|
||||
# """Generate audit summary"""
|
||||
# # This would generate audit summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class SupplierManagementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for supplier management
|
||||
# """
|
||||
# supplier = ModelField(Supplier, help_text='Associated supplier')
|
||||
#
|
||||
# # Process status tracking
|
||||
# supplier_onboarded = models.BooleanField(default=False)
|
||||
# qualifications_verified = models.BooleanField(default=False)
|
||||
# contracts_negotiated = models.BooleanField(default=False)
|
||||
# performance_monitored = models.BooleanField(default=False)
|
||||
# relationship_maintained = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Supplier Management Process'
|
||||
# verbose_name_plural = 'Supplier Management Processes'
|
||||
#
|
||||
#
|
||||
# class SupplierManagementFlow(Flow):
|
||||
# """
|
||||
# Supplier Management Workflow
|
||||
#
|
||||
# This flow manages supplier onboarding, qualification,
|
||||
# performance monitoring, and relationship management.
|
||||
# """
|
||||
#
|
||||
# process_class = SupplierManagementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_supplier_management)
|
||||
# .Next(this.onboard_supplier)
|
||||
# )
|
||||
#
|
||||
# onboard_supplier = (
|
||||
# flow_view(SupplierOnboardingView)
|
||||
# .Permission('inventory.can_onboard_suppliers')
|
||||
# .Next(this.verify_qualifications)
|
||||
# )
|
||||
#
|
||||
# verify_qualifications = (
|
||||
# flow_view(QualificationVerificationView)
|
||||
# .Permission('inventory.can_verify_qualifications')
|
||||
# .Next(this.negotiate_contracts)
|
||||
# )
|
||||
#
|
||||
# negotiate_contracts = (
|
||||
# flow_view(ContractNegotiationView)
|
||||
# .Permission('inventory.can_negotiate_contracts')
|
||||
# .Next(this.monitor_performance)
|
||||
# )
|
||||
#
|
||||
# monitor_performance = (
|
||||
# flow_view(PerformanceMonitoringView)
|
||||
# .Permission('inventory.can_monitor_performance')
|
||||
# .Next(this.maintain_relationship)
|
||||
# )
|
||||
#
|
||||
# maintain_relationship = (
|
||||
# flow_func(this.complete_supplier_management)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_supplier_management)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_supplier_management(self, activation):
|
||||
# """Initialize the supplier management process"""
|
||||
# process = activation.process
|
||||
# supplier = process.supplier
|
||||
#
|
||||
# # Send notification to procurement team
|
||||
# self.notify_supplier_onboarding(supplier)
|
||||
#
|
||||
# def complete_supplier_management(self, activation):
|
||||
# """Finalize the supplier management process"""
|
||||
# process = activation.process
|
||||
# supplier = process.supplier
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.relationship_maintained = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_supplier_management_completion(supplier)
|
||||
#
|
||||
# def end_supplier_management(self, activation):
|
||||
# """End the supplier management workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate supplier management summary
|
||||
# self.generate_supplier_summary(process.supplier)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_supplier_onboarding(self, supplier):
|
||||
# """Notify procurement team of supplier onboarding"""
|
||||
# procurement_staff = User.objects.filter(
|
||||
# groups__name='Procurement Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in procurement_staff:
|
||||
# send_mail(
|
||||
# subject=f'Supplier Onboarding: {supplier.name}',
|
||||
# message=f'New supplier {supplier.name} requires onboarding.',
|
||||
# from_email='procurement@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_supplier_management_completion(self, supplier):
|
||||
# """Notify supplier management completion"""
|
||||
# # This would notify relevant parties
|
||||
# pass
|
||||
#
|
||||
# def generate_supplier_summary(self, supplier):
|
||||
# """Generate supplier management summary"""
|
||||
# # This would generate supplier summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def auto_reorder_items():
|
||||
# """Background task to automatically reorder items"""
|
||||
# try:
|
||||
# # This would check reorder points and create orders
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def monitor_expiring_items():
|
||||
# """Background task to monitor expiring inventory items"""
|
||||
# try:
|
||||
# # This would identify items nearing expiration
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def generate_inventory_reports():
|
||||
# """Background task to generate inventory reports"""
|
||||
# try:
|
||||
# # This would generate daily inventory reports
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def update_supplier_performance():
|
||||
# """Background task to update supplier performance metrics"""
|
||||
# try:
|
||||
# # This would calculate supplier performance metrics
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def schedule_cycle_counts():
|
||||
# """Background task to schedule cycle counts"""
|
||||
# try:
|
||||
# # This would schedule regular cycle counts
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
BIN
laboratory/__pycache__/flows.cpython-312.pyc
Normal file
BIN
laboratory/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
523
laboratory/flows.py
Normal file
523
laboratory/flows.py
Normal file
@ -0,0 +1,523 @@
|
||||
# """
|
||||
# Viewflow workflows for laboratory app.
|
||||
# Provides lab test ordering, specimen processing, and result reporting workflows.
|
||||
# """
|
||||
#
|
||||
# from viewflow import Flow, lock
|
||||
# from viewflow.base import this, flow_func
|
||||
# from viewflow.contrib import celery
|
||||
# from viewflow.decorators import flow_view
|
||||
# from viewflow.fields import CharField, ModelField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.models import Process, Task
|
||||
# from django.contrib.auth.models import User
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import LabOrder, Specimen, LabResult, LabTest
|
||||
# from .views import (
|
||||
# LabOrderCreationView, SpecimenCollectionView, SpecimenReceiptView,
|
||||
# TestProcessingView, ResultEntryView, ResultVerificationView,
|
||||
# ResultReportingView
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class LabOrderProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for laboratory test orders
|
||||
# """
|
||||
# lab_order = ModelField(LabOrder, help_text='Associated lab order')
|
||||
#
|
||||
# # Process status tracking
|
||||
# order_created = models.BooleanField(default=False)
|
||||
# specimen_collected = models.BooleanField(default=False)
|
||||
# specimen_received = models.BooleanField(default=False)
|
||||
# tests_processed = models.BooleanField(default=False)
|
||||
# results_verified = models.BooleanField(default=False)
|
||||
# results_reported = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Lab Order Process'
|
||||
# verbose_name_plural = 'Lab Order Processes'
|
||||
#
|
||||
#
|
||||
# class LabOrderFlow(Flow):
|
||||
# """
|
||||
# Laboratory Test Order Workflow
|
||||
#
|
||||
# This flow manages the complete laboratory testing process from
|
||||
# order creation through result reporting.
|
||||
# """
|
||||
#
|
||||
# process_class = LabOrderProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_lab_order)
|
||||
# .Next(this.create_order)
|
||||
# )
|
||||
#
|
||||
# create_order = (
|
||||
# flow_view(LabOrderCreationView)
|
||||
# .Permission('laboratory.can_create_orders')
|
||||
# .Next(this.schedule_collection)
|
||||
# )
|
||||
#
|
||||
# schedule_collection = (
|
||||
# flow_func(this.schedule_specimen_collection)
|
||||
# .Next(this.collect_specimen)
|
||||
# )
|
||||
#
|
||||
# collect_specimen = (
|
||||
# flow_view(SpecimenCollectionView)
|
||||
# .Permission('laboratory.can_collect_specimens')
|
||||
# .Next(this.transport_specimen)
|
||||
# )
|
||||
#
|
||||
# transport_specimen = (
|
||||
# flow_func(this.handle_specimen_transport)
|
||||
# .Next(this.receive_specimen)
|
||||
# )
|
||||
#
|
||||
# receive_specimen = (
|
||||
# flow_view(SpecimenReceiptView)
|
||||
# .Permission('laboratory.can_receive_specimens')
|
||||
# .Next(this.check_specimen_quality)
|
||||
# )
|
||||
#
|
||||
# check_specimen_quality = (
|
||||
# flow_func(this.validate_specimen_quality)
|
||||
# .Next(this.process_tests)
|
||||
# )
|
||||
#
|
||||
# process_tests = (
|
||||
# flow_view(TestProcessingView)
|
||||
# .Permission('laboratory.can_process_tests')
|
||||
# .Next(this.enter_results)
|
||||
# )
|
||||
#
|
||||
# enter_results = (
|
||||
# flow_view(ResultEntryView)
|
||||
# .Permission('laboratory.can_enter_results')
|
||||
# .Next(this.verify_results)
|
||||
# )
|
||||
#
|
||||
# verify_results = (
|
||||
# flow_view(ResultVerificationView)
|
||||
# .Permission('laboratory.can_verify_results')
|
||||
# .Next(this.check_critical_values)
|
||||
# )
|
||||
#
|
||||
# check_critical_values = (
|
||||
# flow_func(this.check_for_critical_values)
|
||||
# .Next(this.report_results)
|
||||
# )
|
||||
#
|
||||
# report_results = (
|
||||
# flow_view(ResultReportingView)
|
||||
# .Permission('laboratory.can_report_results')
|
||||
# .Next(this.finalize_order)
|
||||
# )
|
||||
#
|
||||
# finalize_order = (
|
||||
# flow_func(this.complete_lab_order)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_lab_order)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_lab_order(self, activation):
|
||||
# """Initialize the lab order process"""
|
||||
# process = activation.process
|
||||
# lab_order = process.lab_order
|
||||
#
|
||||
# # Update order status
|
||||
# lab_order.status = 'PENDING'
|
||||
# lab_order.save()
|
||||
#
|
||||
# # Send notification to lab staff
|
||||
# self.notify_lab_staff(lab_order)
|
||||
#
|
||||
# def schedule_specimen_collection(self, activation):
|
||||
# """Schedule specimen collection based on order priority"""
|
||||
# process = activation.process
|
||||
# lab_order = process.lab_order
|
||||
#
|
||||
# # Calculate collection time based on priority
|
||||
# if lab_order.priority == 'STAT':
|
||||
# collection_time = timezone.now() + timezone.timedelta(minutes=15)
|
||||
# elif lab_order.priority == 'URGENT':
|
||||
# collection_time = timezone.now() + timezone.timedelta(hours=1)
|
||||
# else:
|
||||
# collection_time = timezone.now() + timezone.timedelta(hours=4)
|
||||
#
|
||||
# # Update order with scheduled collection time
|
||||
# lab_order.collection_datetime = collection_time
|
||||
# lab_order.save()
|
||||
#
|
||||
# # Notify collection staff
|
||||
# self.notify_collection_staff(lab_order)
|
||||
#
|
||||
# def handle_specimen_transport(self, activation):
|
||||
# """Handle specimen transport to laboratory"""
|
||||
# process = activation.process
|
||||
# lab_order = process.lab_order
|
||||
#
|
||||
# # Update specimen status to in transit
|
||||
# for specimen in lab_order.specimens.all():
|
||||
# specimen.status = 'IN_TRANSIT'
|
||||
# specimen.save()
|
||||
#
|
||||
# # Schedule transport based on priority
|
||||
# if lab_order.priority in ['STAT', 'URGENT']:
|
||||
# # Immediate transport
|
||||
# self.notify_transport_team(lab_order, urgent=True)
|
||||
# else:
|
||||
# # Regular transport schedule
|
||||
# self.schedule_regular_transport(lab_order)
|
||||
#
|
||||
# def validate_specimen_quality(self, activation):
|
||||
# """Validate specimen quality and reject if necessary"""
|
||||
# process = activation.process
|
||||
# lab_order = process.lab_order
|
||||
#
|
||||
# rejected_specimens = []
|
||||
#
|
||||
# for specimen in lab_order.specimens.all():
|
||||
# if specimen.quality == 'REJECTED':
|
||||
# rejected_specimens.append(specimen)
|
||||
#
|
||||
# # Notify ordering physician of rejection
|
||||
# self.notify_specimen_rejection(specimen)
|
||||
#
|
||||
# # Request new specimen collection
|
||||
# self.request_recollection(specimen)
|
||||
#
|
||||
# if rejected_specimens:
|
||||
# # Update order status if specimens rejected
|
||||
# lab_order.status = 'SPECIMEN_REJECTED'
|
||||
# lab_order.save()
|
||||
# else:
|
||||
# # Proceed with processing
|
||||
# lab_order.status = 'PROCESSING'
|
||||
# lab_order.save()
|
||||
#
|
||||
# process.specimen_received = True
|
||||
# process.save()
|
||||
#
|
||||
# def check_for_critical_values(self, activation):
|
||||
# """Check for critical values and alert if found"""
|
||||
# process = activation.process
|
||||
# lab_order = process.lab_order
|
||||
#
|
||||
# critical_results = []
|
||||
#
|
||||
# for result in lab_order.results.all():
|
||||
# if self.is_critical_value(result):
|
||||
# critical_results.append(result)
|
||||
#
|
||||
# if critical_results:
|
||||
# # Immediate notification for critical values
|
||||
# self.notify_critical_values(lab_order, critical_results)
|
||||
#
|
||||
# # Mark as critical in order
|
||||
# lab_order.has_critical_values = True
|
||||
# lab_order.save()
|
||||
#
|
||||
# def complete_lab_order(self, activation):
|
||||
# """Finalize the lab order process"""
|
||||
# process = activation.process
|
||||
# lab_order = process.lab_order
|
||||
#
|
||||
# # Update order status
|
||||
# lab_order.status = 'COMPLETED'
|
||||
# lab_order.completed_datetime = timezone.now()
|
||||
# lab_order.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.results_reported = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_order_completion(lab_order)
|
||||
#
|
||||
# def end_lab_order(self, activation):
|
||||
# """End the lab order workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate order summary report
|
||||
# self.generate_order_summary(process.lab_order)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_lab_staff(self, lab_order):
|
||||
# """Notify laboratory staff of new order"""
|
||||
# from django.contrib.auth.models import Group
|
||||
#
|
||||
# lab_staff = User.objects.filter(
|
||||
# groups__name='Laboratory Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in lab_staff:
|
||||
# send_mail(
|
||||
# subject=f'New Lab Order: {lab_order.order_number}',
|
||||
# message=f'New {lab_order.get_priority_display()} lab order for {lab_order.patient.get_full_name()}.',
|
||||
# from_email='laboratory@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_collection_staff(self, lab_order):
|
||||
# """Notify specimen collection staff"""
|
||||
# collection_staff = User.objects.filter(
|
||||
# groups__name='Specimen Collection'
|
||||
# )
|
||||
#
|
||||
# for staff in collection_staff:
|
||||
# send_mail(
|
||||
# subject=f'Specimen Collection Required: {lab_order.order_number}',
|
||||
# message=f'Specimen collection scheduled for {lab_order.collection_datetime}.',
|
||||
# from_email='laboratory@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_transport_team(self, lab_order, urgent=False):
|
||||
# """Notify transport team for specimen pickup"""
|
||||
# transport_staff = User.objects.filter(
|
||||
# groups__name='Transport Team'
|
||||
# )
|
||||
#
|
||||
# priority_text = "URGENT" if urgent else "ROUTINE"
|
||||
#
|
||||
# for staff in transport_staff:
|
||||
# send_mail(
|
||||
# subject=f'{priority_text} Transport: {lab_order.order_number}',
|
||||
# message=f'Specimen transport required for lab order {lab_order.order_number}.',
|
||||
# from_email='transport@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def schedule_regular_transport(self, lab_order):
|
||||
# """Schedule regular transport pickup"""
|
||||
# # This would integrate with transport scheduling system
|
||||
# pass
|
||||
#
|
||||
# def notify_specimen_rejection(self, specimen):
|
||||
# """Notify ordering physician of specimen rejection"""
|
||||
# if specimen.order.ordering_physician and specimen.order.ordering_physician.email:
|
||||
# send_mail(
|
||||
# subject=f'Specimen Rejected: {specimen.specimen_number}',
|
||||
# message=f'Specimen rejected due to: {specimen.rejection_reason}. Please reorder.',
|
||||
# from_email='laboratory@hospital.com',
|
||||
# recipient_list=[specimen.order.ordering_physician.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def request_recollection(self, specimen):
|
||||
# """Request new specimen collection"""
|
||||
# # This would trigger a new collection workflow
|
||||
# pass
|
||||
#
|
||||
# def is_critical_value(self, result):
|
||||
# """Check if result value is critical"""
|
||||
# # This would implement critical value checking logic
|
||||
# # based on test-specific critical ranges
|
||||
# return False # Placeholder
|
||||
#
|
||||
# def notify_critical_values(self, lab_order, critical_results):
|
||||
# """Notify physicians of critical values immediately"""
|
||||
# if lab_order.ordering_physician and lab_order.ordering_physician.email:
|
||||
# result_details = "\n".join([
|
||||
# f"{result.test.test_name}: {result.result_value} {result.result_unit}"
|
||||
# for result in critical_results
|
||||
# ])
|
||||
#
|
||||
# send_mail(
|
||||
# subject=f'CRITICAL VALUES: {lab_order.order_number}',
|
||||
# message=f'Critical values detected:\n{result_details}',
|
||||
# from_email='laboratory@hospital.com',
|
||||
# recipient_list=[lab_order.ordering_physician.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_order_completion(self, lab_order):
|
||||
# """Notify ordering physician of completed results"""
|
||||
# if lab_order.ordering_physician and lab_order.ordering_physician.email:
|
||||
# send_mail(
|
||||
# subject=f'Lab Results Available: {lab_order.order_number}',
|
||||
# message=f'Laboratory results are now available for {lab_order.patient.get_full_name()}.',
|
||||
# from_email='laboratory@hospital.com',
|
||||
# recipient_list=[lab_order.ordering_physician.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_order_summary(self, lab_order):
|
||||
# """Generate lab order summary report"""
|
||||
# # This would generate a comprehensive lab order report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class QualityControlProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for quality control procedures
|
||||
# """
|
||||
# qc_batch = CharField(max_length=50, help_text='QC batch identifier')
|
||||
#
|
||||
# # Process status tracking
|
||||
# qc_samples_prepared = models.BooleanField(default=False)
|
||||
# qc_tests_run = models.BooleanField(default=False)
|
||||
# qc_results_reviewed = models.BooleanField(default=False)
|
||||
# qc_approved = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Quality Control Process'
|
||||
# verbose_name_plural = 'Quality Control Processes'
|
||||
#
|
||||
#
|
||||
# class QualityControlFlow(Flow):
|
||||
# """
|
||||
# Laboratory Quality Control Workflow
|
||||
#
|
||||
# This flow manages quality control procedures for laboratory testing.
|
||||
# """
|
||||
#
|
||||
# process_class = QualityControlProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_qc_process)
|
||||
# .Next(this.prepare_qc_samples)
|
||||
# )
|
||||
#
|
||||
# prepare_qc_samples = (
|
||||
# flow_view(QCSamplePreparationView)
|
||||
# .Permission('laboratory.can_prepare_qc_samples')
|
||||
# .Next(this.run_qc_tests)
|
||||
# )
|
||||
#
|
||||
# run_qc_tests = (
|
||||
# flow_view(QCTestExecutionView)
|
||||
# .Permission('laboratory.can_run_qc_tests')
|
||||
# .Next(this.review_qc_results)
|
||||
# )
|
||||
#
|
||||
# review_qc_results = (
|
||||
# flow_view(QCResultReviewView)
|
||||
# .Permission('laboratory.can_review_qc_results')
|
||||
# .Next(this.approve_qc)
|
||||
# )
|
||||
#
|
||||
# approve_qc = (
|
||||
# flow_view(QCApprovalView)
|
||||
# .Permission('laboratory.can_approve_qc')
|
||||
# .Next(this.finalize_qc)
|
||||
# )
|
||||
#
|
||||
# finalize_qc = (
|
||||
# flow_func(this.complete_qc_process)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_qc_process)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_qc_process(self, activation):
|
||||
# """Initialize the QC process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Notify QC staff
|
||||
# self.notify_qc_staff(process.qc_batch)
|
||||
#
|
||||
# def complete_qc_process(self, activation):
|
||||
# """Finalize the QC process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark QC as approved
|
||||
# process.qc_approved = True
|
||||
# process.save()
|
||||
#
|
||||
# # Release patient results if QC passed
|
||||
# self.release_patient_results(process.qc_batch)
|
||||
#
|
||||
# def end_qc_process(self, activation):
|
||||
# """End the QC workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate QC summary report
|
||||
# self.generate_qc_summary(process.qc_batch)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_qc_staff(self, qc_batch):
|
||||
# """Notify QC staff of new QC batch"""
|
||||
# qc_staff = User.objects.filter(
|
||||
# groups__name='Quality Control'
|
||||
# )
|
||||
#
|
||||
# for staff in qc_staff:
|
||||
# send_mail(
|
||||
# subject=f'QC Batch Ready: {qc_batch}',
|
||||
# message=f'Quality control batch {qc_batch} is ready for processing.',
|
||||
# from_email='laboratory@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def release_patient_results(self, qc_batch):
|
||||
# """Release patient results after QC approval"""
|
||||
# # This would release held patient results
|
||||
# pass
|
||||
#
|
||||
# def generate_qc_summary(self, qc_batch):
|
||||
# """Generate QC summary report"""
|
||||
# # This would generate a comprehensive QC report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def auto_schedule_collection(order_id):
|
||||
# """Background task to automatically schedule specimen collection"""
|
||||
# try:
|
||||
# lab_order = LabOrder.objects.get(id=order_id)
|
||||
#
|
||||
# # Auto-assign collection staff based on location and availability
|
||||
# collection_staff = User.objects.filter(
|
||||
# groups__name='Specimen Collection',
|
||||
# is_active=True
|
||||
# ).first()
|
||||
#
|
||||
# if collection_staff:
|
||||
# # Create collection task
|
||||
# # This would integrate with task management system
|
||||
# return True
|
||||
#
|
||||
# return False
|
||||
# except LabOrder.DoesNotExist:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def process_batch_results(batch_id):
|
||||
# """Background task to process batch of test results"""
|
||||
# try:
|
||||
# # This would process a batch of results
|
||||
# # and perform automated quality checks
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def generate_daily_qc_report():
|
||||
# """Background task to generate daily QC report"""
|
||||
# try:
|
||||
# # This would generate daily QC summary
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
File diff suppressed because it is too large
Load Diff
BIN
operating_theatre/__pycache__/flows.cpython-312.pyc
Normal file
BIN
operating_theatre/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
689
operating_theatre/flows.py
Normal file
689
operating_theatre/flows.py
Normal file
@ -0,0 +1,689 @@
|
||||
# """
|
||||
# Viewflow workflows for operating theatre app.
|
||||
# Provides surgical scheduling, perioperative workflows, and OR management.
|
||||
# """
|
||||
#
|
||||
# from viewflow import Flow, lock
|
||||
# from viewflow.base import this, flow_func
|
||||
# from viewflow.contrib import celery
|
||||
# from viewflow.decorators import flow_view
|
||||
# from viewflow.fields import CharField, ModelField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.models import Process, Task
|
||||
# from django.contrib.auth.models import User
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import SurgicalCase, SurgicalNote, EquipmentUsage, OperatingRoom
|
||||
# from .views import (
|
||||
# SurgicalSchedulingView, PreoperativeChecklistView, ORSetupView,
|
||||
# AnesthesiaInductionView, SurgicalProcedureView, PostoperativeView,
|
||||
# SurgicalNoteView, EquipmentTrackingView
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class SurgicalCaseProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for surgical cases
|
||||
# """
|
||||
# surgical_case = ModelField(SurgicalCase, help_text='Associated surgical case')
|
||||
#
|
||||
# # Process status tracking
|
||||
# case_scheduled = models.BooleanField(default=False)
|
||||
# preop_checklist_completed = models.BooleanField(default=False)
|
||||
# or_setup_completed = models.BooleanField(default=False)
|
||||
# anesthesia_induced = models.BooleanField(default=False)
|
||||
# surgery_started = models.BooleanField(default=False)
|
||||
# surgery_completed = models.BooleanField(default=False)
|
||||
# postop_care_initiated = models.BooleanField(default=False)
|
||||
# surgical_note_completed = models.BooleanField(default=False)
|
||||
# case_closed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Surgical Case Process'
|
||||
# verbose_name_plural = 'Surgical Case Processes'
|
||||
#
|
||||
#
|
||||
# class SurgicalCaseFlow(Flow):
|
||||
# """
|
||||
# Surgical Case Workflow
|
||||
#
|
||||
# This flow manages the complete surgical process from scheduling
|
||||
# through postoperative care and documentation.
|
||||
# """
|
||||
#
|
||||
# process_class = SurgicalCaseProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_surgical_case)
|
||||
# .Next(this.schedule_surgery)
|
||||
# )
|
||||
#
|
||||
# schedule_surgery = (
|
||||
# flow_view(SurgicalSchedulingView)
|
||||
# .Permission('operating_theatre.can_schedule_surgery')
|
||||
# .Next(this.preoperative_checklist)
|
||||
# )
|
||||
#
|
||||
# preoperative_checklist = (
|
||||
# flow_view(PreoperativeChecklistView)
|
||||
# .Permission('operating_theatre.can_complete_preop_checklist')
|
||||
# .Next(this.setup_or)
|
||||
# )
|
||||
#
|
||||
# setup_or = (
|
||||
# flow_view(ORSetupView)
|
||||
# .Permission('operating_theatre.can_setup_or')
|
||||
# .Next(this.induce_anesthesia)
|
||||
# )
|
||||
#
|
||||
# induce_anesthesia = (
|
||||
# flow_view(AnesthesiaInductionView)
|
||||
# .Permission('operating_theatre.can_induce_anesthesia')
|
||||
# .Next(this.perform_surgery)
|
||||
# )
|
||||
#
|
||||
# perform_surgery = (
|
||||
# flow_view(SurgicalProcedureView)
|
||||
# .Permission('operating_theatre.can_perform_surgery')
|
||||
# .Next(this.postoperative_care)
|
||||
# )
|
||||
#
|
||||
# postoperative_care = (
|
||||
# flow_view(PostoperativeView)
|
||||
# .Permission('operating_theatre.can_provide_postop_care')
|
||||
# .Next(this.parallel_documentation)
|
||||
# )
|
||||
#
|
||||
# parallel_documentation = (
|
||||
# flow_func(this.start_parallel_documentation)
|
||||
# .Next(this.complete_surgical_note)
|
||||
# .Next(this.track_equipment)
|
||||
# )
|
||||
#
|
||||
# complete_surgical_note = (
|
||||
# flow_view(SurgicalNoteView)
|
||||
# .Permission('operating_theatre.can_complete_surgical_notes')
|
||||
# .Next(this.join_documentation)
|
||||
# )
|
||||
#
|
||||
# track_equipment = (
|
||||
# flow_view(EquipmentTrackingView)
|
||||
# .Permission('operating_theatre.can_track_equipment')
|
||||
# .Next(this.join_documentation)
|
||||
# )
|
||||
#
|
||||
# join_documentation = (
|
||||
# flow_func(this.join_parallel_documentation)
|
||||
# .Next(this.finalize_case)
|
||||
# )
|
||||
#
|
||||
# finalize_case = (
|
||||
# flow_func(this.complete_surgical_case)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_surgical_case)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_surgical_case(self, activation):
|
||||
# """Initialize the surgical case process"""
|
||||
# process = activation.process
|
||||
# surgical_case = process.surgical_case
|
||||
#
|
||||
# # Update case status
|
||||
# surgical_case.status = 'SCHEDULED'
|
||||
# surgical_case.save()
|
||||
#
|
||||
# # Send notification to OR staff
|
||||
# self.notify_or_staff(surgical_case)
|
||||
#
|
||||
# # Check for emergency cases
|
||||
# if surgical_case.priority == 'EMERGENCY':
|
||||
# self.notify_emergency_surgery(surgical_case)
|
||||
#
|
||||
# def start_parallel_documentation(self, activation):
|
||||
# """Start parallel documentation tasks"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Create documentation tasks
|
||||
# self.create_documentation_tasks(process.surgical_case)
|
||||
#
|
||||
# def join_parallel_documentation(self, activation):
|
||||
# """Wait for all documentation to complete"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Check if all documentation is completed
|
||||
# if (process.surgical_note_completed and
|
||||
# self.equipment_tracking_completed(process.surgical_case)):
|
||||
#
|
||||
# # Proceed to case finalization
|
||||
# self.notify_case_ready_for_closure(process.surgical_case)
|
||||
#
|
||||
# def complete_surgical_case(self, activation):
|
||||
# """Finalize the surgical case process"""
|
||||
# process = activation.process
|
||||
# surgical_case = process.surgical_case
|
||||
#
|
||||
# # Update case status
|
||||
# surgical_case.status = 'COMPLETED'
|
||||
# surgical_case.actual_end_time = timezone.now()
|
||||
# surgical_case.save()
|
||||
#
|
||||
# # Update OR status
|
||||
# if surgical_case.operating_room:
|
||||
# surgical_case.operating_room.status = 'TURNOVER'
|
||||
# surgical_case.operating_room.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.case_closed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_case_completion(surgical_case)
|
||||
#
|
||||
# # Schedule OR cleaning
|
||||
# self.schedule_or_cleaning(surgical_case.operating_room)
|
||||
#
|
||||
# def end_surgical_case(self, activation):
|
||||
# """End the surgical case workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate case summary report
|
||||
# self.generate_case_summary(process.surgical_case)
|
||||
#
|
||||
# # Update quality metrics
|
||||
# self.update_quality_metrics(process.surgical_case)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_or_staff(self, surgical_case):
|
||||
# """Notify OR staff of scheduled surgery"""
|
||||
# from django.contrib.auth.models import Group
|
||||
#
|
||||
# or_staff = User.objects.filter(
|
||||
# groups__name='OR Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in or_staff:
|
||||
# send_mail(
|
||||
# subject=f'Surgery Scheduled: {surgical_case.case_number}',
|
||||
# message=f'Surgery scheduled for {surgical_case.patient.get_full_name()} on {surgical_case.scheduled_start_time}.',
|
||||
# from_email='or@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_emergency_surgery(self, surgical_case):
|
||||
# """Notify of emergency surgery"""
|
||||
# emergency_staff = User.objects.filter(
|
||||
# groups__name__in=['OR Staff', 'Anesthesiologists', 'Surgeons']
|
||||
# )
|
||||
#
|
||||
# for staff in emergency_staff:
|
||||
# send_mail(
|
||||
# subject=f'EMERGENCY Surgery: {surgical_case.case_number}',
|
||||
# message=f'Emergency surgery required for {surgical_case.patient.get_full_name()}.',
|
||||
# from_email='or@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_documentation_tasks(self, surgical_case):
|
||||
# """Create documentation tasks"""
|
||||
# # This would create tasks in a task management system
|
||||
# pass
|
||||
#
|
||||
# def equipment_tracking_completed(self, surgical_case):
|
||||
# """Check if equipment tracking is completed"""
|
||||
# # This would check equipment tracking completion
|
||||
# return True
|
||||
#
|
||||
# def notify_case_ready_for_closure(self, surgical_case):
|
||||
# """Notify that case is ready for closure"""
|
||||
# if surgical_case.primary_surgeon and surgical_case.primary_surgeon.email:
|
||||
# send_mail(
|
||||
# subject=f'Case Ready for Closure: {surgical_case.case_number}',
|
||||
# message=f'All documentation completed for {surgical_case.patient.get_full_name()}.',
|
||||
# from_email='or@hospital.com',
|
||||
# recipient_list=[surgical_case.primary_surgeon.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_case_completion(self, surgical_case):
|
||||
# """Notify relevant staff of case completion"""
|
||||
# # Notify recovery room staff
|
||||
# recovery_staff = User.objects.filter(
|
||||
# groups__name='Recovery Room Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in recovery_staff:
|
||||
# send_mail(
|
||||
# subject=f'Patient to Recovery: {surgical_case.patient.get_full_name()}',
|
||||
# message=f'Patient from OR {surgical_case.operating_room.room_number} coming to recovery.',
|
||||
# from_email='or@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def schedule_or_cleaning(self, operating_room):
|
||||
# """Schedule OR cleaning and turnover"""
|
||||
# # This would schedule cleaning with housekeeping
|
||||
# pass
|
||||
#
|
||||
# def generate_case_summary(self, surgical_case):
|
||||
# """Generate surgical case summary report"""
|
||||
# # This would generate a comprehensive case report
|
||||
# pass
|
||||
#
|
||||
# def update_quality_metrics(self, surgical_case):
|
||||
# """Update quality and performance metrics"""
|
||||
# # This would update OR efficiency and quality metrics
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class ORSchedulingProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for OR scheduling
|
||||
# """
|
||||
# scheduling_date = models.DateField(help_text='Date being scheduled')
|
||||
#
|
||||
# # Process status tracking
|
||||
# schedule_reviewed = models.BooleanField(default=False)
|
||||
# conflicts_resolved = models.BooleanField(default=False)
|
||||
# staff_assigned = models.BooleanField(default=False)
|
||||
# equipment_reserved = models.BooleanField(default=False)
|
||||
# schedule_finalized = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'OR Scheduling Process'
|
||||
# verbose_name_plural = 'OR Scheduling Processes'
|
||||
#
|
||||
#
|
||||
# class ORSchedulingFlow(Flow):
|
||||
# """
|
||||
# Operating Room Scheduling Workflow
|
||||
#
|
||||
# This flow manages OR scheduling including conflict resolution,
|
||||
# staff assignment, and resource allocation.
|
||||
# """
|
||||
#
|
||||
# process_class = ORSchedulingProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_or_scheduling)
|
||||
# .Next(this.review_schedule)
|
||||
# )
|
||||
#
|
||||
# review_schedule = (
|
||||
# flow_view(ScheduleReviewView)
|
||||
# .Permission('operating_theatre.can_review_schedule')
|
||||
# .Next(this.resolve_conflicts)
|
||||
# )
|
||||
#
|
||||
# resolve_conflicts = (
|
||||
# flow_view(ConflictResolutionView)
|
||||
# .Permission('operating_theatre.can_resolve_conflicts')
|
||||
# .Next(this.assign_staff)
|
||||
# )
|
||||
#
|
||||
# assign_staff = (
|
||||
# flow_view(StaffAssignmentView)
|
||||
# .Permission('operating_theatre.can_assign_staff')
|
||||
# .Next(this.reserve_equipment)
|
||||
# )
|
||||
#
|
||||
# reserve_equipment = (
|
||||
# flow_view(EquipmentReservationView)
|
||||
# .Permission('operating_theatre.can_reserve_equipment')
|
||||
# .Next(this.finalize_schedule)
|
||||
# )
|
||||
#
|
||||
# finalize_schedule = (
|
||||
# flow_func(this.complete_or_scheduling)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_or_scheduling)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_or_scheduling(self, activation):
|
||||
# """Initialize the OR scheduling process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Notify scheduling staff
|
||||
# self.notify_scheduling_staff(process.scheduling_date)
|
||||
#
|
||||
# def complete_or_scheduling(self, activation):
|
||||
# """Finalize the OR scheduling process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark schedule as finalized
|
||||
# process.schedule_finalized = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send schedule notifications
|
||||
# self.send_schedule_notifications(process.scheduling_date)
|
||||
#
|
||||
# def end_or_scheduling(self, activation):
|
||||
# """End the OR scheduling workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate schedule report
|
||||
# self.generate_schedule_report(process.scheduling_date)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_scheduling_staff(self, scheduling_date):
|
||||
# """Notify scheduling staff"""
|
||||
# scheduling_staff = User.objects.filter(
|
||||
# groups__name='OR Scheduling'
|
||||
# )
|
||||
#
|
||||
# for staff in scheduling_staff:
|
||||
# send_mail(
|
||||
# subject=f'OR Schedule Review Required: {scheduling_date}',
|
||||
# message=f'OR schedule for {scheduling_date} requires review.',
|
||||
# from_email='or@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def send_schedule_notifications(self, scheduling_date):
|
||||
# """Send schedule notifications to all staff"""
|
||||
# # This would send schedule notifications
|
||||
# pass
|
||||
#
|
||||
# def generate_schedule_report(self, scheduling_date):
|
||||
# """Generate OR schedule report"""
|
||||
# # This would generate schedule report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class EquipmentMaintenanceProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for equipment maintenance
|
||||
# """
|
||||
# equipment_id = CharField(max_length=50, help_text='Equipment identifier')
|
||||
# maintenance_type = CharField(max_length=20, help_text='Type of maintenance')
|
||||
#
|
||||
# # Process status tracking
|
||||
# maintenance_scheduled = models.BooleanField(default=False)
|
||||
# equipment_inspected = models.BooleanField(default=False)
|
||||
# maintenance_performed = models.BooleanField(default=False)
|
||||
# quality_check_completed = models.BooleanField(default=False)
|
||||
# equipment_returned = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Equipment Maintenance Process'
|
||||
# verbose_name_plural = 'Equipment Maintenance Processes'
|
||||
#
|
||||
#
|
||||
# class EquipmentMaintenanceFlow(Flow):
|
||||
# """
|
||||
# Equipment Maintenance Workflow
|
||||
#
|
||||
# This flow manages equipment maintenance including scheduling,
|
||||
# inspection, repair, and quality verification.
|
||||
# """
|
||||
#
|
||||
# process_class = EquipmentMaintenanceProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_equipment_maintenance)
|
||||
# .Next(this.schedule_maintenance)
|
||||
# )
|
||||
#
|
||||
# schedule_maintenance = (
|
||||
# flow_view(MaintenanceSchedulingView)
|
||||
# .Permission('operating_theatre.can_schedule_maintenance')
|
||||
# .Next(this.inspect_equipment)
|
||||
# )
|
||||
#
|
||||
# inspect_equipment = (
|
||||
# flow_view(EquipmentInspectionView)
|
||||
# .Permission('operating_theatre.can_inspect_equipment')
|
||||
# .Next(this.perform_maintenance)
|
||||
# )
|
||||
#
|
||||
# perform_maintenance = (
|
||||
# flow_view(MaintenanceExecutionView)
|
||||
# .Permission('operating_theatre.can_perform_maintenance')
|
||||
# .Next(this.quality_check)
|
||||
# )
|
||||
#
|
||||
# quality_check = (
|
||||
# flow_view(MaintenanceQualityCheckView)
|
||||
# .Permission('operating_theatre.can_quality_check_equipment')
|
||||
# .Next(this.return_equipment)
|
||||
# )
|
||||
#
|
||||
# return_equipment = (
|
||||
# flow_func(this.complete_equipment_maintenance)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_equipment_maintenance)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_equipment_maintenance(self, activation):
|
||||
# """Initialize the equipment maintenance process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Notify maintenance staff
|
||||
# self.notify_maintenance_staff(process.equipment_id, process.maintenance_type)
|
||||
#
|
||||
# def complete_equipment_maintenance(self, activation):
|
||||
# """Finalize the equipment maintenance process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark equipment as available
|
||||
# self.return_equipment_to_service(process.equipment_id)
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.equipment_returned = True
|
||||
# process.save()
|
||||
#
|
||||
# def end_equipment_maintenance(self, activation):
|
||||
# """End the equipment maintenance workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate maintenance report
|
||||
# self.generate_maintenance_report(process.equipment_id)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_maintenance_staff(self, equipment_id, maintenance_type):
|
||||
# """Notify maintenance staff"""
|
||||
# maintenance_staff = User.objects.filter(
|
||||
# groups__name='Equipment Maintenance'
|
||||
# )
|
||||
#
|
||||
# for staff in maintenance_staff:
|
||||
# send_mail(
|
||||
# subject=f'Equipment Maintenance Required: {equipment_id}',
|
||||
# message=f'{maintenance_type} maintenance required for equipment {equipment_id}.',
|
||||
# from_email='maintenance@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def return_equipment_to_service(self, equipment_id):
|
||||
# """Return equipment to service"""
|
||||
# # This would update equipment status
|
||||
# pass
|
||||
#
|
||||
# def generate_maintenance_report(self, equipment_id):
|
||||
# """Generate maintenance report"""
|
||||
# # This would generate maintenance report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class SterilizationProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for instrument sterilization
|
||||
# """
|
||||
# sterilization_batch = CharField(max_length=50, help_text='Sterilization batch identifier')
|
||||
#
|
||||
# # Process status tracking
|
||||
# instruments_collected = models.BooleanField(default=False)
|
||||
# cleaning_completed = models.BooleanField(default=False)
|
||||
# packaging_completed = models.BooleanField(default=False)
|
||||
# sterilization_completed = models.BooleanField(default=False)
|
||||
# quality_verified = models.BooleanField(default=False)
|
||||
# instruments_distributed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Sterilization Process'
|
||||
# verbose_name_plural = 'Sterilization Processes'
|
||||
#
|
||||
#
|
||||
# class SterilizationFlow(Flow):
|
||||
# """
|
||||
# Instrument Sterilization Workflow
|
||||
#
|
||||
# This flow manages the sterilization process for surgical instruments
|
||||
# including cleaning, packaging, sterilization, and quality verification.
|
||||
# """
|
||||
#
|
||||
# process_class = SterilizationProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_sterilization)
|
||||
# .Next(this.collect_instruments)
|
||||
# )
|
||||
#
|
||||
# collect_instruments = (
|
||||
# flow_view(InstrumentCollectionView)
|
||||
# .Permission('operating_theatre.can_collect_instruments')
|
||||
# .Next(this.clean_instruments)
|
||||
# )
|
||||
#
|
||||
# clean_instruments = (
|
||||
# flow_view(InstrumentCleaningView)
|
||||
# .Permission('operating_theatre.can_clean_instruments')
|
||||
# .Next(this.package_instruments)
|
||||
# )
|
||||
#
|
||||
# package_instruments = (
|
||||
# flow_view(InstrumentPackagingView)
|
||||
# .Permission('operating_theatre.can_package_instruments')
|
||||
# .Next(this.sterilize_instruments)
|
||||
# )
|
||||
#
|
||||
# sterilize_instruments = (
|
||||
# flow_view(SterilizationExecutionView)
|
||||
# .Permission('operating_theatre.can_sterilize_instruments')
|
||||
# .Next(this.verify_sterilization)
|
||||
# )
|
||||
#
|
||||
# verify_sterilization = (
|
||||
# flow_view(SterilizationVerificationView)
|
||||
# .Permission('operating_theatre.can_verify_sterilization')
|
||||
# .Next(this.distribute_instruments)
|
||||
# )
|
||||
#
|
||||
# distribute_instruments = (
|
||||
# flow_func(this.complete_sterilization)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_sterilization)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_sterilization(self, activation):
|
||||
# """Initialize the sterilization process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Notify sterilization staff
|
||||
# self.notify_sterilization_staff(process.sterilization_batch)
|
||||
#
|
||||
# def complete_sterilization(self, activation):
|
||||
# """Finalize the sterilization process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark instruments as available
|
||||
# self.mark_instruments_available(process.sterilization_batch)
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.instruments_distributed = True
|
||||
# process.save()
|
||||
#
|
||||
# def end_sterilization(self, activation):
|
||||
# """End the sterilization workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate sterilization report
|
||||
# self.generate_sterilization_report(process.sterilization_batch)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_sterilization_staff(self, batch):
|
||||
# """Notify sterilization staff"""
|
||||
# sterilization_staff = User.objects.filter(
|
||||
# groups__name='Sterilization Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in sterilization_staff:
|
||||
# send_mail(
|
||||
# subject=f'Sterilization Batch Ready: {batch}',
|
||||
# message=f'Sterilization batch {batch} is ready for processing.',
|
||||
# from_email='sterilization@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def mark_instruments_available(self, batch):
|
||||
# """Mark instruments as available"""
|
||||
# # This would update instrument availability
|
||||
# pass
|
||||
#
|
||||
# def generate_sterilization_report(self, batch):
|
||||
# """Generate sterilization report"""
|
||||
# # This would generate sterilization report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def auto_schedule_or_cleaning(room_id):
|
||||
# """Background task to automatically schedule OR cleaning"""
|
||||
# try:
|
||||
# # This would schedule OR cleaning
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def monitor_case_delays():
|
||||
# """Background task to monitor surgical case delays"""
|
||||
# try:
|
||||
# # This would monitor for delays and send alerts
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def generate_or_utilization_report():
|
||||
# """Background task to generate OR utilization report"""
|
||||
# try:
|
||||
# # This would generate utilization reports
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def auto_assign_or_staff(case_id):
|
||||
# """Background task to automatically assign OR staff"""
|
||||
# try:
|
||||
# # This would auto-assign staff based on availability
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
BIN
patients/__pycache__/flows.cpython-312.pyc
Normal file
BIN
patients/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
761
patients/flows.py
Normal file
761
patients/flows.py
Normal file
@ -0,0 +1,761 @@
|
||||
# """
|
||||
# Viewflow workflows for patients app.
|
||||
# Provides patient registration, consent management, and record management workflows.
|
||||
# """
|
||||
#
|
||||
# from viewflow import Flow, lock
|
||||
# from viewflow.base import this, flow_func
|
||||
# from viewflow.contrib import celery
|
||||
# from viewflow.decorators import flow_view
|
||||
# from viewflow.fields import CharField, ModelField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.models import Process, Task
|
||||
# from django.contrib.auth.models import User
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import PatientProfile, ConsentForm, ConsentTemplate, PatientNote
|
||||
# from .views import (
|
||||
# PatientRegistrationView, IdentityVerificationView, InsuranceVerificationView,
|
||||
# ConsentCollectionView, MedicalHistoryView, EmergencyContactView,
|
||||
# ConsentPresentationView, ConsentSigningView, ConsentWitnessView,
|
||||
# ConsentVerificationView
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class PatientRegistrationProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for patient registration
|
||||
# """
|
||||
# patient = ModelField(PatientProfile, help_text='Associated patient')
|
||||
#
|
||||
# # Process status tracking
|
||||
# patient_created = models.BooleanField(default=False)
|
||||
# identity_verified = models.BooleanField(default=False)
|
||||
# insurance_verified = models.BooleanField(default=False)
|
||||
# consents_collected = models.BooleanField(default=False)
|
||||
# medical_history_collected = models.BooleanField(default=False)
|
||||
# emergency_contacts_added = models.BooleanField(default=False)
|
||||
# registration_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Patient Registration Process'
|
||||
# verbose_name_plural = 'Patient Registration Processes'
|
||||
#
|
||||
#
|
||||
# class PatientRegistrationFlow(Flow):
|
||||
# """
|
||||
# Patient Registration Workflow
|
||||
#
|
||||
# This flow manages the complete patient registration process including
|
||||
# identity verification, insurance verification, and consent collection.
|
||||
# """
|
||||
#
|
||||
# process_class = PatientRegistrationProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_registration)
|
||||
# .Next(this.create_patient_record)
|
||||
# )
|
||||
#
|
||||
# create_patient_record = (
|
||||
# flow_view(PatientRegistrationView)
|
||||
# .Permission('patients.can_register_patients')
|
||||
# .Next(this.verify_identity)
|
||||
# )
|
||||
#
|
||||
# verify_identity = (
|
||||
# flow_view(IdentityVerificationView)
|
||||
# .Permission('patients.can_verify_identity')
|
||||
# .Next(this.verify_insurance)
|
||||
# )
|
||||
#
|
||||
# verify_insurance = (
|
||||
# flow_view(InsuranceVerificationView)
|
||||
# .Permission('patients.can_verify_insurance')
|
||||
# .Next(this.parallel_data_collection)
|
||||
# )
|
||||
#
|
||||
# parallel_data_collection = (
|
||||
# flow_func(this.start_parallel_collection)
|
||||
# .Next(this.collect_consents)
|
||||
# .Next(this.collect_medical_history)
|
||||
# .Next(this.collect_emergency_contacts)
|
||||
# )
|
||||
#
|
||||
# collect_consents = (
|
||||
# flow_view(ConsentCollectionView)
|
||||
# .Permission('patients.can_collect_consents')
|
||||
# .Next(this.join_data_collection)
|
||||
# )
|
||||
#
|
||||
# collect_medical_history = (
|
||||
# flow_view(MedicalHistoryView)
|
||||
# .Permission('patients.can_collect_medical_history')
|
||||
# .Next(this.join_data_collection)
|
||||
# )
|
||||
#
|
||||
# collect_emergency_contacts = (
|
||||
# flow_view(EmergencyContactView)
|
||||
# .Permission('patients.can_collect_emergency_contacts')
|
||||
# .Next(this.join_data_collection)
|
||||
# )
|
||||
#
|
||||
# join_data_collection = (
|
||||
# flow_func(this.join_parallel_collection)
|
||||
# .Next(this.finalize_registration)
|
||||
# )
|
||||
#
|
||||
# finalize_registration = (
|
||||
# flow_func(this.complete_registration)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_registration)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_registration(self, activation):
|
||||
# """Initialize the registration process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Log registration start
|
||||
# self.log_registration_start(process.patient)
|
||||
#
|
||||
# # Notify registration staff
|
||||
# self.notify_registration_staff(process.patient)
|
||||
#
|
||||
# def start_parallel_collection(self, activation):
|
||||
# """Start parallel data collection tasks"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Create parallel collection tasks
|
||||
# self.create_collection_tasks(process.patient)
|
||||
#
|
||||
# def join_parallel_collection(self, activation):
|
||||
# """Wait for all data collection to complete"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Check if all collection is completed
|
||||
# if (process.consents_collected and
|
||||
# process.medical_history_collected and
|
||||
# process.emergency_contacts_added):
|
||||
#
|
||||
# # Proceed to finalization
|
||||
# self.notify_registration_ready(process.patient)
|
||||
#
|
||||
# def complete_registration(self, activation):
|
||||
# """Finalize the registration process"""
|
||||
# process = activation.process
|
||||
# patient = process.patient
|
||||
#
|
||||
# # Update patient status
|
||||
# patient.registration_status = 'COMPLETED'
|
||||
# patient.save()
|
||||
#
|
||||
# # Generate MRN if not already assigned
|
||||
# if not patient.mrn:
|
||||
# patient.mrn = self.generate_mrn()
|
||||
# patient.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.registration_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_registration_completion(patient)
|
||||
#
|
||||
# # Create patient chart
|
||||
# self.create_patient_chart(patient)
|
||||
#
|
||||
# def end_registration(self, activation):
|
||||
# """End the registration workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate registration summary report
|
||||
# self.generate_registration_summary(process.patient)
|
||||
#
|
||||
# # Update registration metrics
|
||||
# self.update_registration_metrics(process.patient)
|
||||
#
|
||||
# # Helper methods
|
||||
# def log_registration_start(self, patient):
|
||||
# """Log registration start"""
|
||||
# # This would log the registration start
|
||||
# pass
|
||||
#
|
||||
# def notify_registration_staff(self, patient):
|
||||
# """Notify registration staff of new patient"""
|
||||
# from django.contrib.auth.models import Group
|
||||
#
|
||||
# registration_staff = User.objects.filter(
|
||||
# groups__name='Registration Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in registration_staff:
|
||||
# send_mail(
|
||||
# subject=f'New Patient Registration: {patient.get_full_name()}',
|
||||
# message=f'New patient registration started for {patient.get_full_name()}.',
|
||||
# from_email='registration@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_collection_tasks(self, patient):
|
||||
# """Create data collection tasks"""
|
||||
# # This would create tasks in a task management system
|
||||
# pass
|
||||
#
|
||||
# def notify_registration_ready(self, patient):
|
||||
# """Notify that registration is ready for completion"""
|
||||
# registration_staff = User.objects.filter(
|
||||
# groups__name='Registration Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in registration_staff:
|
||||
# send_mail(
|
||||
# subject=f'Registration Ready: {patient.get_full_name()}',
|
||||
# message=f'Patient registration for {patient.get_full_name()} is ready for completion.',
|
||||
# from_email='registration@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_mrn(self):
|
||||
# """Generate unique Medical Record Number"""
|
||||
# from django.utils.crypto import get_random_string
|
||||
# return f"MRN{timezone.now().strftime('%Y%m%d')}{get_random_string(6, '0123456789')}"
|
||||
#
|
||||
# def notify_registration_completion(self, patient):
|
||||
# """Notify relevant parties of registration completion"""
|
||||
# # Notify patient if email available
|
||||
# if patient.email:
|
||||
# send_mail(
|
||||
# subject='Registration Complete',
|
||||
# message=f'Welcome {patient.first_name}! Your registration is complete. Your MRN is: {patient.mrn}',
|
||||
# from_email='registration@hospital.com',
|
||||
# recipient_list=[patient.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_patient_chart(self, patient):
|
||||
# """Create initial patient chart"""
|
||||
# # This would create the initial patient chart
|
||||
# pass
|
||||
#
|
||||
# def generate_registration_summary(self, patient):
|
||||
# """Generate registration summary report"""
|
||||
# # This would generate a comprehensive registration report
|
||||
# pass
|
||||
#
|
||||
# def update_registration_metrics(self, patient):
|
||||
# """Update registration metrics"""
|
||||
# # This would update registration performance metrics
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class ConsentManagementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for consent management
|
||||
# """
|
||||
# consent_form = ModelField(ConsentForm, help_text='Associated consent form')
|
||||
#
|
||||
# # Process status tracking
|
||||
# consent_presented = models.BooleanField(default=False)
|
||||
# patient_reviewed = models.BooleanField(default=False)
|
||||
# questions_answered = models.BooleanField(default=False)
|
||||
# consent_signed = models.BooleanField(default=False)
|
||||
# witness_signed = models.BooleanField(default=False)
|
||||
# consent_verified = models.BooleanField(default=False)
|
||||
# consent_filed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Consent Management Process'
|
||||
# verbose_name_plural = 'Consent Management Processes'
|
||||
#
|
||||
#
|
||||
# class ConsentManagementFlow(Flow):
|
||||
# """
|
||||
# Consent Management Workflow
|
||||
#
|
||||
# This flow manages the consent collection process including presentation,
|
||||
# review, signing, witnessing, and verification.
|
||||
# """
|
||||
#
|
||||
# process_class = ConsentManagementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_consent_process)
|
||||
# .Next(this.present_consent)
|
||||
# )
|
||||
#
|
||||
# present_consent = (
|
||||
# flow_view(ConsentPresentationView)
|
||||
# .Permission('patients.can_present_consents')
|
||||
# .Next(this.patient_review)
|
||||
# )
|
||||
#
|
||||
# patient_review = (
|
||||
# flow_view(PatientConsentReviewView)
|
||||
# .Permission('patients.can_review_consents')
|
||||
# .Next(this.answer_questions)
|
||||
# )
|
||||
#
|
||||
# answer_questions = (
|
||||
# flow_view(ConsentQuestionView)
|
||||
# .Permission('patients.can_answer_consent_questions')
|
||||
# .Next(this.sign_consent)
|
||||
# )
|
||||
#
|
||||
# sign_consent = (
|
||||
# flow_view(ConsentSigningView)
|
||||
# .Permission('patients.can_sign_consents')
|
||||
# .Next(this.witness_consent)
|
||||
# )
|
||||
#
|
||||
# witness_consent = (
|
||||
# flow_view(ConsentWitnessView)
|
||||
# .Permission('patients.can_witness_consents')
|
||||
# .Next(this.verify_consent)
|
||||
# )
|
||||
#
|
||||
# verify_consent = (
|
||||
# flow_view(ConsentVerificationView)
|
||||
# .Permission('patients.can_verify_consents')
|
||||
# .Next(this.file_consent)
|
||||
# )
|
||||
#
|
||||
# file_consent = (
|
||||
# flow_func(this.complete_consent_process)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_consent_process)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_consent_process(self, activation):
|
||||
# """Initialize the consent process"""
|
||||
# process = activation.process
|
||||
# consent = process.consent_form
|
||||
#
|
||||
# # Update consent status
|
||||
# consent.status = 'PENDING'
|
||||
# consent.save()
|
||||
#
|
||||
# # Notify relevant staff
|
||||
# self.notify_consent_required(consent)
|
||||
#
|
||||
# def complete_consent_process(self, activation):
|
||||
# """Finalize the consent process"""
|
||||
# process = activation.process
|
||||
# consent = process.consent_form
|
||||
#
|
||||
# # Update consent status
|
||||
# if consent.is_fully_signed:
|
||||
# consent.status = 'SIGNED'
|
||||
# else:
|
||||
# consent.status = 'DECLINED'
|
||||
#
|
||||
# consent.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.consent_filed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_consent_completion(consent)
|
||||
#
|
||||
# # File consent in patient record
|
||||
# self.file_consent_in_record(consent)
|
||||
#
|
||||
# def end_consent_process(self, activation):
|
||||
# """End the consent workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate consent summary report
|
||||
# self.generate_consent_summary(process.consent_form)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_consent_required(self, consent):
|
||||
# """Notify staff that consent is required"""
|
||||
# nursing_staff = User.objects.filter(
|
||||
# groups__name='Nursing Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in nursing_staff:
|
||||
# send_mail(
|
||||
# subject=f'Consent Required: {consent.patient.get_full_name()}',
|
||||
# message=f'Consent form "{consent.template.name}" required for {consent.patient.get_full_name()}.',
|
||||
# from_email='consents@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_consent_completion(self, consent):
|
||||
# """Notify relevant parties of consent completion"""
|
||||
# # Notify ordering physician if applicable
|
||||
# if hasattr(consent, 'ordering_physician') and consent.ordering_physician:
|
||||
# if consent.ordering_physician.email:
|
||||
# send_mail(
|
||||
# subject=f'Consent {consent.status}: {consent.patient.get_full_name()}',
|
||||
# message=f'Consent form "{consent.template.name}" has been {consent.status.lower()}.',
|
||||
# from_email='consents@hospital.com',
|
||||
# recipient_list=[consent.ordering_physician.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def file_consent_in_record(self, consent):
|
||||
# """File consent in patient record"""
|
||||
# # This would file the consent in the patient's medical record
|
||||
# pass
|
||||
#
|
||||
# def generate_consent_summary(self, consent):
|
||||
# """Generate consent summary report"""
|
||||
# # This would generate a comprehensive consent report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class PatientRecordManagementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for patient record management
|
||||
# """
|
||||
# patient_id = CharField(max_length=50, help_text='Patient identifier')
|
||||
# record_action = CharField(max_length=20, help_text='Record management action')
|
||||
#
|
||||
# # Process status tracking
|
||||
# record_requested = models.BooleanField(default=False)
|
||||
# authorization_verified = models.BooleanField(default=False)
|
||||
# record_prepared = models.BooleanField(default=False)
|
||||
# quality_checked = models.BooleanField(default=False)
|
||||
# record_delivered = models.BooleanField(default=False)
|
||||
# delivery_confirmed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Patient Record Management Process'
|
||||
# verbose_name_plural = 'Patient Record Management Processes'
|
||||
#
|
||||
#
|
||||
# class PatientRecordManagementFlow(Flow):
|
||||
# """
|
||||
# Patient Record Management Workflow
|
||||
#
|
||||
# This flow manages patient record requests including authorization,
|
||||
# preparation, quality checking, and delivery.
|
||||
# """
|
||||
#
|
||||
# process_class = PatientRecordManagementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_record_management)
|
||||
# .Next(this.verify_authorization)
|
||||
# )
|
||||
#
|
||||
# verify_authorization = (
|
||||
# flow_view(RecordAuthorizationView)
|
||||
# .Permission('patients.can_verify_record_authorization')
|
||||
# .Next(this.prepare_record)
|
||||
# )
|
||||
#
|
||||
# prepare_record = (
|
||||
# flow_view(RecordPreparationView)
|
||||
# .Permission('patients.can_prepare_records')
|
||||
# .Next(this.quality_check)
|
||||
# )
|
||||
#
|
||||
# quality_check = (
|
||||
# flow_view(RecordQualityCheckView)
|
||||
# .Permission('patients.can_quality_check_records')
|
||||
# .Next(this.deliver_record)
|
||||
# )
|
||||
#
|
||||
# deliver_record = (
|
||||
# flow_view(RecordDeliveryView)
|
||||
# .Permission('patients.can_deliver_records')
|
||||
# .Next(this.confirm_delivery)
|
||||
# )
|
||||
#
|
||||
# confirm_delivery = (
|
||||
# flow_func(this.complete_record_management)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_record_management)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_record_management(self, activation):
|
||||
# """Initialize the record management process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Log record request
|
||||
# self.log_record_request(process.patient_id, process.record_action)
|
||||
#
|
||||
# # Notify records staff
|
||||
# self.notify_records_staff(process.patient_id, process.record_action)
|
||||
#
|
||||
# def complete_record_management(self, activation):
|
||||
# """Finalize the record management process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark delivery as confirmed
|
||||
# process.delivery_confirmed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Generate delivery confirmation
|
||||
# self.generate_delivery_confirmation(process.patient_id, process.record_action)
|
||||
#
|
||||
# def end_record_management(self, activation):
|
||||
# """End the record management workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate record management summary
|
||||
# self.generate_record_summary(process.patient_id, process.record_action)
|
||||
#
|
||||
# # Helper methods
|
||||
# def log_record_request(self, patient_id, action):
|
||||
# """Log record request"""
|
||||
# # This would log the record request
|
||||
# pass
|
||||
#
|
||||
# def notify_records_staff(self, patient_id, action):
|
||||
# """Notify records staff"""
|
||||
# records_staff = User.objects.filter(
|
||||
# groups__name='Medical Records'
|
||||
# )
|
||||
#
|
||||
# for staff in records_staff:
|
||||
# send_mail(
|
||||
# subject=f'Record Request: {patient_id}',
|
||||
# message=f'Record {action} requested for patient {patient_id}.',
|
||||
# from_email='records@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_delivery_confirmation(self, patient_id, action):
|
||||
# """Generate delivery confirmation"""
|
||||
# # This would generate delivery confirmation
|
||||
# pass
|
||||
#
|
||||
# def generate_record_summary(self, patient_id, action):
|
||||
# """Generate record management summary"""
|
||||
# # This would generate record management summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class PatientTransferProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for patient transfers between facilities
|
||||
# """
|
||||
# patient_id = CharField(max_length=50, help_text='Patient identifier')
|
||||
# transfer_type = CharField(max_length=20, help_text='Type of transfer')
|
||||
#
|
||||
# # Process status tracking
|
||||
# transfer_requested = models.BooleanField(default=False)
|
||||
# receiving_facility_contacted = models.BooleanField(default=False)
|
||||
# transfer_approved = models.BooleanField(default=False)
|
||||
# records_prepared = models.BooleanField(default=False)
|
||||
# transport_arranged = models.BooleanField(default=False)
|
||||
# patient_transferred = models.BooleanField(default=False)
|
||||
# transfer_confirmed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Patient Transfer Process'
|
||||
# verbose_name_plural = 'Patient Transfer Processes'
|
||||
#
|
||||
#
|
||||
# class PatientTransferFlow(Flow):
|
||||
# """
|
||||
# Patient Transfer Workflow
|
||||
#
|
||||
# This flow manages patient transfers between facilities including
|
||||
# coordination, approval, preparation, and execution.
|
||||
# """
|
||||
#
|
||||
# process_class = PatientTransferProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_transfer)
|
||||
# .Next(this.contact_receiving_facility)
|
||||
# )
|
||||
#
|
||||
# contact_receiving_facility = (
|
||||
# flow_view(FacilityContactView)
|
||||
# .Permission('patients.can_contact_facilities')
|
||||
# .Next(this.approve_transfer)
|
||||
# )
|
||||
#
|
||||
# approve_transfer = (
|
||||
# flow_view(TransferApprovalView)
|
||||
# .Permission('patients.can_approve_transfers')
|
||||
# .Next(this.parallel_preparation)
|
||||
# )
|
||||
#
|
||||
# parallel_preparation = (
|
||||
# flow_func(this.start_parallel_preparation)
|
||||
# .Next(this.prepare_records)
|
||||
# .Next(this.arrange_transport)
|
||||
# )
|
||||
#
|
||||
# prepare_records = (
|
||||
# flow_view(TransferRecordPreparationView)
|
||||
# .Permission('patients.can_prepare_transfer_records')
|
||||
# .Next(this.join_preparation)
|
||||
# )
|
||||
#
|
||||
# arrange_transport = (
|
||||
# flow_view(TransportArrangementView)
|
||||
# .Permission('patients.can_arrange_transport')
|
||||
# .Next(this.join_preparation)
|
||||
# )
|
||||
#
|
||||
# join_preparation = (
|
||||
# flow_func(this.join_parallel_preparation)
|
||||
# .Next(this.execute_transfer)
|
||||
# )
|
||||
#
|
||||
# execute_transfer = (
|
||||
# flow_view(TransferExecutionView)
|
||||
# .Permission('patients.can_execute_transfers')
|
||||
# .Next(this.confirm_transfer)
|
||||
# )
|
||||
#
|
||||
# confirm_transfer = (
|
||||
# flow_func(this.complete_transfer)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_transfer)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_transfer(self, activation):
|
||||
# """Initialize the transfer process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Log transfer request
|
||||
# self.log_transfer_request(process.patient_id, process.transfer_type)
|
||||
#
|
||||
# # Notify transfer coordinator
|
||||
# self.notify_transfer_coordinator(process.patient_id, process.transfer_type)
|
||||
#
|
||||
# def start_parallel_preparation(self, activation):
|
||||
# """Start parallel preparation tasks"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Create preparation tasks
|
||||
# self.create_preparation_tasks(process.patient_id)
|
||||
#
|
||||
# def join_parallel_preparation(self, activation):
|
||||
# """Wait for all preparation to complete"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Check if all preparation is completed
|
||||
# if process.records_prepared and process.transport_arranged:
|
||||
# # Proceed to transfer execution
|
||||
# self.notify_transfer_ready(process.patient_id)
|
||||
#
|
||||
# def complete_transfer(self, activation):
|
||||
# """Finalize the transfer process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark transfer as confirmed
|
||||
# process.transfer_confirmed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send confirmation notifications
|
||||
# self.notify_transfer_completion(process.patient_id)
|
||||
#
|
||||
# def end_transfer(self, activation):
|
||||
# """End the transfer workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate transfer summary report
|
||||
# self.generate_transfer_summary(process.patient_id, process.transfer_type)
|
||||
#
|
||||
# # Helper methods
|
||||
# def log_transfer_request(self, patient_id, transfer_type):
|
||||
# """Log transfer request"""
|
||||
# # This would log the transfer request
|
||||
# pass
|
||||
#
|
||||
# def notify_transfer_coordinator(self, patient_id, transfer_type):
|
||||
# """Notify transfer coordinator"""
|
||||
# coordinators = User.objects.filter(
|
||||
# groups__name='Transfer Coordinators'
|
||||
# )
|
||||
#
|
||||
# for coordinator in coordinators:
|
||||
# send_mail(
|
||||
# subject=f'Patient Transfer Request: {patient_id}',
|
||||
# message=f'{transfer_type} transfer requested for patient {patient_id}.',
|
||||
# from_email='transfers@hospital.com',
|
||||
# recipient_list=[coordinator.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def create_preparation_tasks(self, patient_id):
|
||||
# """Create preparation tasks"""
|
||||
# # This would create preparation tasks
|
||||
# pass
|
||||
#
|
||||
# def notify_transfer_ready(self, patient_id):
|
||||
# """Notify that transfer is ready"""
|
||||
# # This would notify that transfer is ready
|
||||
# pass
|
||||
#
|
||||
# def notify_transfer_completion(self, patient_id):
|
||||
# """Notify transfer completion"""
|
||||
# # This would notify transfer completion
|
||||
# pass
|
||||
#
|
||||
# def generate_transfer_summary(self, patient_id, transfer_type):
|
||||
# """Generate transfer summary"""
|
||||
# # This would generate transfer summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def auto_verify_insurance(patient_id):
|
||||
# """Background task to automatically verify insurance"""
|
||||
# try:
|
||||
# # This would perform automated insurance verification
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def monitor_consent_expiry():
|
||||
# """Background task to monitor consent expiry"""
|
||||
# try:
|
||||
# # This would monitor expiring consents
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def generate_patient_reports():
|
||||
# """Background task to generate patient reports"""
|
||||
# try:
|
||||
# # This would generate patient reports
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def auto_schedule_follow_ups(patient_id):
|
||||
# """Background task to automatically schedule follow-ups"""
|
||||
# try:
|
||||
# # This would schedule follow-up appointments
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
@ -1,6 +1,7 @@
|
||||
"""
|
||||
Patients app views for hospital management system with comprehensive CRUD operations.
|
||||
"""
|
||||
import uuid
|
||||
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
@ -17,6 +18,7 @@ from django.core.paginator import Paginator
|
||||
from datetime import timedelta, date
|
||||
|
||||
from appointments.models import AppointmentRequest
|
||||
from core.models import AuditLogEntry
|
||||
from .models import (
|
||||
PatientProfile, EmergencyContact, InsuranceInfo,
|
||||
ConsentTemplate, ConsentForm, PatientNote
|
||||
@ -2288,7 +2290,7 @@ class ConsentManagementView(LoginRequiredMixin, PermissionRequiredMixin, Templat
|
||||
|
||||
# Get expired/expiring consents
|
||||
today = timezone.now().date()
|
||||
expiry_threshold = today + timezone.timedelta(days=30)
|
||||
expiry_threshold = today + timedelta(days=30)
|
||||
|
||||
expiring_consents = ConsentForm.objects.filter(
|
||||
patient__tenant=self.request.user.tenant,
|
||||
@ -2359,14 +2361,10 @@ class ConsentManagementView(LoginRequiredMixin, PermissionRequiredMixin, Templat
|
||||
return redirect('patients:consent_management')
|
||||
|
||||
# Process template form
|
||||
template_form = ConsentTemplateForm(
|
||||
request.POST,
|
||||
user=request.user,
|
||||
tenant=request.user.tenant
|
||||
)
|
||||
template_form = ConsentTemplateForm
|
||||
|
||||
if template_form.is_valid():
|
||||
template = template_form.save()
|
||||
if template_form.is_valid:
|
||||
template = template_form.save
|
||||
messages.success(
|
||||
request,
|
||||
f"Consent template '{template.name}' created successfully."
|
||||
@ -2718,7 +2716,14 @@ class ConsentManagementView(LoginRequiredMixin, PermissionRequiredMixin, Templat
|
||||
# 'patient': patient
|
||||
# })
|
||||
#
|
||||
#
|
||||
|
||||
@login_required
|
||||
def insurance_details_api(request, insurance_id):
|
||||
insurance_info = get_object_or_404(InsuranceInfo, pk=insurance_id)
|
||||
return JsonResponse({'insurance_info': insurance_info})
|
||||
|
||||
|
||||
|
||||
# @login_required
|
||||
# def insurance_info_list(request, patient_id):
|
||||
# """
|
||||
|
||||
BIN
pharmacy/__pycache__/flows.cpython-312.pyc
Normal file
BIN
pharmacy/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
672
pharmacy/flows.py
Normal file
672
pharmacy/flows.py
Normal file
@ -0,0 +1,672 @@
|
||||
# """
|
||||
# Viewflow workflows for pharmacy app.
|
||||
# Provides prescription processing, dispensing, and medication administration workflows.
|
||||
# """
|
||||
#
|
||||
# from viewflow import Flow, lock
|
||||
# from viewflow.base import this, flow_func
|
||||
# from viewflow.contrib import celery
|
||||
# from viewflow.decorators import flow_view
|
||||
# from viewflow.fields import CharField, ModelField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.models import Process, Task
|
||||
# from django.contrib.auth.models import User
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import Prescription, DispenseRecord, MedicationAdministration, DrugInteraction
|
||||
# from .views import (
|
||||
# PrescriptionReviewView, DrugInteractionCheckView, InventoryCheckView,
|
||||
# MedicationPreparationView, PharmacistVerificationView, DispenseView,
|
||||
# PatientCounselingView, MedicationAdministrationView
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class PrescriptionProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for prescription processing
|
||||
# """
|
||||
# prescription = ModelField(Prescription, help_text='Associated prescription')
|
||||
#
|
||||
# # Process status tracking
|
||||
# prescription_received = models.BooleanField(default=False)
|
||||
# clinical_review_completed = models.BooleanField(default=False)
|
||||
# drug_interactions_checked = models.BooleanField(default=False)
|
||||
# inventory_verified = models.BooleanField(default=False)
|
||||
# medication_prepared = models.BooleanField(default=False)
|
||||
# pharmacist_verified = models.BooleanField(default=False)
|
||||
# patient_counseled = models.BooleanField(default=False)
|
||||
# dispensed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Prescription Process'
|
||||
# verbose_name_plural = 'Prescription Processes'
|
||||
#
|
||||
#
|
||||
# class PrescriptionFlow(Flow):
|
||||
# """
|
||||
# Pharmacy Prescription Processing Workflow
|
||||
#
|
||||
# This flow manages the complete prescription processing from
|
||||
# receipt through dispensing and patient counseling.
|
||||
# """
|
||||
#
|
||||
# process_class = PrescriptionProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_prescription)
|
||||
# .Next(this.clinical_review)
|
||||
# )
|
||||
#
|
||||
# clinical_review = (
|
||||
# flow_view(PrescriptionReviewView)
|
||||
# .Permission('pharmacy.can_review_prescriptions')
|
||||
# .Next(this.check_drug_interactions)
|
||||
# )
|
||||
#
|
||||
# check_drug_interactions = (
|
||||
# flow_view(DrugInteractionCheckView)
|
||||
# .Permission('pharmacy.can_check_interactions')
|
||||
# .Next(this.verify_inventory)
|
||||
# )
|
||||
#
|
||||
# verify_inventory = (
|
||||
# flow_view(InventoryCheckView)
|
||||
# .Permission('pharmacy.can_check_inventory')
|
||||
# .Next(this.prepare_medication)
|
||||
# )
|
||||
#
|
||||
# prepare_medication = (
|
||||
# flow_view(MedicationPreparationView)
|
||||
# .Permission('pharmacy.can_prepare_medications')
|
||||
# .Next(this.pharmacist_verification)
|
||||
# )
|
||||
#
|
||||
# pharmacist_verification = (
|
||||
# flow_view(PharmacistVerificationView)
|
||||
# .Permission('pharmacy.can_verify_prescriptions')
|
||||
# .Next(this.dispense_medication)
|
||||
# )
|
||||
#
|
||||
# dispense_medication = (
|
||||
# flow_view(DispenseView)
|
||||
# .Permission('pharmacy.can_dispense_medications')
|
||||
# .Next(this.patient_counseling)
|
||||
# )
|
||||
#
|
||||
# patient_counseling = (
|
||||
# flow_view(PatientCounselingView)
|
||||
# .Permission('pharmacy.can_counsel_patients')
|
||||
# .Next(this.finalize_prescription)
|
||||
# )
|
||||
#
|
||||
# finalize_prescription = (
|
||||
# flow_func(this.complete_prescription)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_prescription)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_prescription(self, activation):
|
||||
# """Initialize the prescription process"""
|
||||
# process = activation.process
|
||||
# prescription = process.prescription
|
||||
#
|
||||
# # Update prescription status
|
||||
# prescription.status = 'RECEIVED'
|
||||
# prescription.save()
|
||||
#
|
||||
# # Send notification to pharmacy staff
|
||||
# self.notify_pharmacy_staff(prescription)
|
||||
#
|
||||
# # Check for priority prescriptions
|
||||
# if prescription.priority in ['STAT', 'URGENT']:
|
||||
# self.notify_priority_prescription(prescription)
|
||||
#
|
||||
# def complete_prescription(self, activation):
|
||||
# """Finalize the prescription process"""
|
||||
# process = activation.process
|
||||
# prescription = process.prescription
|
||||
#
|
||||
# # Update prescription status
|
||||
# prescription.status = 'COMPLETED'
|
||||
# prescription.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.dispensed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_prescription_completion(prescription)
|
||||
#
|
||||
# # Schedule follow-up if needed
|
||||
# self.schedule_follow_up(prescription)
|
||||
#
|
||||
# def end_prescription(self, activation):
|
||||
# """End the prescription workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate prescription summary report
|
||||
# self.generate_prescription_summary(process.prescription)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_pharmacy_staff(self, prescription):
|
||||
# """Notify pharmacy staff of new prescription"""
|
||||
# from django.contrib.auth.models import Group
|
||||
#
|
||||
# pharmacy_staff = User.objects.filter(
|
||||
# groups__name='Pharmacy Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in pharmacy_staff:
|
||||
# send_mail(
|
||||
# subject=f'New Prescription: {prescription.prescription_number}',
|
||||
# message=f'New {prescription.get_priority_display()} prescription for {prescription.patient.get_full_name()}.',
|
||||
# from_email='pharmacy@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_priority_prescription(self, prescription):
|
||||
# """Notify of high priority prescription"""
|
||||
# pharmacists = User.objects.filter(
|
||||
# groups__name='Pharmacists'
|
||||
# )
|
||||
#
|
||||
# for pharmacist in pharmacists:
|
||||
# send_mail(
|
||||
# subject=f'PRIORITY Prescription: {prescription.prescription_number}',
|
||||
# message=f'{prescription.get_priority_display()} prescription requires immediate attention.',
|
||||
# from_email='pharmacy@hospital.com',
|
||||
# recipient_list=[pharmacist.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_prescription_completion(self, prescription):
|
||||
# """Notify prescribing physician of completion"""
|
||||
# if prescription.prescribing_physician and prescription.prescribing_physician.email:
|
||||
# send_mail(
|
||||
# subject=f'Prescription Dispensed: {prescription.prescription_number}',
|
||||
# message=f'Prescription has been dispensed for {prescription.patient.get_full_name()}.',
|
||||
# from_email='pharmacy@hospital.com',
|
||||
# recipient_list=[prescription.prescribing_physician.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def schedule_follow_up(self, prescription):
|
||||
# """Schedule follow-up for certain medications"""
|
||||
# # This would schedule follow-up based on medication type
|
||||
# pass
|
||||
#
|
||||
# def generate_prescription_summary(self, prescription):
|
||||
# """Generate prescription summary report"""
|
||||
# # This would generate a comprehensive prescription report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class MedicationAdministrationProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for medication administration
|
||||
# """
|
||||
# administration = ModelField(MedicationAdministration, help_text='Associated administration record')
|
||||
#
|
||||
# # Process status tracking
|
||||
# medication_prepared = models.BooleanField(default=False)
|
||||
# patient_identified = models.BooleanField(default=False)
|
||||
# medication_verified = models.BooleanField(default=False)
|
||||
# administration_completed = models.BooleanField(default=False)
|
||||
# response_documented = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Medication Administration Process'
|
||||
# verbose_name_plural = 'Medication Administration Processes'
|
||||
#
|
||||
#
|
||||
# class MedicationAdministrationFlow(Flow):
|
||||
# """
|
||||
# Medication Administration Workflow
|
||||
#
|
||||
# This flow manages the medication administration process for inpatients
|
||||
# following the "5 Rights" of medication administration.
|
||||
# """
|
||||
#
|
||||
# process_class = MedicationAdministrationProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_administration)
|
||||
# .Next(this.prepare_medication)
|
||||
# )
|
||||
#
|
||||
# prepare_medication = (
|
||||
# flow_view(MedicationPreparationView)
|
||||
# .Permission('pharmacy.can_prepare_medications')
|
||||
# .Next(this.verify_patient)
|
||||
# )
|
||||
#
|
||||
# verify_patient = (
|
||||
# flow_view(PatientIdentificationView)
|
||||
# .Permission('pharmacy.can_administer_medications')
|
||||
# .Next(this.verify_medication)
|
||||
# )
|
||||
#
|
||||
# verify_medication = (
|
||||
# flow_view(MedicationVerificationView)
|
||||
# .Permission('pharmacy.can_administer_medications')
|
||||
# .Next(this.administer_medication)
|
||||
# )
|
||||
#
|
||||
# administer_medication = (
|
||||
# flow_view(MedicationAdministrationView)
|
||||
# .Permission('pharmacy.can_administer_medications')
|
||||
# .Next(this.document_response)
|
||||
# )
|
||||
#
|
||||
# document_response = (
|
||||
# flow_view(AdministrationDocumentationView)
|
||||
# .Permission('pharmacy.can_administer_medications')
|
||||
# .Next(this.finalize_administration)
|
||||
# )
|
||||
#
|
||||
# finalize_administration = (
|
||||
# flow_func(this.complete_administration)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_administration)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_administration(self, activation):
|
||||
# """Initialize the administration process"""
|
||||
# process = activation.process
|
||||
# administration = process.administration
|
||||
#
|
||||
# # Update administration status
|
||||
# administration.status = 'SCHEDULED'
|
||||
# administration.save()
|
||||
#
|
||||
# # Check for overdue medications
|
||||
# if administration.is_overdue:
|
||||
# self.notify_overdue_medication(administration)
|
||||
#
|
||||
# def complete_administration(self, activation):
|
||||
# """Finalize the administration process"""
|
||||
# process = activation.process
|
||||
# administration = process.administration
|
||||
#
|
||||
# # Update administration status
|
||||
# administration.status = 'GIVEN'
|
||||
# administration.actual_datetime = timezone.now()
|
||||
# administration.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.administration_completed = True
|
||||
# process.response_documented = True
|
||||
# process.save()
|
||||
#
|
||||
# # Check for adverse reactions
|
||||
# self.check_adverse_reactions(administration)
|
||||
#
|
||||
# def end_administration(self, activation):
|
||||
# """End the administration workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Update medication administration record
|
||||
# self.update_mar(process.administration)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_overdue_medication(self, administration):
|
||||
# """Notify nursing staff of overdue medication"""
|
||||
# nursing_staff = User.objects.filter(
|
||||
# groups__name='Nursing Staff',
|
||||
# profile__department=administration.patient.current_ward
|
||||
# )
|
||||
#
|
||||
# for nurse in nursing_staff:
|
||||
# send_mail(
|
||||
# subject=f'Overdue Medication: {administration.patient.get_full_name()}',
|
||||
# message=f'Medication administration is overdue for {administration.prescription.medication.display_name}.',
|
||||
# from_email='pharmacy@hospital.com',
|
||||
# recipient_list=[nurse.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def check_adverse_reactions(self, administration):
|
||||
# """Check for potential adverse reactions"""
|
||||
# # This would implement adverse reaction monitoring
|
||||
# pass
|
||||
#
|
||||
# def update_mar(self, administration):
|
||||
# """Update Medication Administration Record"""
|
||||
# # This would update the MAR system
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class DrugInteractionProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for drug interaction checking
|
||||
# """
|
||||
# patient_id = CharField(max_length=50, help_text='Patient identifier')
|
||||
# interaction_severity = CharField(max_length=20, help_text='Interaction severity level')
|
||||
#
|
||||
# # Process status tracking
|
||||
# interactions_identified = models.BooleanField(default=False)
|
||||
# clinical_review_completed = models.BooleanField(default=False)
|
||||
# physician_notified = models.BooleanField(default=False)
|
||||
# resolution_documented = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Drug Interaction Process'
|
||||
# verbose_name_plural = 'Drug Interaction Processes'
|
||||
#
|
||||
#
|
||||
# class DrugInteractionFlow(Flow):
|
||||
# """
|
||||
# Drug Interaction Checking Workflow
|
||||
#
|
||||
# This flow manages drug interaction checking and clinical decision support.
|
||||
# """
|
||||
#
|
||||
# process_class = DrugInteractionProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_interaction_check)
|
||||
# .Next(this.identify_interactions)
|
||||
# )
|
||||
#
|
||||
# identify_interactions = (
|
||||
# flow_func(this.check_drug_interactions)
|
||||
# .Next(this.clinical_review)
|
||||
# )
|
||||
#
|
||||
# clinical_review = (
|
||||
# flow_view(InteractionReviewView)
|
||||
# .Permission('pharmacy.can_review_interactions')
|
||||
# .Next(this.notify_physician)
|
||||
# )
|
||||
#
|
||||
# notify_physician = (
|
||||
# flow_func(this.send_physician_notification)
|
||||
# .Next(this.document_resolution)
|
||||
# )
|
||||
#
|
||||
# document_resolution = (
|
||||
# flow_view(InteractionResolutionView)
|
||||
# .Permission('pharmacy.can_resolve_interactions')
|
||||
# .Next(this.finalize_interaction_check)
|
||||
# )
|
||||
#
|
||||
# finalize_interaction_check = (
|
||||
# flow_func(this.complete_interaction_check)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_interaction_check)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_interaction_check(self, activation):
|
||||
# """Initialize the interaction checking process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Log interaction check initiation
|
||||
# self.log_interaction_check(process.patient_id)
|
||||
#
|
||||
# def check_drug_interactions(self, activation):
|
||||
# """Check for drug interactions"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Perform interaction checking logic
|
||||
# interactions = self.perform_interaction_check(process.patient_id)
|
||||
#
|
||||
# if interactions:
|
||||
# process.interactions_identified = True
|
||||
# process.interaction_severity = self.determine_severity(interactions)
|
||||
# process.save()
|
||||
#
|
||||
# # Alert if severe interactions found
|
||||
# if process.interaction_severity in ['MAJOR', 'CONTRAINDICATED']:
|
||||
# self.alert_severe_interaction(process.patient_id, interactions)
|
||||
#
|
||||
# def send_physician_notification(self, activation):
|
||||
# """Send notification to prescribing physician"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Notify physician of interactions
|
||||
# self.notify_prescribing_physician(process.patient_id, process.interaction_severity)
|
||||
#
|
||||
# process.physician_notified = True
|
||||
# process.save()
|
||||
#
|
||||
# def complete_interaction_check(self, activation):
|
||||
# """Finalize the interaction checking process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.resolution_documented = True
|
||||
# process.save()
|
||||
#
|
||||
# def end_interaction_check(self, activation):
|
||||
# """End the interaction checking workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate interaction report
|
||||
# self.generate_interaction_report(process.patient_id)
|
||||
#
|
||||
# # Helper methods
|
||||
# def log_interaction_check(self, patient_id):
|
||||
# """Log interaction check initiation"""
|
||||
# # This would log the interaction check
|
||||
# pass
|
||||
#
|
||||
# def perform_interaction_check(self, patient_id):
|
||||
# """Perform drug interaction checking"""
|
||||
# # This would implement interaction checking logic
|
||||
# return []
|
||||
#
|
||||
# def determine_severity(self, interactions):
|
||||
# """Determine overall severity of interactions"""
|
||||
# # This would determine the highest severity level
|
||||
# return 'MINOR'
|
||||
#
|
||||
# def alert_severe_interaction(self, patient_id, interactions):
|
||||
# """Alert staff of severe interactions"""
|
||||
# # This would send immediate alerts
|
||||
# pass
|
||||
#
|
||||
# def notify_prescribing_physician(self, patient_id, severity):
|
||||
# """Notify prescribing physician"""
|
||||
# # This would send notification to physician
|
||||
# pass
|
||||
#
|
||||
# def generate_interaction_report(self, patient_id):
|
||||
# """Generate interaction checking report"""
|
||||
# # This would generate a comprehensive interaction report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class InventoryManagementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for pharmacy inventory management
|
||||
# """
|
||||
# medication_id = CharField(max_length=50, help_text='Medication identifier')
|
||||
# action_type = CharField(max_length=20, help_text='Inventory action type')
|
||||
#
|
||||
# # Process status tracking
|
||||
# stock_checked = models.BooleanField(default=False)
|
||||
# order_placed = models.BooleanField(default=False)
|
||||
# shipment_received = models.BooleanField(default=False)
|
||||
# inventory_updated = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Inventory Management Process'
|
||||
# verbose_name_plural = 'Inventory Management Processes'
|
||||
#
|
||||
#
|
||||
# class InventoryManagementFlow(Flow):
|
||||
# """
|
||||
# Pharmacy Inventory Management Workflow
|
||||
#
|
||||
# This flow manages pharmacy inventory including ordering, receiving, and stock management.
|
||||
# """
|
||||
#
|
||||
# process_class = InventoryManagementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_inventory_management)
|
||||
# .Next(this.check_stock_levels)
|
||||
# )
|
||||
#
|
||||
# check_stock_levels = (
|
||||
# flow_func(this.monitor_stock_levels)
|
||||
# .Next(this.place_order)
|
||||
# )
|
||||
#
|
||||
# place_order = (
|
||||
# flow_view(InventoryOrderView)
|
||||
# .Permission('pharmacy.can_manage_inventory')
|
||||
# .Next(this.receive_shipment)
|
||||
# )
|
||||
#
|
||||
# receive_shipment = (
|
||||
# flow_view(ShipmentReceiptView)
|
||||
# .Permission('pharmacy.can_receive_inventory')
|
||||
# .Next(this.update_inventory)
|
||||
# )
|
||||
#
|
||||
# update_inventory = (
|
||||
# flow_func(this.update_stock_levels)
|
||||
# .Next(this.finalize_inventory)
|
||||
# )
|
||||
#
|
||||
# finalize_inventory = (
|
||||
# flow_func(this.complete_inventory_management)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_inventory_management)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_inventory_management(self, activation):
|
||||
# """Initialize the inventory management process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Log inventory management initiation
|
||||
# self.log_inventory_action(process.medication_id, process.action_type)
|
||||
#
|
||||
# def monitor_stock_levels(self, activation):
|
||||
# """Monitor stock levels and identify low stock"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Check stock levels
|
||||
# low_stock_items = self.check_low_stock(process.medication_id)
|
||||
#
|
||||
# if low_stock_items:
|
||||
# process.stock_checked = True
|
||||
# process.save()
|
||||
#
|
||||
# # Alert pharmacy staff of low stock
|
||||
# self.alert_low_stock(low_stock_items)
|
||||
#
|
||||
# def update_stock_levels(self, activation):
|
||||
# """Update inventory stock levels"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Update stock levels in system
|
||||
# self.update_medication_stock(process.medication_id)
|
||||
#
|
||||
# process.inventory_updated = True
|
||||
# process.save()
|
||||
#
|
||||
# def complete_inventory_management(self, activation):
|
||||
# """Finalize the inventory management process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate inventory report
|
||||
# self.generate_inventory_report(process.medication_id)
|
||||
#
|
||||
# def end_inventory_management(self, activation):
|
||||
# """End the inventory management workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Archive inventory transaction
|
||||
# self.archive_inventory_transaction(process.medication_id)
|
||||
#
|
||||
# # Helper methods
|
||||
# def log_inventory_action(self, medication_id, action_type):
|
||||
# """Log inventory action"""
|
||||
# # This would log the inventory action
|
||||
# pass
|
||||
#
|
||||
# def check_low_stock(self, medication_id):
|
||||
# """Check for low stock items"""
|
||||
# # This would check stock levels
|
||||
# return []
|
||||
#
|
||||
# def alert_low_stock(self, low_stock_items):
|
||||
# """Alert staff of low stock"""
|
||||
# # This would send low stock alerts
|
||||
# pass
|
||||
#
|
||||
# def update_medication_stock(self, medication_id):
|
||||
# """Update medication stock levels"""
|
||||
# # This would update stock levels
|
||||
# pass
|
||||
#
|
||||
# def generate_inventory_report(self, medication_id):
|
||||
# """Generate inventory report"""
|
||||
# # This would generate inventory report
|
||||
# pass
|
||||
#
|
||||
# def archive_inventory_transaction(self, medication_id):
|
||||
# """Archive inventory transaction"""
|
||||
# # This would archive the transaction
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def auto_check_drug_interactions(patient_id):
|
||||
# """Background task to automatically check drug interactions"""
|
||||
# try:
|
||||
# # This would perform automated interaction checking
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def monitor_medication_adherence(patient_id):
|
||||
# """Background task to monitor medication adherence"""
|
||||
# try:
|
||||
# # This would monitor patient adherence
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def generate_pharmacy_reports():
|
||||
# """Background task to generate daily pharmacy reports"""
|
||||
# try:
|
||||
# # This would generate daily pharmacy reports
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def auto_reorder_medications():
|
||||
# """Background task to automatically reorder low stock medications"""
|
||||
# try:
|
||||
# # This would automatically reorder medications
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
@ -4,45 +4,61 @@ version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"base>=0.0.0",
|
||||
"black>=25.1.0",
|
||||
"boto3>=1.40.1",
|
||||
"boto3==1.40.25",
|
||||
"botocore==1.40.25",
|
||||
"celery>=5.5.3",
|
||||
"coverage>=7.10.2",
|
||||
"charset-normalizer==3.4.3",
|
||||
"coverage==7.10.6",
|
||||
"crispy-bootstrap5>=2025.6",
|
||||
"django>=5.2.4",
|
||||
"django-allauth>=65.10.0",
|
||||
"django-anymail>=13.0.1",
|
||||
"cron-descriptor==2.0.6",
|
||||
"decorators>=2.0.7",
|
||||
"django==5.2.6",
|
||||
"django-allauth==65.11.1",
|
||||
"django-anymail==13.1",
|
||||
"django-celery-beat>=2.8.1",
|
||||
"django-cors-headers>=4.7.0",
|
||||
"django-crispy-forms>=2.4",
|
||||
"django-debug-toolbar>=6.0.0",
|
||||
"django-extensions>=4.1",
|
||||
"django-filter>=25.1",
|
||||
"django-guardian>=3.0.3",
|
||||
"django-guardian==3.1.0",
|
||||
"django-health-check>=3.20.0",
|
||||
"django-otp>=1.6.1",
|
||||
"django-redis>=6.0.0",
|
||||
"django-storages>=1.14.6",
|
||||
"djangorestframework>=3.16.0",
|
||||
"django-viewflow>=2.2.12",
|
||||
"djangorestframework==3.16.1",
|
||||
"drf-spectacular>=0.28.0",
|
||||
"factory-boy>=3.3.3",
|
||||
"faker==37.6.0",
|
||||
"filelock==3.19.1",
|
||||
"flake8>=7.3.0",
|
||||
"gunicorn>=23.0.0",
|
||||
"identify==2.6.14",
|
||||
"jsonschema==4.25.1",
|
||||
"mysqlclient>=2.2.7",
|
||||
"openpyxl>=3.1.5",
|
||||
"pandas>=2.3.1",
|
||||
"pandas==2.3.2",
|
||||
"pillow>=11.3.0",
|
||||
"pre-commit>=4.2.0",
|
||||
"platformdirs==4.4.0",
|
||||
"pre-commit==4.3.0",
|
||||
"prompt-toolkit==3.0.52",
|
||||
"psutil>=7.0.0",
|
||||
"psycopg2-binary>=2.9.10",
|
||||
"pytest==8.4.2",
|
||||
"pytest-django>=4.11.1",
|
||||
"python-dateutil>=2.9.0.post0",
|
||||
"python-decouple>=3.8",
|
||||
"pytz>=2025.2",
|
||||
"redis>=6.2.0",
|
||||
"redis==6.4.0",
|
||||
"reportlab>=4.4.3",
|
||||
"requests>=2.32.4",
|
||||
"sentry-sdk>=2.34.1",
|
||||
"requests==2.32.5",
|
||||
"rpds-py==0.27.1",
|
||||
"sentry-sdk==2.37.0",
|
||||
"typing-extensions==4.15.0",
|
||||
"urllib3>=2.5.0",
|
||||
"virtualenv==20.34.0",
|
||||
"whitenoise>=6.9.0",
|
||||
]
|
||||
|
||||
BIN
quality/__pycache__/flows.cpython-312.pyc
Normal file
BIN
quality/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
983
quality/flows.py
Normal file
983
quality/flows.py
Normal file
@ -0,0 +1,983 @@
|
||||
# """
|
||||
# Viewflow workflows for quality app.
|
||||
# Provides quality assurance, incident management, and improvement workflows.
|
||||
# """
|
||||
#
|
||||
# from viewflow import Flow, lock
|
||||
# from viewflow.base import this, flow_func
|
||||
# from viewflow.contrib import celery
|
||||
# from viewflow.decorators import flow_view
|
||||
# from viewflow.fields import CharField, ModelField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.models import Process, Task
|
||||
# from django.contrib.auth.models import User
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import (
|
||||
# QualityIndicator, QualityMeasurement, IncidentReport, RiskAssessment,
|
||||
# AuditPlan, AuditFinding, ImprovementProject
|
||||
# )
|
||||
# from .views import (
|
||||
# IncidentReportingView, IncidentInvestigationView, RootCauseAnalysisView,
|
||||
# CorrectiveActionView, IncidentClosureView, QualityMeasurementView,
|
||||
# IndicatorAnalysisView, AuditPlanningView, AuditExecutionView,
|
||||
# FindingManagementView, ImprovementProjectView, RiskAssessmentView
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class IncidentManagementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for incident management
|
||||
# """
|
||||
# incident_report = ModelField(IncidentReport, help_text='Associated incident report')
|
||||
#
|
||||
# # Process status tracking
|
||||
# incident_reported = models.BooleanField(default=False)
|
||||
# initial_assessment_completed = models.BooleanField(default=False)
|
||||
# investigation_assigned = models.BooleanField(default=False)
|
||||
# investigation_completed = models.BooleanField(default=False)
|
||||
# root_cause_identified = models.BooleanField(default=False)
|
||||
# corrective_actions_planned = models.BooleanField(default=False)
|
||||
# actions_implemented = models.BooleanField(default=False)
|
||||
# effectiveness_verified = models.BooleanField(default=False)
|
||||
# incident_closed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Incident Management Process'
|
||||
# verbose_name_plural = 'Incident Management Processes'
|
||||
#
|
||||
#
|
||||
# class IncidentManagementFlow(Flow):
|
||||
# """
|
||||
# Incident Management Workflow
|
||||
#
|
||||
# This flow manages patient safety incidents from reporting through
|
||||
# investigation, corrective action, and closure.
|
||||
# """
|
||||
#
|
||||
# process_class = IncidentManagementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_incident_management)
|
||||
# .Next(this.report_incident)
|
||||
# )
|
||||
#
|
||||
# report_incident = (
|
||||
# flow_view(IncidentReportingView)
|
||||
# .Permission('quality.can_report_incidents')
|
||||
# .Next(this.assess_incident)
|
||||
# )
|
||||
#
|
||||
# assess_incident = (
|
||||
# flow_func(this.perform_initial_assessment)
|
||||
# .Next(this.assign_investigation)
|
||||
# )
|
||||
#
|
||||
# assign_investigation = (
|
||||
# flow_view(InvestigationAssignmentView)
|
||||
# .Permission('quality.can_assign_investigations')
|
||||
# .Next(this.investigate_incident)
|
||||
# )
|
||||
#
|
||||
# investigate_incident = (
|
||||
# flow_view(IncidentInvestigationView)
|
||||
# .Permission('quality.can_investigate_incidents')
|
||||
# .Next(this.analyze_root_cause)
|
||||
# )
|
||||
#
|
||||
# analyze_root_cause = (
|
||||
# flow_view(RootCauseAnalysisView)
|
||||
# .Permission('quality.can_analyze_root_causes')
|
||||
# .Next(this.plan_corrective_actions)
|
||||
# )
|
||||
#
|
||||
# plan_corrective_actions = (
|
||||
# flow_view(CorrectiveActionView)
|
||||
# .Permission('quality.can_plan_corrective_actions')
|
||||
# .Next(this.implement_actions)
|
||||
# )
|
||||
#
|
||||
# implement_actions = (
|
||||
# flow_view(ActionImplementationView)
|
||||
# .Permission('quality.can_implement_actions')
|
||||
# .Next(this.verify_effectiveness)
|
||||
# )
|
||||
#
|
||||
# verify_effectiveness = (
|
||||
# flow_view(EffectivenessVerificationView)
|
||||
# .Permission('quality.can_verify_effectiveness')
|
||||
# .Next(this.close_incident)
|
||||
# )
|
||||
#
|
||||
# close_incident = (
|
||||
# flow_func(this.complete_incident_management)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_incident_management)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_incident_management(self, activation):
|
||||
# """Initialize the incident management process"""
|
||||
# process = activation.process
|
||||
# incident = process.incident_report
|
||||
#
|
||||
# # Update incident status
|
||||
# incident.status = 'reported'
|
||||
# incident.save()
|
||||
#
|
||||
# # Send immediate notifications
|
||||
# self.notify_incident_reported(incident)
|
||||
#
|
||||
# # Check for high-severity incidents
|
||||
# if incident.severity in ['severe_harm', 'death']:
|
||||
# self.notify_critical_incident(incident)
|
||||
# self.notify_regulatory_bodies(incident)
|
||||
#
|
||||
# def perform_initial_assessment(self, activation):
|
||||
# """Perform initial incident assessment"""
|
||||
# process = activation.process
|
||||
# incident = process.incident_report
|
||||
#
|
||||
# # Update incident status
|
||||
# incident.status = 'under_investigation'
|
||||
# incident.save()
|
||||
#
|
||||
# # Mark assessment completed
|
||||
# process.initial_assessment_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Determine investigation priority
|
||||
# self.determine_investigation_priority(incident)
|
||||
#
|
||||
# # Send assessment notifications
|
||||
# self.notify_assessment_completed(incident)
|
||||
#
|
||||
# def complete_incident_management(self, activation):
|
||||
# """Finalize the incident management process"""
|
||||
# process = activation.process
|
||||
# incident = process.incident_report
|
||||
#
|
||||
# # Update incident status
|
||||
# incident.status = 'closed'
|
||||
# incident.closed_date = timezone.now()
|
||||
# incident.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.incident_closed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_incident_closure(incident)
|
||||
#
|
||||
# # Update quality metrics
|
||||
# self.update_incident_metrics(incident)
|
||||
#
|
||||
# # Generate lessons learned
|
||||
# self.generate_lessons_learned(incident)
|
||||
#
|
||||
# def end_incident_management(self, activation):
|
||||
# """End the incident management workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate incident summary report
|
||||
# self.generate_incident_summary(process.incident_report)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_incident_reported(self, incident):
|
||||
# """Notify quality staff of incident report"""
|
||||
# from django.contrib.auth.models import Group
|
||||
#
|
||||
# quality_staff = User.objects.filter(
|
||||
# groups__name='Quality Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in quality_staff:
|
||||
# send_mail(
|
||||
# subject=f'Incident Reported: {incident.incident_number}',
|
||||
# message=f'New {incident.get_severity_display()} incident reported: {incident.title}',
|
||||
# from_email='quality@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_critical_incident(self, incident):
|
||||
# """Notify of critical incident"""
|
||||
# quality_managers = User.objects.filter(
|
||||
# groups__name='Quality Managers'
|
||||
# )
|
||||
#
|
||||
# for manager in quality_managers:
|
||||
# send_mail(
|
||||
# subject=f'CRITICAL INCIDENT: {incident.incident_number}',
|
||||
# message=f'Critical incident requiring immediate attention: {incident.title}',
|
||||
# from_email='quality@hospital.com',
|
||||
# recipient_list=[manager.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_regulatory_bodies(self, incident):
|
||||
# """Notify regulatory bodies if required"""
|
||||
# if incident.regulatory_notification:
|
||||
# # This would implement regulatory notification logic
|
||||
# pass
|
||||
#
|
||||
# def determine_investigation_priority(self, incident):
|
||||
# """Determine investigation priority based on severity"""
|
||||
# severity_priority_map = {
|
||||
# 'death': 'urgent',
|
||||
# 'severe_harm': 'high',
|
||||
# 'moderate_harm': 'medium',
|
||||
# 'minor_harm': 'low',
|
||||
# 'no_harm': 'low'
|
||||
# }
|
||||
#
|
||||
# incident.priority = severity_priority_map.get(incident.severity, 'medium')
|
||||
# incident.save()
|
||||
#
|
||||
# def notify_assessment_completed(self, incident):
|
||||
# """Notify assessment completion"""
|
||||
# if incident.assigned_to and incident.assigned_to.email:
|
||||
# send_mail(
|
||||
# subject=f'Investigation Assignment: {incident.incident_number}',
|
||||
# message=f'You have been assigned to investigate incident: {incident.title}',
|
||||
# from_email='quality@hospital.com',
|
||||
# recipient_list=[incident.assigned_to.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_incident_closure(self, incident):
|
||||
# """Notify incident closure"""
|
||||
# # Notify reporter
|
||||
# if incident.reported_by and incident.reported_by.email:
|
||||
# send_mail(
|
||||
# subject=f'Incident Closed: {incident.incident_number}',
|
||||
# message=f'The incident you reported has been investigated and closed.',
|
||||
# from_email='quality@hospital.com',
|
||||
# recipient_list=[incident.reported_by.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def update_incident_metrics(self, incident):
|
||||
# """Update incident quality metrics"""
|
||||
# # This would update incident reporting metrics
|
||||
# pass
|
||||
#
|
||||
# def generate_lessons_learned(self, incident):
|
||||
# """Generate lessons learned from incident"""
|
||||
# # This would create lessons learned documentation
|
||||
# pass
|
||||
#
|
||||
# def generate_incident_summary(self, incident):
|
||||
# """Generate incident summary report"""
|
||||
# # This would generate comprehensive incident report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class QualityMonitoringProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for quality monitoring
|
||||
# """
|
||||
# quality_indicator = ModelField(QualityIndicator, help_text='Associated quality indicator')
|
||||
#
|
||||
# # Process status tracking
|
||||
# data_collected = models.BooleanField(default=False)
|
||||
# measurement_calculated = models.BooleanField(default=False)
|
||||
# analysis_completed = models.BooleanField(default=False)
|
||||
# trends_identified = models.BooleanField(default=False)
|
||||
# actions_recommended = models.BooleanField(default=False)
|
||||
# report_generated = models.BooleanField(default=False)
|
||||
# monitoring_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Quality Monitoring Process'
|
||||
# verbose_name_plural = 'Quality Monitoring Processes'
|
||||
#
|
||||
#
|
||||
# class QualityMonitoringFlow(Flow):
|
||||
# """
|
||||
# Quality Monitoring Workflow
|
||||
#
|
||||
# This flow manages quality indicator monitoring including data
|
||||
# collection, analysis, and reporting.
|
||||
# """
|
||||
#
|
||||
# process_class = QualityMonitoringProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_quality_monitoring)
|
||||
# .Next(this.collect_data)
|
||||
# )
|
||||
#
|
||||
# collect_data = (
|
||||
# flow_view(DataCollectionView)
|
||||
# .Permission('quality.can_collect_data')
|
||||
# .Next(this.calculate_measurement)
|
||||
# )
|
||||
#
|
||||
# calculate_measurement = (
|
||||
# flow_view(QualityMeasurementView)
|
||||
# .Permission('quality.can_calculate_measurements')
|
||||
# .Next(this.analyze_results)
|
||||
# )
|
||||
#
|
||||
# analyze_results = (
|
||||
# flow_view(IndicatorAnalysisView)
|
||||
# .Permission('quality.can_analyze_indicators')
|
||||
# .Next(this.identify_trends)
|
||||
# )
|
||||
#
|
||||
# identify_trends = (
|
||||
# flow_func(this.perform_trend_analysis)
|
||||
# .Next(this.recommend_actions)
|
||||
# )
|
||||
#
|
||||
# recommend_actions = (
|
||||
# flow_view(ActionRecommendationView)
|
||||
# .Permission('quality.can_recommend_actions')
|
||||
# .Next(this.generate_report)
|
||||
# )
|
||||
#
|
||||
# generate_report = (
|
||||
# flow_view(QualityReportView)
|
||||
# .Permission('quality.can_generate_reports')
|
||||
# .Next(this.complete_monitoring)
|
||||
# )
|
||||
#
|
||||
# complete_monitoring = (
|
||||
# flow_func(this.finalize_quality_monitoring)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_quality_monitoring)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_quality_monitoring(self, activation):
|
||||
# """Initialize the quality monitoring process"""
|
||||
# process = activation.process
|
||||
# indicator = process.quality_indicator
|
||||
#
|
||||
# # Send notification to responsible staff
|
||||
# self.notify_monitoring_due(indicator)
|
||||
#
|
||||
# def perform_trend_analysis(self, activation):
|
||||
# """Perform trend analysis on quality data"""
|
||||
# process = activation.process
|
||||
# indicator = process.quality_indicator
|
||||
#
|
||||
# # Analyze trends in measurements
|
||||
# trends = self.analyze_indicator_trends(indicator)
|
||||
#
|
||||
# if trends:
|
||||
# process.trends_identified = True
|
||||
# process.save()
|
||||
#
|
||||
# # Alert if concerning trends identified
|
||||
# if self.is_concerning_trend(trends):
|
||||
# self.alert_quality_managers(indicator, trends)
|
||||
#
|
||||
# def finalize_quality_monitoring(self, activation):
|
||||
# """Finalize the quality monitoring process"""
|
||||
# process = activation.process
|
||||
# indicator = process.quality_indicator
|
||||
#
|
||||
# # Mark monitoring as completed
|
||||
# process.monitoring_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_monitoring_completion(indicator)
|
||||
#
|
||||
# # Schedule next monitoring cycle
|
||||
# self.schedule_next_monitoring(indicator)
|
||||
#
|
||||
# def end_quality_monitoring(self, activation):
|
||||
# """End the quality monitoring workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate monitoring summary
|
||||
# self.generate_monitoring_summary(process.quality_indicator)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_monitoring_due(self, indicator):
|
||||
# """Notify responsible staff of monitoring due"""
|
||||
# if indicator.responsible_user and indicator.responsible_user.email:
|
||||
# send_mail(
|
||||
# subject=f'Quality Monitoring Due: {indicator.name}',
|
||||
# message=f'Quality indicator monitoring is due for: {indicator.name}',
|
||||
# from_email='quality@hospital.com',
|
||||
# recipient_list=[indicator.responsible_user.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def analyze_indicator_trends(self, indicator):
|
||||
# """Analyze trends in quality indicator"""
|
||||
# # This would implement trend analysis logic
|
||||
# return {}
|
||||
#
|
||||
# def is_concerning_trend(self, trends):
|
||||
# """Check if trends are concerning"""
|
||||
# # This would implement trend evaluation logic
|
||||
# return False
|
||||
#
|
||||
# def alert_quality_managers(self, indicator, trends):
|
||||
# """Alert quality managers of concerning trends"""
|
||||
# quality_managers = User.objects.filter(
|
||||
# groups__name='Quality Managers'
|
||||
# )
|
||||
#
|
||||
# for manager in quality_managers:
|
||||
# send_mail(
|
||||
# subject=f'Quality Alert: {indicator.name}',
|
||||
# message=f'Concerning trend identified in quality indicator: {indicator.name}',
|
||||
# from_email='quality@hospital.com',
|
||||
# recipient_list=[manager.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_monitoring_completion(self, indicator):
|
||||
# """Notify monitoring completion"""
|
||||
# # This would notify relevant parties
|
||||
# pass
|
||||
#
|
||||
# def schedule_next_monitoring(self, indicator):
|
||||
# """Schedule next monitoring cycle"""
|
||||
# # This would schedule the next monitoring cycle
|
||||
# pass
|
||||
#
|
||||
# def generate_monitoring_summary(self, indicator):
|
||||
# """Generate monitoring summary"""
|
||||
# # This would generate monitoring summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class AuditManagementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for audit management
|
||||
# """
|
||||
# audit_plan = ModelField(AuditPlan, help_text='Associated audit plan')
|
||||
#
|
||||
# # Process status tracking
|
||||
# audit_planned = models.BooleanField(default=False)
|
||||
# team_assigned = models.BooleanField(default=False)
|
||||
# audit_conducted = models.BooleanField(default=False)
|
||||
# findings_documented = models.BooleanField(default=False)
|
||||
# corrective_actions_planned = models.BooleanField(default=False)
|
||||
# actions_implemented = models.BooleanField(default=False)
|
||||
# follow_up_completed = models.BooleanField(default=False)
|
||||
# audit_closed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Audit Management Process'
|
||||
# verbose_name_plural = 'Audit Management Processes'
|
||||
#
|
||||
#
|
||||
# class AuditManagementFlow(Flow):
|
||||
# """
|
||||
# Audit Management Workflow
|
||||
#
|
||||
# This flow manages quality audits from planning through
|
||||
# execution, finding management, and closure.
|
||||
# """
|
||||
#
|
||||
# process_class = AuditManagementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_audit_management)
|
||||
# .Next(this.plan_audit)
|
||||
# )
|
||||
#
|
||||
# plan_audit = (
|
||||
# flow_view(AuditPlanningView)
|
||||
# .Permission('quality.can_plan_audits')
|
||||
# .Next(this.assign_team)
|
||||
# )
|
||||
#
|
||||
# assign_team = (
|
||||
# flow_view(AuditTeamAssignmentView)
|
||||
# .Permission('quality.can_assign_audit_teams')
|
||||
# .Next(this.conduct_audit)
|
||||
# )
|
||||
#
|
||||
# conduct_audit = (
|
||||
# flow_view(AuditExecutionView)
|
||||
# .Permission('quality.can_conduct_audits')
|
||||
# .Next(this.document_findings)
|
||||
# )
|
||||
#
|
||||
# document_findings = (
|
||||
# flow_view(FindingManagementView)
|
||||
# .Permission('quality.can_document_findings')
|
||||
# .Next(this.plan_corrective_actions)
|
||||
# )
|
||||
#
|
||||
# plan_corrective_actions = (
|
||||
# flow_view(CorrectiveActionPlanningView)
|
||||
# .Permission('quality.can_plan_corrective_actions')
|
||||
# .Next(this.implement_actions)
|
||||
# )
|
||||
#
|
||||
# implement_actions = (
|
||||
# flow_view(ActionImplementationView)
|
||||
# .Permission('quality.can_implement_actions')
|
||||
# .Next(this.follow_up)
|
||||
# )
|
||||
#
|
||||
# follow_up = (
|
||||
# flow_view(AuditFollowUpView)
|
||||
# .Permission('quality.can_follow_up_audits')
|
||||
# .Next(this.close_audit)
|
||||
# )
|
||||
#
|
||||
# close_audit = (
|
||||
# flow_func(this.complete_audit_management)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_audit_management)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_audit_management(self, activation):
|
||||
# """Initialize the audit management process"""
|
||||
# process = activation.process
|
||||
# audit = process.audit_plan
|
||||
#
|
||||
# # Update audit status
|
||||
# audit.status = 'planned'
|
||||
# audit.save()
|
||||
#
|
||||
# # Send notification to audit team
|
||||
# self.notify_audit_planned(audit)
|
||||
#
|
||||
# def complete_audit_management(self, activation):
|
||||
# """Finalize the audit management process"""
|
||||
# process = activation.process
|
||||
# audit = process.audit_plan
|
||||
#
|
||||
# # Update audit status
|
||||
# audit.status = 'completed'
|
||||
# audit.actual_end_date = timezone.now().date()
|
||||
# audit.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.audit_closed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_audit_completion(audit)
|
||||
#
|
||||
# # Update audit metrics
|
||||
# self.update_audit_metrics(audit)
|
||||
#
|
||||
# def end_audit_management(self, activation):
|
||||
# """End the audit management workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate audit summary report
|
||||
# self.generate_audit_summary(process.audit_plan)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_audit_planned(self, audit):
|
||||
# """Notify audit team of planned audit"""
|
||||
# audit_team = audit.audit_team.all()
|
||||
# for member in audit_team:
|
||||
# if member.email:
|
||||
# send_mail(
|
||||
# subject=f'Audit Planned: {audit.title}',
|
||||
# message=f'You have been assigned to audit: {audit.title}',
|
||||
# from_email='quality@hospital.com',
|
||||
# recipient_list=[member.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_audit_completion(self, audit):
|
||||
# """Notify audit completion"""
|
||||
# # Notify department being audited
|
||||
# if audit.department:
|
||||
# department_staff = User.objects.filter(
|
||||
# department=audit.department
|
||||
# )
|
||||
# for staff in department_staff:
|
||||
# if staff.email:
|
||||
# send_mail(
|
||||
# subject=f'Audit Completed: {audit.title}',
|
||||
# message=f'The audit of your department has been completed.',
|
||||
# from_email='quality@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def update_audit_metrics(self, audit):
|
||||
# """Update audit performance metrics"""
|
||||
# # This would update audit metrics
|
||||
# pass
|
||||
#
|
||||
# def generate_audit_summary(self, audit):
|
||||
# """Generate audit summary report"""
|
||||
# # This would generate comprehensive audit report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class ImprovementProjectProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for improvement projects
|
||||
# """
|
||||
# improvement_project = ModelField(ImprovementProject, help_text='Associated improvement project')
|
||||
#
|
||||
# # Process status tracking
|
||||
# project_initiated = models.BooleanField(default=False)
|
||||
# team_assembled = models.BooleanField(default=False)
|
||||
# baseline_established = models.BooleanField(default=False)
|
||||
# improvements_implemented = models.BooleanField(default=False)
|
||||
# results_measured = models.BooleanField(default=False)
|
||||
# sustainability_ensured = models.BooleanField(default=False)
|
||||
# project_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Improvement Project Process'
|
||||
# verbose_name_plural = 'Improvement Project Processes'
|
||||
#
|
||||
#
|
||||
# class ImprovementProjectFlow(Flow):
|
||||
# """
|
||||
# Improvement Project Workflow
|
||||
#
|
||||
# This flow manages quality improvement projects using
|
||||
# structured methodologies like PDSA and Lean.
|
||||
# """
|
||||
#
|
||||
# process_class = ImprovementProjectProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_improvement_project)
|
||||
# .Next(this.initiate_project)
|
||||
# )
|
||||
#
|
||||
# initiate_project = (
|
||||
# flow_view(ProjectInitiationView)
|
||||
# .Permission('quality.can_initiate_projects')
|
||||
# .Next(this.assemble_team)
|
||||
# )
|
||||
#
|
||||
# assemble_team = (
|
||||
# flow_view(TeamAssemblyView)
|
||||
# .Permission('quality.can_assemble_teams')
|
||||
# .Next(this.establish_baseline)
|
||||
# )
|
||||
#
|
||||
# establish_baseline = (
|
||||
# flow_view(BaselineEstablishmentView)
|
||||
# .Permission('quality.can_establish_baseline')
|
||||
# .Next(this.implement_improvements)
|
||||
# )
|
||||
#
|
||||
# implement_improvements = (
|
||||
# flow_view(ImprovementImplementationView)
|
||||
# .Permission('quality.can_implement_improvements')
|
||||
# .Next(this.measure_results)
|
||||
# )
|
||||
#
|
||||
# measure_results = (
|
||||
# flow_view(ResultsMeasurementView)
|
||||
# .Permission('quality.can_measure_results')
|
||||
# .Next(this.ensure_sustainability)
|
||||
# )
|
||||
#
|
||||
# ensure_sustainability = (
|
||||
# flow_view(SustainabilityView)
|
||||
# .Permission('quality.can_ensure_sustainability')
|
||||
# .Next(this.complete_project)
|
||||
# )
|
||||
#
|
||||
# complete_project = (
|
||||
# flow_func(this.finalize_improvement_project)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_improvement_project)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_improvement_project(self, activation):
|
||||
# """Initialize the improvement project process"""
|
||||
# process = activation.process
|
||||
# project = process.improvement_project
|
||||
#
|
||||
# # Update project status
|
||||
# project.status = 'planned'
|
||||
# project.save()
|
||||
#
|
||||
# # Send notification to project team
|
||||
# self.notify_project_initiated(project)
|
||||
#
|
||||
# def finalize_improvement_project(self, activation):
|
||||
# """Finalize the improvement project process"""
|
||||
# process = activation.process
|
||||
# project = process.improvement_project
|
||||
#
|
||||
# # Update project status
|
||||
# project.status = 'completed'
|
||||
# project.actual_end_date = timezone.now().date()
|
||||
# project.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.project_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_project_completion(project)
|
||||
#
|
||||
# # Calculate ROI
|
||||
# self.calculate_project_roi(project)
|
||||
#
|
||||
# def end_improvement_project(self, activation):
|
||||
# """End the improvement project workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate project summary report
|
||||
# self.generate_project_summary(process.improvement_project)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_project_initiated(self, project):
|
||||
# """Notify project team of project initiation"""
|
||||
# project_team = project.project_team.all()
|
||||
# for member in project_team:
|
||||
# if member.email:
|
||||
# send_mail(
|
||||
# subject=f'Improvement Project Started: {project.title}',
|
||||
# message=f'You have been assigned to improvement project: {project.title}',
|
||||
# from_email='quality@hospital.com',
|
||||
# recipient_list=[member.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_project_completion(self, project):
|
||||
# """Notify project completion"""
|
||||
# # Notify sponsor
|
||||
# if project.sponsor and project.sponsor.email:
|
||||
# send_mail(
|
||||
# subject=f'Project Completed: {project.title}',
|
||||
# message=f'The improvement project you sponsored has been completed.',
|
||||
# from_email='quality@hospital.com',
|
||||
# recipient_list=[project.sponsor.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def calculate_project_roi(self, project):
|
||||
# """Calculate project return on investment"""
|
||||
# # This would implement ROI calculation logic
|
||||
# pass
|
||||
#
|
||||
# def generate_project_summary(self, project):
|
||||
# """Generate project summary report"""
|
||||
# # This would generate comprehensive project report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class RiskManagementProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for risk management
|
||||
# """
|
||||
# risk_assessment = ModelField(RiskAssessment, help_text='Associated risk assessment')
|
||||
#
|
||||
# # Process status tracking
|
||||
# risk_identified = models.BooleanField(default=False)
|
||||
# risk_assessed = models.BooleanField(default=False)
|
||||
# controls_evaluated = models.BooleanField(default=False)
|
||||
# mitigation_planned = models.BooleanField(default=False)
|
||||
# controls_implemented = models.BooleanField(default=False)
|
||||
# effectiveness_monitored = models.BooleanField(default=False)
|
||||
# risk_managed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Risk Management Process'
|
||||
# verbose_name_plural = 'Risk Management Processes'
|
||||
#
|
||||
#
|
||||
# class RiskManagementFlow(Flow):
|
||||
# """
|
||||
# Risk Management Workflow
|
||||
#
|
||||
# This flow manages risk identification, assessment,
|
||||
# mitigation, and monitoring activities.
|
||||
# """
|
||||
#
|
||||
# process_class = RiskManagementProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_risk_management)
|
||||
# .Next(this.identify_risk)
|
||||
# )
|
||||
#
|
||||
# identify_risk = (
|
||||
# flow_view(RiskIdentificationView)
|
||||
# .Permission('quality.can_identify_risks')
|
||||
# .Next(this.assess_risk)
|
||||
# )
|
||||
#
|
||||
# assess_risk = (
|
||||
# flow_view(RiskAssessmentView)
|
||||
# .Permission('quality.can_assess_risks')
|
||||
# .Next(this.evaluate_controls)
|
||||
# )
|
||||
#
|
||||
# evaluate_controls = (
|
||||
# flow_view(ControlEvaluationView)
|
||||
# .Permission('quality.can_evaluate_controls')
|
||||
# .Next(this.plan_mitigation)
|
||||
# )
|
||||
#
|
||||
# plan_mitigation = (
|
||||
# flow_view(MitigationPlanningView)
|
||||
# .Permission('quality.can_plan_mitigation')
|
||||
# .Next(this.implement_controls)
|
||||
# )
|
||||
#
|
||||
# implement_controls = (
|
||||
# flow_view(ControlImplementationView)
|
||||
# .Permission('quality.can_implement_controls')
|
||||
# .Next(this.monitor_effectiveness)
|
||||
# )
|
||||
#
|
||||
# monitor_effectiveness = (
|
||||
# flow_view(EffectivenessMonitoringView)
|
||||
# .Permission('quality.can_monitor_effectiveness')
|
||||
# .Next(this.manage_risk)
|
||||
# )
|
||||
#
|
||||
# manage_risk = (
|
||||
# flow_func(this.complete_risk_management)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_risk_management)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_risk_management(self, activation):
|
||||
# """Initialize the risk management process"""
|
||||
# process = activation.process
|
||||
# risk = process.risk_assessment
|
||||
#
|
||||
# # Update risk status
|
||||
# risk.status = 'active'
|
||||
# risk.save()
|
||||
#
|
||||
# # Send notification to risk owner
|
||||
# self.notify_risk_identified(risk)
|
||||
#
|
||||
# def complete_risk_management(self, activation):
|
||||
# """Finalize the risk management process"""
|
||||
# process = activation.process
|
||||
# risk = process.risk_assessment
|
||||
#
|
||||
# # Update risk status based on residual risk level
|
||||
# if risk.residual_risk_level in ['low', 'medium']:
|
||||
# risk.status = 'active'
|
||||
# else:
|
||||
# risk.status = 'under_review'
|
||||
#
|
||||
# risk.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.risk_managed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_risk_management_completion(risk)
|
||||
#
|
||||
# # Schedule risk review
|
||||
# self.schedule_risk_review(risk)
|
||||
#
|
||||
# def end_risk_management(self, activation):
|
||||
# """End the risk management workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate risk management summary
|
||||
# self.generate_risk_summary(process.risk_assessment)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_risk_identified(self, risk):
|
||||
# """Notify risk owner of identified risk"""
|
||||
# if risk.responsible_person and risk.responsible_person.email:
|
||||
# send_mail(
|
||||
# subject=f'Risk Assignment: {risk.title}',
|
||||
# message=f'You have been assigned responsibility for risk: {risk.title}',
|
||||
# from_email='quality@hospital.com',
|
||||
# recipient_list=[risk.responsible_person.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_risk_management_completion(self, risk):
|
||||
# """Notify risk management completion"""
|
||||
# # This would notify relevant parties
|
||||
# pass
|
||||
#
|
||||
# def schedule_risk_review(self, risk):
|
||||
# """Schedule risk review"""
|
||||
# # This would schedule periodic risk reviews
|
||||
# pass
|
||||
#
|
||||
# def generate_risk_summary(self, risk):
|
||||
# """Generate risk management summary"""
|
||||
# # This would generate risk summary
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def auto_generate_quality_reports():
|
||||
# """Background task to automatically generate quality reports"""
|
||||
# try:
|
||||
# # This would generate scheduled quality reports
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def monitor_quality_indicators():
|
||||
# """Background task to monitor quality indicators"""
|
||||
# try:
|
||||
# # This would check quality indicators for threshold breaches
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def schedule_audits():
|
||||
# """Background task to schedule audits"""
|
||||
# try:
|
||||
# # This would schedule regular audits
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def track_corrective_actions():
|
||||
# """Background task to track corrective action progress"""
|
||||
# try:
|
||||
# # This would monitor corrective action due dates
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def risk_monitoring():
|
||||
# """Background task to monitor risks"""
|
||||
# try:
|
||||
# # This would monitor risk levels and control effectiveness
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
BIN
radiology/__pycache__/flows.cpython-312.pyc
Normal file
BIN
radiology/__pycache__/flows.cpython-312.pyc
Normal file
Binary file not shown.
746
radiology/flows.py
Normal file
746
radiology/flows.py
Normal file
@ -0,0 +1,746 @@
|
||||
# """
|
||||
# Viewflow workflows for radiology app.
|
||||
# Provides imaging order processing, study execution, and report generation workflows.
|
||||
# """
|
||||
#
|
||||
# from viewflow import Flow, lock
|
||||
# from viewflow.base import this, flow_func
|
||||
# from viewflow.contrib import celery
|
||||
# from viewflow.decorators import flow_view
|
||||
# from viewflow.fields import CharField, ModelField
|
||||
# from viewflow.forms import ModelForm
|
||||
# from viewflow.views import CreateProcessView, UpdateProcessView
|
||||
# from viewflow.models import Process, Task
|
||||
# from django.contrib.auth.models import User
|
||||
# from django.urls import reverse_lazy
|
||||
# from django.utils import timezone
|
||||
# from django.db import transaction
|
||||
# from django.core.mail import send_mail
|
||||
#
|
||||
# from .models import ImagingOrder, ImagingStudy, RadiologyReport, ReportTemplate
|
||||
# from .views import (
|
||||
# ImagingOrderView, OrderVerificationView, StudySchedulingView,
|
||||
# PatientPreparationView, StudyExecutionView, ImageQualityCheckView,
|
||||
# ReportDictationView, ReportTranscriptionView, ReportVerificationView,
|
||||
# CriticalFindingNotificationView
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class ImagingOrderProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for imaging orders
|
||||
# """
|
||||
# imaging_order = ModelField(ImagingOrder, help_text='Associated imaging order')
|
||||
#
|
||||
# # Process status tracking
|
||||
# order_received = models.BooleanField(default=False)
|
||||
# order_verified = models.BooleanField(default=False)
|
||||
# study_scheduled = models.BooleanField(default=False)
|
||||
# patient_prepared = models.BooleanField(default=False)
|
||||
# study_completed = models.BooleanField(default=False)
|
||||
# images_reviewed = models.BooleanField(default=False)
|
||||
# report_dictated = models.BooleanField(default=False)
|
||||
# report_finalized = models.BooleanField(default=False)
|
||||
# results_communicated = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Imaging Order Process'
|
||||
# verbose_name_plural = 'Imaging Order Processes'
|
||||
#
|
||||
#
|
||||
# class ImagingOrderFlow(Flow):
|
||||
# """
|
||||
# Imaging Order Workflow
|
||||
#
|
||||
# This flow manages the complete imaging process from order receipt
|
||||
# through study execution and report delivery.
|
||||
# """
|
||||
#
|
||||
# process_class = ImagingOrderProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_imaging_order)
|
||||
# .Next(this.receive_order)
|
||||
# )
|
||||
#
|
||||
# receive_order = (
|
||||
# flow_view(ImagingOrderView)
|
||||
# .Permission('radiology.can_receive_orders')
|
||||
# .Next(this.verify_order)
|
||||
# )
|
||||
#
|
||||
# verify_order = (
|
||||
# flow_view(OrderVerificationView)
|
||||
# .Permission('radiology.can_verify_orders')
|
||||
# .Next(this.schedule_study)
|
||||
# )
|
||||
#
|
||||
# schedule_study = (
|
||||
# flow_view(StudySchedulingView)
|
||||
# .Permission('radiology.can_schedule_studies')
|
||||
# .Next(this.prepare_patient)
|
||||
# )
|
||||
#
|
||||
# prepare_patient = (
|
||||
# flow_view(PatientPreparationView)
|
||||
# .Permission('radiology.can_prepare_patients')
|
||||
# .Next(this.execute_study)
|
||||
# )
|
||||
#
|
||||
# execute_study = (
|
||||
# flow_view(StudyExecutionView)
|
||||
# .Permission('radiology.can_execute_studies')
|
||||
# .Next(this.review_images)
|
||||
# )
|
||||
#
|
||||
# review_images = (
|
||||
# flow_view(ImageQualityCheckView)
|
||||
# .Permission('radiology.can_review_images')
|
||||
# .Next(this.dictate_report)
|
||||
# )
|
||||
#
|
||||
# dictate_report = (
|
||||
# flow_view(ReportDictationView)
|
||||
# .Permission('radiology.can_dictate_reports')
|
||||
# .Next(this.transcribe_report)
|
||||
# )
|
||||
#
|
||||
# transcribe_report = (
|
||||
# flow_view(ReportTranscriptionView)
|
||||
# .Permission('radiology.can_transcribe_reports')
|
||||
# .Next(this.verify_report)
|
||||
# )
|
||||
#
|
||||
# verify_report = (
|
||||
# flow_view(ReportVerificationView)
|
||||
# .Permission('radiology.can_verify_reports')
|
||||
# .Next(this.check_critical_findings)
|
||||
# )
|
||||
#
|
||||
# check_critical_findings = (
|
||||
# flow_func(this.assess_critical_findings)
|
||||
# .Next(this.finalize_report)
|
||||
# )
|
||||
#
|
||||
# finalize_report = (
|
||||
# flow_func(this.complete_imaging_order)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_imaging_order)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_imaging_order(self, activation):
|
||||
# """Initialize the imaging order process"""
|
||||
# process = activation.process
|
||||
# order = process.imaging_order
|
||||
#
|
||||
# # Update order status
|
||||
# order.status = 'RECEIVED'
|
||||
# order.save()
|
||||
#
|
||||
# # Send notification to radiology staff
|
||||
# self.notify_radiology_staff(order)
|
||||
#
|
||||
# # Check for STAT orders
|
||||
# if order.priority in ['STAT', 'EMERGENCY']:
|
||||
# self.notify_stat_order(order)
|
||||
#
|
||||
# def assess_critical_findings(self, activation):
|
||||
# """Check for critical findings and handle notifications"""
|
||||
# process = activation.process
|
||||
# order = process.imaging_order
|
||||
#
|
||||
# # Get the associated report
|
||||
# try:
|
||||
# report = RadiologyReport.objects.get(study__imaging_order=order)
|
||||
#
|
||||
# if report.critical_finding:
|
||||
# # Handle critical finding notification
|
||||
# self.handle_critical_finding(report)
|
||||
#
|
||||
# # Mark as critical communicated
|
||||
# report.critical_communicated = True
|
||||
# report.critical_communicated_datetime = timezone.now()
|
||||
# report.save()
|
||||
# except RadiologyReport.DoesNotExist:
|
||||
# pass
|
||||
#
|
||||
# def complete_imaging_order(self, activation):
|
||||
# """Finalize the imaging order process"""
|
||||
# process = activation.process
|
||||
# order = process.imaging_order
|
||||
#
|
||||
# # Update order status
|
||||
# order.status = 'COMPLETED'
|
||||
# order.completion_datetime = timezone.now()
|
||||
# order.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.results_communicated = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send completion notifications
|
||||
# self.notify_order_completion(order)
|
||||
#
|
||||
# # Update quality metrics
|
||||
# self.update_quality_metrics(order)
|
||||
#
|
||||
# def end_imaging_order(self, activation):
|
||||
# """End the imaging order workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate order summary report
|
||||
# self.generate_order_summary(process.imaging_order)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_radiology_staff(self, order):
|
||||
# """Notify radiology staff of new order"""
|
||||
# from django.contrib.auth.models import Group
|
||||
#
|
||||
# radiology_staff = User.objects.filter(
|
||||
# groups__name='Radiology Staff'
|
||||
# )
|
||||
#
|
||||
# for staff in radiology_staff:
|
||||
# send_mail(
|
||||
# subject=f'New Imaging Order: {order.order_number}',
|
||||
# message=f'New {order.get_priority_display()} imaging order for {order.patient.get_full_name()}.',
|
||||
# from_email='radiology@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_stat_order(self, order):
|
||||
# """Notify of STAT imaging order"""
|
||||
# radiologists = User.objects.filter(
|
||||
# groups__name='Radiologists'
|
||||
# )
|
||||
#
|
||||
# for radiologist in radiologists:
|
||||
# send_mail(
|
||||
# subject=f'STAT Imaging Order: {order.order_number}',
|
||||
# message=f'{order.get_priority_display()} imaging order requires immediate attention.',
|
||||
# from_email='radiology@hospital.com',
|
||||
# recipient_list=[radiologist.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def handle_critical_finding(self, report):
|
||||
# """Handle critical finding notification"""
|
||||
# # Notify ordering physician immediately
|
||||
# if report.study.referring_physician and report.study.referring_physician.email:
|
||||
# send_mail(
|
||||
# subject=f'CRITICAL FINDING: {report.study.accession_number}',
|
||||
# message=f'Critical finding identified in imaging study. Please review immediately.',
|
||||
# from_email='radiology@hospital.com',
|
||||
# recipient_list=[report.study.referring_physician.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# # Notify radiology supervisor
|
||||
# supervisors = User.objects.filter(
|
||||
# groups__name='Radiology Supervisors'
|
||||
# )
|
||||
#
|
||||
# for supervisor in supervisors:
|
||||
# send_mail(
|
||||
# subject=f'Critical Finding Communicated: {report.study.accession_number}',
|
||||
# message=f'Critical finding has been communicated for study {report.study.accession_number}.',
|
||||
# from_email='radiology@hospital.com',
|
||||
# recipient_list=[supervisor.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def notify_order_completion(self, order):
|
||||
# """Notify ordering physician of completed study"""
|
||||
# if order.ordering_provider and order.ordering_provider.email:
|
||||
# send_mail(
|
||||
# subject=f'Imaging Results Available: {order.order_number}',
|
||||
# message=f'Imaging results are now available for {order.patient.get_full_name()}.',
|
||||
# from_email='radiology@hospital.com',
|
||||
# recipient_list=[order.ordering_provider.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def update_quality_metrics(self, order):
|
||||
# """Update radiology quality metrics"""
|
||||
# # This would update quality and performance metrics
|
||||
# pass
|
||||
#
|
||||
# def generate_order_summary(self, order):
|
||||
# """Generate imaging order summary report"""
|
||||
# # This would generate a comprehensive order report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class RadiologyReportProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for radiology reports
|
||||
# """
|
||||
# radiology_report = ModelField(RadiologyReport, help_text='Associated radiology report')
|
||||
#
|
||||
# # Process status tracking
|
||||
# report_initiated = models.BooleanField(default=False)
|
||||
# preliminary_read = models.BooleanField(default=False)
|
||||
# report_dictated = models.BooleanField(default=False)
|
||||
# report_transcribed = models.BooleanField(default=False)
|
||||
# report_reviewed = models.BooleanField(default=False)
|
||||
# report_signed = models.BooleanField(default=False)
|
||||
# report_distributed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Radiology Report Process'
|
||||
# verbose_name_plural = 'Radiology Report Processes'
|
||||
#
|
||||
#
|
||||
# class RadiologyReportFlow(Flow):
|
||||
# """
|
||||
# Radiology Report Workflow
|
||||
#
|
||||
# This flow manages the radiology reporting process from initial
|
||||
# interpretation through final report distribution.
|
||||
# """
|
||||
#
|
||||
# process_class = RadiologyReportProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_report)
|
||||
# .Next(this.preliminary_interpretation)
|
||||
# )
|
||||
#
|
||||
# preliminary_interpretation = (
|
||||
# flow_view(PreliminaryInterpretationView)
|
||||
# .Permission('radiology.can_preliminary_interpret')
|
||||
# .Next(this.dictate_report)
|
||||
# )
|
||||
#
|
||||
# dictate_report = (
|
||||
# flow_view(ReportDictationView)
|
||||
# .Permission('radiology.can_dictate_reports')
|
||||
# .Next(this.transcribe_report)
|
||||
# )
|
||||
#
|
||||
# transcribe_report = (
|
||||
# flow_view(ReportTranscriptionView)
|
||||
# .Permission('radiology.can_transcribe_reports')
|
||||
# .Next(this.review_report)
|
||||
# )
|
||||
#
|
||||
# review_report = (
|
||||
# flow_view(ReportReviewView)
|
||||
# .Permission('radiology.can_review_reports')
|
||||
# .Next(this.sign_report)
|
||||
# )
|
||||
#
|
||||
# sign_report = (
|
||||
# flow_view(ReportSigningView)
|
||||
# .Permission('radiology.can_sign_reports')
|
||||
# .Next(this.distribute_report)
|
||||
# )
|
||||
#
|
||||
# distribute_report = (
|
||||
# flow_func(this.complete_report)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_report)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_report(self, activation):
|
||||
# """Initialize the report process"""
|
||||
# process = activation.process
|
||||
# report = process.radiology_report
|
||||
#
|
||||
# # Update report status
|
||||
# report.status = 'DRAFT'
|
||||
# report.save()
|
||||
#
|
||||
# # Assign to radiologist
|
||||
# self.assign_radiologist(report)
|
||||
#
|
||||
# def complete_report(self, activation):
|
||||
# """Finalize the report process"""
|
||||
# process = activation.process
|
||||
# report = process.radiology_report
|
||||
#
|
||||
# # Update report status
|
||||
# report.status = 'FINAL'
|
||||
# report.finalized_datetime = timezone.now()
|
||||
# report.save()
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.report_distributed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Send distribution notifications
|
||||
# self.distribute_final_report(report)
|
||||
#
|
||||
# def end_report(self, activation):
|
||||
# """End the report workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate report metrics
|
||||
# self.generate_report_metrics(process.radiology_report)
|
||||
#
|
||||
# # Helper methods
|
||||
# def assign_radiologist(self, report):
|
||||
# """Assign radiologist to report"""
|
||||
# # This would implement radiologist assignment logic
|
||||
# pass
|
||||
#
|
||||
# def distribute_final_report(self, report):
|
||||
# """Distribute final report"""
|
||||
# # Notify referring physician
|
||||
# if report.study.referring_physician and report.study.referring_physician.email:
|
||||
# send_mail(
|
||||
# subject=f'Final Report Available: {report.study.accession_number}',
|
||||
# message=f'Final radiology report is available for {report.study.patient.get_full_name()}.',
|
||||
# from_email='radiology@hospital.com',
|
||||
# recipient_list=[report.study.referring_physician.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_report_metrics(self, report):
|
||||
# """Generate report performance metrics"""
|
||||
# # This would generate reporting metrics
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class QualityAssuranceProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for radiology quality assurance
|
||||
# """
|
||||
# study_id = CharField(max_length=50, help_text='Study identifier')
|
||||
# qa_type = CharField(max_length=20, help_text='Type of QA review')
|
||||
#
|
||||
# # Process status tracking
|
||||
# qa_initiated = models.BooleanField(default=False)
|
||||
# images_reviewed = models.BooleanField(default=False)
|
||||
# report_reviewed = models.BooleanField(default=False)
|
||||
# discrepancies_identified = models.BooleanField(default=False)
|
||||
# feedback_provided = models.BooleanField(default=False)
|
||||
# qa_completed = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Quality Assurance Process'
|
||||
# verbose_name_plural = 'Quality Assurance Processes'
|
||||
#
|
||||
#
|
||||
# class QualityAssuranceFlow(Flow):
|
||||
# """
|
||||
# Radiology Quality Assurance Workflow
|
||||
#
|
||||
# This flow manages quality assurance reviews including
|
||||
# image quality, report accuracy, and peer review.
|
||||
# """
|
||||
#
|
||||
# process_class = QualityAssuranceProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_qa_review)
|
||||
# .Next(this.review_images)
|
||||
# )
|
||||
#
|
||||
# review_images = (
|
||||
# flow_view(ImageQualityReviewView)
|
||||
# .Permission('radiology.can_review_image_quality')
|
||||
# .Next(this.review_report)
|
||||
# )
|
||||
#
|
||||
# review_report = (
|
||||
# flow_view(ReportQualityReviewView)
|
||||
# .Permission('radiology.can_review_report_quality')
|
||||
# .Next(this.identify_discrepancies)
|
||||
# )
|
||||
#
|
||||
# identify_discrepancies = (
|
||||
# flow_func(this.assess_discrepancies)
|
||||
# .Next(this.provide_feedback)
|
||||
# )
|
||||
#
|
||||
# provide_feedback = (
|
||||
# flow_view(QAFeedbackView)
|
||||
# .Permission('radiology.can_provide_qa_feedback')
|
||||
# .Next(this.finalize_qa)
|
||||
# )
|
||||
#
|
||||
# finalize_qa = (
|
||||
# flow_func(this.complete_qa_review)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_qa_review)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_qa_review(self, activation):
|
||||
# """Initialize the QA review process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Notify QA staff
|
||||
# self.notify_qa_staff(process.study_id, process.qa_type)
|
||||
#
|
||||
# def assess_discrepancies(self, activation):
|
||||
# """Assess for discrepancies"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Check for discrepancies
|
||||
# discrepancies = self.check_discrepancies(process.study_id)
|
||||
#
|
||||
# if discrepancies:
|
||||
# process.discrepancies_identified = True
|
||||
# process.save()
|
||||
#
|
||||
# # Alert QA supervisor
|
||||
# self.alert_qa_supervisor(process.study_id, discrepancies)
|
||||
#
|
||||
# def complete_qa_review(self, activation):
|
||||
# """Finalize the QA review process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark QA as completed
|
||||
# process.qa_completed = True
|
||||
# process.save()
|
||||
#
|
||||
# # Generate QA report
|
||||
# self.generate_qa_report(process.study_id, process.qa_type)
|
||||
#
|
||||
# def end_qa_review(self, activation):
|
||||
# """End the QA review workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Update QA metrics
|
||||
# self.update_qa_metrics(process.study_id, process.qa_type)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_qa_staff(self, study_id, qa_type):
|
||||
# """Notify QA staff"""
|
||||
# qa_staff = User.objects.filter(
|
||||
# groups__name='Radiology QA'
|
||||
# )
|
||||
#
|
||||
# for staff in qa_staff:
|
||||
# send_mail(
|
||||
# subject=f'QA Review Required: {study_id}',
|
||||
# message=f'{qa_type} QA review required for study {study_id}.',
|
||||
# from_email='radiology@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def check_discrepancies(self, study_id):
|
||||
# """Check for discrepancies"""
|
||||
# # This would implement discrepancy checking logic
|
||||
# return []
|
||||
#
|
||||
# def alert_qa_supervisor(self, study_id, discrepancies):
|
||||
# """Alert QA supervisor of discrepancies"""
|
||||
# supervisors = User.objects.filter(
|
||||
# groups__name='Radiology QA Supervisors'
|
||||
# )
|
||||
#
|
||||
# for supervisor in supervisors:
|
||||
# send_mail(
|
||||
# subject=f'QA Discrepancies Found: {study_id}',
|
||||
# message=f'Quality assurance discrepancies identified for study {study_id}.',
|
||||
# from_email='radiology@hospital.com',
|
||||
# recipient_list=[supervisor.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def generate_qa_report(self, study_id, qa_type):
|
||||
# """Generate QA report"""
|
||||
# # This would generate QA report
|
||||
# pass
|
||||
#
|
||||
# def update_qa_metrics(self, study_id, qa_type):
|
||||
# """Update QA metrics"""
|
||||
# # This would update QA metrics
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class EquipmentMaintenanceProcess(Process):
|
||||
# """
|
||||
# Viewflow process model for radiology equipment maintenance
|
||||
# """
|
||||
# equipment_id = CharField(max_length=50, help_text='Equipment identifier')
|
||||
# maintenance_type = CharField(max_length=20, help_text='Type of maintenance')
|
||||
#
|
||||
# # Process status tracking
|
||||
# maintenance_scheduled = models.BooleanField(default=False)
|
||||
# equipment_inspected = models.BooleanField(default=False)
|
||||
# maintenance_performed = models.BooleanField(default=False)
|
||||
# quality_testing_completed = models.BooleanField(default=False)
|
||||
# equipment_calibrated = models.BooleanField(default=False)
|
||||
# equipment_returned = models.BooleanField(default=False)
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name = 'Equipment Maintenance Process'
|
||||
# verbose_name_plural = 'Equipment Maintenance Processes'
|
||||
#
|
||||
#
|
||||
# class EquipmentMaintenanceFlow(Flow):
|
||||
# """
|
||||
# Radiology Equipment Maintenance Workflow
|
||||
#
|
||||
# This flow manages equipment maintenance including scheduling,
|
||||
# inspection, repair, calibration, and quality testing.
|
||||
# """
|
||||
#
|
||||
# process_class = EquipmentMaintenanceProcess
|
||||
#
|
||||
# # Flow definition
|
||||
# start = (
|
||||
# flow_func(this.start_equipment_maintenance)
|
||||
# .Next(this.schedule_maintenance)
|
||||
# )
|
||||
#
|
||||
# schedule_maintenance = (
|
||||
# flow_view(MaintenanceSchedulingView)
|
||||
# .Permission('radiology.can_schedule_maintenance')
|
||||
# .Next(this.inspect_equipment)
|
||||
# )
|
||||
#
|
||||
# inspect_equipment = (
|
||||
# flow_view(EquipmentInspectionView)
|
||||
# .Permission('radiology.can_inspect_equipment')
|
||||
# .Next(this.perform_maintenance)
|
||||
# )
|
||||
#
|
||||
# perform_maintenance = (
|
||||
# flow_view(MaintenanceExecutionView)
|
||||
# .Permission('radiology.can_perform_maintenance')
|
||||
# .Next(this.quality_testing)
|
||||
# )
|
||||
#
|
||||
# quality_testing = (
|
||||
# flow_view(QualityTestingView)
|
||||
# .Permission('radiology.can_perform_quality_testing')
|
||||
# .Next(this.calibrate_equipment)
|
||||
# )
|
||||
#
|
||||
# calibrate_equipment = (
|
||||
# flow_view(EquipmentCalibrationView)
|
||||
# .Permission('radiology.can_calibrate_equipment')
|
||||
# .Next(this.return_equipment)
|
||||
# )
|
||||
#
|
||||
# return_equipment = (
|
||||
# flow_func(this.complete_equipment_maintenance)
|
||||
# .Next(this.end)
|
||||
# )
|
||||
#
|
||||
# end = flow_func(this.end_equipment_maintenance)
|
||||
#
|
||||
# # Flow functions
|
||||
# def start_equipment_maintenance(self, activation):
|
||||
# """Initialize the equipment maintenance process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Notify maintenance staff
|
||||
# self.notify_maintenance_staff(process.equipment_id, process.maintenance_type)
|
||||
#
|
||||
# # Take equipment offline
|
||||
# self.take_equipment_offline(process.equipment_id)
|
||||
#
|
||||
# def complete_equipment_maintenance(self, activation):
|
||||
# """Finalize the equipment maintenance process"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Mark equipment as available
|
||||
# self.return_equipment_to_service(process.equipment_id)
|
||||
#
|
||||
# # Mark process as completed
|
||||
# process.equipment_returned = True
|
||||
# process.save()
|
||||
#
|
||||
# # Notify completion
|
||||
# self.notify_maintenance_completion(process.equipment_id)
|
||||
#
|
||||
# def end_equipment_maintenance(self, activation):
|
||||
# """End the equipment maintenance workflow"""
|
||||
# process = activation.process
|
||||
#
|
||||
# # Generate maintenance report
|
||||
# self.generate_maintenance_report(process.equipment_id, process.maintenance_type)
|
||||
#
|
||||
# # Helper methods
|
||||
# def notify_maintenance_staff(self, equipment_id, maintenance_type):
|
||||
# """Notify maintenance staff"""
|
||||
# maintenance_staff = User.objects.filter(
|
||||
# groups__name='Radiology Maintenance'
|
||||
# )
|
||||
#
|
||||
# for staff in maintenance_staff:
|
||||
# send_mail(
|
||||
# subject=f'Equipment Maintenance Required: {equipment_id}',
|
||||
# message=f'{maintenance_type} maintenance required for equipment {equipment_id}.',
|
||||
# from_email='maintenance@hospital.com',
|
||||
# recipient_list=[staff.email],
|
||||
# fail_silently=True
|
||||
# )
|
||||
#
|
||||
# def take_equipment_offline(self, equipment_id):
|
||||
# """Take equipment offline for maintenance"""
|
||||
# # This would update equipment status
|
||||
# pass
|
||||
#
|
||||
# def return_equipment_to_service(self, equipment_id):
|
||||
# """Return equipment to service"""
|
||||
# # This would update equipment status
|
||||
# pass
|
||||
#
|
||||
# def notify_maintenance_completion(self, equipment_id):
|
||||
# """Notify maintenance completion"""
|
||||
# # This would notify relevant staff
|
||||
# pass
|
||||
#
|
||||
# def generate_maintenance_report(self, equipment_id, maintenance_type):
|
||||
# """Generate maintenance report"""
|
||||
# # This would generate maintenance report
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# # Celery tasks for background processing
|
||||
# @celery.job
|
||||
# def auto_schedule_studies():
|
||||
# """Background task to automatically schedule imaging studies"""
|
||||
# try:
|
||||
# # This would perform automated study scheduling
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def monitor_report_turnaround_times():
|
||||
# """Background task to monitor report turnaround times"""
|
||||
# try:
|
||||
# # This would monitor reporting performance
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def generate_radiology_metrics():
|
||||
# """Background task to generate radiology performance metrics"""
|
||||
# try:
|
||||
# # This would generate performance reports
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
#
|
||||
# @celery.job
|
||||
# def auto_assign_radiologists():
|
||||
# """Background task to automatically assign radiologists to studies"""
|
||||
# try:
|
||||
# # This would auto-assign radiologists
|
||||
# return True
|
||||
# except Exception:
|
||||
# return False
|
||||
#
|
||||
BIN
templates/.DS_Store
vendored
BIN
templates/.DS_Store
vendored
Binary file not shown.
@ -435,7 +435,7 @@
|
||||
<a href="{% url 'billing:claim_detail' claim.claim_id %}" class="btn btn-outline-primary btn-sm" title="View">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<a href="{% url 'billing:claim_print' claim.claim_id %}" class="btn btn-outline-secondary btn-sm" title="Print">
|
||||
<a href="" class="btn btn-outline-secondary btn-sm" title="Print">
|
||||
<i class="fas fa-print"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -5,479 +5,541 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-1">{% if object %}Edit Medical Bill{% else %}Create Medical Bill{% endif %}</h1>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'billing:dashboard' %}">Billing</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'billing:bill_list' %}">Medical Bills</a></li>
|
||||
<li class="breadcrumb-item active">{% if object %}Edit{% else %}Create{% endif %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<a href="{% url 'billing:bill_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-2"></i>Back to List
|
||||
</a>
|
||||
{% if object %}
|
||||
<a href="{% url 'billing:bill_detail' object.bill_id %}" class="btn btn-outline-primary">
|
||||
<i class="fas fa-eye me-2"></i>View Details
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- Page Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-1">{% if object %}Edit Medical Bill{% else %}Create Medical Bill{% endif %}</h1>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'billing:dashboard' %}">Billing</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'billing:bill_list' %}">Medical Bills</a></li>
|
||||
<li class="breadcrumb-item active">{% if object %}Edit{% else %}Create{% endif %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<a href="{% url 'billing:bill_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-2"></i>Back to List
|
||||
</a>
|
||||
{% if object %}
|
||||
<a href="{% url 'billing:bill_detail' object.bill_id %}" class="btn btn-outline-primary">
|
||||
<i class="fas fa-eye me-2"></i>View Details
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" id="billForm" hx-post="{% if object %}{% url 'billing:bill_update' object.bill_id %}{% else %}{% url 'billing:bill_create' %}{% endif %}" hx-target="#form-container">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="row">
|
||||
<!-- Main Form -->
|
||||
<div class="col-lg-8">
|
||||
<div id="form-container">
|
||||
<!-- Patient Information -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-user me-2"></i>Patient Information
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating">
|
||||
{{ form.patient }}
|
||||
<label for="{{ form.patient.id_for_label }}">Patient *</label>
|
||||
{% if form.patient.errors %}
|
||||
<div class="invalid-feedback d-block">
|
||||
{{ form.patient.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating">
|
||||
{{ form.encounter }}
|
||||
<label for="{{ form.encounter.id_for_label }}">Related Encounter</label>
|
||||
{% if form.encounter.errors %}
|
||||
<div class="invalid-feedback d-block">
|
||||
{{ form.encounter.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post"
|
||||
id="billForm"
|
||||
hx-post="{% if object %}{% url 'billing:bill_update' object.bill_id %}{% else %}{% url 'billing:bill_create' %}{% endif %}"
|
||||
hx-target="#form-container">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Bill Details -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-file-invoice me-2"></i>Bill Details
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<div class="form-floating">
|
||||
{{ form.bill_number }}
|
||||
<label for="{{ form.bill_number.id_for_label }}">Bill Number *</label>
|
||||
{% if form.bill_number.errors %}
|
||||
<div class="invalid-feedback d-block">
|
||||
{{ form.bill_number.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-floating">
|
||||
{{ form.bill_date }}
|
||||
<label for="{{ form.bill_date.id_for_label }}">Bill Date *</label>
|
||||
{% if form.bill_date.errors %}
|
||||
<div class="invalid-feedback d-block">
|
||||
{{ form.bill_date.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-floating">
|
||||
{{ form.due_date }}
|
||||
<label for="{{ form.due_date.id_for_label }}">Due Date</label>
|
||||
{% if form.due_date.errors %}
|
||||
<div class="invalid-feedback d-block">
|
||||
{{ form.due_date.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating">
|
||||
{{ form.status }}
|
||||
<label for="{{ form.status.id_for_label }}">Status *</label>
|
||||
{% if form.status.errors %}
|
||||
<div class="invalid-feedback d-block">
|
||||
{{ form.status.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating">
|
||||
{{ form.billing_provider }}
|
||||
<label for="{{ form.billing_provider.id_for_label }}">Billing Provider</label>
|
||||
{% if form.billing_provider.errors %}
|
||||
<div class="invalid-feedback d-block">
|
||||
{{ form.billing_provider.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="form-floating">
|
||||
{{ form.notes }}
|
||||
<label for="{{ form.notes.id_for_label }}">Notes</label>
|
||||
{% if form.notes.errors %}
|
||||
<div class="invalid-feedback d-block">
|
||||
{{ form.notes.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<!-- Main Form -->
|
||||
<div class="col-lg-8">
|
||||
<div id="form-container">
|
||||
|
||||
<!-- Line Items -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-list me-2"></i>Line Items
|
||||
</h5>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="addLineItem()">
|
||||
<i class="fas fa-plus me-1"></i>Add Item
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="line-items-container">
|
||||
<!-- Line items will be dynamically added here -->
|
||||
{% if object.billlineitem_set.all %}
|
||||
{% for line_item in object.billlineitem_set.all %}
|
||||
<div class="line-item-row border rounded p-3 mb-3">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="service_code_{{ forloop.counter0 }}" value="{{ line_item.service_code }}" placeholder="Service Code">
|
||||
<label>Service Code</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="description_{{ forloop.counter0 }}" value="{{ line_item.description }}" placeholder="Description">
|
||||
<label>Description</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<input type="number" class="form-control quantity-input" name="quantity_{{ forloop.counter0 }}" value="{{ line_item.quantity }}" min="1" step="1" placeholder="Qty">
|
||||
<label>Quantity</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<input type="number" class="form-control unit-price-input" name="unit_price_{{ forloop.counter0 }}" value="{{ line_item.unit_price }}" min="0" step="0.01" placeholder="Unit Price">
|
||||
<label>Unit Price</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button type="button" class="btn btn-outline-danger h-100 w-100" onclick="removeLineItem(this)">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-list fa-2x mb-3 opacity-50"></i>
|
||||
<p>No line items added yet. Click "Add Item" to get started.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<a href="{% url 'billing:bill_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-times me-2"></i>Cancel
|
||||
</a>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button type="submit" name="action" value="save_draft" class="btn btn-outline-primary">
|
||||
<i class="fas fa-save me-2"></i>Save as Draft
|
||||
</button>
|
||||
<button type="submit" name="action" value="save_and_continue" class="btn btn-primary">
|
||||
<i class="fas fa-check me-2"></i>{% if object %}Update{% else %}Create{% endif %} Bill
|
||||
</button>
|
||||
{% if not object %}
|
||||
<button type="submit" name="action" value="save_and_submit" class="btn btn-success">
|
||||
<i class="fas fa-paper-plane me-2"></i>Create & Submit
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Patient & Context -->
|
||||
<div class="panel panel-inverse mb-4">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title"><i class="fas fa-user me-2"></i>Patient & Context</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if object %}
|
||||
<div class="alert alert-secondary small d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong>Bill #</strong> <code>{{ object.bill_number }}</code>
|
||||
</div>
|
||||
<div><strong>Created:</strong> {{ object.created_at|date:"M d, Y H:i" }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating">
|
||||
{{ form.patient }}
|
||||
<label for="{{ form.patient.id_for_label }}">Patient *</label>
|
||||
{% if form.patient.errors %}<div class="invalid-feedback d-block">{{ form.patient.errors.0 }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating">
|
||||
{{ form.bill_type }}
|
||||
<label for="{{ form.bill_type.id_for_label }}">Bill Type *</label>
|
||||
{% if form.bill_type.errors %}<div class="invalid-feedback d-block">{{ form.bill_type.errors.0 }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mt-1">
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating">
|
||||
{{ form.encounter }}
|
||||
<label for="{{ form.encounter.id_for_label }}">Related Encounter</label>
|
||||
{% if form.encounter.errors %}<div class="invalid-feedback d-block">{{ form.encounter.errors.0 }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating">
|
||||
{{ form.admission }}
|
||||
<label for="{{ form.admission.id_for_label }}">Admission</label>
|
||||
{% if form.admission.errors %}<div class="invalid-feedback d-block">{{ form.admission.errors.0 }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mt-1">
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating">
|
||||
{{ form.payment_terms }}
|
||||
<label for="{{ form.payment_terms.id_for_label }}">Payment Terms</label>
|
||||
{% if form.payment_terms.errors %}<div class="invalid-feedback d-block">{{ form.payment_terms.errors.0 }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Service & Dates -->
|
||||
<div class="panel panel-inverse mb-4">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title"><i class="fas fa-calendar-alt me-2"></i>Service & Dates</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<div class="form-floating">
|
||||
{{ form.service_date_from }}
|
||||
<label for="{{ form.service_date_from.id_for_label }}">Service From *</label>
|
||||
{% if form.service_date_from.errors %}<div class="invalid-feedback d-block">{{ form.service_date_from.errors.0 }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-floating">
|
||||
{{ form.service_date_to }}
|
||||
<label for="{{ form.service_date_to.id_for_label }}">Service To *</label>
|
||||
{% if form.service_date_to.errors %}<div class="invalid-feedback d-block">{{ form.service_date_to.errors.0 }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-floating">
|
||||
{{ form.bill_date }}
|
||||
<label for="{{ form.bill_date.id_for_label }}">Bill Date *</label>
|
||||
{% if form.bill_date.errors %}<div class="invalid-feedback d-block">{{ form.bill_date.errors.0 }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 mt-1">
|
||||
<div class="col-md-4">
|
||||
<div class="form-floating">
|
||||
{{ form.due_date }}
|
||||
<label for="{{ form.due_date.id_for_label }}">Due Date</label>
|
||||
{% if form.due_date.errors %}<div class="invalid-feedback d-block">{{ form.due_date.errors.0 }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Providers & Insurance -->
|
||||
<div class="panel panel-inverse mb-4">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title"><i class="fas fa-user-md me-2"></i>Providers & Insurance</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating">
|
||||
{{ form.attending_provider }}
|
||||
<label for="{{ form.attending_provider.id_for_label }}">Attending Provider</label>
|
||||
{% if form.attending_provider.errors %}<div class="invalid-feedback d-block">{{ form.attending_provider.errors.0 }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating">
|
||||
{{ form.billing_provider }}
|
||||
<label for="{{ form.billing_provider.id_for_label }}">Billing Provider</label>
|
||||
{% if form.billing_provider.errors %}<div class="invalid-feedback d-block">{{ form.billing_provider.errors.0 }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 mt-1">
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating">
|
||||
{{ form.primary_insurance }}
|
||||
<label for="{{ form.primary_insurance.id_for_label }}">Primary Insurance</label>
|
||||
{% if form.primary_insurance.errors %}<div class="invalid-feedback d-block">{{ form.primary_insurance.errors.0 }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating">
|
||||
{{ form.secondary_insurance }}
|
||||
<label for="{{ form.secondary_insurance.id_for_label }}">Secondary Insurance</label>
|
||||
{% if form.secondary_insurance.errors %}<div class="invalid-feedback d-block">{{ form.secondary_insurance.errors.0 }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notes -->
|
||||
<div class="panel panel-inverse mb-4">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title"><i class="fas fa-sticky-note me-2"></i>Notes</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="mb-0">
|
||||
{{ form.notes }}
|
||||
{% if form.notes.errors %}<div class="invalid-feedback d-block">{{ form.notes.errors.0 }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Line Items -->
|
||||
<div class="panel panel-inverse mb-4">
|
||||
<div class="panel-heading d-flex justify-content-between align-items-center">
|
||||
<h4 class="panel-title"><i class="fas fa-list me-2"></i>Line Items</h4>
|
||||
<div class="panel-heading-btn d-flex align-items-center gap-2">
|
||||
<button type="button" class="btn btn-xs btn-primary" onclick="addLineItem()">
|
||||
<i class="fas fa-plus me-1"></i>Add Item
|
||||
</button>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div id="line-items-container">
|
||||
{% if object and object.billlineitem_set.all %}
|
||||
{% for line_item in object.billlineitem_set.all %}
|
||||
<div class="line-item-row border rounded p-3 mb-3">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="service_code_{{ forloop.counter0 }}" value="{{ line_item.service_code }}" placeholder="Service Code">
|
||||
<label>Service Code</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="description_{{ forloop.counter0 }}" value="{{ line_item.description }}" placeholder="Description">
|
||||
<label>Description</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<input type="number" class="form-control quantity-input" name="quantity_{{ forloop.counter0 }}" value="{{ line_item.quantity }}" min="1" step="1" placeholder="Qty">
|
||||
<label>Quantity</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<input type="number" class="form-control unit-price-input" name="unit_price_{{ forloop.counter0 }}" value="{{ line_item.unit_price }}" min="0" step="0.01" placeholder="Unit Price">
|
||||
<label>Unit Price</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button type="button" class="btn btn-outline-danger h-100 w-100" onclick="removeLineItem(this)">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-list fa-2x mb-3 opacity-50"></i>
|
||||
<p>No line items added yet. Click "Add Item" to get started.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="panel panel-inverse">
|
||||
<div class="panel-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<a href="{% url 'billing:bill_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-times me-2"></i>Cancel
|
||||
</a>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button type="submit" name="action" value="save_draft" class="btn btn-outline-primary">
|
||||
<i class="fas fa-save me-2"></i>Save as Draft
|
||||
</button>
|
||||
<button type="submit" name="action" value="save_and_continue" class="btn btn-primary">
|
||||
<i class="fas fa-check me-2"></i>{% if object %}Update{% else %}Create{% endif %} Bill
|
||||
</button>
|
||||
{% if not object %}
|
||||
<button type="submit" name="action" value="save_and_submit" class="btn btn-success">
|
||||
<i class="fas fa-paper-plane me-2"></i>Create & Submit
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- /#form-container -->
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<!-- Bill Summary -->
|
||||
<div class="panel panel-inverse mb-4">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title"><i class="fas fa-calculator me-2"></i>Bill Summary</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<!-- Readout -->
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Subtotal:</span> <span id="subtotal-amount">$0.00</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Tax:</span> <span id="tax-amount">$0.00</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Discount:</span> <span id="discount-amount">$0.00</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between fw-bold">
|
||||
<span>Total:</span> <span id="total-amount">$0.00</span>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<!-- Bill Summary -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-calculator me-2"></i>Bill Summary
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Subtotal:</span>
|
||||
<span id="subtotal-amount">$0.00</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Tax:</span>
|
||||
<span id="tax-amount">$0.00</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Discount:</span>
|
||||
<span id="discount-amount">$0.00</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between fw-bold">
|
||||
<span>Total:</span>
|
||||
<span id="total-amount">$0.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help Panel -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-question-circle me-2"></i>Help & Guidelines
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="accordion accordion-flush" id="helpAccordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#help-patient">
|
||||
Patient Selection
|
||||
</button>
|
||||
</h2>
|
||||
<div id="help-patient" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
||||
<div class="accordion-body">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><i class="fas fa-check text-success me-2"></i>Select the patient for this bill</li>
|
||||
<li><i class="fas fa-check text-success me-2"></i>Link to a specific encounter if applicable</li>
|
||||
<li><i class="fas fa-check text-success me-2"></i>Verify patient insurance information</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#help-codes">
|
||||
Service Codes
|
||||
</button>
|
||||
</h2>
|
||||
<div id="help-codes" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
||||
<div class="accordion-body">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><i class="fas fa-info text-info me-2"></i>Use standard CPT/HCPCS codes</li>
|
||||
<li><i class="fas fa-info text-info me-2"></i>Include modifier codes when applicable</li>
|
||||
<li><i class="fas fa-info text-info me-2"></i>Verify codes with current fee schedule</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#help-status">
|
||||
Bill Status
|
||||
</button>
|
||||
</h2>
|
||||
<div id="help-status" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
||||
<div class="accordion-body">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><strong>Draft:</strong> Bill is being prepared</li>
|
||||
<li><strong>Pending:</strong> Ready for review</li>
|
||||
<li><strong>Submitted:</strong> Sent to patient/insurance</li>
|
||||
<li><strong>Paid:</strong> Payment received in full</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Hidden bound fields so totals post to the form -->
|
||||
<div class="d-none">
|
||||
{{ form.subtotal }}
|
||||
{{ form.tax_amount }}
|
||||
{{ form.discount_amount }}
|
||||
{{ form.adjustment_amount }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Help Panel -->
|
||||
<div class="panel panel-inverse">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title"><i class="fas fa-question-circle me-2"></i>Help & Guidelines</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="accordion accordion-flush" id="helpAccordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#help-patient">
|
||||
Patient Selection
|
||||
</button>
|
||||
</h2>
|
||||
<div id="help-patient" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
||||
<div class="accordion-body">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><i class="fas fa-check text-success me-2"></i>Select the patient for this bill</li>
|
||||
<li><i class="fas fa-check text-success me-2"></i>Link to a specific encounter if applicable</li>
|
||||
<li><i class="fas fa-check text-success me-2"></i>Verify patient insurance information</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#help-codes">
|
||||
Service Codes
|
||||
</button>
|
||||
</h2>
|
||||
<div id="help-codes" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
||||
<div class="accordion-body">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><i class="fas fa-info text-info me-2"></i>Use standard CPT/HCPCS codes</li>
|
||||
<li><i class="fas fa-info text-info me-2"></i>Include modifier codes when applicable</li>
|
||||
<li><i class="fas fa-info text-info me-2"></i>Verify codes with current fee schedule</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#help-status">
|
||||
Bill Lifecycle
|
||||
</button>
|
||||
</h2>
|
||||
<div id="help-status" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
||||
<div class="accordion-body">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><strong>Draft:</strong> Bill is being prepared</li>
|
||||
<li><strong>Pending:</strong> Ready for review</li>
|
||||
<li><strong>Submitted:</strong> Sent to patient/insurance</li>
|
||||
<li><strong>Paid:</strong> Payment received in full</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /accordion -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /.row -->
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Line Item Template -->
|
||||
<template id="line-item-template">
|
||||
<div class="line-item-row border rounded p-3 mb-3">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="service_code_NEW" placeholder="Service Code">
|
||||
<label>Service Code</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="description_NEW" placeholder="Description">
|
||||
<label>Description</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<input type="number" class="form-control quantity-input" name="quantity_NEW" value="1" min="1" step="1" placeholder="Qty">
|
||||
<label>Quantity</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<input type="number" class="form-control unit-price-input" name="unit_price_NEW" value="0.00" min="0" step="0.01" placeholder="Unit Price">
|
||||
<label>Unit Price</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button type="button" class="btn btn-outline-danger h-100 w-100" onclick="removeLineItem(this)">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="line-item-row border rounded p-3 mb-3">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="service_code_NEW" placeholder="Service Code">
|
||||
<label>Service Code</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="description_NEW" placeholder="Description">
|
||||
<label>Description</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<input type="number" class="form-control quantity-input" name="quantity_NEW" value="1" min="1" step="1" placeholder="Qty">
|
||||
<label>Quantity</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<input type="number" class="form-control unit-price-input" name="unit_price_NEW" value="0.00" min="0" step="0.01" placeholder="Unit Price">
|
||||
<label>Unit Price</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button type="button" class="btn btn-outline-danger h-100 w-100" onclick="removeLineItem(this)">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let lineItemCounter = {{ object.billlineitem_set.count|default:0 }};
|
||||
let lineItemCounter = {% if object %}{{ object.billlineitem_set.count|default:0 }}{% else %}0{% endif %};
|
||||
|
||||
function addLineItem() {
|
||||
const template = document.getElementById('line-item-template');
|
||||
const container = document.getElementById('line-items-container');
|
||||
|
||||
// Remove empty state if present
|
||||
const emptyState = container.querySelector('.text-center.text-muted');
|
||||
if (emptyState) {
|
||||
emptyState.remove();
|
||||
}
|
||||
|
||||
// Clone template
|
||||
const clone = template.content.cloneNode(true);
|
||||
|
||||
// Update field names with counter
|
||||
clone.querySelectorAll('input').forEach(input => {
|
||||
input.name = input.name.replace('NEW', lineItemCounter);
|
||||
});
|
||||
|
||||
container.appendChild(clone);
|
||||
lineItemCounter++;
|
||||
|
||||
// Recalculate totals
|
||||
calculateTotals();
|
||||
const template = document.getElementById('line-item-template');
|
||||
const container = document.getElementById('line-items-container');
|
||||
const emptyState = container.querySelector('.text-center.text-muted');
|
||||
if (emptyState) emptyState.remove();
|
||||
|
||||
const clone = template.content.cloneNode(true);
|
||||
clone.querySelectorAll('input').forEach(input => {
|
||||
input.name = input.name.replace('NEW', lineItemCounter);
|
||||
});
|
||||
container.appendChild(clone);
|
||||
lineItemCounter++;
|
||||
calculateTotals();
|
||||
}
|
||||
|
||||
function removeLineItem(button) {
|
||||
const container = document.getElementById('line-items-container');
|
||||
const lineItem = button.closest('.line-item-row');
|
||||
|
||||
lineItem.remove();
|
||||
|
||||
// Show empty state if no items left
|
||||
if (container.children.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-list fa-2x mb-3 opacity-50"></i>
|
||||
<p>No line items added yet. Click "Add Item" to get started.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Recalculate totals
|
||||
calculateTotals();
|
||||
const container = document.getElementById('line-items-container');
|
||||
const row = button.closest('.line-item-row');
|
||||
if (row) row.remove();
|
||||
|
||||
if (!container.querySelector('.line-item-row')) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-list fa-2x mb-3 opacity-50"></i>
|
||||
<p>No line items added yet. Click "Add Item" to get started.</p>
|
||||
</div>`;
|
||||
}
|
||||
calculateTotals();
|
||||
}
|
||||
|
||||
function calculateTotals() {
|
||||
let subtotal = 0;
|
||||
|
||||
document.querySelectorAll('.line-item-row').forEach(row => {
|
||||
const quantity = parseFloat(row.querySelector('.quantity-input').value) || 0;
|
||||
const unitPrice = parseFloat(row.querySelector('.unit-price-input').value) || 0;
|
||||
subtotal += quantity * unitPrice;
|
||||
});
|
||||
|
||||
const tax = subtotal * 0.0; // Adjust tax rate as needed
|
||||
const discount = 0; // Add discount logic if needed
|
||||
const total = subtotal + tax - discount;
|
||||
|
||||
document.getElementById('subtotal-amount').textContent = `$${subtotal.toFixed(2)}`;
|
||||
document.getElementById('tax-amount').textContent = `$${tax.toFixed(2)}`;
|
||||
document.getElementById('discount-amount').textContent = `$${discount.toFixed(2)}`;
|
||||
document.getElementById('total-amount').textContent = `$${total.toFixed(2)}`;
|
||||
let subtotal = 0;
|
||||
|
||||
document.querySelectorAll('.line-item-row').forEach(row => {
|
||||
const q = parseFloat(row.querySelector('.quantity-input')?.value) || 0;
|
||||
const p = parseFloat(row.querySelector('.unit-price-input')?.value) || 0;
|
||||
subtotal += q * p;
|
||||
});
|
||||
|
||||
// Adjust rates/logic as needed
|
||||
const tax = subtotal * 0.00;
|
||||
const discount = 0.00;
|
||||
const total = subtotal + tax - discount;
|
||||
|
||||
// Visual readout
|
||||
document.getElementById('subtotal-amount').textContent = `$${subtotal.toFixed(2)}`;
|
||||
document.getElementById('tax-amount').textContent = `$${tax.toFixed(2)}`;
|
||||
document.getElementById('discount-amount').textContent = `$${discount.toFixed(2)}`;
|
||||
document.getElementById('total-amount').textContent = `$${total.toFixed(2)}`;
|
||||
|
||||
// Sync hidden bound fields
|
||||
const subtotalInput = document.getElementById('id_subtotal');
|
||||
const taxInput = document.getElementById('id_tax_amount');
|
||||
const discountInput = document.getElementById('id_discount_amount');
|
||||
const adjustInput = document.getElementById('id_adjustment_amount');
|
||||
|
||||
if (subtotalInput) subtotalInput.value = subtotal.toFixed(2);
|
||||
if (taxInput) taxInput.value = tax.toFixed(2);
|
||||
if (discountInput) discountInput.value = discount.toFixed(2);
|
||||
if (adjustInput && !adjustInput.value) adjustInput.value = (0).toFixed(2);
|
||||
}
|
||||
|
||||
// Auto-calculate totals when quantities or prices change
|
||||
document.addEventListener('input', function(e) {
|
||||
if (e.target.classList.contains('quantity-input') || e.target.classList.contains('unit-price-input')) {
|
||||
calculateTotals();
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize totals on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener('input', e => {
|
||||
if (e.target.classList.contains('quantity-input') || e.target.classList.contains('unit-price-input')) {
|
||||
calculateTotals();
|
||||
}
|
||||
});
|
||||
|
||||
// Form validation
|
||||
document.addEventListener('DOMContentLoaded', calculateTotals);
|
||||
|
||||
// Validate line items and ensure totals are synced before submit
|
||||
document.getElementById('billForm').addEventListener('submit', function(e) {
|
||||
const lineItems = document.querySelectorAll('.line-item-row');
|
||||
|
||||
if (lineItems.length === 0) {
|
||||
e.preventDefault();
|
||||
alert('Please add at least one line item to the bill.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate line items
|
||||
let hasErrors = false;
|
||||
lineItems.forEach(row => {
|
||||
const serviceCode = row.querySelector('input[name*="service_code"]').value.trim();
|
||||
const description = row.querySelector('input[name*="description"]').value.trim();
|
||||
const quantity = row.querySelector('.quantity-input').value;
|
||||
const unitPrice = row.querySelector('.unit-price-input').value;
|
||||
|
||||
if (!serviceCode || !description || !quantity || !unitPrice) {
|
||||
hasErrors = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasErrors) {
|
||||
e.preventDefault();
|
||||
alert('Please fill in all required fields for each line item.');
|
||||
return false;
|
||||
const rows = document.querySelectorAll('.line-item-row');
|
||||
if (!rows.length) {
|
||||
e.preventDefault();
|
||||
alert('Please add at least one line item to the bill.');
|
||||
return false;
|
||||
}
|
||||
for (const row of rows) {
|
||||
const code = (row.querySelector('input[name*="service_code"]')?.value || '').trim();
|
||||
const desc = (row.querySelector('input[name*="description"]')?.value || '').trim();
|
||||
const qty = row.querySelector('.quantity-input')?.value;
|
||||
const price = row.querySelector('.unit-price-input')?.value;
|
||||
if (!code || !desc || !qty || !price) {
|
||||
e.preventDefault();
|
||||
alert('Please fill in all required fields for each line item.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Make sure numbers are in the form
|
||||
calculateTotals();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
<style>
|
||||
.badge { font-size: .75rem; }
|
||||
.table-borderless td { padding: .5rem 0; }
|
||||
.bg-gradient { background-image: linear-gradient(180deg, rgba(255,255,255,.15), rgba(255,255,255,0)); }
|
||||
.line-item-row .btn-outline-danger:hover { transform: translateY(-1px); }
|
||||
@media (max-width: 768px) {
|
||||
.btn-group { display: flex; flex-direction: column; gap: .5rem; }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@ -5,405 +5,395 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-1">Medical Bills</h1>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'billing:dashboard' %}">Billing</a></li>
|
||||
<li class="breadcrumb-item active">Medical Bills</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<a href="{% url 'billing:bill_create' %}" class="btn btn-primary">
|
||||
<i class="fas fa-plus me-2"></i>Create Bill
|
||||
</a>
|
||||
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
|
||||
<span class="visually-hidden">Toggle Dropdown</span>
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{% url 'billing:export_bills' %}">
|
||||
<i class="fas fa-download me-2"></i>Export Bills
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="window.print()">
|
||||
<i class="fas fa-print me-2"></i>Print List
|
||||
</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Page Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-1">Medical Bills</h1>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'billing:dashboard' %}">Billing</a></li>
|
||||
<li class="breadcrumb-item active">Medical Bills</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Statistics Cards -->
|
||||
<div class="row mb-4" hx-get="{% url 'billing:billing_stats' %}" hx-trigger="load, every 60s">
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-gradient-primary text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="card-title mb-1 opacity-75">Total Bills</h6>
|
||||
<h3 class="mb-0">{{ stats.total_bills }}</h3>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
<i class="fas fa-file-invoice fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-gradient-success text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="card-title mb-1 opacity-75">Total Amount</h6>
|
||||
<h3 class="mb-0"><span class="symbol">ê</span>{{ stats.total_amount|default:0|floatformat:'2g' }}</h3>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
<i class="fas fa-dollar-sign fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-gradient-info text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="card-title mb-1 opacity-75">Total Paid</h6>
|
||||
<h3 class="mb-0"><span class="symbol">ê</span>{{ stats.total_paid|default:0|floatformat:'2g' }}</h3>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
<i class="fas fa-check-circle fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-gradient-warning text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="card-title mb-1 opacity-75">Outstanding</h6>
|
||||
<h3 class="mb-0"><span class="symbol">ê</span>{{ stats.outstanding_amount|default:0|floatformat:'2g' }}</h3>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
<i class="fas fa-exclamation-triangle fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<a href="{% url 'billing:bill_create' %}" class="btn btn-primary">
|
||||
<i class="fas fa-plus me-2"></i>Create Bill
|
||||
</a>
|
||||
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
|
||||
<span class="visually-hidden">Toggle Dropdown</span>
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class="dropdown-item" href="{% url 'billing:export_bills' %}">
|
||||
<i class="fas fa-download me-2"></i>Export Bills
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" onclick="window.print()">
|
||||
<i class="fas fa-print me-2"></i>Print List
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filter Card -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-search me-2"></i>Search & Filter
|
||||
</h5>
|
||||
</div>
|
||||
<!-- Statistics Cards -->
|
||||
<div class="row mb-4" hx-get="{% url 'billing:billing_stats' %}" hx-trigger="load, every 60s">
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-gradient-primary text-white">
|
||||
<div class="card-body">
|
||||
<form method="get" class="row g-3" hx-get="{% url 'billing:bill_list' %}" hx-target="#bill-list-container" hx-trigger="submit, change delay:500ms">
|
||||
<div class="col-md-3">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" id="search" name="search" value="{{ request.GET.search }}" placeholder="Search bills...">
|
||||
<label for="search">Search Bills</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<select class="form-select" id="status" name="status">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="DRAFT" {% if request.GET.status == 'DRAFT' %}selected{% endif %}>Draft</option>
|
||||
<option value="PENDING" {% if request.GET.status == 'PENDING' %}selected{% endif %}>Pending</option>
|
||||
<option value="SUBMITTED" {% if request.GET.status == 'SUBMITTED' %}selected{% endif %}>Submitted</option>
|
||||
<option value="PARTIAL_PAID" {% if request.GET.status == 'PARTIAL_PAID' %}selected{% endif %}>Partially Paid</option>
|
||||
<option value="PAID" {% if request.GET.status == 'PAID' %}selected{% endif %}>Paid</option>
|
||||
<option value="OVERDUE" {% if request.GET.status == 'OVERDUE' %}selected{% endif %}>Overdue</option>
|
||||
<option value="COLLECTIONS" {% if request.GET.status == 'COLLECTIONS' %}selected{% endif %}>Collections</option>
|
||||
<option value="WRITTEN_OFF" {% if request.GET.status == 'WRITTEN_OFF' %}selected{% endif %}>Written Off</option>
|
||||
<option value="CANCELLED" {% if request.GET.status == 'CANCELLED' %}selected{% endif %}>Cancelled</option>
|
||||
</select>
|
||||
<label for="status">Status</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<input type="date" class="form-control" id="date_from" name="date_from" value="{{ request.GET.date_from }}">
|
||||
<label for="date_from">From Date</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<input type="date" class="form-control" id="date_to" name="date_to" value="{{ request.GET.date_to }}">
|
||||
<label for="date_to">To Date</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<input type="number" class="form-control" id="min_amount" name="min_amount" value="{{ request.GET.min_amount }}" step="0.01" placeholder="Min Amount">
|
||||
<label for="min_amount">Min Amount</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button type="submit" class="btn btn-primary h-100 w-100">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="card-title mb-1 opacity-75">Total Bills</h6>
|
||||
<h3 class="mb-0">{{ stats.total_bills|default:0 }}</h3>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
<i class="fas fa-file-invoice fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-gradient-success text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="card-title mb-1 opacity-75">Total Amount</h6>
|
||||
<h3 class="mb-0"><span class="symbol">ê</span>{{ stats.total_amount|default:0|floatformat:'2g' }}</h3>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
<i class="fas fa-dollar-sign fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-gradient-info text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="card-title mb-1 opacity-75">Total Paid</h6>
|
||||
<h3 class="mb-0"><span class="symbol">ê</span>{{ stats.total_paid|default:0|floatformat:'2g' }}</h3>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
<i class="fas fa-check-circle fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-gradient-warning text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="card-title mb-1 opacity-75">Outstanding</h6>
|
||||
<h3 class="mb-0"><span class="symbol">ê</span>{{ stats.outstanding_amount|default:0|floatformat:'2g' }}</h3>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
<i class="fas fa-exclamation-triangle fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bills List Card -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-file-invoice me-2"></i>Medical Bills
|
||||
<span class="badge bg-secondary ms-2">{{ page_obj.paginator.count }} total</span>
|
||||
</h5>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="selectAll()">
|
||||
<i class="fas fa-check-square me-1"></i>Select All
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="clearSelection()">
|
||||
<i class="fas fa-square me-1"></i>Clear
|
||||
</button>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-cog me-1"></i>Bulk Actions
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#" onclick="bulkAction('export')">
|
||||
<i class="fas fa-download me-2"></i>Export Selected
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="bulkAction('print')">
|
||||
<i class="fas fa-print me-2"></i>Print Selected
|
||||
</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-warning" href="#" onclick="bulkAction('submit')">
|
||||
<i class="fas fa-paper-plane me-2"></i>Submit Selected
|
||||
</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div id="bill-list-container">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th width="40">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="selectAllCheckbox" onchange="toggleSelectAll()">
|
||||
</div>
|
||||
</th>
|
||||
<th>Bill Number</th>
|
||||
<th>Patient</th>
|
||||
<th>Date</th>
|
||||
<th>Total Amount</th>
|
||||
<th>Paid Amount</th>
|
||||
<th>Balance</th>
|
||||
<th>Status</th>
|
||||
<th>Due Date</th>
|
||||
<th width="120">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for bill in page_obj %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input bill-checkbox" type="checkbox" value="{{ bill.bill_id }}">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'billing:bill_detail' bill.bill_id %}" class="text-decoration-none fw-bold">
|
||||
{{ bill.bill_number }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<strong>{{ bill.patient.get_full_name }}</strong>
|
||||
<br><small class="text-muted">MRN: {{ bill.patient.mrn }}</small>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ bill.bill_date|date:"M d, Y" }}</td>
|
||||
<td class="text-end"><span class="symbol">ê</span>{{ bill.total_amount|floatformat:2 }}</td>
|
||||
<td class="text-end"><span class="symbol">ê</span>{{ bill.paid_amount|floatformat:2 }}</td>
|
||||
<td class="text-end">
|
||||
<span class="{% if bill.balance_amount > 0 %}text-danger fw-bold{% else %}text-success{% endif %}">
|
||||
<span class="symbol">ê</span>{{ bill.balance_amount|floatformat:2 }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if bill.status == 'DRAFT' %}
|
||||
<span class="badge bg-secondary">Draft</span>
|
||||
{% elif bill.status == 'PENDING' %}
|
||||
<span class="badge bg-warning">Pending</span>
|
||||
{% elif bill.status == 'SUBMITTED' %}
|
||||
<span class="badge bg-info">Submitted</span>
|
||||
{% elif bill.status == 'PARTIAL_PAID' %}
|
||||
<span class="badge bg-primary">Partially Paid</span>
|
||||
{% elif bill.status == 'PAID' %}
|
||||
<span class="badge bg-success">Paid</span>
|
||||
{% elif bill.status == 'OVERDUE' %}
|
||||
<span class="badge bg-danger">Overdue</span>
|
||||
{% elif bill.status == 'COLLECTIONS' %}
|
||||
<span class="badge bg-dark">Collections</span>
|
||||
{% elif bill.status == 'WRITTEN_OFF' %}
|
||||
<span class="badge bg-secondary">Written Off</span>
|
||||
{% elif bill.status == 'CANCELLED' %}
|
||||
<span class="badge bg-secondary">Cancelled</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if bill.due_date %}
|
||||
<span class="{% if bill.is_overdue %}text-danger{% elif bill.days_until_due <= 7 %}text-warning{% endif %}">
|
||||
{{ bill.due_date|date:"M d, Y" }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{% url 'billing:bill_detail' bill.bill_id %}" class="btn btn-outline-primary btn-sm" title="View Details">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
{% if bill.status == 'DRAFT' %}
|
||||
<a href="{% url 'billing:bill_update' bill.bill_id %}" class="btn btn-outline-secondary btn-sm" title="Edit">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
|
||||
<span class="visually-hidden">Toggle Dropdown</span>
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="">
|
||||
<i class="fas fa-print me-2"></i>Print
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="">
|
||||
<i class="fas fa-envelope me-2"></i>Email
|
||||
</a></li>
|
||||
{% if bill.status == 'DRAFT' %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-warning" href="">
|
||||
<i class="fas fa-paper-plane me-2"></i>Submit
|
||||
</a></li>
|
||||
{% endif %}
|
||||
{% if bill.status in 'DRAFT,PENDING' %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-danger" href="">
|
||||
<i class="fas fa-trash me-2"></i>Delete
|
||||
</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="10" class="text-center py-5">
|
||||
<div class="text-muted">
|
||||
<i class="fas fa-file-invoice fa-3x mb-3 opacity-50"></i>
|
||||
<h5>No bills found</h5>
|
||||
<p>No medical bills match your current filters.</p>
|
||||
<a href="{% url 'billing:bill_create' %}" class="btn btn-primary">
|
||||
<i class="fas fa-plus me-2"></i>Create First Bill
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if is_paginated %}
|
||||
{% include 'partial/pagination.html' %}
|
||||
{% endif %}
|
||||
<!-- Search & Filter (Panel) -->
|
||||
<div class="panel panel-inverse mb-4">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title"><i class="fas fa-search me-2"></i>Search & Filter</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form method="get"
|
||||
class="row g-3"
|
||||
hx-get="{% url 'billing:bill_list' %}"
|
||||
hx-target="#bill-list-container"
|
||||
hx-trigger="submit, change delay:500ms">
|
||||
<div class="col-md-3">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" id="search" name="search" value="{{ request.GET.search }}" placeholder="Search bills...">
|
||||
<label for="search">Search Bills</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<select class="form-select" id="status" name="status">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="DRAFT" {% if request.GET.status == 'DRAFT' %}selected{% endif %}>Draft</option>
|
||||
<option value="PENDING" {% if request.GET.status == 'PENDING' %}selected{% endif %}>Pending</option>
|
||||
<option value="SUBMITTED" {% if request.GET.status == 'SUBMITTED' %}selected{% endif %}>Submitted</option>
|
||||
<option value="PARTIAL_PAID" {% if request.GET.status == 'PARTIAL_PAID' %}selected{% endif %}>Partially Paid</option>
|
||||
<option value="PAID" {% if request.GET.status == 'PAID' %}selected{% endif %}>Paid</option>
|
||||
<option value="OVERDUE" {% if request.GET.status == 'OVERDUE' %}selected{% endif %}>Overdue</option>
|
||||
<option value="COLLECTIONS" {% if request.GET.status == 'COLLECTIONS' %}selected{% endif %}>Collections</option>
|
||||
<option value="WRITTEN_OFF" {% if request.GET.status == 'WRITTEN_OFF' %}selected{% endif %}>Written Off</option>
|
||||
<option value="CANCELLED" {% if request.GET.status == 'CANCELLED' %}selected{% endif %}>Cancelled</option>
|
||||
</select>
|
||||
<label for="status">Status</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<input type="date" class="form-control" id="date_from" name="date_from" value="{{ request.GET.date_from }}">
|
||||
<label for="date_from">From Date</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<input type="date" class="form-control" id="date_to" name="date_to" value="{{ request.GET.date_to }}">
|
||||
<label for="date_to">To Date</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-floating">
|
||||
<input type="number" class="form-control" id="min_amount" name="min_amount" value="{{ request.GET.min_amount }}" step="0.01" placeholder="Min Amount">
|
||||
<label for="min_amount">Min Amount</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button type="submit" class="btn btn-primary h-100 w-100">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bills List (Panel) -->
|
||||
<div class="panel panel-inverse">
|
||||
<div class="panel-heading d-flex justify-content-between align-items-center">
|
||||
<h4 class="panel-title">
|
||||
<i class="fas fa-file-invoice me-2"></i>Medical Bills
|
||||
<span class="badge bg-secondary ms-2">{{ page_obj.paginator.count }} total</span>
|
||||
</h4>
|
||||
<div class="panel-heading-btn d-flex gap-2">
|
||||
<button type="button" class="btn btn-xs btn-outline-secondary" onclick="selectAll()">
|
||||
<i class="fas fa-check-square me-1"></i>Select All
|
||||
</button>
|
||||
<button type="button" class="btn btn-xs btn-outline-secondary" onclick="clearSelection()">
|
||||
<i class="fas fa-square me-1"></i>Clear
|
||||
</button>
|
||||
<div class="btn-group btn-group-xs" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-cog me-1"></i>Bulk Actions
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#" onclick="bulkAction('export')">
|
||||
<i class="fas fa-download me-2"></i>Export Selected
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="bulkAction('print')">
|
||||
<i class="fas fa-print me-2"></i>Print Selected
|
||||
</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-warning" href="#" onclick="bulkAction('submit')">
|
||||
<i class="fas fa-paper-plane me-2"></i>Submit Selected
|
||||
</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body p-0">
|
||||
<div id="bill-list-container">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th width="40">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="selectAllCheckbox" onchange="toggleSelectAll()">
|
||||
</div>
|
||||
</th>
|
||||
<th>Bill Number</th>
|
||||
<th>Patient</th>
|
||||
<th>Date</th>
|
||||
<th class="text-end">Total Amount</th>
|
||||
<th class="text-end">Paid Amount</th>
|
||||
<th class="text-end">Balance</th>
|
||||
<th>Status</th>
|
||||
<th>Due Date</th>
|
||||
<th width="120">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for bill in page_obj %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input bill-checkbox" type="checkbox" value="{{ bill.bill_id }}">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'billing:bill_detail' bill.bill_id %}" class="text-decoration-none fw-bold">
|
||||
{{ bill.bill_number }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<strong>{{ bill.patient.get_full_name }}</strong>
|
||||
<br><small class="text-muted">MRN: {{ bill.patient.mrn }}</small>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ bill.bill_date|date:"M d, Y" }}</td>
|
||||
<td class="text-end"><span class="symbol">ê</span>{{ bill.total_amount|floatformat:2 }}</td>
|
||||
<td class="text-end"><span class="symbol">ê</span>{{ bill.paid_amount|floatformat:2 }}</td>
|
||||
<td class="text-end">
|
||||
<span class="{% if bill.balance_amount > 0 %}text-danger fw-bold{% else %}text-success{% endif %}">
|
||||
<span class="symbol">ê</span>{{ bill.balance_amount|floatformat:2 }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if bill.status == 'DRAFT' %}
|
||||
<span class="badge bg-secondary">Draft</span>
|
||||
{% elif bill.status == 'PENDING' %}
|
||||
<span class="badge bg-warning">Pending</span>
|
||||
{% elif bill.status == 'SUBMITTED' %}
|
||||
<span class="badge bg-info">Submitted</span>
|
||||
{% elif bill.status == 'PARTIAL_PAID' %}
|
||||
<span class="badge bg-primary">Partially Paid</span>
|
||||
{% elif bill.status == 'PAID' %}
|
||||
<span class="badge bg-success">Paid</span>
|
||||
{% elif bill.status == 'OVERDUE' %}
|
||||
<span class="badge bg-danger">Overdue</span>
|
||||
{% elif bill.status == 'COLLECTIONS' %}
|
||||
<span class="badge bg-dark">Collections</span>
|
||||
{% elif bill.status == 'WRITTEN_OFF' %}
|
||||
<span class="badge bg-secondary">Written Off</span>
|
||||
{% elif bill.status == 'CANCELLED' %}
|
||||
<span class="badge bg-secondary">Cancelled</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if bill.due_date %}
|
||||
<span class="{% if bill.is_overdue %}text-danger{% elif bill.days_until_due <= 7 %}text-warning{% endif %}">
|
||||
{{ bill.due_date|date:"M d, Y" }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{% url 'billing:bill_detail' bill.bill_id %}" class="btn btn-outline-primary btn-sm" title="View Details">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
{% if bill.status == 'DRAFT' %}
|
||||
<a href="{% url 'billing:bill_update' bill.bill_id %}" class="btn btn-outline-secondary btn-sm" title="Edit">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
|
||||
<span class="visually-hidden">Toggle Dropdown</span>
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="">
|
||||
<i class="fas fa-print me-2"></i>Print
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="">
|
||||
<i class="fas fa-envelope me-2"></i>Email
|
||||
</a></li>
|
||||
{% if bill.status == 'DRAFT' %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-warning" href="">
|
||||
<i class="fas fa-paper-plane me-2"></i>Submit
|
||||
</a></li>
|
||||
{% endif %}
|
||||
{% if bill.status == 'DRAFT' or bill.status == 'PENDING' %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-danger" href="">
|
||||
<i class="fas fa-trash me-2"></i>Delete
|
||||
</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="10" class="text-center py-5">
|
||||
<div class="text-muted">
|
||||
<i class="fas fa-file-invoice fa-3x mb-3 opacity-50"></i>
|
||||
<h5>No bills found</h5>
|
||||
<p>No medical bills match your current filters.</p>
|
||||
<a href="{% url 'billing:bill_create' %}" class="btn btn-primary">
|
||||
<i class="fas fa-plus me-2"></i>Create First Bill
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div> <!-- /#bill-list-container -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if is_paginated %}
|
||||
{% include 'partial/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Bulk selection functionality
|
||||
function toggleSelectAll() {
|
||||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||||
const billCheckboxes = document.querySelectorAll('.bill-checkbox');
|
||||
|
||||
billCheckboxes.forEach(checkbox => {
|
||||
checkbox.checked = selectAllCheckbox.checked;
|
||||
});
|
||||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||||
document.querySelectorAll('.bill-checkbox').forEach(cb => cb.checked = selectAllCheckbox.checked);
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
const billCheckboxes = document.querySelectorAll('.bill-checkbox');
|
||||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||||
|
||||
billCheckboxes.forEach(checkbox => {
|
||||
checkbox.checked = true;
|
||||
});
|
||||
selectAllCheckbox.checked = true;
|
||||
document.querySelectorAll('.bill-checkbox').forEach(cb => cb.checked = true);
|
||||
document.getElementById('selectAllCheckbox').checked = true;
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
const billCheckboxes = document.querySelectorAll('.bill-checkbox');
|
||||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||||
|
||||
billCheckboxes.forEach(checkbox => {
|
||||
checkbox.checked = false;
|
||||
});
|
||||
selectAllCheckbox.checked = false;
|
||||
document.querySelectorAll('.bill-checkbox').forEach(cb => cb.checked = false);
|
||||
const all = document.getElementById('selectAllCheckbox');
|
||||
all.checked = false; all.indeterminate = false;
|
||||
}
|
||||
|
||||
function bulkAction(action) {
|
||||
const selectedBills = Array.from(document.querySelectorAll('.bill-checkbox:checked')).map(cb => cb.value);
|
||||
|
||||
if (selectedBills.length === 0) {
|
||||
alert('Please select at least one bill.');
|
||||
return;
|
||||
}
|
||||
|
||||
switch(action) {
|
||||
case 'export':
|
||||
window.location.href = `{% url 'billing:export_bills' %}?bills=${selectedBills.join(',')}`;
|
||||
break;
|
||||
case 'print':
|
||||
window.open(`{% url 'billing:print_bills' %}?bills=${selectedBills.join(',')}`, '_blank');
|
||||
break;
|
||||
case 'submit':
|
||||
if (confirm(`Submit ${selectedBills.length} selected bills?`)) {
|
||||
// Submit via HTMX or form
|
||||
htmx.ajax('POST', '{% url "billing:bulk_submit_bills" %}', {
|
||||
values: {bills: selectedBills, csrfmiddlewaretoken: '{{ csrf_token }}'}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
const selected = Array.from(document.querySelectorAll('.bill-checkbox:checked')).map(cb => cb.value);
|
||||
if (!selected.length) { alert('Please select at least one bill.'); return; }
|
||||
switch(action) {
|
||||
case 'export':
|
||||
window.location.href = `{% url 'billing:export_bills' %}?bills=${selected.join(',')}`;
|
||||
break;
|
||||
case 'print':
|
||||
window.open(`{% url 'billing:print_bills' %}?bills=${selected.join(',')}`, '_blank');
|
||||
break;
|
||||
case 'submit':
|
||||
if (confirm(`Submit ${selected.length} selected bills?`)) {
|
||||
htmx.ajax('POST', '{% url "billing:bulk_submit_bills" %}', {
|
||||
values: { bills: selected, csrfmiddlewaretoken: '{{ csrf_token }}' }
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update select all checkbox based on individual selections
|
||||
document.addEventListener('change', function(e) {
|
||||
if (e.target.classList.contains('bill-checkbox')) {
|
||||
const billCheckboxes = document.querySelectorAll('.bill-checkbox');
|
||||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||||
const checkedCount = document.querySelectorAll('.bill-checkbox:checked').length;
|
||||
|
||||
selectAllCheckbox.checked = checkedCount === billCheckboxes.length;
|
||||
selectAllCheckbox.indeterminate = checkedCount > 0 && checkedCount < billCheckboxes.length;
|
||||
}
|
||||
document.addEventListener('change', e => {
|
||||
if (!e.target.classList.contains('bill-checkbox')) return;
|
||||
const all = document.querySelectorAll('.bill-checkbox');
|
||||
const checked = document.querySelectorAll('.bill-checkbox:checked').length;
|
||||
const master = document.getElementById('selectAllCheckbox');
|
||||
master.checked = checked === all.length;
|
||||
master.indeterminate = checked > 0 && checked < all.length;
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
<style>
|
||||
.bg-gradient { background-image: linear-gradient(180deg, rgba(255,255,255,.15), rgba(255,255,255,0)); }
|
||||
@media (max-width: 768px) {
|
||||
.btn-group { display: flex; flex-direction: column; gap: .5rem; }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@ -31,13 +31,13 @@
|
||||
<i class="fas fa-cog me-2"></i>Actions
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{% url 'billing:claim_print' object.claim_id %}">
|
||||
<li><a class="dropdown-item" href="">
|
||||
<i class="fas fa-print me-2"></i>Print Claim
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'billing:claim_download' object.claim_id %}">
|
||||
<li><a class="dropdown-item" href="">
|
||||
<i class="fas fa-download me-2"></i>Download PDF
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'billing:claim_export' object.claim_id %}">
|
||||
<li><a class="dropdown-item" href="">
|
||||
<i class="fas fa-file-export me-2"></i>Export to EDI
|
||||
</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
@ -53,7 +53,7 @@
|
||||
<i class="fas fa-redo me-2"></i>Resubmit Claim
|
||||
</a></li>
|
||||
{% elif object.status in 'SUBMITTED,PENDING' %}
|
||||
<li><a class="dropdown-item text-primary" href="{% url 'billing:claim_status_check' object.claim_id %}">
|
||||
<li><a class="dropdown-item text-primary" href="">
|
||||
<i class="fas fa-sync me-2"></i>Check Status
|
||||
</a></li>
|
||||
{% endif %}
|
||||
@ -101,7 +101,7 @@
|
||||
{% elif object.status == 'APPROVED' %}
|
||||
<div class="alert alert-success mb-4">
|
||||
<h6 class="alert-heading"><i class="fas fa-check-circle me-2"></i>Claim Approved</h6>
|
||||
This claim has been approved for ${{ object.approved_amount|floatformat:2 }} on {{ object.approval_date|date:"M d, Y" }}.
|
||||
This claim has been approved for <span class="symbol">ê</span>{{ object.approved_amount|floatformat:'2g' }} on {{ object.approval_date|date:"M d, Y" }}.
|
||||
</div>
|
||||
{% elif object.status == 'PAID' %}
|
||||
<div class="alert alert-success mb-4">
|
||||
@ -180,13 +180,13 @@
|
||||
<table class="table table-borderless table-sm">
|
||||
<tr>
|
||||
<td class="fw-bold">Billed Amount:</td>
|
||||
<td class="fw-bold">${{ object.billed_amount|floatformat:2 }}</td>
|
||||
<td class="fw-bold"><span class="symbol">ê</span>{{ object.billed_amount|floatformat:'2g' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">Approved Amount:</td>
|
||||
<td>
|
||||
{% if object.approved_amount %}
|
||||
<span class="text-success fw-bold">${{ object.approved_amount|floatformat:2 }}</span>
|
||||
<span class="text-success fw-bold"><span class="symbol">ê</span>{{ object.approved_amount|floatformat:'2g' }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
@ -196,7 +196,7 @@
|
||||
<td class="fw-bold">Paid Amount:</td>
|
||||
<td>
|
||||
{% if object.paid_amount %}
|
||||
<span class="text-success fw-bold">${{ object.paid_amount|floatformat:2 }}</span>
|
||||
<span class="text-success fw-bold"><span class="symbol">ê</span>{{ object.paid_amount|floatformat:'2g' }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
@ -251,7 +251,7 @@
|
||||
<table class="table table-borderless table-sm">
|
||||
<tr>
|
||||
<td class="fw-bold">Total Amount:</td>
|
||||
<td class="fw-bold">${{ object.medical_bill.total_amount|floatformat:2 }}</td>
|
||||
<td class="fw-bold"><span class="symbol">ê</span>{{ object.medical_bill.total_amount|floatformat:'2g' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">Bill Status:</td>
|
||||
@ -390,7 +390,7 @@
|
||||
<td class="fw-bold">Copay:</td>
|
||||
<td>
|
||||
{% if object.insurance_info.copay_amount %}
|
||||
${{ object.insurance_info.copay_amount|floatformat:2 }}
|
||||
<span class="symbol">ê</span>{{ object.insurance_info.copay_amount|floatformat:'2g' }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
@ -432,10 +432,10 @@
|
||||
<td>{{ line_item.description }}</td>
|
||||
<td>{{ line_item.service_date|date:"M d, Y" }}</td>
|
||||
<td class="text-center">{{ line_item.quantity }}</td>
|
||||
<td class="text-end">${{ line_item.billed_amount|floatformat:2 }}</td>
|
||||
<td class="text-end"><span class="symbol">ê</span>{{ line_item.billed_amount|floatformat:'2g' }}</td>
|
||||
<td class="text-end">
|
||||
{% if line_item.approved_amount %}
|
||||
<span class="text-success fw-bold">${{ line_item.approved_amount|floatformat:2 }}</span>
|
||||
<span class="text-success fw-bold"><span class="symbol">ê</span>{{ line_item.approved_amount|floatformat:'2g' }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
@ -478,13 +478,13 @@
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Billed Amount:</span>
|
||||
<span class="fw-bold">${{ object.billed_amount|floatformat:2 }}</span>
|
||||
<span class="fw-bold"><span class="symbol">ê</span>{{ object.billed_amount|floatformat:'2g' }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Approved Amount:</span>
|
||||
<span class="{% if object.approved_amount %}text-success fw-bold{% else %}text-muted{% endif %}">
|
||||
{% if object.approved_amount %}
|
||||
${{ object.approved_amount|floatformat:2 }}
|
||||
<span class="symbol">ê</span>{{ object.approved_amount|floatformat:'2g' }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
@ -494,7 +494,7 @@
|
||||
<span>Paid Amount:</span>
|
||||
<span class="{% if object.paid_amount %}text-success fw-bold{% else %}text-muted{% endif %}">
|
||||
{% if object.paid_amount %}
|
||||
${{ object.paid_amount|floatformat:2 }}
|
||||
<span class="symbol">ê</span>{{ object.paid_amount|floatformat:'2g' }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
@ -504,7 +504,7 @@
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="fw-bold">Outstanding:</span>
|
||||
<span class="fw-bold {% if object.outstanding_amount > 0 %}text-danger{% else %}text-success{% endif %}">
|
||||
${{ object.outstanding_amount|floatformat:2 }}
|
||||
<span class="symbol">ê</span>{{ object.outstanding_amount|floatformat:'2g' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -534,14 +534,14 @@
|
||||
<i class="fas fa-redo me-2"></i>Resubmit Claim
|
||||
</a>
|
||||
{% elif object.status in 'SUBMITTED,PENDING' %}
|
||||
<a href="{% url 'billing:claim_status_check' object.claim_id %}" class="btn btn-primary">
|
||||
<a href="" class="btn btn-primary">
|
||||
<i class="fas fa-sync me-2"></i>Check Status
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'billing:claim_print' object.claim_id %}" class="btn btn-outline-primary">
|
||||
<a href="" class="btn btn-outline-primary">
|
||||
<i class="fas fa-print me-2"></i>Print Claim
|
||||
</a>
|
||||
<a href="{% url 'billing:claim_download' object.claim_id %}" class="btn btn-outline-secondary">
|
||||
<a href="" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-download me-2"></i>Download PDF
|
||||
</a>
|
||||
<a href="{% url 'billing:bill_detail' object.medical_bill.bill_id %}" class="btn btn-outline-info">
|
||||
@ -572,7 +572,7 @@
|
||||
<div class="timeline-marker bg-info"></div>
|
||||
<div class="timeline-content">
|
||||
<h6 class="timeline-title">Claim Submitted</h6>
|
||||
<p class="timeline-text">{{ object.submission_date|date:"M d, Y g:i A" }}</p>
|
||||
<p class="timeline-text">{{ object.submission_date }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -582,7 +582,7 @@
|
||||
<div class="timeline-content">
|
||||
<h6 class="timeline-title">Claim Approved</h6>
|
||||
<p class="timeline-text">
|
||||
${{ object.approved_amount|floatformat:2 }} approved
|
||||
<span class="symbol">ê</span>{{ object.approved_amount|floatformat:'2g' }} approved
|
||||
<br><small class="text-muted">{{ object.approval_date|date:"M d, Y g:i A"|default:"Unknown" }}</small>
|
||||
</p>
|
||||
</div>
|
||||
@ -608,7 +608,7 @@
|
||||
<div class="timeline-content">
|
||||
<h6 class="timeline-title">Partially Approved</h6>
|
||||
<p class="timeline-text">
|
||||
${{ object.approved_amount|floatformat:2 }} of ${{ object.billed_amount|floatformat:2 }}
|
||||
<span class="symbol">ê</span>{{ object.approved_amount|floatformat:'2g' }} of <span class="symbol">ê</span>{{ object.billed_amount|floatformat:'2g' }}
|
||||
<br><small class="text-muted">{{ object.approval_date|date:"M d, Y g:i A"|default:"Unknown" }}</small>
|
||||
</p>
|
||||
</div>
|
||||
@ -620,7 +620,7 @@
|
||||
<div class="timeline-content">
|
||||
<h6 class="timeline-title">Payment Received</h6>
|
||||
<p class="timeline-text">
|
||||
${{ object.paid_amount|floatformat:2 }} received
|
||||
<span class="symbol">ê</span>{{ object.paid_amount|floatformat:'2g' }} received
|
||||
<br><small class="text-muted">{{ object.payment_date|date:"M d, Y g:i A"|default:"Unknown" }}</small>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -27,7 +27,7 @@
|
||||
<li><a class="dropdown-item" href="{% url 'billing:export_claims' %}">
|
||||
<i class="fas fa-download me-2"></i>Export Claims
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'billing:batch_submit_claims' %}">
|
||||
<li><a class="dropdown-item" href="">
|
||||
<i class="fas fa-upload me-2"></i>Batch Submit
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="window.print()">
|
||||
@ -38,68 +38,68 @@
|
||||
</div>
|
||||
|
||||
<!-- Statistics Cards -->
|
||||
<div class="row mb-4" hx-get="{% url 'billing:claim_stats' %}" hx-trigger="load, every 60s">
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-gradient-info text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="card-title mb-1 opacity-75">Total Claims</h6>
|
||||
<h3 class="mb-0">{{ stats.total_claims|default:0 }}</h3>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
<i class="fas fa-file-medical fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-gradient-primary text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="card-title mb-1 opacity-75">Total Amount</h6>
|
||||
<h3 class="mb-0">${{ stats.total_amount|default:0|floatformat:2 }}</h3>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
<i class="fas fa-dollar-sign fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-gradient-warning text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="card-title mb-1 opacity-75">Pending</h6>
|
||||
<h3 class="mb-0">{{ stats.pending_count|default:0 }}</h3>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
<i class="fas fa-clock fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-gradient-danger text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="card-title mb-1 opacity-75">Denied</h6>
|
||||
<h3 class="mb-0">{{ stats.denied_count|default:0 }}</h3>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
<i class="fas fa-times-circle fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{# <div class="row mb-4" hx-get="{% url 'billing:claim_stats' %}" hx-trigger="load, every 60s">#}
|
||||
{# <div class="col-md-3 mb-3">#}
|
||||
{# <div class="card bg-gradient-info text-white">#}
|
||||
{# <div class="card-body">#}
|
||||
{# <div class="d-flex justify-content-between align-items-center">#}
|
||||
{# <div>#}
|
||||
{# <h6 class="card-title mb-1 opacity-75">Total Claims</h6>#}
|
||||
{# <h3 class="mb-0">{{ stats.total_claims|default:0 }}</h3>#}
|
||||
{# </div>#}
|
||||
{# <div class="text-white-50">#}
|
||||
{# <i class="fas fa-file-medical fa-2x"></i>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# <div class="col-md-3 mb-3">#}
|
||||
{# <div class="card bg-gradient-primary text-white">#}
|
||||
{# <div class="card-body">#}
|
||||
{# <div class="d-flex justify-content-between align-items-center">#}
|
||||
{# <div>#}
|
||||
{# <h6 class="card-title mb-1 opacity-75">Total Amount</h6>#}
|
||||
{# <h3 class="mb-0">${{ stats.total_amount|default:0|floatformat:2 }}</h3>#}
|
||||
{# </div>#}
|
||||
{# <div class="text-white-50">#}
|
||||
{# <i class="fas fa-dollar-sign fa-2x"></i>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# <div class="col-md-3 mb-3">#}
|
||||
{# <div class="card bg-gradient-warning text-white">#}
|
||||
{# <div class="card-body">#}
|
||||
{# <div class="d-flex justify-content-between align-items-center">#}
|
||||
{# <div>#}
|
||||
{# <h6 class="card-title mb-1 opacity-75">Pending</h6>#}
|
||||
{# <h3 class="mb-0">{{ stats.pending_count|default:0 }}</h3>#}
|
||||
{# </div>#}
|
||||
{# <div class="text-white-50">#}
|
||||
{# <i class="fas fa-clock fa-2x"></i>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# <div class="col-md-3 mb-3">#}
|
||||
{# <div class="card bg-gradient-danger text-white">#}
|
||||
{# <div class="card-body">#}
|
||||
{# <div class="d-flex justify-content-between align-items-center">#}
|
||||
{# <div>#}
|
||||
{# <h6 class="card-title mb-1 opacity-75">Denied</h6>#}
|
||||
{# <h3 class="mb-0">{{ stats.denied_count|default:0 }}</h3>#}
|
||||
{# </div>#}
|
||||
{# <div class="text-white-50">#}
|
||||
{# <i class="fas fa-times-circle fa-2x"></i>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
|
||||
<!-- Search and Filter Card -->
|
||||
<div class="card mb-4">
|
||||
@ -300,31 +300,32 @@
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
|
||||
<span class="visually-hidden">Toggle Dropdown</span>
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{% url 'billing:claim_print' claim.claim_id %}">
|
||||
<li><a class="dropdown-item" href="">
|
||||
<i class="fas fa-print me-2"></i>Print Claim
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'billing:claim_download' claim.claim_id %}">
|
||||
<li><a class="dropdown-item" href="">
|
||||
<i class="fas fa-download me-2"></i>Download
|
||||
</a></li>
|
||||
{% if claim.status == 'DRAFT' %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-info" href="{% url 'billing:claim_submit' claim.claim_id %}">
|
||||
<li><a class="dropdown-item text-info" href="">
|
||||
<i class="fas fa-paper-plane me-2"></i>Submit
|
||||
</a></li>
|
||||
{% elif claim.status == 'DENIED' %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-warning" href="{% url 'billing:claim_appeal' claim.claim_id %}">
|
||||
<li><a class="dropdown-item text-warning" href="">
|
||||
<i class="fas fa-gavel me-2"></i>Appeal
|
||||
</a></li>
|
||||
<li><a class="dropdown-item text-info" href="{% url 'billing:claim_resubmit' claim.claim_id %}">
|
||||
<li><a class="dropdown-item text-info" href="">
|
||||
<i class="fas fa-redo me-2"></i>Resubmit
|
||||
</a></li>
|
||||
{% endif %}
|
||||
{% if claim.status in 'DRAFT,DENIED' %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-danger" href="{% url 'billing:claim_delete' claim.claim_id %}">
|
||||
<li><a class="dropdown-item text-danger" href="">
|
||||
<i class="fas fa-trash me-2"></i>Delete
|
||||
</a></li>
|
||||
{% endif %}
|
||||
@ -354,48 +355,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<div class="card-footer">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="text-muted">
|
||||
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ page_obj.paginator.count }} claims
|
||||
</div>
|
||||
<nav aria-label="Claims pagination">
|
||||
<ul class="pagination pagination-sm mb-0">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page=1">First</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.previous_page_number }}">Previous</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% for num in page_obj.paginator.page_range %}
|
||||
{% if page_obj.number == num %}
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ num }}</span>
|
||||
</li>
|
||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ num }}">{{ num }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.next_page_number }}">Next</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.paginator.num_pages }}">Last</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if is_paginated %}
|
||||
{% include 'partial/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -430,34 +392,34 @@ function clearSelection() {
|
||||
selectAllCheckbox.checked = false;
|
||||
}
|
||||
|
||||
function bulkAction(action) {
|
||||
const selectedClaims = Array.from(document.querySelectorAll('.claim-checkbox:checked')).map(cb => cb.value);
|
||||
|
||||
if (selectedClaims.length === 0) {
|
||||
alert('Please select at least one claim.');
|
||||
return;
|
||||
}
|
||||
|
||||
switch(action) {
|
||||
case 'export':
|
||||
window.location.href = `{% url 'billing:export_claims' %}?claims=${selectedClaims.join(',')}`;
|
||||
break;
|
||||
case 'submit':
|
||||
if (confirm(`Submit ${selectedClaims.length} selected claims?`)) {
|
||||
htmx.ajax('POST', '{% url "billing:bulk_submit_claims" %}', {
|
||||
values: {claims: selectedClaims, csrfmiddlewaretoken: '{{ csrf_token }}'}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'resubmit':
|
||||
if (confirm(`Resubmit ${selectedClaims.length} selected claims?`)) {
|
||||
htmx.ajax('POST', '{% url "billing:bulk_resubmit_claims" %}', {
|
||||
values: {claims: selectedClaims, csrfmiddlewaretoken: '{{ csrf_token }}'}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
{#function bulkAction(action) {#}
|
||||
{# const selectedClaims = Array.from(document.querySelectorAll('.claim-checkbox:checked')).map(cb => cb.value);#}
|
||||
{# #}
|
||||
{# if (selectedClaims.length === 0) {#}
|
||||
{# alert('Please select at least one claim.');#}
|
||||
{# return;#}
|
||||
{# }#}
|
||||
{# #}
|
||||
{# switch(action) {#}
|
||||
{# case 'export':#}
|
||||
{# window.location.href = `{% url 'billing:export_claims' %}?claims=${selectedClaims.join(',')}`;#}
|
||||
{# break;#}
|
||||
{# case 'submit':#}
|
||||
{# if (confirm(`Submit ${selectedClaims.length} selected claims?`)) {#}
|
||||
{# htmx.ajax('POST', '{% url "billing:bulk_submit_claims" %}', {#}
|
||||
{# values: {claims: selectedClaims, csrfmiddlewaretoken: '{{ csrf_token }}'}#}
|
||||
{# });#}
|
||||
{# }#}
|
||||
{# break;#}
|
||||
{# case 'resubmit':#}
|
||||
{# if (confirm(`Resubmit ${selectedClaims.length} selected claims?`)) {#}
|
||||
{# htmx.ajax('POST', '{% url "billing:bulk_resubmit_claims" %}', {#}
|
||||
{# values: {claims: selectedClaims, csrfmiddlewaretoken: '{{ csrf_token }}'}#}
|
||||
{# });#}
|
||||
{# }#}
|
||||
{# break;#}
|
||||
{# }#}
|
||||
{# }#}
|
||||
|
||||
// Update select all checkbox based on individual selections
|
||||
document.addEventListener('change', function(e) {
|
||||
|
||||
@ -708,7 +708,7 @@ function validateIssue() {
|
||||
}
|
||||
|
||||
// Check quantity match
|
||||
var requestedQuantity = {{ blood_request.quantity_requested }};
|
||||
var requestedQuantity = {{ blood_request.units_requested }};
|
||||
if (selectedUnits.length !== requestedQuantity) {
|
||||
errors.push('Selected units (' + selectedUnits.length + ') do not match requested quantity (' + requestedQuantity + ')');
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h6 class="card-title">Total Requests</h6>
|
||||
<h3 class="mb-0">{{ page_obj.paginator.count }}</h3>
|
||||
<h3 class="mb-0">{{ page_obj.count }}</h3>
|
||||
</div>
|
||||
<div class="align-self-center">
|
||||
<i class="fa fa-clipboard-list fa-2x"></i>
|
||||
@ -160,7 +160,7 @@
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h6 class="card-title">Completed</h6>
|
||||
<h6 class="card-title">Ready</h6>
|
||||
<h3 class="mb-0" id="completedCount">0</h3>
|
||||
</div>
|
||||
<div class="align-self-center">
|
||||
@ -404,7 +404,7 @@ function calculateSummaryStats() {
|
||||
if (urgency === 'emergency') {
|
||||
emergency++;
|
||||
}
|
||||
if (status === 'completed') {
|
||||
if (status === 'ready') {
|
||||
completed++;
|
||||
}
|
||||
});
|
||||
|
||||
@ -184,7 +184,7 @@
|
||||
|
||||
<!-- BEGIN table -->
|
||||
<div class="table-responsive">
|
||||
<table id="transfusionTable" class="table table-striped table-bordered align-middle">
|
||||
<table class="table table-striped table-bordered align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Patient</th>
|
||||
@ -304,72 +304,30 @@
|
||||
<!-- END table -->
|
||||
|
||||
<!-- BEGIN pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<nav aria-label="Transfusion pagination">
|
||||
<ul class="pagination justify-content-center">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page=1{% if status_filter %}&status={{ status_filter }}{% endif %}{% if date_filter %}&date_range={{ date_filter }}{% endif %}">
|
||||
<i class="fa fa-angle-double-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if date_filter %}&date_range={{ date_filter }}{% endif %}">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% for num in page_obj.paginator.page_range %}
|
||||
{% if page_obj.number == num %}
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ num }}</span>
|
||||
</li>
|
||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ num }}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if date_filter %}&date_range={{ date_filter }}{% endif %}">{{ num }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if date_filter %}&date_range={{ date_filter }}{% endif %}">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if date_filter %}&date_range={{ date_filter }}{% endif %}">
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% if is_paginated %}
|
||||
{% include 'partial/pagination.html' %}
|
||||
{% endif %}
|
||||
<!-- END pagination -->
|
||||
|
||||
<!-- BEGIN summary -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-6">
|
||||
<p class="text-muted">
|
||||
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ page_obj.paginator.count }} transfusions
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-end">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="window.print()">
|
||||
<i class="fa fa-print"></i> Print
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="exportToCSV()">
|
||||
<i class="fa fa-download"></i> Export
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-info btn-sm" onclick="showTransfusionReport()">
|
||||
<i class="fa fa-chart-bar"></i> Report
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{# <div class="row mt-3">#}
|
||||
{# <div class="col-md-6">#}
|
||||
{##}
|
||||
{# </div>#}
|
||||
{# <div class="col-md-6 text-end">#}
|
||||
{# <div class="btn-group" role="group">#}
|
||||
{# <button type="button" class="btn btn-outline-secondary btn-sm" onclick="window.print()">#}
|
||||
{# <i class="fa fa-print"></i> Print#}
|
||||
{# </button>#}
|
||||
{# <button type="button" class="btn btn-outline-secondary btn-sm" onclick="exportToCSV()">#}
|
||||
{# <i class="fa fa-download"></i> Export#}
|
||||
{# </button>#}
|
||||
{# <button type="button" class="btn btn-outline-info btn-sm" onclick="showTransfusionReport()">#}
|
||||
{# <i class="fa fa-chart-bar"></i> Report#}
|
||||
{# </button>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
<!-- END summary -->
|
||||
</div>
|
||||
</div>
|
||||
@ -431,6 +389,32 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- END vital signs modal -->
|
||||
<!-- Stop Transfusion Modal -->
|
||||
<div class="modal fade" id="stopTransfusionModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-warning-subtle">
|
||||
<h5 class="modal-title">Stop Transfusion?</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div id="stopTransfusionAlert" class="alert d-none" role="alert"></div>
|
||||
<p>Are you sure you want to stop this transfusion?</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
Continue Transfusion
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" id="confirmStopTransfusionBtn">
|
||||
<span class="spinner-border spinner-border-sm me-2 d-none" id="stopSpinner" role="status" aria-hidden="true"></span>
|
||||
Yes, Stop Transfusion
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
@ -438,18 +422,12 @@
|
||||
<script src="{% static 'plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
|
||||
<script src="{% static 'plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
|
||||
<script src="{% static 'plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Initialize DataTable
|
||||
$('#transfusionTable').DataTable({
|
||||
responsive: true,
|
||||
pageLength: 25,
|
||||
order: [[3, 'desc']], // Sort by start time
|
||||
columnDefs: [
|
||||
{ orderable: false, targets: [9] } // Actions column
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
// Calculate summary statistics
|
||||
calculateSummaryStats();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user