from datetime import datetime, timedelta from typing import Optional from fastapi import APIRouter, Depends, Form, HTTPException, Request from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from sqlalchemy.orm import Session from app.core.database import get_db from app.models.invoice import Invoice, InvoiceItem from app.schemas.invoice import InvoiceCreate, InvoiceItemCreate, InvoiceStatusUpdate router = APIRouter() templates = Jinja2Templates(directory="app/templates") @router.get("/", response_class=HTMLResponse) async def home(request: Request, db: Session = Depends(get_db)): """Render the home page with recent invoices.""" invoices = db.query(Invoice).order_by(Invoice.date_created.desc()).limit(10).all() return templates.TemplateResponse( "home.html", {"request": request, "invoices": invoices} ) @router.get("/create", response_class=HTMLResponse) async def create_invoice_page(request: Request): """Render the create invoice form.""" # Set default due date to 30 days from now due_date = (datetime.now() + timedelta(days=30)).strftime("%Y-%m-%d") return templates.TemplateResponse( "create_invoice.html", {"request": request, "due_date": due_date} ) @router.post("/create", response_class=HTMLResponse) async def create_invoice( request: Request, customer_name: str = Form(...), customer_email: Optional[str] = Form(None), customer_address: Optional[str] = Form(None), due_date: datetime = Form(...), notes: Optional[str] = Form(None), db: Session = Depends(get_db), ): """Process the invoice creation form and create a new invoice.""" form_data = await request.form() # Extract invoice items from form data items = [] item_index = 0 while True: description_key = f"items[{item_index}][description]" quantity_key = f"items[{item_index}][quantity]" unit_price_key = f"items[{item_index}][unit_price]" if description_key not in form_data: break description = form_data.get(description_key) quantity = form_data.get(quantity_key) unit_price = form_data.get(unit_price_key) if description and quantity and unit_price: items.append( InvoiceItemCreate( description=description, quantity=float(quantity), unit_price=float(unit_price), ) ) item_index += 1 # Create the invoice if not items: # If no valid items, return to form with error return templates.TemplateResponse( "create_invoice.html", { "request": request, "error": "At least one invoice item is required", "customer_name": customer_name, "customer_email": customer_email, "customer_address": customer_address, "due_date": due_date.strftime("%Y-%m-%d"), "notes": notes, }, ) invoice_data = InvoiceCreate( customer_name=customer_name, customer_email=customer_email, customer_address=customer_address, due_date=due_date, notes=notes, items=items, ) # Create new invoice in database from app.core.utils import generate_invoice_number db_invoice = Invoice( invoice_number=generate_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 ) 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) # Redirect to invoice details page return RedirectResponse( url=f"/invoice/{db_invoice.id}", status_code=303 ) # 303 See Other @router.get("/search", response_class=HTMLResponse) async def search_invoice_page(request: Request): """Render the search invoice form.""" return templates.TemplateResponse("search_invoice.html", {"request": request}) @router.post("/search", response_class=HTMLResponse) async def search_invoice( request: Request, invoice_number: str = Form(...), db: Session = Depends(get_db) ): """Process the search form and find an invoice by number.""" invoice = ( db.query(Invoice).filter(Invoice.invoice_number == invoice_number).first() ) if not invoice: return templates.TemplateResponse( "search_invoice.html", { "request": request, "error": f"Invoice with number {invoice_number} not found", "invoice_number": invoice_number, }, ) # Redirect to invoice details page return RedirectResponse(url=f"/invoice/{invoice.id}", status_code=303) @router.get("/invoice/{invoice_id}", response_class=HTMLResponse) async def invoice_details(request: Request, invoice_id: int, db: Session = Depends(get_db)): """Render the invoice details page.""" invoice = db.query(Invoice).filter(Invoice.id == invoice_id).first() if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") return templates.TemplateResponse( "invoice_details.html", {"request": request, "invoice": invoice} ) @router.post("/invoice/{invoice_id}/status", response_class=HTMLResponse) async def update_status( request: Request, invoice_id: int, status: str = Form(...), db: Session = Depends(get_db), ): """Update the status of an invoice.""" invoice = db.query(Invoice).filter(Invoice.id == invoice_id).first() if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") status_update = InvoiceStatusUpdate(status=status) invoice.status = status_update.status db.commit() return RedirectResponse( url=f"/invoice/{invoice.id}", status_code=303 )