""" Splynx Repository Module This module provides a repository wrapper around the Splynx API client. It adds additional error handling, retry logic, and commonly used operations. """ import logging import time from typing import Dict, Any, Optional, List logger = logging.getLogger(__name__) class SplynxRepository: """ Repository for Splynx API operations. This class wraps the Splynx API client and provides higher-level operations with consistent error handling and retry logic. """ def __init__(self, splynx_client, max_retries: int = 3, retry_delay: float = 1.0): """ Initialize the Splynx repository. Args: splynx_client: Splynx API client instance max_retries: Maximum number of retry attempts for transient failures retry_delay: Delay in seconds between retries """ self.splynx = splynx_client self.max_retries = max_retries self.retry_delay = retry_delay def _retry_operation(self, operation_func, operation_name: str): """ Execute an operation with retry logic for transient failures. Args: operation_func: Function to execute operation_name: Description for logging Returns: Result of operation_func or None if all retries failed """ last_error = None for attempt in range(self.max_retries): try: return operation_func() except Exception as e: last_error = e if attempt < self.max_retries - 1: logger.warning(f"{operation_name} failed (attempt {attempt + 1}/{self.max_retries}): {e}") time.sleep(self.retry_delay) else: logger.error(f"{operation_name} failed after {self.max_retries} attempts: {e}") return None def get_customer(self, customer_id: int) -> Optional[Dict[str, Any]]: """ Get customer information from Splynx. Args: customer_id: Customer ID in Splynx Returns: Customer data dictionary or None if not found """ def _get(): customer_data = self.splynx.Customer(customer_id) return customer_data if customer_data != 'unknown' else None return self._retry_operation(_get, f"Get customer {customer_id}") def get_customer_name(self, customer_id: int) -> str: """ Get customer name from Splynx. Args: customer_id: Customer ID in Splynx Returns: Customer name or "Unknown Customer" if not found """ customer_data = self.get_customer(customer_id) if customer_data: return customer_data.get('name', 'Unknown Customer') return 'Unknown Customer' def mark_invoices_paid(self, customer_id: int) -> List[Dict[str, Any]]: """ Mark all unpaid/pending invoices as paid for a customer. Args: customer_id: Customer ID in Splynx Returns: List of updated invoice dictionaries """ def _mark(): result = self.splynx.get( url=f"/api/2.0/admin/finance/invoices?main_attributes[customer_id]={customer_id}&main_attributes[status]=not_paid&main_attributes[status]=pending" ) invoice_pay = {"status": "paid"} updated_invoices = [] for invoice in result: res = self.splynx.put(url=f"/api/2.0/admin/finance/invoices/{invoice['id']}", params=invoice_pay) if res: updated_invoices.append(res) logger.info(f"Marked {len(updated_invoices)} invoices as paid for customer {customer_id}") return updated_invoices return self._retry_operation(_mark, f"Mark invoices paid for customer {customer_id}") or [] def mark_invoices_pending(self, customer_id: int) -> List[Dict[str, Any]]: """ Mark all unpaid invoices as pending for a customer. Args: customer_id: Customer ID in Splynx Returns: List of updated invoice dictionaries """ def _mark(): result = self.splynx.get( url=f"/api/2.0/admin/finance/invoices?main_attributes[customer_id]={customer_id}&main_attributes[status]=not_paid" ) invoice_pay = {"status": "pending"} updated_invoices = [] for invoice in result: res = self.splynx.put(url=f"/api/2.0/admin/finance/invoices/{invoice['id']}", params=invoice_pay) if res: updated_invoices.append(res) logger.info(f"Marked {len(updated_invoices)} invoices as pending for customer {customer_id}") return updated_invoices return self._retry_operation(_mark, f"Mark invoices pending for customer {customer_id}") or [] def revert_invoices_to_unpaid(self, customer_id: int) -> List[Dict[str, Any]]: """ Revert pending invoices back to unpaid status for a customer. Args: customer_id: Customer ID in Splynx Returns: List of updated invoice dictionaries """ def _revert(): result = self.splynx.get( url=f"/api/2.0/admin/finance/invoices?main_attributes[customer_id]={customer_id}&main_attributes[status]=pending" ) invoice_pay = {"status": "not_paid"} updated_invoices = [] for invoice in result: res = self.splynx.put(url=f"/api/2.0/admin/finance/invoices/{invoice['id']}", params=invoice_pay) if res: updated_invoices.append(res) logger.info(f"Reverted {len(updated_invoices)} invoices to unpaid for customer {customer_id}") return updated_invoices return self._retry_operation(_revert, f"Revert invoices to unpaid for customer {customer_id}") or [] def add_payment(self, customer_id: int, amount: float, payment_intent_id: str, payment_id: int) -> Optional[int]: """ Add a payment record to Splynx. Args: customer_id: Customer ID in Splynx amount: Payment amount payment_intent_id: Stripe payment intent ID payment_id: Internal payment ID Returns: Splynx payment ID if successful, None otherwise """ def _add(): from datetime import datetime stripe_pay = { "customer_id": customer_id, "amount": amount, "date": str(datetime.now().strftime('%Y-%m-%d')), "field_1": payment_intent_id, "field_2": f"Payment_ID (Batch): {payment_id}" } res = self.splynx.post(url="/api/2.0/admin/finance/payments", params=stripe_pay) if res: logger.info(f"Added payment to Splynx for customer {customer_id}: payment ID {res['id']}") return res['id'] else: logger.error(f"Failed to add payment to Splynx for customer {customer_id}") return None return self._retry_operation(_add, f"Add payment to Splynx for customer {customer_id}") def delete_payment(self, customer_id: int, payment_intent_id: str) -> Dict[str, Any]: """ Delete a Splynx payment record by customer ID and payment intent. Args: customer_id: Customer ID in Splynx payment_intent_id: Stripe payment intent ID Returns: Dictionary with success status and details """ try: params = { 'main_attributes': { 'customer_id': customer_id, 'field_1': payment_intent_id }, } query_string = self.splynx.build_splynx_query_params(params) result = self.splynx.get(url=f"/api/2.0/admin/finance/payments?{query_string}") if not result: logger.warning(f"No Splynx payment found for customer {customer_id}, payment intent {payment_intent_id}") return {'success': False, 'error': 'No payment found to delete'} logger.info(f"Found {len(result)} Splynx payment(s) to delete for customer {customer_id}") delete_success = self.splynx.delete(url=f"/api/2.0/admin/finance/payments/{result[0]['id']}") if delete_success: logger.info(f"Successfully deleted Splynx Payment ID: {result[0]['id']} for customer: {customer_id}") return { 'success': True, 'deleted_payment_id': result[0]['id'], 'customer_id': customer_id, 'payment_intent': payment_intent_id } else: logger.error(f"Failed to delete Splynx Payment ID: {result[0]['id']} for customer: {customer_id}") return {'success': False, 'error': 'Delete operation failed'} except Exception as e: logger.error(f"Error deleting Splynx payment for customer {customer_id}: {e}") return {'success': False, 'error': str(e)} def create_ticket(self, customer_id: int, subject: str, priority: str = "medium", type_id: int = 1, group_id: int = 7, status_id: int = 1) -> Dict[str, Any]: """ Create a support ticket in Splynx. Args: customer_id: Customer ID in Splynx subject: Ticket subject priority: Ticket priority (low, medium, high) type_id: Ticket type ID group_id: Ticket group ID status_id: Ticket status ID Returns: Dictionary with success status and ticket ID """ def _create(): return self.splynx.create_ticket( customer_id=customer_id, subject=subject, priority=priority, type_id=type_id, group_id=group_id, status_id=status_id ) result = self._retry_operation(_create, f"Create ticket for customer {customer_id}") if result and result.get('success'): logger.info(f"Created ticket #{result['ticket_id']} for customer {customer_id}") return result or {'success': False, 'error': 'Failed to create ticket'} def add_ticket_message(self, ticket_id: int, message: str, is_admin: bool = False, hide_for_customer: bool = False, message_type: str = "message") -> bool: """ Add a message to an existing ticket. Args: ticket_id: Ticket ID message: Message content (HTML) is_admin: Whether message is from admin hide_for_customer: Whether to hide message from customer message_type: Message type ("message" or "note") Returns: True if successful, False otherwise """ def _add(): return self.splynx.add_ticket_message( ticket_id=ticket_id, message=message, is_admin=is_admin, hide_for_customer=hide_for_customer, message_type=message_type ) result = self._retry_operation(_add, f"Add message to ticket {ticket_id}") if result: logger.info(f"Added {message_type} to ticket #{ticket_id}") return True return False