255 lines
7.5 KiB
Python

from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.responses import JSONResponse
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.core.utils import generate_invoice_number
from app.models.invoice import Invoice, InvoiceItem
from app.schemas.invoice import (
InvoiceCreate,
InvoiceDB,
InvoiceStatusUpdate,
InvoiceUpdate,
invoice_db_filterable,
)
router = APIRouter()
@router.post("/", response_model=InvoiceDB, status_code=status.HTTP_201_CREATED)
def create_invoice(invoice_data: InvoiceCreate, db: Session = Depends(get_db)):
"""
Create a new invoice.
"""
# Generate unique invoice number
invoice_number = generate_invoice_number()
# Create new invoice
db_invoice = Invoice(
invoice_number=invoice_number,
date_created=datetime.utcnow(),
due_date=invoice_data.due_date,
customer_name=invoice_data.customer_name,
customer_email=invoice_data.customer_email,
customer_address=invoice_data.customer_address,
notes=invoice_data.notes,
status="PENDING",
total_amount=0, # Will be calculated from items
)
# Add to DB
db.add(db_invoice)
db.flush() # Flush to get the ID
# Create invoice items
total_amount = 0
for item_data in invoice_data.items:
item_amount = item_data.quantity * item_data.unit_price
total_amount += item_amount
db_item = InvoiceItem(
invoice_id=db_invoice.id,
description=item_data.description,
quantity=item_data.quantity,
unit_price=item_data.unit_price,
amount=item_amount,
)
db.add(db_item)
# Update total amount
db_invoice.total_amount = total_amount
# Commit the transaction
db.commit()
db.refresh(db_invoice)
return db_invoice
@router.get("/")
def get_invoices(
skip: int = 0,
limit: int = 100,
fields: Optional[str] = None,
status: Optional[str] = None,
db: Session = Depends(get_db)
):
"""
Retrieve a list of invoices.
Parameters:
- skip: Number of records to skip
- limit: Maximum number of records to return
- fields: Comma-separated list of fields to include in the response (e.g., "id,invoice_number,total_amount")
If not provided, all fields will be returned.
- status: Filter invoices by status (e.g., "PENDING", "PAID", "CANCELLED")
If not provided, all invoices regardless of status will be returned.
"""
# Build the query with filters
query = db.query(Invoice)
# Apply status filter if provided
if status:
# Validate the status value
allowed_statuses = ["PENDING", "PAID", "CANCELLED"]
if status not in allowed_statuses:
raise HTTPException(
status_code=400,
detail=f"Status must be one of {', '.join(allowed_statuses)}"
)
# Apply the filter
query = query.filter(Invoice.status == status)
# Apply pagination
invoices = query.offset(skip).limit(limit).all()
# If fields parameter is provided, filter the response
if fields:
# Process the response to include only the specified fields
filtered_response = invoice_db_filterable.process_response(invoices, fields)
return JSONResponse(content=filtered_response)
# Otherwise, return all fields
return invoices
@router.get("/{invoice_id}")
def get_invoice(
invoice_id: int,
fields: Optional[str] = None,
db: Session = Depends(get_db)
):
"""
Retrieve a specific invoice by ID.
Parameters:
- invoice_id: ID of the invoice to retrieve
- fields: Comma-separated list of fields to include in the response (e.g., "id,invoice_number,total_amount")
If not provided, all fields will be returned.
"""
invoice = db.query(Invoice).filter(Invoice.id == invoice_id).first()
if invoice is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Invoice with ID {invoice_id} not found",
)
# If fields parameter is provided, filter the response
if fields:
# Process the response to include only the specified fields
filtered_response = invoice_db_filterable.process_response(invoice, fields)
return JSONResponse(content=filtered_response)
# Otherwise, return all fields
return invoice
@router.get("/find", response_model=InvoiceDB)
def find_invoice_by_number(
invoice_number: str,
fields: Optional[str] = None,
db: Session = Depends(get_db)
):
"""
Find an invoice by its invoice number.
Parameters:
- invoice_number: The invoice number to search for
- fields: Comma-separated list of fields to include in the response (e.g., "id,invoice_number,total_amount")
If not provided, all fields will be returned.
"""
invoice = db.query(Invoice).filter(Invoice.invoice_number == invoice_number).first()
if invoice is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Invoice with number {invoice_number} not found",
)
# If fields parameter is provided, filter the response
if fields:
# Process the response to include only the specified fields
filtered_response = invoice_db_filterable.process_response(invoice, fields)
return JSONResponse(content=filtered_response)
# Otherwise, return all fields
return invoice
@router.patch("/{invoice_id}", response_model=InvoiceDB)
def update_invoice(
invoice_id: int,
invoice_update: InvoiceUpdate,
db: Session = Depends(get_db)
):
"""
Update an existing invoice.
"""
# Get the invoice
invoice = db.query(Invoice).filter(Invoice.id == invoice_id).first()
if invoice is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Invoice with ID {invoice_id} not found",
)
# Update invoice fields
update_data = invoice_update.dict(exclude_unset=True)
for field, value in update_data.items():
setattr(invoice, field, value)
# Commit changes
db.commit()
db.refresh(invoice)
return invoice
@router.patch("/{invoice_id}/status", response_model=InvoiceDB)
def update_invoice_status(
invoice_id: int,
status_update: InvoiceStatusUpdate,
db: Session = Depends(get_db)
):
"""
Update the status of an invoice.
"""
# Get the invoice
invoice = db.query(Invoice).filter(Invoice.id == invoice_id).first()
if invoice is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Invoice with ID {invoice_id} not found",
)
# Update status
invoice.status = status_update.status
# Commit changes
db.commit()
db.refresh(invoice)
return invoice
@router.delete("/{invoice_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
def delete_invoice(invoice_id: int, db: Session = Depends(get_db)):
"""
Delete an invoice.
"""
# Get the invoice
invoice = db.query(Invoice).filter(Invoice.id == invoice_id).first()
if invoice is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Invoice with ID {invoice_id} not found",
)
# Delete the invoice
db.delete(invoice)
db.commit()
return None