250 lines
8.0 KiB
Python
250 lines
8.0 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
|
from sqlalchemy.orm import Session
|
|
from typing import Optional
|
|
from datetime import datetime
|
|
|
|
from ..config.database import get_db
|
|
from ..middleware.auth import get_current_user, authorize_roles
|
|
from ..models.user import User
|
|
from ..models.invoice import Invoice, InvoiceStatus
|
|
from ..models.booking import Booking
|
|
from ..services.invoice_service import InvoiceService
|
|
|
|
router = APIRouter(prefix="/invoices", tags=["invoices"])
|
|
|
|
|
|
@router.get("/")
|
|
async def get_invoices(
|
|
booking_id: Optional[int] = Query(None),
|
|
status_filter: Optional[str] = Query(None, alias="status"),
|
|
page: int = Query(1, ge=1),
|
|
limit: int = Query(10, ge=1, le=100),
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Get invoices for current user (or all invoices for admin)"""
|
|
try:
|
|
# Admin can see all invoices, users can only see their own
|
|
user_id = None if current_user.role_id == 1 else current_user.id
|
|
|
|
result = InvoiceService.get_invoices(
|
|
db=db,
|
|
user_id=user_id,
|
|
booking_id=booking_id,
|
|
status=status_filter,
|
|
page=page,
|
|
limit=limit
|
|
)
|
|
|
|
return {
|
|
"status": "success",
|
|
"data": result
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.get("/{id}")
|
|
async def get_invoice_by_id(
|
|
id: int,
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Get invoice by ID"""
|
|
try:
|
|
invoice = InvoiceService.get_invoice(id, db)
|
|
|
|
if not invoice:
|
|
raise HTTPException(status_code=404, detail="Invoice not found")
|
|
|
|
# Check access: admin can see all, users can only see their own
|
|
if current_user.role_id != 1 and invoice["user_id"] != current_user.id:
|
|
raise HTTPException(status_code=403, detail="Forbidden")
|
|
|
|
return {
|
|
"status": "success",
|
|
"data": {"invoice": invoice}
|
|
}
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.post("/")
|
|
async def create_invoice(
|
|
invoice_data: dict,
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Create a new invoice from a booking (Admin/Staff only)"""
|
|
try:
|
|
# Only admin/staff can create invoices
|
|
if current_user.role_id not in [1, 2]:
|
|
raise HTTPException(status_code=403, detail="Forbidden")
|
|
|
|
booking_id = invoice_data.get("booking_id")
|
|
if not booking_id:
|
|
raise HTTPException(status_code=400, detail="booking_id is required")
|
|
|
|
# Check if booking exists
|
|
booking = db.query(Booking).filter(Booking.id == booking_id).first()
|
|
if not booking:
|
|
raise HTTPException(status_code=404, detail="Booking not found")
|
|
|
|
# Create invoice
|
|
invoice = InvoiceService.create_invoice_from_booking(
|
|
booking_id=booking_id,
|
|
db=db,
|
|
created_by_id=current_user.id,
|
|
tax_rate=invoice_data.get("tax_rate", 0.0),
|
|
discount_amount=invoice_data.get("discount_amount", 0.0),
|
|
due_days=invoice_data.get("due_days", 30),
|
|
company_name=invoice_data.get("company_name"),
|
|
company_address=invoice_data.get("company_address"),
|
|
company_phone=invoice_data.get("company_phone"),
|
|
company_email=invoice_data.get("company_email"),
|
|
company_tax_id=invoice_data.get("company_tax_id"),
|
|
company_logo_url=invoice_data.get("company_logo_url"),
|
|
customer_tax_id=invoice_data.get("customer_tax_id"),
|
|
notes=invoice_data.get("notes"),
|
|
terms_and_conditions=invoice_data.get("terms_and_conditions"),
|
|
payment_instructions=invoice_data.get("payment_instructions"),
|
|
)
|
|
|
|
return {
|
|
"status": "success",
|
|
"message": "Invoice created successfully",
|
|
"data": {"invoice": invoice}
|
|
}
|
|
except HTTPException:
|
|
raise
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.put("/{id}")
|
|
async def update_invoice(
|
|
id: int,
|
|
invoice_data: dict,
|
|
current_user: User = Depends(authorize_roles("admin", "staff")),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Update an invoice (Admin/Staff only)"""
|
|
try:
|
|
invoice = db.query(Invoice).filter(Invoice.id == id).first()
|
|
if not invoice:
|
|
raise HTTPException(status_code=404, detail="Invoice not found")
|
|
|
|
# Update invoice
|
|
updated_invoice = InvoiceService.update_invoice(
|
|
invoice_id=id,
|
|
db=db,
|
|
updated_by_id=current_user.id,
|
|
**invoice_data
|
|
)
|
|
|
|
return {
|
|
"status": "success",
|
|
"message": "Invoice updated successfully",
|
|
"data": {"invoice": updated_invoice}
|
|
}
|
|
except HTTPException:
|
|
raise
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.post("/{id}/mark-paid")
|
|
async def mark_invoice_as_paid(
|
|
id: int,
|
|
payment_data: dict,
|
|
current_user: User = Depends(authorize_roles("admin", "staff")),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Mark an invoice as paid (Admin/Staff only)"""
|
|
try:
|
|
amount = payment_data.get("amount")
|
|
|
|
updated_invoice = InvoiceService.mark_invoice_as_paid(
|
|
invoice_id=id,
|
|
db=db,
|
|
amount=amount,
|
|
updated_by_id=current_user.id
|
|
)
|
|
|
|
return {
|
|
"status": "success",
|
|
"message": "Invoice marked as paid successfully",
|
|
"data": {"invoice": updated_invoice}
|
|
}
|
|
except HTTPException:
|
|
raise
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.delete("/{id}")
|
|
async def delete_invoice(
|
|
id: int,
|
|
current_user: User = Depends(authorize_roles("admin")),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Delete an invoice (Admin only)"""
|
|
try:
|
|
invoice = db.query(Invoice).filter(Invoice.id == id).first()
|
|
if not invoice:
|
|
raise HTTPException(status_code=404, detail="Invoice not found")
|
|
|
|
db.delete(invoice)
|
|
db.commit()
|
|
|
|
return {
|
|
"status": "success",
|
|
"message": "Invoice deleted successfully"
|
|
}
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
db.rollback()
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.get("/booking/{booking_id}")
|
|
async def get_invoices_by_booking(
|
|
booking_id: int,
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Get all invoices for a specific booking"""
|
|
try:
|
|
# Check if booking exists and user has access
|
|
booking = db.query(Booking).filter(Booking.id == booking_id).first()
|
|
if not booking:
|
|
raise HTTPException(status_code=404, detail="Booking not found")
|
|
|
|
# Check access: admin can see all, users can only see their own bookings
|
|
if current_user.role_id != 1 and booking.user_id != current_user.id:
|
|
raise HTTPException(status_code=403, detail="Forbidden")
|
|
|
|
result = InvoiceService.get_invoices(
|
|
db=db,
|
|
booking_id=booking_id
|
|
)
|
|
|
|
return {
|
|
"status": "success",
|
|
"data": result
|
|
}
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|