""" Logging and utility services for Plutus payment processing application. This module provides database logging functionality for tracking application activities, particularly automated script executions and payment processing operations. """ from datetime import datetime, timezone from typing import Optional, Union from app import db from models import Logs # System user ID for automated processes SYSTEM_USER_ID = 1 def log_activity( user_id: int, action: str, entity_type: str, entity_id: Optional[int] = None, details: Optional[str] = None, ip_address: Optional[str] = None ) -> Optional[int]: """ Log an activity to the database. Args: user_id (int): ID of the user performing the action (use SYSTEM_USER_ID for automated processes) action (str): Type of action performed (e.g., 'BATCH_RUN', 'PAYMENT_PROCESSED', 'API_ACCESS') entity_type (str): Type of entity involved (e.g., 'Batch', 'Payment', 'PaymentPlan') entity_id (int, optional): ID of the specific entity involved details (str, optional): Detailed description of the activity ip_address (str, optional): IP address of the request (for web requests) Returns: int: ID of the created log entry, or None if failed """ try: log_entry = Logs( User_ID=user_id, Action=action, Entity_Type=entity_type, Entity_ID=entity_id, Log_Entry=details, IP_Address=ip_address, Added=datetime.now(timezone.utc) ) db.session.add(log_entry) db.session.commit() return log_entry.id except Exception as e: db.session.rollback() print(f"Failed to log activity: {e}") return None def log_script_start(script_name: str, mode: str, environment: str) -> Optional[int]: """ Log the start of a script execution. Args: script_name (str): Name of the script being executed mode (str): Running mode (batch, payintent, payplan) environment (str): Environment (live, sandbox) Returns: int: Log entry ID or None if failed """ details = f"{script_name} started in {mode} mode ({environment} environment)" return log_activity( user_id=SYSTEM_USER_ID, action="SCRIPT_START", entity_type="Script", details=details ) def log_script_completion( script_name: str, mode: str, success_count: int = 0, failed_count: int = 0, total_amount: float = 0.0, batch_ids: Optional[list] = None, duration_seconds: Optional[float] = None, errors: Optional[list] = None ) -> Optional[int]: """ Log the completion of a script execution with summary statistics. Args: script_name (str): Name of the script that completed mode (str): Running mode that was executed success_count (int): Number of successful operations failed_count (int): Number of failed operations total_amount (float): Total amount processed batch_ids (list, optional): List of batch IDs created duration_seconds (float, optional): Execution time in seconds errors (list, optional): List of error messages encountered Returns: int: Log entry ID or None if failed """ total_operations = success_count + failed_count success_rate = (success_count / total_operations * 100) if total_operations > 0 else 0 details_parts = [ f"{script_name} completed in {mode} mode", f"Total operations: {total_operations}", f"Successful: {success_count}", f"Failed: {failed_count}", f"Success rate: {success_rate:.1f}%" ] if total_amount > 0: details_parts.append(f"Total amount: ${total_amount:,.2f}") if batch_ids: details_parts.append(f"Batch IDs: {', '.join(map(str, batch_ids))}") if duration_seconds: details_parts.append(f"Duration: {duration_seconds:.1f}s") if errors: details_parts.append(f"Errors encountered: {len(errors)}") if len(errors) <= 3: details_parts.extend([f"- {error}" for error in errors]) else: details_parts.extend([f"- {error}" for error in errors[:3]]) details_parts.append(f"... and {len(errors) - 3} more errors") details = "\\n".join(details_parts) action = "SCRIPT_SUCCESS" if failed_count == 0 else "SCRIPT_PARTIAL" if success_count > 0 else "SCRIPT_FAILED" return log_activity( user_id=SYSTEM_USER_ID, action=action, entity_type="Script", details=details ) def log_batch_created(batch_id: int, payment_method: str, customer_count: int) -> Optional[int]: """ Log the creation of a payment batch. Args: batch_id (int): ID of the created batch payment_method (str): Payment method type (Direct Debit, Card, etc.) customer_count (int): Number of customers in the batch Returns: int: Log entry ID or None if failed """ details = f"Payment batch created for {payment_method} with {customer_count} customers" return log_activity( user_id=SYSTEM_USER_ID, action="BATCH_CREATED", entity_type="PaymentBatch", entity_id=batch_id, details=details ) def log_payment_plan_run( active_plans: int, due_plans: int, processed_count: int, failed_count: int, total_amount: float ) -> Optional[int]: """ Log the results of a payment plan execution. Args: active_plans (int): Total number of active payment plans due_plans (int): Number of plans due for payment today processed_count (int): Number of payments successfully processed failed_count (int): Number of failed payments total_amount (float): Total amount processed Returns: int: Log entry ID or None if failed """ details = ( f"Payment plan execution: {active_plans} active plans, " f"{due_plans} due today, {processed_count} successful, " f"{failed_count} failed, ${total_amount:,.2f} total" ) action = "PAYPLAN_SUCCESS" if failed_count == 0 else "PAYPLAN_PARTIAL" if processed_count > 0 else "PAYPLAN_FAILED" return log_activity( user_id=SYSTEM_USER_ID, action=action, entity_type="PaymentPlan", details=details ) def log_payment_intent_followup( pending_count: int, succeeded_count: int, failed_count: int, still_pending: int ) -> Optional[int]: """ Log the results of payment intent follow-up processing. Args: pending_count (int): Number of payment intents checked succeeded_count (int): Number that succeeded failed_count (int): Number that failed still_pending (int): Number still pending Returns: int: Log entry ID or None if failed """ details = ( f"Payment intent follow-up: {pending_count} intents checked, " f"{succeeded_count} succeeded, {failed_count} failed, " f"{still_pending} still pending" ) return log_activity( user_id=SYSTEM_USER_ID, action="PAYINTENT_FOLLOWUP", entity_type="PaymentIntent", details=details )