| from datetime import datetime, timedelta |
| import logging |
| from typing import Any, Dict, List |
| from accounting.models import Entity, Invoice, InvoiceStatus |
| from sqlalchemy.orm import Session |
|
|
| from core.websockets import manager |
|
|
| logger = logging.getLogger(__name__) |
|
|
| class CollectionAgent: |
| """ |
| Automated agent for monitoring Accounts Receivable and sending follow-ups. |
| """ |
|
|
| def __init__(self, db: Session): |
| self.db = db |
|
|
| async def check_overdue_invoices(self, workspace_id: str) -> List[Dict[str, Any]]: |
| """ |
| Identify invoices that are past their due date and trigger follow-ups. |
| """ |
| now = datetime.utcnow() |
| overdue_invoices = self.db.query(Invoice).filter( |
| Invoice.workspace_id == workspace_id, |
| Invoice.status == InvoiceStatus.OPEN, |
| Invoice.due_date < now |
| ).all() |
|
|
| reminders_sent = [] |
|
|
| for invoice in overdue_invoices: |
| |
| invoice.status = InvoiceStatus.OVERDUE |
| |
| |
| reminder = self._generate_reminder_message(invoice) |
| |
| |
| logger.info(f"Sending reminder for Invoice {invoice.invoice_number} to {invoice.customer.name}") |
| |
| |
| await manager.broadcast(f"workspace:{workspace_id}", { |
| "type": "accounting.reminder_sent", |
| "data": { |
| "invoice_id": invoice.id, |
| "customer": invoice.customer.name, |
| "amount": invoice.amount, |
| "reminder": reminder |
| } |
| }) |
|
|
| reminders_sent.append({ |
| "invoice_id": invoice.id, |
| "customer": invoice.customer.name, |
| "amount": invoice.amount |
| }) |
|
|
| self.db.commit() |
| return reminders_sent |
|
|
| def _generate_reminder_message(self, invoice: Invoice) -> str: |
| """AI-assisted (template for now) reminder generation""" |
| days_overdue = (datetime.utcnow() - invoice.due_date).days |
| return ( |
| f"Hello {invoice.customer.name}, this is a reminder that Invoice {invoice.invoice_number} " |
| f"for ${invoice.amount:,.2f} is now {days_overdue} days overdue. " |
| "Please process the payment at your earliest convenience." |
| ) |
|
|
| def generate_aging_report(self, workspace_id: str) -> Dict[str, Any]: |
| """Generate a summary of AR aging""" |
| invoices = self.db.query(Invoice).filter( |
| Invoice.workspace_id == workspace_id, |
| Invoice.status.in_([InvoiceStatus.OPEN, InvoiceStatus.OVERDUE]) |
| ).all() |
|
|
| now = datetime.utcnow() |
| report = { |
| "current": 0.0, |
| "overdue_30": 0.0, |
| "overdue_60": 0.0, |
| "overdue_90": 0.0, |
| "total_ar": 0.0 |
| } |
|
|
| for inv in invoices: |
| days = (now - inv.due_date).days |
| report["total_ar"] += inv.amount |
| if days <= 0: |
| report["current"] += inv.amount |
| elif days <= 30: |
| report["overdue_30"] += inv.amount |
| elif days <= 60: |
| report["overdue_60"] += inv.amount |
| else: |
| report["overdue_90"] += inv.amount |
|
|
| return report |
|
|