Files
orion/app/modules/orders/exceptions.py
Samir Boulahtit 1b8a40f1ff
All checks were successful
CI / dependency-scanning (push) Successful in 27s
CI / docs (push) Successful in 35s
CI / ruff (push) Successful in 8s
CI / pytest (push) Successful in 34m22s
CI / validate (push) Successful in 19s
CI / deploy (push) Successful in 2m25s
feat(validators): add noqa suppression support to security and performance validators
- Add centralized _is_noqa_suppressed() to BaseValidator with normalization
  (accepts both SEC001 and SEC-001 formats for ruff compatibility)
- Wire noqa support into all 21 security and 18 performance check functions
- Add ruff external config for SEC/PERF/MOD/EXC codes in pyproject.toml
- Convert all 280 Python noqa comments to dashless format (ruff-compatible)
- Add site/ to IGNORE_PATTERNS (excludes mkdocs build output)
- Suppress 152 false positive findings (test passwords, seed data, validator
  self-references, Apple Wallet SHA1, etc.)
- Security: 79 errors → 0, 60 warnings → 0
- Performance: 80 warnings → 77 (3 test script suppressions)
- Add proposal doc with noqa inventory and remaining findings recommendations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 22:56:56 +01:00

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: MOD025
"""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: MOD025
"""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: MOD025
"""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: MOD025
"""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: MOD025
"""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",
)