from typing import Any, List from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, Response from sqlalchemy.orm import Session from app import crud, models, schemas from app.api import deps from app.services.pdf_generator import generate_invoice_pdf router = APIRouter() @router.get("/", response_model=List[schemas.Invoice]) def read_invoices( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Retrieve invoices. """ if crud.user.is_superuser(current_user): invoices = crud.invoice.get_multi(db, skip=skip, limit=limit) elif current_user.organization_id: # Filter invoices by the user's organization invoices = ( db.query(models.Invoice) .filter(models.Invoice.organization_id == current_user.organization_id) .offset(skip) .limit(limit) .all() ) else: invoices = [] return invoices @router.post("/", response_model=schemas.Invoice) def create_invoice( *, db: Session = Depends(deps.get_db), invoice_in: schemas.InvoiceCreate, current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Create new invoice. """ # Verify organization access if not crud.user.is_superuser(current_user) and ( current_user.organization_id != invoice_in.organization_id ): raise HTTPException(status_code=400, detail="Not enough permissions") # Verify client belongs to the organization client = crud.client.get(db, id=invoice_in.client_id) if not client or client.organization_id != invoice_in.organization_id: raise HTTPException(status_code=400, detail="Invalid client for this organization") # Create invoice invoice_data = invoice_in.dict(exclude={"items"}) invoice = models.Invoice(**invoice_data, created_by_id=current_user.id) db.add(invoice) db.commit() db.refresh(invoice) # Create invoice items for item_data in invoice_in.items: item = models.InvoiceItem(**item_data.dict(), invoice_id=invoice.id) db.add(item) db.commit() db.refresh(invoice) return invoice @router.get("/{id}", response_model=schemas.Invoice) def read_invoice( *, db: Session = Depends(deps.get_db), id: int, current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Get invoice by ID. """ invoice = crud.invoice.get(db, id=id) if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") if not crud.user.is_superuser(current_user) and ( current_user.organization_id != invoice.organization_id ): raise HTTPException(status_code=400, detail="Not enough permissions") return invoice @router.put("/{id}", response_model=schemas.Invoice) def update_invoice( *, db: Session = Depends(deps.get_db), id: int, invoice_in: schemas.InvoiceUpdate, current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Update an invoice. """ invoice = crud.invoice.get(db, id=id) if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") if not crud.user.is_superuser(current_user) and ( current_user.organization_id != invoice.organization_id ): raise HTTPException(status_code=400, detail="Not enough permissions") # Prevent changing organization_id if not a superuser if ( invoice_in.organization_id is not None and invoice_in.organization_id != invoice.organization_id and not crud.user.is_superuser(current_user) ): raise HTTPException( status_code=400, detail="Cannot change invoice's organization" ) # Update basic invoice data update_data = invoice_in.dict(exclude={"items"}, exclude_unset=True) for field, value in update_data.items(): setattr(invoice, field, value) # Update invoice items if provided if invoice_in.items: # Delete existing items db.query(models.InvoiceItem).filter(models.InvoiceItem.invoice_id == id).delete() # Create new items for item_data in invoice_in.items: item = models.InvoiceItem(**item_data.dict(), invoice_id=id) db.add(item) db.commit() db.refresh(invoice) return invoice @router.delete("/{id}", response_model=schemas.Invoice) def delete_invoice( *, db: Session = Depends(deps.get_db), id: int, current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Delete an invoice. """ invoice = crud.invoice.get(db, id=id) if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") if not crud.user.is_superuser(current_user) and ( current_user.organization_id != invoice.organization_id ): raise HTTPException(status_code=400, detail="Not enough permissions") invoice = crud.invoice.remove(db, id=id) return invoice @router.get("/{id}/pdf", response_class=Response) def generate_pdf( *, db: Session = Depends(deps.get_db), id: int, current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Generate a PDF for the invoice. """ invoice = crud.invoice.get(db, id=id) if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") if not crud.user.is_superuser(current_user) and ( current_user.organization_id != invoice.organization_id ): raise HTTPException(status_code=400, detail="Not enough permissions") # Generate PDF pdf_content = generate_invoice_pdf(db, invoice) # Return PDF as response filename = f"invoice_{invoice.invoice_number}_{datetime.now().strftime('%Y%m%d')}.pdf" return Response( content=pdf_content, media_type="application/pdf", headers={"Content-Disposition": f"attachment; filename={filename}"} )