""" Finance business logic services. This module contains service classes that encapsulate business logic for financial operations, clearance checks, and payment processing. """ import logging from datetime import date, timedelta from decimal import Decimal from typing import Dict, List, Optional, Tuple from django.db import transaction from django.utils import timezone from finance.models import Invoice, Payment, Payer logger = logging.getLogger(__name__) class FinancialClearanceService: """Service class for financial clearance operations.""" @staticmethod def check_clearance(patient, service_type: str = None) -> Tuple[bool, str]: """ Check if patient has financial clearance for service. Args: patient: Patient instance service_type: Optional service type to check specific requirements Returns: Tuple[bool, str]: (is_cleared, message) """ try: # Check for outstanding invoices outstanding_invoices = Invoice.objects.filter( patient=patient, status__in=['ISSUED', 'PARTIALLY_PAID', 'OVERDUE'] ) if outstanding_invoices.exists(): total_outstanding = sum( invoice.amount_due for invoice in outstanding_invoices ) # Allow small outstanding amounts (less than 10 SAR) if total_outstanding > Decimal('10.00'): invoice_numbers = ', '.join([ inv.invoice_number for inv in outstanding_invoices[:3] ]) return False, ( f"Outstanding invoices must be paid. " f"Total due: {total_outstanding} SAR. " f"Invoices: {invoice_numbers}" ) # Check for overdue invoices (stricter check) overdue_invoices = Invoice.objects.filter( patient=patient, status='OVERDUE' ) if overdue_invoices.exists(): return False, "Overdue invoices must be paid before check-in" # Check if service requires pre-payment if service_type: requires_prepayment = FinancialClearanceService._check_prepayment_requirement( patient, service_type ) if requires_prepayment: return False, f"Service '{service_type}' requires pre-payment" # Check insurance coverage if applicable active_payer = Payer.objects.filter( patient=patient, is_active=True, payer_type='INSURANCE' ).first() if active_payer: # Verify insurance is not expired # This would integrate with insurance verification system logger.info(f"Patient {patient.mrn} has active insurance: {active_payer.name}") logger.info(f"Financial clearance approved for patient {patient.mrn}") return True, "Financial clearance approved" except Exception as e: logger.error(f"Error checking financial clearance for patient {patient.mrn}: {e}") return False, f"Error checking financial clearance: {str(e)}" @staticmethod def _check_prepayment_requirement(patient, service_type: str) -> bool: """ Check if service requires pre-payment. Args: patient: Patient instance service_type: Service type to check Returns: bool: True if pre-payment required """ # Define services that require pre-payment prepayment_services = [ 'SURGERY', 'PROCEDURE', 'IMAGING_ADVANCED', ] # Check if service type requires pre-payment return service_type.upper() in prepayment_services @staticmethod def get_outstanding_balance(patient) -> Decimal: """ Get total outstanding balance for patient. Args: patient: Patient instance Returns: Decimal: Total outstanding amount """ outstanding_invoices = Invoice.objects.filter( patient=patient, status__in=['ISSUED', 'PARTIALLY_PAID', 'OVERDUE'] ) total = sum( invoice.amount_due for invoice in outstanding_invoices ) return Decimal(str(total)) @staticmethod def get_outstanding_invoices(patient) -> List[Invoice]: """ Get list of outstanding invoices for patient. Args: patient: Patient instance Returns: List[Invoice]: Outstanding invoices """ return list(Invoice.objects.filter( patient=patient, status__in=['ISSUED', 'PARTIALLY_PAID', 'OVERDUE'] ).order_by('-issue_date')) @staticmethod @transaction.atomic def process_payment( invoice: Invoice, amount: Decimal, payment_method: str, processed_by, **kwargs ) -> Payment: """ Process a payment for an invoice. Args: invoice: Invoice instance amount: Payment amount payment_method: Payment method (CASH, CARD, etc.) processed_by: User processing the payment **kwargs: Additional payment fields Returns: Payment: Created payment instance """ # Create payment record payment = Payment.objects.create( tenant=invoice.tenant, invoice=invoice, payment_date=timezone.now(), amount=amount, method=payment_method, status='COMPLETED', processed_by=processed_by, **kwargs ) logger.info( f"Payment processed: {payment.id} for invoice {invoice.invoice_number} " f"Amount: {amount}" ) return payment @staticmethod def check_insurance_eligibility(patient, service_type: str = None) -> Tuple[bool, str, Optional[Payer]]: """ Check if patient has active insurance coverage. Args: patient: Patient instance service_type: Optional service type to check coverage Returns: Tuple[bool, str, Optional[Payer]]: (has_coverage, message, payer) """ active_payer = Payer.objects.filter( patient=patient, is_active=True, payer_type='INSURANCE' ).first() if not active_payer: return False, "No active insurance coverage", None # Check coverage percentage if active_payer.coverage_percentage <= 0: return False, f"Insurance {active_payer.name} has 0% coverage", active_payer logger.info( f"Patient {patient.mrn} has {active_payer.coverage_percentage}% " f"coverage with {active_payer.name}" ) return True, f"Covered {active_payer.coverage_percentage}% by {active_payer.name}", active_payer class InvoiceService: """Service class for invoice operations.""" @staticmethod @transaction.atomic def create_invoice_from_appointment(appointment) -> Invoice: """ Create an invoice from a completed appointment. Args: appointment: Appointment instance Returns: Invoice: Created invoice instance """ from finance.models import InvoiceLineItem, Service # Check if invoice already exists existing_invoice = Invoice.objects.filter( appointment=appointment ).first() if existing_invoice: logger.info(f"Invoice already exists for appointment {appointment.id}") return existing_invoice # Get service pricing service = Service.objects.filter( clinic=appointment.clinic, code=appointment.service_type ).first() if not service: logger.warning( f"No service found for {appointment.service_type} at {appointment.clinic.name_en}" ) # Use default pricing unit_price = Decimal('100.00') else: unit_price = service.base_price.amount # Calculate amounts subtotal = unit_price tax_rate = Decimal('0.15') # 15% VAT tax = subtotal * tax_rate total = subtotal + tax # Create invoice invoice = Invoice.objects.create( tenant=appointment.tenant, patient=appointment.patient, appointment=appointment, issue_date=date.today(), due_date=date.today() + timedelta(days=30), subtotal=subtotal, tax=tax, total=total, status='ISSUED' ) # Create line item InvoiceLineItem.objects.create( invoice=invoice, service=service, description=f"{appointment.service_type} - {appointment.clinic.name_en}", quantity=1, unit_price=unit_price, total=unit_price ) logger.info( f"Invoice created: {invoice.invoice_number} for appointment {appointment.appointment_number}" ) return invoice