Automated Action 0a65bff5f3 Implement invoice generation service
- Create FastAPI app structure
- Set up SQLAlchemy with SQLite for database management
- Implement invoice and invoice item models
- Add Alembic for database migrations
- Create invoice generation and retrieval API endpoints
- Add health check endpoint
- Set up Ruff for linting
- Update README with project details
2025-05-18 20:01:08 +00:00

185 lines
5.0 KiB
Python

from datetime import datetime
from typing import List
from fastapi import APIRouter, Depends, HTTPException, status
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,
InvoiceSearchQuery,
InvoiceStatusUpdate,
InvoiceUpdate,
)
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("/", response_model=List[InvoiceDB])
def get_invoices(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)
):
"""
Retrieve a list of invoices.
"""
invoices = db.query(Invoice).offset(skip).limit(limit).all()
return invoices
@router.get("/{invoice_id}", response_model=InvoiceDB)
def get_invoice(invoice_id: int, db: Session = Depends(get_db)):
"""
Retrieve a specific invoice by ID.
"""
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",
)
return invoice
@router.post("/find", response_model=InvoiceDB)
def find_invoice_by_number(query: InvoiceSearchQuery, db: Session = Depends(get_db)):
"""
Find an invoice by its invoice number.
"""
invoice = db.query(Invoice).filter(Invoice.invoice_number == query.invoice_number).first()
if invoice is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Invoice with number {query.invoice_number} not found",
)
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