# app/modules/orders/exceptions.py """ Orders module exceptions. This module provides exception classes for order operations including: - Order management (not found, validation, status) - Order item exceptions (unresolved, resolution) - Invoice operations (PDF generation, status transitions) """ from typing import Any from app.exceptions.base import ( BusinessLogicException, OrionException, ResourceNotFoundException, ValidationException, ) __all__ = [ # Order exceptions "OrderNotFoundException", "OrderAlreadyExistsException", "OrderValidationException", "InvalidOrderStatusException", "OrderCannotBeCancelledException", # Order item exception exceptions "OrderItemExceptionNotFoundException", "OrderHasUnresolvedExceptionsException", "ExceptionAlreadyResolvedException", "InvalidProductForExceptionException", # Invoice exceptions "InvoiceNotFoundException", "InvoiceSettingsNotFoundException", "InvoiceSettingsAlreadyExistException", "InvoiceValidationException", "InvoicePDFGenerationException", "InvoicePDFNotFoundException", "InvalidInvoiceStatusTransitionException", "OrderNotFoundForInvoiceException", ] # ============================================================================= # Order Exceptions # ============================================================================= class OrderNotFoundException(ResourceNotFoundException): """Raised when an order is not found.""" def __init__(self, order_identifier: str): super().__init__( resource_type="Order", identifier=order_identifier, message=f"Order '{order_identifier}' not found", error_code="ORDER_NOT_FOUND", ) class OrderAlreadyExistsException(ValidationException): # noqa: MOD-025 """Raised when trying to create a duplicate order.""" def __init__(self, order_number: str): super().__init__( message=f"Order with number '{order_number}' already exists", details={"order_number": order_number}, ) self.error_code = "ORDER_ALREADY_EXISTS" class OrderValidationException(ValidationException): """Raised when order data validation fails.""" def __init__(self, message: str, details: dict | None = None): super().__init__(message=message, details=details) self.error_code = "ORDER_VALIDATION_FAILED" class InvalidOrderStatusException(BusinessLogicException): # noqa: MOD-025 """Raised when trying to set an invalid order status.""" def __init__(self, current_status: str, new_status: str): super().__init__( message=f"Cannot change order status from '{current_status}' to '{new_status}'", error_code="INVALID_ORDER_STATUS_CHANGE", details={"current_status": current_status, "new_status": new_status}, ) class OrderCannotBeCancelledException(BusinessLogicException): # noqa: MOD-025 """Raised when order cannot be cancelled.""" def __init__(self, order_number: str, reason: str): super().__init__( message=f"Order '{order_number}' cannot be cancelled: {reason}", error_code="ORDER_CANNOT_BE_CANCELLED", details={"order_number": order_number, "reason": reason}, ) # ============================================================================= # Order Item Exception Exceptions # ============================================================================= class OrderItemExceptionNotFoundException(ResourceNotFoundException): """Raised when an order item exception is not found.""" def __init__(self, exception_id: int | str): super().__init__( resource_type="OrderItemException", identifier=str(exception_id), error_code="ORDER_ITEM_EXCEPTION_NOT_FOUND", ) class OrderHasUnresolvedExceptionsException(BusinessLogicException): """Raised when trying to confirm an order with unresolved exceptions.""" def __init__(self, order_id: int, unresolved_count: int): super().__init__( message=( f"Order has {unresolved_count} unresolved product exception(s). " f"Please resolve all exceptions before confirming the order." ), error_code="ORDER_HAS_UNRESOLVED_EXCEPTIONS", details={ "order_id": order_id, "unresolved_count": unresolved_count, }, ) class ExceptionAlreadyResolvedException(BusinessLogicException): """Raised when trying to resolve an already resolved exception.""" def __init__(self, exception_id: int): super().__init__( message=f"Exception {exception_id} has already been resolved", error_code="EXCEPTION_ALREADY_RESOLVED", details={"exception_id": exception_id}, ) class InvalidProductForExceptionException(BusinessLogicException): """Raised when the product provided for resolution is invalid.""" def __init__(self, product_id: int, reason: str): super().__init__( message=f"Cannot use product {product_id} for resolution: {reason}", error_code="INVALID_PRODUCT_FOR_EXCEPTION", details={"product_id": product_id, "reason": reason}, ) # ============================================================================= # Invoice Exceptions # ============================================================================= 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 store.""" def __init__(self, store_id: int): super().__init__( resource_type="InvoiceSettings", identifier=str(store_id), message="Invoice settings not found. Create settings first.", error_code="INVOICE_SETTINGS_NOT_FOUND", ) class InvoiceSettingsAlreadyExistException(OrionException): # noqa: MOD-025 """Raised when trying to create invoice settings that already exist.""" def __init__(self, store_id: int): super().__init__( message=f"Invoice settings already exist for store {store_id}", error_code="INVOICE_SETTINGS_ALREADY_EXIST", status_code=409, details={"store_id": store_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(OrionException): """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 OrderNotFoundForInvoiceException(ResourceNotFoundException): # noqa: MOD-025 """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", )