103 lines
3.5 KiB
Python
103 lines
3.5 KiB
Python
"""Pay-off endpoints."""
|
|
from datetime import date
|
|
from decimal import Decimal
|
|
from enum import Enum
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from pydantic import BaseModel, Field, field_validator
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.database import get_db, PO10DAY
|
|
from app.logging import get_logger
|
|
|
|
router = APIRouter()
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class PayoffStatus(str, Enum):
|
|
"""Enum for payoff status values."""
|
|
PROCESSED = "processed"
|
|
PENDING = "pending"
|
|
FAILED = "failed"
|
|
CANCELLED = "cancelled"
|
|
|
|
|
|
class PayoffRequest(BaseModel):
|
|
"""Request model for loan payoff."""
|
|
loan_id: str = Field(..., description="Loan identifier", min_length=1, max_length=50)
|
|
payoff_amount: Decimal = Field(..., description="Payoff amount", gt=0, decimal_places=2)
|
|
payoff_date: date = Field(..., description="Payoff date")
|
|
borrower_name: Optional[str] = Field(None, description="Borrower name", max_length=100)
|
|
payment_method: str = Field(..., description="Payment method", regex="^(check|wire|ach|cash)$")
|
|
notes: Optional[str] = Field(None, description="Additional notes", max_length=500)
|
|
|
|
@field_validator('payoff_date')
|
|
def validate_payoff_date(cls, v):
|
|
"""Validate payoff date is not in the future."""
|
|
from datetime import date
|
|
if v > date.today():
|
|
raise ValueError('Payoff date cannot be in the future')
|
|
return v
|
|
|
|
|
|
class PayoffResponse(BaseModel):
|
|
"""Response model for loan payoff."""
|
|
status: PayoffStatus
|
|
loan_id: str
|
|
payoff_amount: Decimal
|
|
payoff_date: date
|
|
transaction_id: str
|
|
|
|
|
|
@router.post("/payoff", response_model=PayoffResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_payoff(payoff_request: PayoffRequest, db: Session = Depends(get_db)) -> PayoffResponse:
|
|
"""Create a loan payoff record."""
|
|
|
|
logger.info(f"Processing payoff for loan {payoff_request.loan_id}")
|
|
|
|
# Check if loan_id already exists
|
|
existing_payoff = db.query(PO10DAY).filter(PO10DAY.loan_id == payoff_request.loan_id).first()
|
|
if existing_payoff:
|
|
logger.error(f"Payoff already exists for loan {payoff_request.loan_id}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_409_CONFLICT,
|
|
detail=f"Payoff already exists for loan {payoff_request.loan_id}"
|
|
)
|
|
|
|
import uuid
|
|
transaction_id = str(uuid.uuid4())
|
|
|
|
# Create database entry
|
|
try:
|
|
po10day_record = PO10DAY(
|
|
loan_id=payoff_request.loan_id,
|
|
payoff_amount=payoff_request.payoff_amount,
|
|
payoff_date=payoff_request.payoff_date,
|
|
borrower_name=payoff_request.borrower_name,
|
|
payment_method=payoff_request.payment_method,
|
|
notes=payoff_request.notes,
|
|
transaction_id=transaction_id
|
|
)
|
|
|
|
db.add(po10day_record)
|
|
db.commit()
|
|
db.refresh(po10day_record)
|
|
|
|
logger.info(f"Payoff created successfully for loan {payoff_request.loan_id}, transaction {transaction_id}")
|
|
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"Failed to create payoff for loan {payoff_request.loan_id}", exc_info=e)
|
|
raise e
|
|
|
|
response = PayoffResponse(
|
|
status=PayoffStatus.PROCESSED,
|
|
loan_id=payoff_request.loan_id,
|
|
payoff_amount=payoff_request.payoff_amount,
|
|
payoff_date=payoff_request.payoff_date,
|
|
transaction_id=transaction_id
|
|
)
|
|
|
|
return response
|