Files
orion/app/exceptions/invoice.py
Samir Boulahtit 319fba5d39 fix: resolve architecture validation violations
- Add invoice exceptions module with proper exception hierarchy
- Replace HTTPException with service-layer exceptions in invoice API
- Add InvoicePDFGeneratedResponse and InvoiceStatsResponse Pydantic models
- Replace db.commit() with db.flush() in services for proper transaction control
- Update invoice service to use exceptions from app/exceptions/invoice.py

All 14 errors and 14 warnings are now resolved. Validation passes with
only INFO-level findings.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 18:23:49 +01:00

119 lines
3.6 KiB
Python

# app/exceptions/invoice.py
"""
Invoice-related exceptions.
This module provides exception classes for invoice operations including:
- Invoice not found errors
- Invoice settings validation
- PDF generation errors
- Invoice status transitions
"""
from typing import Any
from .base import BusinessLogicException, ResourceNotFoundException, WizamartException
class InvoiceNotFoundException(ResourceNotFoundException):
"""Raised when an invoice is not found."""
def __init__(self, invoice_id: int | str):
super().__init__(
resource_type="Invoice",
identifier=str(invoice_id),
error_code="INVOICE_NOT_FOUND",
)
class InvoiceSettingsNotFoundException(ResourceNotFoundException):
"""Raised when invoice settings are not found for a vendor."""
def __init__(self, vendor_id: int):
super().__init__(
resource_type="InvoiceSettings",
identifier=str(vendor_id),
message="Invoice settings not found. Create settings first.",
error_code="INVOICE_SETTINGS_NOT_FOUND",
)
class InvoiceSettingsAlreadyExistException(WizamartException):
"""Raised when trying to create invoice settings that already exist."""
def __init__(self, vendor_id: int):
super().__init__(
message=f"Invoice settings already exist for vendor {vendor_id}",
error_code="INVOICE_SETTINGS_ALREADY_EXIST",
status_code=409,
details={"vendor_id": vendor_id},
)
class InvoiceValidationException(BusinessLogicException):
"""Raised when invoice data validation fails."""
def __init__(self, message: str, details: dict[str, Any] | None = None):
super().__init__(
message=message,
error_code="INVOICE_VALIDATION_ERROR",
details=details,
)
class InvoicePDFGenerationException(WizamartException):
"""Raised when PDF generation fails."""
def __init__(self, invoice_id: int, reason: str):
super().__init__(
message=f"Failed to generate PDF for invoice {invoice_id}: {reason}",
error_code="INVOICE_PDF_GENERATION_FAILED",
status_code=500,
details={"invoice_id": invoice_id, "reason": reason},
)
class InvoicePDFNotFoundException(ResourceNotFoundException):
"""Raised when invoice PDF file is not found."""
def __init__(self, invoice_id: int):
super().__init__(
resource_type="InvoicePDF",
identifier=str(invoice_id),
message="PDF file not found. Generate the PDF first.",
error_code="INVOICE_PDF_NOT_FOUND",
)
class InvalidInvoiceStatusTransitionException(BusinessLogicException):
"""Raised when an invalid invoice status transition is attempted."""
def __init__(
self,
current_status: str,
new_status: str,
reason: str | None = None,
):
message = f"Cannot change invoice status from '{current_status}' to '{new_status}'"
if reason:
message += f": {reason}"
super().__init__(
message=message,
error_code="INVALID_INVOICE_STATUS_TRANSITION",
details={
"current_status": current_status,
"new_status": new_status,
},
)
class OrderNotFoundException(ResourceNotFoundException):
"""Raised when an order for invoice creation is not found."""
def __init__(self, order_id: int):
super().__init__(
resource_type="Order",
identifier=str(order_id),
error_code="ORDER_NOT_FOUND_FOR_INVOICE",
)