This commit is contained in:
Iliyan Angelov
2025-11-28 02:40:05 +02:00
parent 627959f52b
commit 312f85530c
246 changed files with 23535 additions and 3428 deletions

View File

@@ -1,8 +1,9 @@
from fastapi import APIRouter, Depends, HTTPException, status, Query
from fastapi import APIRouter, Depends, HTTPException, status, Query, Request
from sqlalchemy.orm import Session
from typing import Optional
from datetime import datetime
from ..config.database import get_db
from ..config.logging_config import get_logger
from ..middleware.auth import get_current_user, authorize_roles
from ..models.user import User
from ..models.invoice import Invoice, InvoiceStatus
@@ -10,15 +11,25 @@ from ..models.booking import Booking
from ..services.invoice_service import InvoiceService
from ..utils.role_helpers import can_access_all_invoices, can_create_invoices
from ..utils.response_helpers import success_response
from ..utils.request_helpers import get_request_id
from ..schemas.invoice import (
CreateInvoiceRequest,
UpdateInvoiceRequest,
MarkInvoicePaidRequest
)
logger = get_logger(__name__)
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)):
async def get_invoices(request: Request, 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)):
try:
user_id = None if can_access_all_invoices(current_user, db) 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 success_response(data=result)
except Exception as e:
db.rollback()
logger.error(f'Error fetching invoices: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.get('/{id}')
@@ -33,64 +44,96 @@ async def get_invoice_by_id(id: int, current_user: User=Depends(get_current_user
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f'Error fetching invoice by id: {str(e)}', exc_info=True)
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)):
async def create_invoice(request: Request, invoice_data: CreateInvoiceRequest, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
try:
if not can_create_invoices(current_user, db):
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')
try:
booking_id = int(booking_id)
except (ValueError, TypeError):
raise HTTPException(status_code=400, detail='booking_id must be a valid integer')
booking_id = invoice_data.booking_id
booking = db.query(Booking).filter(Booking.id == booking_id).first()
if not booking:
raise HTTPException(status_code=404, detail='Booking not found')
invoice_kwargs = {'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')}
invoice_kwargs = {
'company_name': invoice_data.company_name,
'company_address': invoice_data.company_address,
'company_phone': invoice_data.company_phone,
'company_email': invoice_data.company_email,
'company_tax_id': invoice_data.company_tax_id,
'company_logo_url': invoice_data.company_logo_url,
'customer_tax_id': invoice_data.customer_tax_id,
'notes': invoice_data.notes,
'terms_and_conditions': invoice_data.terms_and_conditions,
'payment_instructions': invoice_data.payment_instructions
}
invoice_notes = invoice_kwargs.get('notes', '')
if booking.promotion_code:
promotion_note = f'Promotion Code: {booking.promotion_code}'
invoice_notes = f'{promotion_note}\n{invoice_notes}'.strip() if invoice_notes else promotion_note
invoice_kwargs['notes'] = invoice_notes
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), **invoice_kwargs)
request_id = get_request_id(request)
invoice = InvoiceService.create_invoice_from_booking(
booking_id=booking_id,
db=db,
created_by_id=current_user.id,
tax_rate=invoice_data.tax_rate,
discount_amount=invoice_data.discount_amount,
due_days=invoice_data.due_days,
request_id=request_id,
**invoice_kwargs
)
return success_response(data={'invoice': invoice}, message='Invoice created successfully')
except HTTPException:
raise
except ValueError as e:
db.rollback()
logger.error(f'Error creating invoice: {str(e)}', exc_info=True)
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
db.rollback()
logger.error(f'Error creating invoice: {str(e)}', exc_info=True)
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', 'accountant')), db: Session=Depends(get_db)):
async def update_invoice(request: Request, id: int, invoice_data: UpdateInvoiceRequest, current_user: User=Depends(authorize_roles('admin', 'staff', 'accountant')), db: Session=Depends(get_db)):
try:
invoice = db.query(Invoice).filter(Invoice.id == id).first()
if not invoice:
raise HTTPException(status_code=404, detail='Invoice not found')
updated_invoice = InvoiceService.update_invoice(invoice_id=id, db=db, updated_by_id=current_user.id, **invoice_data)
request_id = get_request_id(request)
invoice_dict = invoice_data.model_dump(exclude_unset=True)
updated_invoice = InvoiceService.update_invoice(invoice_id=id, db=db, updated_by_id=current_user.id, request_id=request_id, **invoice_dict)
return success_response(data={'invoice': updated_invoice}, message='Invoice updated successfully')
except HTTPException:
raise
except ValueError as e:
db.rollback()
logger.error(f'Error updating invoice: {str(e)}', exc_info=True)
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
db.rollback()
logger.error(f'Error updating invoice: {str(e)}', exc_info=True)
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', 'accountant')), db: Session=Depends(get_db)):
async def mark_invoice_as_paid(request: Request, id: int, payment_data: MarkInvoicePaidRequest, current_user: User=Depends(authorize_roles('admin', 'staff', 'accountant')), db: Session=Depends(get_db)):
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)
request_id = get_request_id(request)
amount = payment_data.amount
updated_invoice = InvoiceService.mark_invoice_as_paid(invoice_id=id, db=db, amount=amount, updated_by_id=current_user.id, request_id=request_id)
return success_response(data={'invoice': updated_invoice}, message='Invoice marked as paid successfully')
except HTTPException:
raise
except ValueError as e:
db.rollback()
logger.error(f'Error marking invoice as paid: {str(e)}', exc_info=True)
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
db.rollback()
logger.error(f'Error marking invoice as paid: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.delete('/{id}')
@@ -121,4 +164,6 @@ async def get_invoices_by_booking(booking_id: int, current_user: User=Depends(ge
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f'Error fetching invoices by booking: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))