This commit is contained in:
Marwan Alwali 2025-09-08 03:00:23 +03:00
parent 35be20ae4c
commit 23158e9fbf
130 changed files with 30974 additions and 4732 deletions

BIN
.DS_Store vendored

Binary file not shown.

3
.idea/misc.xml generated
View File

@ -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>

Binary file not shown.

800
accounts/flows.py Normal file
View 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
#

View File

@ -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>')
# )
# )
#

View File

@ -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

Binary file not shown.

Binary file not shown.

845
analytics/flows.py Normal file
View 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
#

View File

@ -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>')
# )
# )
#

View File

@ -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)}
#

Binary file not shown.

871
appointments/flows.py Normal file
View 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
#

View File

@ -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>')
# )
# )
#

View File

@ -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
#

Binary file not shown.

1022
billing/flows.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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>')
# )
# )
#
#
#

View File

@ -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')),

View File

@ -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)
# }
#

View File

@ -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

View File

@ -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'),

View File

@ -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 donors 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

Binary file not shown.

1046
communications/flows.py Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

849
core/flows.py Normal file
View 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
#

View File

@ -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 = []

Binary file not shown.

Binary file not shown.

Binary file not shown.

929
emr/flows.py Normal file
View 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
#

View File

@ -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'),

View File

@ -46,6 +46,8 @@ THIRD_PARTY_APPS = [
'corsheaders',
'django_extensions',
'allauth',
'viewflow',
'viewflow.workflow',
# 'allauth.socialaccount',
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

846
hr/flows.py Normal file
View 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
#

View 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"),
),
]

View File

@ -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 (shouldnt 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

View File

@ -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

View File

@ -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 templates 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 (

Binary file not shown.

757
inpatients/flows.py Normal file
View 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')}"
#

Binary file not shown.

781
integration/flows.py Normal file
View 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
#

Binary file not shown.

905
inventory/flows.py Normal file
View 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
#

Binary file not shown.

523
laboratory/flows.py Normal file
View 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

Binary file not shown.

689
operating_theatre/flows.py Normal file
View 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
#

Binary file not shown.

761
patients/flows.py Normal file
View 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
#

View File

@ -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):
# """

Binary file not shown.

672
pharmacy/flows.py Normal file
View 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
#

View File

@ -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",
]

Binary file not shown.

983
quality/flows.py Normal file
View 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
#

Binary file not shown.

746
radiology/flows.py Normal file
View 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

Binary file not shown.

View File

@ -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>

View File

@ -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 %}

View File

@ -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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</span>{{ bill.total_amount|floatformat:2 }}</td>
<td class="text-end"><span class="symbol">&#xea;</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">&#xea;</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">&#xea;</span>{{ bill.total_amount|floatformat:2 }}</td>
<td class="text-end"><span class="symbol">&#xea;</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">&#xea;</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 %}

View File

@ -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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</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">&#xea;</span>{{ object.approved_amount|floatformat:'2g' }} of <span class="symbol">&#xea;</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">&#xea;</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

View File

@ -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) {

View File

@ -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 + ')');
}

View File

@ -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++;
}
});

View File

@ -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