Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart with Orion/orion/ORION across 184 files. This includes database identifiers, email addresses, domain references, R2 bucket names, DNS prefixes, encryption salt, Celery app name, config defaults, Docker configs, CI configs, documentation, seed data, and templates. Renames homepage-wizamart.html template to homepage-orion.html. Fixes duplicate file_pattern key in api.yaml architecture rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
264 lines
8.7 KiB
Python
264 lines
8.7 KiB
Python
# 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",
|
|
)
|