You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
11 KiB
314 lines
11 KiB
"""
|
|
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
|
|
|