annator-command-center / api /apar_routes.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 2)
a10e62e verified
Raw
History Blame Contribute Delete
7.54 kB
"""
AP/AR API Routes - Phase 41
"""
from datetime import datetime
import io
import logging
from typing import Any, Dict, List, Optional
from fastapi import Depends, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from core.base_routes import BaseAPIRouter
router = BaseAPIRouter(prefix="/apar", tags=["AP/AR"])
class APIntakeRequest(BaseModel):
vendor: str
amount: float
due_date: Optional[str] = None
line_items: List[Dict[str, Any]] = []
payment_terms: str = "Net 30"
source: str = "email"
class ARGenerateRequest(BaseModel):
customer: str
amount: float
due_date: Optional[str] = None
line_items: List[Dict[str, Any]] = []
source: str = "manual"
# ==================== ACCOUNTS PAYABLE ====================
@router.post("/ap/intake")
async def intake_ap_invoice(request: APIntakeRequest):
from core.apar_engine import apar_engine
data = {
"vendor": request.vendor,
"amount": request.amount,
"due_date": request.due_date,
"line_items": request.line_items,
"payment_terms": request.payment_terms
}
invoice = apar_engine.intake_invoice(request.source, data)
return router.success_response(
data={
"id": invoice.id,
"vendor": invoice.vendor,
"amount": invoice.amount,
"status": invoice.status.value,
"auto_approved": invoice.approved_by == "auto"
},
message="AP invoice intake successful"
)
@router.post("/ap/{invoice_id}/approve")
async def approve_ap_invoice(invoice_id: str, approver: str = "user"):
from core.apar_engine import apar_engine
invoice = apar_engine.approve_invoice(invoice_id, approver)
return router.success_response(
data={"status": "approved", "id": invoice_id},
message="Invoice approved successfully"
)
@router.get("/ap/pending")
async def get_pending_approvals():
from core.apar_engine import apar_engine
pending = apar_engine.get_pending_approvals()
return router.success_response(
data={
"count": len(pending),
"invoices": [
{"id": inv.id, "vendor": inv.vendor, "amount": inv.amount}
for inv in pending
]
},
message=f"Retrieved {len(pending)} pending approvals"
)
@router.get("/ap/upcoming")
async def get_upcoming_payments(days: int = 7):
from core.apar_engine import apar_engine
upcoming = apar_engine.get_upcoming_payments(days)
return router.success_response(
data={
"count": len(upcoming),
"total_due": sum(inv.amount for inv in upcoming),
"invoices": [
{"id": inv.id, "vendor": inv.vendor, "amount": inv.amount, "due_date": inv.due_date.isoformat()}
for inv in upcoming
]
},
message=f"Retrieved {len(upcoming)} upcoming payments"
)
# ==================== ACCOUNTS RECEIVABLE ====================
@router.post("/ar/generate")
async def generate_ar_invoice(request: ARGenerateRequest):
from core.apar_engine import apar_engine
data = {
"customer": request.customer,
"amount": request.amount,
"due_date": request.due_date,
"line_items": request.line_items
}
invoice = apar_engine.generate_invoice(request.source, data)
return router.success_response(
data={
"id": invoice.id,
"customer": invoice.customer,
"amount": invoice.amount
},
message="AR invoice generated successfully"
)
@router.post("/ar/{invoice_id}/send")
async def send_ar_invoice(invoice_id: str):
from core.apar_engine import apar_engine
invoice = apar_engine.send_invoice(invoice_id)
return router.success_response(
data={"status": "sent", "id": invoice_id},
message="Invoice sent successfully"
)
@router.post("/ar/{invoice_id}/paid")
async def mark_ar_paid(invoice_id: str):
from core.apar_engine import apar_engine
invoice = apar_engine.mark_paid(invoice_id)
return router.success_response(
data={"status": "paid", "id": invoice_id},
message="Invoice marked as paid"
)
@router.get("/ar/overdue")
async def get_overdue_invoices():
from core.apar_engine import apar_engine
overdue = apar_engine.get_overdue_invoices()
return router.success_response(
data={
"count": len(overdue),
"invoices": [
{"id": inv.id, "customer": inv.customer, "amount": inv.amount}
for inv in overdue
]
},
message=f"Retrieved {len(overdue)} overdue invoices"
)
@router.get("/all")
async def get_all_invoices():
"""Get all invoices (AR and AP)"""
from core.apar_engine import apar_engine
all_invoices = apar_engine.get_all_invoices()
formatted_invoices = []
for inv in all_invoices:
# Check if it is an ARInvoice based on customer attribute
is_ar = hasattr(inv, "customer")
formatted_invoices.append({
"id": inv.id,
"customer": inv.customer if is_ar else None,
"vendor": getattr(inv, "vendor", None) if not is_ar else None,
"amount": inv.amount,
"due_date": inv.due_date.isoformat(),
"status": inv.status.value,
"type": "AR" if is_ar else "AP"
})
return router.success_response(
data={
"count": len(formatted_invoices),
"invoices": formatted_invoices
},
message=f"Retrieved {len(formatted_invoices)} invoices"
)
@router.post("/ar/{invoice_id}/remind")
async def send_reminder(invoice_id: str):
from core.apar_engine import apar_engine
reminder = apar_engine.generate_reminder(invoice_id)
return router.success_response(
data=reminder,
message="Reminder generated successfully"
)
@router.get("/summary")
async def get_collection_summary():
from core.apar_engine import apar_engine
summary = apar_engine.get_collection_summary()
return router.success_response(
data=summary,
message="Collection summary retrieved successfully"
)
@router.get("/ar/{invoice_id}/download")
async def download_ar_invoice(invoice_id: str):
from core.apar_engine import apar_engine
try:
pdf_bytes = apar_engine.generate_invoice_pdf(invoice_id)
file_obj = io.BytesIO(pdf_bytes)
return StreamingResponse(
file_obj,
media_type="application/pdf",
headers={"Content-Disposition": f"attachment; filename=invoice_{invoice_id}.pdf"}
)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except ImportError as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/ap/{invoice_id}/download")
async def download_ap_invoice(invoice_id: str):
from core.apar_engine import apar_engine
try:
pdf_bytes = apar_engine.generate_invoice_pdf(invoice_id)
file_obj = io.BytesIO(pdf_bytes)
return StreamingResponse(
file_obj,
media_type="application/pdf",
headers={"Content-Disposition": f"attachment; filename=invoice_{invoice_id}.pdf"}
)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except ImportError as e:
raise HTTPException(status_code=500, detail=str(e))