refactor: migrate templates and static files to self-contained modules
Templates Migration: - Migrate admin templates to modules (tenancy, billing, monitoring, marketplace, etc.) - Migrate vendor templates to modules (tenancy, billing, orders, messaging, etc.) - Migrate storefront templates to modules (catalog, customers, orders, cart, checkout, cms) - Migrate public templates to modules (billing, marketplace, cms) - Keep shared templates in app/templates/ (base.html, errors/, partials/, macros/) - Migrate letzshop partials to marketplace module Static Files Migration: - Migrate admin JS to modules: tenancy (23 files), core (5 files), monitoring (1 file) - Migrate vendor JS to modules: tenancy (4 files), core (2 files) - Migrate shared JS: vendor-selector.js to core, media-picker.js to cms - Migrate storefront JS: storefront-layout.js to core - Keep framework JS in static/ (api-client, utils, money, icons, log-config, lib/) - Update all template references to use module_static paths Naming Consistency: - Rename static/platform/ to static/public/ - Rename app/templates/platform/ to app/templates/public/ - Update all extends and static references Documentation: - Update module-system.md with shared templates documentation - Update frontend-structure.md with new module JS organization Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,45 +1,32 @@
|
||||
# app/exceptions/__init__.py
|
||||
"""
|
||||
Custom exception classes for the API.
|
||||
Base exception classes for the application.
|
||||
|
||||
This module provides frontend-friendly exceptions with consistent error codes,
|
||||
messages, and HTTP status mappings.
|
||||
This module provides only framework-level exceptions. Domain-specific exceptions
|
||||
have been moved to their respective modules:
|
||||
|
||||
- tenancy: VendorNotFoundException, CompanyNotFoundException, etc.
|
||||
- orders: OrderNotFoundException, InvoiceNotFoundException, etc.
|
||||
- inventory: InventoryNotFoundException, InsufficientInventoryException, etc.
|
||||
- billing: TierNotFoundException, SubscriptionNotFoundException, etc.
|
||||
- marketplace: ImportJobNotFoundException, MarketplaceProductNotFoundException, etc.
|
||||
- messaging: ConversationNotFoundException, MessageNotFoundException, etc.
|
||||
- customers: CustomerNotFoundException, AddressNotFoundException, etc.
|
||||
- cart: CartItemNotFoundException, EmptyCartException, etc.
|
||||
- catalog: ProductNotFoundException, ProductValidationException, etc.
|
||||
- cms: ContentPageNotFoundException, MediaNotFoundException, etc.
|
||||
- monitoring: ScanNotFoundException, ViolationNotFoundException, etc.
|
||||
|
||||
Import pattern:
|
||||
# Base exceptions (framework-level)
|
||||
from app.exceptions import ValidationException, ResourceNotFoundException
|
||||
|
||||
# Domain exceptions (module-level)
|
||||
from app.modules.orders.exceptions import OrderNotFoundException
|
||||
from app.modules.tenancy.exceptions import VendorNotFoundException
|
||||
"""
|
||||
|
||||
# Address exceptions
|
||||
from .address import (
|
||||
AddressLimitExceededException,
|
||||
AddressNotFoundException,
|
||||
InvalidAddressTypeException,
|
||||
)
|
||||
|
||||
# Admin exceptions
|
||||
from .admin import (
|
||||
AdminOperationException,
|
||||
BulkOperationException,
|
||||
CannotModifyAdminException,
|
||||
CannotModifySelfException,
|
||||
ConfirmationRequiredException,
|
||||
InvalidAdminActionException,
|
||||
UserCannotBeDeletedException,
|
||||
UserNotFoundException,
|
||||
UserRoleChangeException,
|
||||
UserStatusChangeException,
|
||||
VendorVerificationException,
|
||||
)
|
||||
|
||||
# Authentication exceptions
|
||||
from .auth import (
|
||||
AdminRequiredException,
|
||||
InsufficientPermissionsException,
|
||||
InvalidCredentialsException,
|
||||
InvalidTokenException,
|
||||
TokenExpiredException,
|
||||
UserAlreadyExistsException,
|
||||
UserNotActiveException,
|
||||
)
|
||||
|
||||
# Base exceptions
|
||||
# Base exceptions - these are the only exports from root
|
||||
from .base import (
|
||||
AuthenticationException,
|
||||
AuthorizationException,
|
||||
@@ -53,425 +40,21 @@ from .base import (
|
||||
WizamartException,
|
||||
)
|
||||
|
||||
# Billing exceptions
|
||||
from .billing import (
|
||||
InvalidWebhookSignatureException,
|
||||
NoActiveSubscriptionException,
|
||||
PaymentSystemNotConfiguredException,
|
||||
StripePriceNotConfiguredException,
|
||||
SubscriptionAlreadyCancelledException,
|
||||
SubscriptionNotCancelledException,
|
||||
TierNotFoundException,
|
||||
WebhookMissingSignatureException,
|
||||
)
|
||||
|
||||
# Cart exceptions
|
||||
from .cart import (
|
||||
CartItemNotFoundException,
|
||||
CartValidationException,
|
||||
EmptyCartException,
|
||||
InsufficientInventoryForCartException,
|
||||
InvalidCartQuantityException,
|
||||
ProductNotAvailableForCartException,
|
||||
)
|
||||
|
||||
# Code quality exceptions
|
||||
from .code_quality import (
|
||||
InvalidViolationStatusException,
|
||||
ScanExecutionException,
|
||||
ScanNotFoundException,
|
||||
ScanParseException,
|
||||
ScanTimeoutException,
|
||||
ViolationNotFoundException,
|
||||
ViolationOperationException,
|
||||
)
|
||||
|
||||
# Company exceptions
|
||||
from .company import (
|
||||
CompanyAlreadyExistsException,
|
||||
CompanyHasVendorsException,
|
||||
CompanyNotActiveException,
|
||||
CompanyNotFoundException,
|
||||
CompanyNotVerifiedException,
|
||||
CompanyValidationException,
|
||||
InvalidCompanyDataException,
|
||||
UnauthorizedCompanyAccessException,
|
||||
)
|
||||
|
||||
# Customer exceptions
|
||||
from .customer import (
|
||||
CustomerAlreadyExistsException,
|
||||
CustomerAuthorizationException,
|
||||
CustomerNotActiveException,
|
||||
CustomerNotFoundException,
|
||||
CustomerValidationException,
|
||||
DuplicateCustomerEmailException,
|
||||
InvalidCustomerCredentialsException,
|
||||
)
|
||||
|
||||
# Invoice exceptions
|
||||
from .invoice import (
|
||||
InvoiceNotFoundException,
|
||||
InvoicePDFGenerationException,
|
||||
InvoicePDFNotFoundException,
|
||||
InvoiceSettingsAlreadyExistException,
|
||||
InvoiceSettingsNotFoundException,
|
||||
InvoiceValidationException,
|
||||
InvalidInvoiceStatusTransitionException,
|
||||
OrderNotFoundException as OrderNotFoundForInvoiceException,
|
||||
)
|
||||
|
||||
# Inventory exceptions
|
||||
from .inventory import (
|
||||
InsufficientInventoryException,
|
||||
InvalidInventoryOperationException,
|
||||
InvalidQuantityException,
|
||||
InventoryNotFoundException,
|
||||
InventoryValidationException,
|
||||
LocationNotFoundException,
|
||||
NegativeInventoryException,
|
||||
)
|
||||
|
||||
# Message exceptions
|
||||
from .message import (
|
||||
AttachmentNotFoundException,
|
||||
ConversationClosedException,
|
||||
ConversationNotFoundException,
|
||||
InvalidConversationTypeException,
|
||||
InvalidRecipientTypeException,
|
||||
MessageAttachmentException,
|
||||
MessageNotFoundException,
|
||||
UnauthorizedConversationAccessException,
|
||||
)
|
||||
|
||||
# Marketplace import job exceptions
|
||||
from .marketplace_import_job import (
|
||||
ImportJobAlreadyProcessingException,
|
||||
ImportJobCannotBeCancelledException,
|
||||
ImportJobCannotBeDeletedException,
|
||||
ImportJobNotFoundException,
|
||||
ImportJobNotOwnedException,
|
||||
ImportRateLimitException,
|
||||
InvalidImportDataException,
|
||||
InvalidMarketplaceException,
|
||||
MarketplaceConnectionException,
|
||||
MarketplaceDataParsingException,
|
||||
MarketplaceImportException,
|
||||
)
|
||||
|
||||
# Marketplace product exceptions
|
||||
from .marketplace_product import (
|
||||
InvalidGTINException,
|
||||
InvalidMarketplaceProductDataException,
|
||||
MarketplaceProductAlreadyExistsException,
|
||||
MarketplaceProductCSVImportException,
|
||||
MarketplaceProductNotFoundException,
|
||||
MarketplaceProductValidationException,
|
||||
)
|
||||
|
||||
# Order exceptions
|
||||
from .order import (
|
||||
InvalidOrderStatusException,
|
||||
OrderAlreadyExistsException,
|
||||
OrderCannotBeCancelledException,
|
||||
OrderNotFoundException,
|
||||
OrderValidationException,
|
||||
)
|
||||
|
||||
# Order item exception exceptions
|
||||
from .order_item_exception import (
|
||||
ExceptionAlreadyResolvedException,
|
||||
InvalidProductForExceptionException,
|
||||
OrderHasUnresolvedExceptionsException,
|
||||
OrderItemExceptionNotFoundException,
|
||||
)
|
||||
|
||||
# Product exceptions
|
||||
from .product import (
|
||||
CannotDeleteProductWithInventoryException,
|
||||
CannotDeleteProductWithOrdersException,
|
||||
InvalidProductDataException,
|
||||
ProductAlreadyExistsException,
|
||||
ProductNotActiveException,
|
||||
ProductNotFoundException,
|
||||
ProductNotInCatalogException,
|
||||
ProductValidationException,
|
||||
)
|
||||
|
||||
# Team exceptions
|
||||
from .team import (
|
||||
CannotModifyOwnRoleException,
|
||||
CannotRemoveOwnerException,
|
||||
InsufficientTeamPermissionsException,
|
||||
InvalidInvitationDataException,
|
||||
InvalidInvitationTokenException,
|
||||
InvalidRoleException,
|
||||
MaxTeamMembersReachedException,
|
||||
RoleNotFoundException,
|
||||
TeamInvitationAlreadyAcceptedException,
|
||||
TeamInvitationExpiredException,
|
||||
TeamInvitationNotFoundException,
|
||||
TeamMemberAlreadyExistsException,
|
||||
TeamMemberNotFoundException,
|
||||
TeamValidationException,
|
||||
UnauthorizedTeamActionException,
|
||||
)
|
||||
|
||||
# Vendor exceptions
|
||||
from .vendor import (
|
||||
InsufficientVendorPermissionsException,
|
||||
InvalidVendorDataException,
|
||||
MaxVendorsReachedException,
|
||||
UnauthorizedVendorAccessException,
|
||||
VendorAccessDeniedException,
|
||||
VendorAlreadyExistsException,
|
||||
VendorNotActiveException,
|
||||
VendorNotFoundException,
|
||||
VendorNotVerifiedException,
|
||||
VendorOwnerOnlyException,
|
||||
VendorValidationException,
|
||||
)
|
||||
|
||||
# Vendor domain exceptions
|
||||
from .vendor_domain import (
|
||||
DNSVerificationException,
|
||||
DomainAlreadyVerifiedException,
|
||||
DomainNotVerifiedException,
|
||||
DomainVerificationFailedException,
|
||||
InvalidDomainFormatException,
|
||||
MaxDomainsReachedException,
|
||||
MultiplePrimaryDomainsException,
|
||||
ReservedDomainException,
|
||||
UnauthorizedDomainAccessException,
|
||||
VendorDomainAlreadyExistsException,
|
||||
VendorDomainNotFoundException,
|
||||
)
|
||||
|
||||
# Vendor theme exceptions
|
||||
from .vendor_theme import (
|
||||
InvalidColorFormatException,
|
||||
InvalidFontFamilyException,
|
||||
InvalidThemeDataException,
|
||||
ThemeOperationException,
|
||||
ThemePresetAlreadyAppliedException,
|
||||
ThemePresetNotFoundException,
|
||||
ThemeValidationException,
|
||||
VendorThemeNotFoundException,
|
||||
)
|
||||
|
||||
# Onboarding exceptions
|
||||
from .onboarding import (
|
||||
LetzshopConnectionFailedException,
|
||||
OnboardingAlreadyCompletedException,
|
||||
OnboardingCsvUrlRequiredException,
|
||||
OnboardingNotFoundException,
|
||||
OnboardingStepOrderException,
|
||||
OnboardingSyncJobNotFoundException,
|
||||
OnboardingSyncNotCompleteException,
|
||||
)
|
||||
|
||||
# Feature management exceptions
|
||||
from .feature import (
|
||||
FeatureNotFoundError,
|
||||
InvalidFeatureCodesError,
|
||||
TierNotFoundError,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Base exceptions
|
||||
# Base exception class
|
||||
"WizamartException",
|
||||
# Validation and business logic
|
||||
"ValidationException",
|
||||
"BusinessLogicException",
|
||||
# Authentication and authorization
|
||||
"AuthenticationException",
|
||||
"AuthorizationException",
|
||||
# Resource operations
|
||||
"ResourceNotFoundException",
|
||||
"ConflictException",
|
||||
"BusinessLogicException",
|
||||
# External services
|
||||
"ExternalServiceException",
|
||||
"RateLimitException",
|
||||
"ServiceUnavailableException",
|
||||
# Auth exceptions
|
||||
"InvalidCredentialsException",
|
||||
"TokenExpiredException",
|
||||
"InvalidTokenException",
|
||||
"InsufficientPermissionsException",
|
||||
"UserNotActiveException",
|
||||
"AdminRequiredException",
|
||||
"UserAlreadyExistsException",
|
||||
# Customer exceptions
|
||||
"CustomerNotFoundException",
|
||||
"CustomerAlreadyExistsException",
|
||||
"DuplicateCustomerEmailException",
|
||||
"CustomerNotActiveException",
|
||||
"InvalidCustomerCredentialsException",
|
||||
"CustomerValidationException",
|
||||
"CustomerAuthorizationException",
|
||||
# Team exceptions
|
||||
"TeamMemberNotFoundException",
|
||||
"TeamMemberAlreadyExistsException",
|
||||
"TeamInvitationNotFoundException",
|
||||
"TeamInvitationExpiredException",
|
||||
"TeamInvitationAlreadyAcceptedException",
|
||||
"UnauthorizedTeamActionException",
|
||||
"CannotRemoveOwnerException",
|
||||
"CannotModifyOwnRoleException",
|
||||
"RoleNotFoundException",
|
||||
"InvalidRoleException",
|
||||
"InsufficientTeamPermissionsException",
|
||||
"MaxTeamMembersReachedException",
|
||||
"TeamValidationException",
|
||||
"InvalidInvitationDataException",
|
||||
"InvalidInvitationTokenException",
|
||||
# Invoice exceptions
|
||||
"InvoiceNotFoundException",
|
||||
"InvoiceSettingsNotFoundException",
|
||||
"InvoiceSettingsAlreadyExistException",
|
||||
"InvoiceValidationException",
|
||||
"InvoicePDFGenerationException",
|
||||
"InvoicePDFNotFoundException",
|
||||
"InvalidInvoiceStatusTransitionException",
|
||||
"OrderNotFoundForInvoiceException",
|
||||
# Inventory exceptions
|
||||
"InventoryNotFoundException",
|
||||
"InsufficientInventoryException",
|
||||
"InvalidInventoryOperationException",
|
||||
"InventoryValidationException",
|
||||
"NegativeInventoryException",
|
||||
"InvalidQuantityException",
|
||||
"LocationNotFoundException",
|
||||
# Vendor exceptions
|
||||
"InsufficientVendorPermissionsException",
|
||||
"InvalidVendorDataException",
|
||||
"MaxVendorsReachedException",
|
||||
"UnauthorizedVendorAccessException",
|
||||
"VendorAccessDeniedException",
|
||||
"VendorAlreadyExistsException",
|
||||
"VendorNotActiveException",
|
||||
"VendorNotFoundException",
|
||||
"VendorNotVerifiedException",
|
||||
"VendorOwnerOnlyException",
|
||||
"VendorValidationException",
|
||||
# Vendor Domain
|
||||
"VendorDomainNotFoundException",
|
||||
"VendorDomainAlreadyExistsException",
|
||||
"InvalidDomainFormatException",
|
||||
"ReservedDomainException",
|
||||
"DomainNotVerifiedException",
|
||||
"DomainVerificationFailedException",
|
||||
"DomainAlreadyVerifiedException",
|
||||
"MultiplePrimaryDomainsException",
|
||||
"DNSVerificationException",
|
||||
"MaxDomainsReachedException",
|
||||
"UnauthorizedDomainAccessException",
|
||||
# Vendor Theme
|
||||
"VendorThemeNotFoundException",
|
||||
"InvalidThemeDataException",
|
||||
"ThemePresetNotFoundException",
|
||||
"ThemeValidationException",
|
||||
"ThemePresetAlreadyAppliedException",
|
||||
"InvalidColorFormatException",
|
||||
"InvalidFontFamilyException",
|
||||
"ThemeOperationException",
|
||||
# Product exceptions
|
||||
"ProductNotFoundException",
|
||||
"ProductAlreadyExistsException",
|
||||
"ProductNotInCatalogException",
|
||||
"ProductNotActiveException",
|
||||
"InvalidProductDataException",
|
||||
"ProductValidationException",
|
||||
"CannotDeleteProductWithInventoryException",
|
||||
"CannotDeleteProductWithOrdersException",
|
||||
# Order exceptions
|
||||
"OrderNotFoundException",
|
||||
"OrderAlreadyExistsException",
|
||||
"OrderValidationException",
|
||||
"InvalidOrderStatusException",
|
||||
"OrderCannotBeCancelledException",
|
||||
# Order item exception exceptions
|
||||
"OrderItemExceptionNotFoundException",
|
||||
"OrderHasUnresolvedExceptionsException",
|
||||
"ExceptionAlreadyResolvedException",
|
||||
"InvalidProductForExceptionException",
|
||||
# Cart exceptions
|
||||
"CartItemNotFoundException",
|
||||
"EmptyCartException",
|
||||
"CartValidationException",
|
||||
"InsufficientInventoryForCartException",
|
||||
"InvalidCartQuantityException",
|
||||
"ProductNotAvailableForCartException",
|
||||
# Company exceptions
|
||||
"CompanyNotFoundException",
|
||||
"CompanyAlreadyExistsException",
|
||||
"CompanyNotActiveException",
|
||||
"CompanyNotVerifiedException",
|
||||
"UnauthorizedCompanyAccessException",
|
||||
"InvalidCompanyDataException",
|
||||
"CompanyValidationException",
|
||||
"CompanyHasVendorsException",
|
||||
# MarketplaceProduct exceptions
|
||||
"MarketplaceProductNotFoundException",
|
||||
"MarketplaceProductAlreadyExistsException",
|
||||
"InvalidMarketplaceProductDataException",
|
||||
"MarketplaceProductValidationException",
|
||||
"InvalidGTINException",
|
||||
"MarketplaceProductCSVImportException",
|
||||
# Marketplace import exceptions
|
||||
"MarketplaceImportException",
|
||||
"ImportJobNotFoundException",
|
||||
"ImportJobNotOwnedException",
|
||||
"InvalidImportDataException",
|
||||
"ImportJobCannotBeCancelledException",
|
||||
"ImportJobCannotBeDeletedException",
|
||||
"MarketplaceConnectionException",
|
||||
"MarketplaceDataParsingException",
|
||||
"ImportRateLimitException",
|
||||
"InvalidMarketplaceException",
|
||||
"ImportJobAlreadyProcessingException",
|
||||
# Admin exceptions
|
||||
"UserNotFoundException",
|
||||
"UserStatusChangeException",
|
||||
"VendorVerificationException",
|
||||
"AdminOperationException",
|
||||
"CannotModifyAdminException",
|
||||
"CannotModifySelfException",
|
||||
"InvalidAdminActionException",
|
||||
"BulkOperationException",
|
||||
"ConfirmationRequiredException",
|
||||
# Code quality exceptions
|
||||
"ViolationNotFoundException",
|
||||
"ScanNotFoundException",
|
||||
"ScanExecutionException",
|
||||
"ScanTimeoutException",
|
||||
"ScanParseException",
|
||||
"ViolationOperationException",
|
||||
"InvalidViolationStatusException",
|
||||
# Message exceptions
|
||||
"ConversationNotFoundException",
|
||||
"MessageNotFoundException",
|
||||
"ConversationClosedException",
|
||||
"MessageAttachmentException",
|
||||
"UnauthorizedConversationAccessException",
|
||||
"InvalidConversationTypeException",
|
||||
"InvalidRecipientTypeException",
|
||||
"AttachmentNotFoundException",
|
||||
# Billing exceptions
|
||||
"PaymentSystemNotConfiguredException",
|
||||
"TierNotFoundException",
|
||||
"StripePriceNotConfiguredException",
|
||||
"NoActiveSubscriptionException",
|
||||
"SubscriptionNotCancelledException",
|
||||
"SubscriptionAlreadyCancelledException",
|
||||
"InvalidWebhookSignatureException",
|
||||
"WebhookMissingSignatureException",
|
||||
# Onboarding exceptions
|
||||
"OnboardingNotFoundException",
|
||||
"OnboardingStepOrderException",
|
||||
"OnboardingAlreadyCompletedException",
|
||||
"LetzshopConnectionFailedException",
|
||||
"OnboardingCsvUrlRequiredException",
|
||||
"OnboardingSyncJobNotFoundException",
|
||||
"OnboardingSyncNotCompleteException",
|
||||
# Feature exceptions
|
||||
"FeatureNotFoundError",
|
||||
"TierNotFoundError",
|
||||
"InvalidFeatureCodesError",
|
||||
# Rate limiting
|
||||
"RateLimitException",
|
||||
]
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# app/exceptions/address.py
|
||||
"""
|
||||
Address-related custom exceptions.
|
||||
|
||||
Used for customer address management operations.
|
||||
"""
|
||||
|
||||
from .base import BusinessLogicException, ResourceNotFoundException
|
||||
|
||||
|
||||
class AddressNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a customer address is not found."""
|
||||
|
||||
def __init__(self, address_id: str | int):
|
||||
super().__init__(
|
||||
resource_type="Address",
|
||||
identifier=str(address_id),
|
||||
)
|
||||
|
||||
|
||||
class AddressLimitExceededException(BusinessLogicException):
|
||||
"""Raised when customer exceeds maximum number of addresses."""
|
||||
|
||||
def __init__(self, max_addresses: int = 10):
|
||||
super().__init__(
|
||||
message=f"Maximum number of addresses ({max_addresses}) reached",
|
||||
error_code="ADDRESS_LIMIT_EXCEEDED",
|
||||
)
|
||||
|
||||
|
||||
class InvalidAddressTypeException(BusinessLogicException):
|
||||
"""Raised when an invalid address type is provided."""
|
||||
|
||||
def __init__(self, address_type: str):
|
||||
super().__init__(
|
||||
message=f"Invalid address type '{address_type}'. Must be 'shipping' or 'billing'",
|
||||
error_code="INVALID_ADDRESS_TYPE",
|
||||
)
|
||||
@@ -1,248 +0,0 @@
|
||||
# app/exceptions/admin.py
|
||||
"""
|
||||
Admin operations specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .base import (
|
||||
AuthorizationException,
|
||||
BusinessLogicException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class UserNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when user is not found in admin operations."""
|
||||
|
||||
def __init__(self, user_identifier: str, identifier_type: str = "ID"):
|
||||
if identifier_type.lower() == "username":
|
||||
message = f"User with username '{user_identifier}' not found"
|
||||
elif identifier_type.lower() == "email":
|
||||
message = f"User with email '{user_identifier}' not found"
|
||||
else:
|
||||
message = f"User with ID '{user_identifier}' not found"
|
||||
|
||||
super().__init__(
|
||||
resource_type="User",
|
||||
identifier=user_identifier,
|
||||
message=message,
|
||||
error_code="USER_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class UserStatusChangeException(BusinessLogicException):
|
||||
"""Raised when user status cannot be changed."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user_id: int,
|
||||
current_status: str,
|
||||
attempted_action: str,
|
||||
reason: str | None = None,
|
||||
):
|
||||
message = f"Cannot {attempted_action} user {user_id} (current status: {current_status})"
|
||||
if reason:
|
||||
message += f": {reason}"
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="USER_STATUS_CHANGE_FAILED",
|
||||
details={
|
||||
"user_id": user_id,
|
||||
"current_status": current_status,
|
||||
"attempted_action": attempted_action,
|
||||
"reason": reason,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class AdminOperationException(BusinessLogicException):
|
||||
"""Raised when admin operation fails."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
operation: str,
|
||||
reason: str,
|
||||
target_type: str | None = None,
|
||||
target_id: str | None = None,
|
||||
):
|
||||
message = f"Admin operation '{operation}' failed: {reason}"
|
||||
|
||||
details = {
|
||||
"operation": operation,
|
||||
"reason": reason,
|
||||
}
|
||||
|
||||
if target_type:
|
||||
details["target_type"] = target_type
|
||||
if target_id:
|
||||
details["target_id"] = target_id
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="ADMIN_OPERATION_FAILED",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class CannotModifyAdminException(AuthorizationException):
|
||||
"""Raised when trying to modify another admin user."""
|
||||
|
||||
def __init__(self, target_user_id: int, admin_user_id: int):
|
||||
super().__init__(
|
||||
message=f"Cannot modify admin user {target_user_id}",
|
||||
error_code="CANNOT_MODIFY_ADMIN",
|
||||
details={
|
||||
"target_user_id": target_user_id,
|
||||
"admin_user_id": admin_user_id,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class CannotModifySelfException(BusinessLogicException):
|
||||
"""Raised when admin tries to modify their own status."""
|
||||
|
||||
def __init__(self, user_id: int, operation: str):
|
||||
super().__init__(
|
||||
message=f"Cannot perform '{operation}' on your own account",
|
||||
error_code="CANNOT_MODIFY_SELF",
|
||||
details={
|
||||
"user_id": user_id,
|
||||
"operation": operation,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class InvalidAdminActionException(ValidationException):
|
||||
"""Raised when admin action is invalid."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
action: str,
|
||||
reason: str,
|
||||
valid_actions: list | None = None,
|
||||
):
|
||||
details = {
|
||||
"action": action,
|
||||
"reason": reason,
|
||||
}
|
||||
|
||||
if valid_actions:
|
||||
details["valid_actions"] = valid_actions
|
||||
|
||||
super().__init__(
|
||||
message=f"Invalid admin action '{action}': {reason}",
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "INVALID_ADMIN_ACTION"
|
||||
|
||||
|
||||
class BulkOperationException(BusinessLogicException):
|
||||
"""Raised when bulk admin operation fails."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
operation: str,
|
||||
total_items: int,
|
||||
failed_items: int,
|
||||
errors: dict[str, Any] | None = None,
|
||||
):
|
||||
message = f"Bulk {operation} completed with errors: {failed_items}/{total_items} failed"
|
||||
|
||||
details = {
|
||||
"operation": operation,
|
||||
"total_items": total_items,
|
||||
"failed_items": failed_items,
|
||||
"success_items": total_items - failed_items,
|
||||
}
|
||||
|
||||
if errors:
|
||||
details["errors"] = errors
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="BULK_OPERATION_PARTIAL_FAILURE",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class ConfirmationRequiredException(BusinessLogicException):
|
||||
"""Raised when a destructive operation requires explicit confirmation."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
operation: str,
|
||||
message: str | None = None,
|
||||
confirmation_param: str = "confirm",
|
||||
):
|
||||
if not message:
|
||||
message = f"Operation '{operation}' requires confirmation parameter: {confirmation_param}=true"
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="CONFIRMATION_REQUIRED",
|
||||
details={
|
||||
"operation": operation,
|
||||
"confirmation_param": confirmation_param,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class VendorVerificationException(BusinessLogicException):
|
||||
"""Raised when vendor verification fails."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vendor_id: int,
|
||||
reason: str,
|
||||
current_verification_status: bool | None = None,
|
||||
):
|
||||
details = {
|
||||
"vendor_id": vendor_id,
|
||||
"reason": reason,
|
||||
}
|
||||
|
||||
if current_verification_status is not None:
|
||||
details["current_verification_status"] = current_verification_status
|
||||
|
||||
super().__init__(
|
||||
message=f"Vendor verification failed for vendor {vendor_id}: {reason}",
|
||||
error_code="VENDOR_VERIFICATION_FAILED",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class UserCannotBeDeletedException(BusinessLogicException):
|
||||
"""Raised when a user cannot be deleted due to ownership constraints."""
|
||||
|
||||
def __init__(self, user_id: int, reason: str, owned_count: int = 0):
|
||||
details = {
|
||||
"user_id": user_id,
|
||||
"reason": reason,
|
||||
}
|
||||
if owned_count > 0:
|
||||
details["owned_companies_count"] = owned_count
|
||||
|
||||
super().__init__(
|
||||
message=f"Cannot delete user {user_id}: {reason}",
|
||||
error_code="USER_CANNOT_BE_DELETED",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class UserRoleChangeException(BusinessLogicException):
|
||||
"""Raised when user role cannot be changed."""
|
||||
|
||||
def __init__(self, user_id: int, current_role: str, target_role: str, reason: str):
|
||||
super().__init__(
|
||||
message=f"Cannot change user {user_id} role from {current_role} to {target_role}: {reason}",
|
||||
error_code="USER_ROLE_CHANGE_FAILED",
|
||||
details={
|
||||
"user_id": user_id,
|
||||
"current_role": current_role,
|
||||
"target_role": target_role,
|
||||
"reason": reason,
|
||||
},
|
||||
)
|
||||
@@ -1,94 +0,0 @@
|
||||
# app/exceptions/auth.py
|
||||
"""
|
||||
Authentication and authorization specific exceptions.
|
||||
"""
|
||||
|
||||
from .base import AuthenticationException, AuthorizationException, ConflictException
|
||||
|
||||
|
||||
class InvalidCredentialsException(AuthenticationException):
|
||||
"""Raised when login credentials are invalid."""
|
||||
|
||||
def __init__(self, message: str = "Invalid username or password"):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="INVALID_CREDENTIALS",
|
||||
)
|
||||
|
||||
|
||||
class TokenExpiredException(AuthenticationException):
|
||||
"""Raised when JWT token has expired."""
|
||||
|
||||
def __init__(self, message: str = "Token has expired"):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="TOKEN_EXPIRED",
|
||||
)
|
||||
|
||||
|
||||
class InvalidTokenException(AuthenticationException):
|
||||
"""Raised when JWT token is invalid or malformed."""
|
||||
|
||||
def __init__(self, message: str = "Invalid token"):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="INVALID_TOKEN",
|
||||
)
|
||||
|
||||
|
||||
class InsufficientPermissionsException(AuthorizationException):
|
||||
"""Raised when user lacks required permissions for an action."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Insufficient permissions for this action",
|
||||
required_permission: str | None = None,
|
||||
):
|
||||
details = {}
|
||||
if required_permission:
|
||||
details["required_permission"] = required_permission
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="INSUFFICIENT_PERMISSIONS",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class UserNotActiveException(AuthorizationException):
|
||||
"""Raised when user account is not active."""
|
||||
|
||||
def __init__(self, message: str = "User account is not active"):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="USER_NOT_ACTIVE",
|
||||
)
|
||||
|
||||
|
||||
class AdminRequiredException(AuthorizationException):
|
||||
"""Raised when admin privileges are required."""
|
||||
|
||||
def __init__(self, message: str = "Admin privileges required"):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="ADMIN_REQUIRED",
|
||||
)
|
||||
|
||||
|
||||
class UserAlreadyExistsException(ConflictException):
|
||||
"""Raised when trying to register with existing username/email."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "User already exists",
|
||||
field: str | None = None,
|
||||
):
|
||||
details = {}
|
||||
if field:
|
||||
details["field"] = field
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="USER_ALREADY_EXISTS",
|
||||
details=details,
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
# Backup/recovery exceptions
|
||||
@@ -1,95 +0,0 @@
|
||||
# app/exceptions/billing.py
|
||||
"""
|
||||
Billing and subscription related exceptions.
|
||||
|
||||
This module provides exceptions for:
|
||||
- Payment system configuration issues
|
||||
- Subscription management errors
|
||||
- Tier-related errors
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .base import BusinessLogicException, ResourceNotFoundException, ServiceUnavailableException
|
||||
|
||||
|
||||
class PaymentSystemNotConfiguredException(ServiceUnavailableException):
|
||||
"""Raised when the payment system (Stripe) is not configured."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(message="Payment system not configured")
|
||||
|
||||
|
||||
class TierNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a subscription tier is not found."""
|
||||
|
||||
def __init__(self, tier_code: str):
|
||||
super().__init__(
|
||||
resource_type="SubscriptionTier",
|
||||
identifier=tier_code,
|
||||
message=f"Subscription tier '{tier_code}' not found",
|
||||
error_code="TIER_NOT_FOUND",
|
||||
)
|
||||
self.tier_code = tier_code
|
||||
|
||||
|
||||
class StripePriceNotConfiguredException(BusinessLogicException):
|
||||
"""Raised when Stripe price is not configured for a tier."""
|
||||
|
||||
def __init__(self, tier_code: str):
|
||||
super().__init__(
|
||||
message=f"Stripe price not configured for tier '{tier_code}'",
|
||||
error_code="STRIPE_PRICE_NOT_CONFIGURED",
|
||||
details={"tier_code": tier_code},
|
||||
)
|
||||
self.tier_code = tier_code
|
||||
|
||||
|
||||
class NoActiveSubscriptionException(BusinessLogicException):
|
||||
"""Raised when no active subscription exists for an operation that requires one."""
|
||||
|
||||
def __init__(self, message: str = "No active subscription found"):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="NO_ACTIVE_SUBSCRIPTION",
|
||||
)
|
||||
|
||||
|
||||
class SubscriptionNotCancelledException(BusinessLogicException):
|
||||
"""Raised when trying to reactivate a subscription that is not cancelled."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
message="Subscription is not cancelled",
|
||||
error_code="SUBSCRIPTION_NOT_CANCELLED",
|
||||
)
|
||||
|
||||
|
||||
class SubscriptionAlreadyCancelledException(BusinessLogicException):
|
||||
"""Raised when trying to cancel an already cancelled subscription."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
message="Subscription is already cancelled",
|
||||
error_code="SUBSCRIPTION_ALREADY_CANCELLED",
|
||||
)
|
||||
|
||||
|
||||
class InvalidWebhookSignatureException(BusinessLogicException):
|
||||
"""Raised when Stripe webhook signature verification fails."""
|
||||
|
||||
def __init__(self, message: str = "Invalid webhook signature"):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="INVALID_WEBHOOK_SIGNATURE",
|
||||
)
|
||||
|
||||
|
||||
class WebhookMissingSignatureException(BusinessLogicException):
|
||||
"""Raised when Stripe webhook is missing the signature header."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
message="Missing Stripe-Signature header",
|
||||
error_code="WEBHOOK_MISSING_SIGNATURE",
|
||||
)
|
||||
@@ -1,107 +0,0 @@
|
||||
# app/exceptions/cart.py
|
||||
"""
|
||||
Shopping cart specific exceptions.
|
||||
"""
|
||||
|
||||
from .base import BusinessLogicException, ResourceNotFoundException, ValidationException
|
||||
|
||||
|
||||
class CartItemNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a cart item is not found."""
|
||||
|
||||
def __init__(self, product_id: int, session_id: str):
|
||||
super().__init__(
|
||||
resource_type="CartItem",
|
||||
identifier=str(product_id),
|
||||
message=f"Product {product_id} not found in cart",
|
||||
error_code="CART_ITEM_NOT_FOUND",
|
||||
)
|
||||
self.details.update({"product_id": product_id, "session_id": session_id})
|
||||
|
||||
|
||||
class EmptyCartException(ValidationException):
|
||||
"""Raised when trying to perform operations on an empty cart."""
|
||||
|
||||
def __init__(self, session_id: str):
|
||||
super().__init__(message="Cart is empty", details={"session_id": session_id})
|
||||
self.error_code = "CART_EMPTY"
|
||||
|
||||
|
||||
class CartValidationException(ValidationException):
|
||||
"""Raised when cart data validation fails."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Cart validation failed",
|
||||
field: str | None = None,
|
||||
details: dict | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "CART_VALIDATION_FAILED"
|
||||
|
||||
|
||||
class InsufficientInventoryForCartException(BusinessLogicException):
|
||||
"""Raised when product doesn't have enough inventory for cart operation."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
product_id: int,
|
||||
product_name: str,
|
||||
requested: int,
|
||||
available: int,
|
||||
):
|
||||
message = f"Insufficient inventory for product '{product_name}'. Requested: {requested}, Available: {available}"
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="INSUFFICIENT_INVENTORY_FOR_CART",
|
||||
details={
|
||||
"product_id": product_id,
|
||||
"product_name": product_name,
|
||||
"requested_quantity": requested,
|
||||
"available_quantity": available,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class InvalidCartQuantityException(ValidationException):
|
||||
"""Raised when cart quantity is invalid."""
|
||||
|
||||
def __init__(
|
||||
self, quantity: int, min_quantity: int = 1, max_quantity: int | None = None
|
||||
):
|
||||
if quantity < min_quantity:
|
||||
message = f"Quantity must be at least {min_quantity}"
|
||||
elif max_quantity and quantity > max_quantity:
|
||||
message = f"Quantity cannot exceed {max_quantity}"
|
||||
else:
|
||||
message = f"Invalid quantity: {quantity}"
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
field="quantity",
|
||||
details={
|
||||
"quantity": quantity,
|
||||
"min_quantity": min_quantity,
|
||||
"max_quantity": max_quantity,
|
||||
},
|
||||
)
|
||||
self.error_code = "INVALID_CART_QUANTITY"
|
||||
|
||||
|
||||
class ProductNotAvailableForCartException(BusinessLogicException):
|
||||
"""Raised when product is not available for adding to cart."""
|
||||
|
||||
def __init__(self, product_id: int, reason: str):
|
||||
super().__init__(
|
||||
message=f"Product {product_id} cannot be added to cart: {reason}",
|
||||
error_code="PRODUCT_NOT_AVAILABLE_FOR_CART",
|
||||
details={
|
||||
"product_id": product_id,
|
||||
"reason": reason,
|
||||
},
|
||||
)
|
||||
@@ -1,98 +0,0 @@
|
||||
# app/exceptions/code_quality.py
|
||||
"""
|
||||
Code Quality Domain Exceptions
|
||||
|
||||
These exceptions are raised by the code quality service layer
|
||||
and converted to HTTP responses by the global exception handler.
|
||||
|
||||
Note: These exceptions are also re-exported from app.modules.dev_tools.exceptions
|
||||
for module-based access.
|
||||
"""
|
||||
|
||||
from app.exceptions.base import (
|
||||
BusinessLogicException,
|
||||
ExternalServiceException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class ViolationNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a violation is not found."""
|
||||
|
||||
def __init__(self, violation_id: int):
|
||||
super().__init__(
|
||||
resource_type="Violation",
|
||||
identifier=str(violation_id),
|
||||
error_code="VIOLATION_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class ScanNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a scan is not found."""
|
||||
|
||||
def __init__(self, scan_id: int):
|
||||
super().__init__(
|
||||
resource_type="Scan",
|
||||
identifier=str(scan_id),
|
||||
error_code="SCAN_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class ScanExecutionException(ExternalServiceException):
|
||||
"""Raised when architecture scan execution fails."""
|
||||
|
||||
def __init__(self, reason: str):
|
||||
super().__init__(
|
||||
service_name="ArchitectureValidator",
|
||||
message=f"Scan execution failed: {reason}",
|
||||
error_code="SCAN_EXECUTION_FAILED",
|
||||
)
|
||||
|
||||
|
||||
class ScanTimeoutException(ExternalServiceException):
|
||||
"""Raised when architecture scan times out."""
|
||||
|
||||
def __init__(self, timeout_seconds: int = 300):
|
||||
super().__init__(
|
||||
service_name="ArchitectureValidator",
|
||||
message=f"Scan timed out after {timeout_seconds} seconds",
|
||||
error_code="SCAN_TIMEOUT",
|
||||
)
|
||||
|
||||
|
||||
class ScanParseException(BusinessLogicException):
|
||||
"""Raised when scan results cannot be parsed."""
|
||||
|
||||
def __init__(self, reason: str):
|
||||
super().__init__(
|
||||
message=f"Failed to parse scan results: {reason}",
|
||||
error_code="SCAN_PARSE_FAILED",
|
||||
)
|
||||
|
||||
|
||||
class ViolationOperationException(BusinessLogicException):
|
||||
"""Raised when a violation operation fails."""
|
||||
|
||||
def __init__(self, operation: str, violation_id: int, reason: str):
|
||||
super().__init__(
|
||||
message=f"Failed to {operation} violation {violation_id}: {reason}",
|
||||
error_code="VIOLATION_OPERATION_FAILED",
|
||||
details={
|
||||
"operation": operation,
|
||||
"violation_id": violation_id,
|
||||
"reason": reason,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class InvalidViolationStatusException(ValidationException):
|
||||
"""Raised when a violation status transition is invalid."""
|
||||
|
||||
def __init__(self, violation_id: int, current_status: str, target_status: str):
|
||||
super().__init__(
|
||||
message=f"Cannot change violation {violation_id} from '{current_status}' to '{target_status}'",
|
||||
field="status",
|
||||
value=target_status,
|
||||
)
|
||||
self.error_code = "INVALID_VIOLATION_STATUS"
|
||||
@@ -1,128 +0,0 @@
|
||||
# app/exceptions/company.py
|
||||
"""
|
||||
Company management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .base import (
|
||||
AuthorizationException,
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class CompanyNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a company is not found."""
|
||||
|
||||
def __init__(self, company_identifier: str | int, identifier_type: str = "id"):
|
||||
if identifier_type.lower() == "id":
|
||||
message = f"Company with ID '{company_identifier}' not found"
|
||||
else:
|
||||
message = f"Company with name '{company_identifier}' not found"
|
||||
|
||||
super().__init__(
|
||||
resource_type="Company",
|
||||
identifier=str(company_identifier),
|
||||
message=message,
|
||||
error_code="COMPANY_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class CompanyAlreadyExistsException(ConflictException):
|
||||
"""Raised when trying to create a company that already exists."""
|
||||
|
||||
def __init__(self, company_name: str):
|
||||
super().__init__(
|
||||
message=f"Company with name '{company_name}' already exists",
|
||||
error_code="COMPANY_ALREADY_EXISTS",
|
||||
details={"company_name": company_name},
|
||||
)
|
||||
|
||||
|
||||
class CompanyNotActiveException(BusinessLogicException):
|
||||
"""Raised when trying to perform operations on inactive company."""
|
||||
|
||||
def __init__(self, company_id: int):
|
||||
super().__init__(
|
||||
message=f"Company with ID '{company_id}' is not active",
|
||||
error_code="COMPANY_NOT_ACTIVE",
|
||||
details={"company_id": company_id},
|
||||
)
|
||||
|
||||
|
||||
class CompanyNotVerifiedException(BusinessLogicException):
|
||||
"""Raised when trying to perform operations requiring verified company."""
|
||||
|
||||
def __init__(self, company_id: int):
|
||||
super().__init__(
|
||||
message=f"Company with ID '{company_id}' is not verified",
|
||||
error_code="COMPANY_NOT_VERIFIED",
|
||||
details={"company_id": company_id},
|
||||
)
|
||||
|
||||
|
||||
class UnauthorizedCompanyAccessException(AuthorizationException):
|
||||
"""Raised when user tries to access company they don't own."""
|
||||
|
||||
def __init__(self, company_id: int, user_id: int | None = None):
|
||||
details = {"company_id": company_id}
|
||||
if user_id:
|
||||
details["user_id"] = user_id
|
||||
|
||||
super().__init__(
|
||||
message=f"Unauthorized access to company with ID '{company_id}'",
|
||||
error_code="UNAUTHORIZED_COMPANY_ACCESS",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class InvalidCompanyDataException(ValidationException):
|
||||
"""Raised when company data is invalid or incomplete."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid company data",
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "INVALID_COMPANY_DATA"
|
||||
|
||||
|
||||
class CompanyValidationException(ValidationException):
|
||||
"""Raised when company validation fails."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Company validation failed",
|
||||
field: str | None = None,
|
||||
validation_errors: dict[str, str] | None = None,
|
||||
):
|
||||
details = {}
|
||||
if validation_errors:
|
||||
details["validation_errors"] = validation_errors
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "COMPANY_VALIDATION_FAILED"
|
||||
|
||||
|
||||
class CompanyHasVendorsException(BusinessLogicException):
|
||||
"""Raised when trying to delete a company that still has active vendors."""
|
||||
|
||||
def __init__(self, company_id: int, vendor_count: int):
|
||||
super().__init__(
|
||||
message=f"Cannot delete company with ID '{company_id}' because it has {vendor_count} associated vendor(s)",
|
||||
error_code="COMPANY_HAS_VENDORS",
|
||||
details={"company_id": company_id, "vendor_count": vendor_count},
|
||||
)
|
||||
@@ -1,116 +0,0 @@
|
||||
# app/exceptions/customer.py
|
||||
"""
|
||||
Customer management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .base import (
|
||||
AuthenticationException,
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class CustomerNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a customer is not found."""
|
||||
|
||||
def __init__(self, customer_identifier: str):
|
||||
super().__init__(
|
||||
resource_type="Customer",
|
||||
identifier=customer_identifier,
|
||||
message=f"Customer '{customer_identifier}' not found",
|
||||
error_code="CUSTOMER_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class CustomerAlreadyExistsException(ConflictException):
|
||||
"""Raised when trying to create a customer that already exists."""
|
||||
|
||||
def __init__(self, email: str):
|
||||
super().__init__(
|
||||
message=f"Customer with email '{email}' already exists",
|
||||
error_code="CUSTOMER_ALREADY_EXISTS",
|
||||
details={"email": email},
|
||||
)
|
||||
|
||||
|
||||
class DuplicateCustomerEmailException(ConflictException):
|
||||
"""Raised when email already exists for vendor."""
|
||||
|
||||
def __init__(self, email: str, vendor_code: str):
|
||||
super().__init__(
|
||||
message=f"Email '{email}' is already registered for this vendor",
|
||||
error_code="DUPLICATE_CUSTOMER_EMAIL",
|
||||
details={"email": email, "vendor_code": vendor_code},
|
||||
)
|
||||
|
||||
|
||||
class CustomerNotActiveException(BusinessLogicException):
|
||||
"""Raised when trying to perform operations on inactive customer."""
|
||||
|
||||
def __init__(self, email: str):
|
||||
super().__init__(
|
||||
message=f"Customer account '{email}' is not active",
|
||||
error_code="CUSTOMER_NOT_ACTIVE",
|
||||
details={"email": email},
|
||||
)
|
||||
|
||||
|
||||
class InvalidCustomerCredentialsException(AuthenticationException):
|
||||
"""Raised when customer credentials are invalid."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
message="Invalid email or password",
|
||||
error_code="INVALID_CUSTOMER_CREDENTIALS",
|
||||
)
|
||||
|
||||
|
||||
class CustomerValidationException(ValidationException):
|
||||
"""Raised when customer data validation fails."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Customer validation failed",
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(message=message, field=field, details=details)
|
||||
self.error_code = "CUSTOMER_VALIDATION_FAILED"
|
||||
|
||||
|
||||
class CustomerAuthorizationException(BusinessLogicException):
|
||||
"""Raised when customer is not authorized for operation."""
|
||||
|
||||
def __init__(self, customer_email: str, operation: str):
|
||||
super().__init__(
|
||||
message=f"Customer '{customer_email}' not authorized for: {operation}",
|
||||
error_code="CUSTOMER_NOT_AUTHORIZED",
|
||||
details={"customer_email": customer_email, "operation": operation},
|
||||
)
|
||||
|
||||
|
||||
class InvalidPasswordResetTokenException(ValidationException):
|
||||
"""Raised when password reset token is invalid or expired."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
message="Invalid or expired password reset link. Please request a new one.",
|
||||
field="reset_token",
|
||||
)
|
||||
self.error_code = "INVALID_RESET_TOKEN"
|
||||
|
||||
|
||||
class PasswordTooShortException(ValidationException):
|
||||
"""Raised when password doesn't meet minimum length requirement."""
|
||||
|
||||
def __init__(self, min_length: int = 8):
|
||||
super().__init__(
|
||||
message=f"Password must be at least {min_length} characters long",
|
||||
field="password",
|
||||
details={"min_length": min_length},
|
||||
)
|
||||
self.error_code = "PASSWORD_TOO_SHORT"
|
||||
@@ -1,42 +0,0 @@
|
||||
# app/exceptions/feature.py
|
||||
"""
|
||||
Feature management exceptions.
|
||||
"""
|
||||
|
||||
from app.exceptions.base import ResourceNotFoundException, ValidationException
|
||||
|
||||
|
||||
class FeatureNotFoundError(ResourceNotFoundException):
|
||||
"""Feature not found."""
|
||||
|
||||
def __init__(self, feature_code: str):
|
||||
super().__init__(
|
||||
resource_type="Feature",
|
||||
identifier=feature_code,
|
||||
message=f"Feature '{feature_code}' not found",
|
||||
)
|
||||
self.feature_code = feature_code
|
||||
|
||||
|
||||
class TierNotFoundError(ResourceNotFoundException):
|
||||
"""Subscription tier not found."""
|
||||
|
||||
def __init__(self, tier_code: str):
|
||||
super().__init__(
|
||||
resource_type="SubscriptionTier",
|
||||
identifier=tier_code,
|
||||
message=f"Tier '{tier_code}' not found",
|
||||
)
|
||||
self.tier_code = tier_code
|
||||
|
||||
|
||||
class InvalidFeatureCodesError(ValidationException):
|
||||
"""Invalid feature codes provided."""
|
||||
|
||||
def __init__(self, invalid_codes: set[str]):
|
||||
codes_str = ", ".join(sorted(invalid_codes))
|
||||
super().__init__(
|
||||
message=f"Invalid feature codes: {codes_str}",
|
||||
details={"invalid_codes": list(invalid_codes)},
|
||||
)
|
||||
self.invalid_codes = invalid_codes
|
||||
@@ -1,135 +0,0 @@
|
||||
# app/exceptions/inventory.py
|
||||
"""
|
||||
Inventory management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .base import BusinessLogicException, ResourceNotFoundException, ValidationException
|
||||
|
||||
|
||||
class InventoryNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when inventory record is not found."""
|
||||
|
||||
def __init__(self, identifier: str, identifier_type: str = "ID"):
|
||||
if identifier_type.lower() == "gtin":
|
||||
message = f"No inventory found for GTIN '{identifier}'"
|
||||
else:
|
||||
message = (
|
||||
f"Inventory record with {identifier_type} '{identifier}' not found"
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
resource_type="Inventory",
|
||||
identifier=identifier,
|
||||
message=message,
|
||||
error_code="INVENTORY_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class InsufficientInventoryException(BusinessLogicException):
|
||||
"""Raised when trying to remove more inventory than available."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
gtin: str,
|
||||
location: str,
|
||||
requested: int,
|
||||
available: int,
|
||||
):
|
||||
message = f"Insufficient inventory for GTIN '{gtin}' at '{location}'. Requested: {requested}, Available: {available}"
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="INSUFFICIENT_INVENTORY",
|
||||
details={
|
||||
"gtin": gtin,
|
||||
"location": location,
|
||||
"requested_quantity": requested,
|
||||
"available_quantity": available,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class InvalidInventoryOperationException(ValidationException):
|
||||
"""Raised when inventory operation is invalid."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
operation: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
if not details:
|
||||
details = {}
|
||||
|
||||
if operation:
|
||||
details["operation"] = operation
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "INVALID_INVENTORY_OPERATION"
|
||||
|
||||
|
||||
class InventoryValidationException(ValidationException):
|
||||
"""Raised when inventory data validation fails."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Inventory validation failed",
|
||||
field: str | None = None,
|
||||
validation_errors: dict[str, str] | None = None,
|
||||
):
|
||||
details = {}
|
||||
if validation_errors:
|
||||
details["validation_errors"] = validation_errors
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "INVENTORY_VALIDATION_FAILED"
|
||||
|
||||
|
||||
class NegativeInventoryException(BusinessLogicException):
|
||||
"""Raised when inventory quantity would become negative."""
|
||||
|
||||
def __init__(self, gtin: str, location: str, resulting_quantity: int):
|
||||
message = f"Inventory operation would result in negative quantity ({resulting_quantity}) for GTIN '{gtin}' at '{location}'"
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="NEGATIVE_INVENTORY_NOT_ALLOWED",
|
||||
details={
|
||||
"gtin": gtin,
|
||||
"location": location,
|
||||
"resulting_quantity": resulting_quantity,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class InvalidQuantityException(ValidationException):
|
||||
"""Raised when quantity value is invalid."""
|
||||
|
||||
def __init__(self, quantity: Any, message: str = "Invalid quantity"):
|
||||
super().__init__(
|
||||
message=f"{message}: {quantity}",
|
||||
field="quantity",
|
||||
details={"quantity": quantity},
|
||||
)
|
||||
self.error_code = "INVALID_QUANTITY"
|
||||
|
||||
|
||||
class LocationNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when inventory location is not found."""
|
||||
|
||||
def __init__(self, location: str):
|
||||
super().__init__(
|
||||
resource_type="Location",
|
||||
identifier=location,
|
||||
message=f"Inventory location '{location}' not found",
|
||||
error_code="LOCATION_NOT_FOUND",
|
||||
)
|
||||
@@ -1,118 +0,0 @@
|
||||
# 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",
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
# Import/marketplace exceptions
|
||||
@@ -1,203 +0,0 @@
|
||||
# app/exceptions/marketplace_import_job.py
|
||||
"""
|
||||
Marketplace import specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .base import (
|
||||
AuthorizationException,
|
||||
BusinessLogicException,
|
||||
ExternalServiceException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class MarketplaceImportException(BusinessLogicException):
|
||||
"""Base exception for marketplace import operations."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
error_code: str = "MARKETPLACE_IMPORT_ERROR",
|
||||
marketplace: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
if not details:
|
||||
details = {}
|
||||
|
||||
if marketplace:
|
||||
details["marketplace"] = marketplace
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code=error_code,
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class ImportJobNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when import job is not found."""
|
||||
|
||||
def __init__(self, job_id: int):
|
||||
super().__init__(
|
||||
resource_type="ImportJob",
|
||||
identifier=str(job_id),
|
||||
message=f"Import job with ID '{job_id}' not found",
|
||||
error_code="IMPORT_JOB_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class ImportJobNotOwnedException(AuthorizationException):
|
||||
"""Raised when user tries to access import job they don't own."""
|
||||
|
||||
def __init__(self, job_id: int, user_id: int | None = None):
|
||||
details = {"job_id": job_id}
|
||||
if user_id:
|
||||
details["user_id"] = user_id
|
||||
|
||||
super().__init__(
|
||||
message=f"Unauthorized access to import job '{job_id}'",
|
||||
error_code="IMPORT_JOB_NOT_OWNED",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class InvalidImportDataException(ValidationException):
|
||||
"""Raised when import data is invalid."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid import data",
|
||||
field: str | None = None,
|
||||
row_number: int | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
if not details:
|
||||
details = {}
|
||||
|
||||
if row_number:
|
||||
details["row_number"] = row_number
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "INVALID_IMPORT_DATA"
|
||||
|
||||
|
||||
class ImportJobCannotBeCancelledException(BusinessLogicException):
|
||||
"""Raised when trying to cancel job that cannot be cancelled."""
|
||||
|
||||
def __init__(self, job_id: int, current_status: str):
|
||||
super().__init__(
|
||||
message=f"Import job '{job_id}' cannot be cancelled (current status: {current_status})",
|
||||
error_code="IMPORT_JOB_CANNOT_BE_CANCELLED",
|
||||
details={
|
||||
"job_id": job_id,
|
||||
"current_status": current_status,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class ImportJobCannotBeDeletedException(BusinessLogicException):
|
||||
"""Raised when trying to delete job that cannot be deleted."""
|
||||
|
||||
def __init__(self, job_id: int, current_status: str):
|
||||
super().__init__(
|
||||
message=f"Import job '{job_id}' cannot be deleted (current status: {current_status})",
|
||||
error_code="IMPORT_JOB_CANNOT_BE_DELETED",
|
||||
details={
|
||||
"job_id": job_id,
|
||||
"current_status": current_status,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class MarketplaceConnectionException(ExternalServiceException):
|
||||
"""Raised when marketplace connection fails."""
|
||||
|
||||
def __init__(
|
||||
self, marketplace: str, message: str = "Failed to connect to marketplace"
|
||||
):
|
||||
super().__init__(
|
||||
service=marketplace,
|
||||
message=f"{message}: {marketplace}",
|
||||
error_code="MARKETPLACE_CONNECTION_FAILED",
|
||||
)
|
||||
|
||||
|
||||
class MarketplaceDataParsingException(ValidationException):
|
||||
"""Raised when marketplace data cannot be parsed."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
marketplace: str,
|
||||
message: str = "Failed to parse marketplace data",
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
if not details:
|
||||
details = {}
|
||||
details["marketplace"] = marketplace
|
||||
|
||||
super().__init__(
|
||||
message=f"{message} from {marketplace}",
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "MARKETPLACE_DATA_PARSING_FAILED"
|
||||
|
||||
|
||||
class ImportRateLimitException(BusinessLogicException):
|
||||
"""Raised when import rate limit is exceeded."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
max_imports: int,
|
||||
time_window: str,
|
||||
retry_after: int | None = None,
|
||||
):
|
||||
details = {
|
||||
"max_imports": max_imports,
|
||||
"time_window": time_window,
|
||||
}
|
||||
|
||||
if retry_after:
|
||||
details["retry_after"] = retry_after
|
||||
|
||||
super().__init__(
|
||||
message=f"Import rate limit exceeded: {max_imports} imports per {time_window}",
|
||||
error_code="IMPORT_RATE_LIMIT_EXCEEDED",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class InvalidMarketplaceException(ValidationException):
|
||||
"""Raised when marketplace is not supported."""
|
||||
|
||||
def __init__(self, marketplace: str, supported_marketplaces: list | None = None):
|
||||
details = {"marketplace": marketplace}
|
||||
if supported_marketplaces:
|
||||
details["supported_marketplaces"] = supported_marketplaces
|
||||
|
||||
super().__init__(
|
||||
message=f"Unsupported marketplace: {marketplace}",
|
||||
field="marketplace",
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "INVALID_MARKETPLACE"
|
||||
|
||||
|
||||
class ImportJobAlreadyProcessingException(BusinessLogicException):
|
||||
"""Raised when trying to start import while another is already processing."""
|
||||
|
||||
def __init__(self, vendor_code: str, existing_job_id: int):
|
||||
super().__init__(
|
||||
message=f"Import already in progress for vendor '{vendor_code}'",
|
||||
error_code="IMPORT_JOB_ALREADY_PROCESSING",
|
||||
details={
|
||||
"vendor_code": vendor_code,
|
||||
"existing_job_id": existing_job_id,
|
||||
},
|
||||
)
|
||||
@@ -1,108 +0,0 @@
|
||||
# app/exceptions/marketplace_products.py
|
||||
"""
|
||||
MarketplaceProduct management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .base import (
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class MarketplaceProductNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a product is not found."""
|
||||
|
||||
def __init__(self, marketplace_product_id: str):
|
||||
super().__init__(
|
||||
resource_type="MarketplaceProduct",
|
||||
identifier=marketplace_product_id,
|
||||
message=f"MarketplaceProduct with ID '{marketplace_product_id}' not found",
|
||||
error_code="PRODUCT_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class MarketplaceProductAlreadyExistsException(ConflictException):
|
||||
"""Raised when trying to create a product that already exists."""
|
||||
|
||||
def __init__(self, marketplace_product_id: str):
|
||||
super().__init__(
|
||||
message=f"MarketplaceProduct with ID '{marketplace_product_id}' already exists",
|
||||
error_code="PRODUCT_ALREADY_EXISTS",
|
||||
details={"marketplace_product_id": marketplace_product_id},
|
||||
)
|
||||
|
||||
|
||||
class InvalidMarketplaceProductDataException(ValidationException):
|
||||
"""Raised when product data is invalid."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid product data",
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "INVALID_PRODUCT_DATA"
|
||||
|
||||
|
||||
class MarketplaceProductValidationException(ValidationException):
|
||||
"""Raised when product validation fails."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
field: str | None = None,
|
||||
validation_errors: dict[str, str] | None = None,
|
||||
):
|
||||
details = {}
|
||||
if validation_errors:
|
||||
details["validation_errors"] = validation_errors
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "PRODUCT_VALIDATION_FAILED"
|
||||
|
||||
|
||||
class InvalidGTINException(ValidationException):
|
||||
"""Raised when GTIN format is invalid."""
|
||||
|
||||
def __init__(self, gtin: str, message: str = "Invalid GTIN format"):
|
||||
super().__init__(
|
||||
message=f"{message}: {gtin}",
|
||||
field="gtin",
|
||||
details={"gtin": gtin},
|
||||
)
|
||||
self.error_code = "INVALID_GTIN"
|
||||
|
||||
|
||||
class MarketplaceProductCSVImportException(BusinessLogicException):
|
||||
"""Raised when product CSV import fails."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "MarketplaceProduct CSV import failed",
|
||||
row_number: int | None = None,
|
||||
errors: dict[str, Any] | None = None,
|
||||
):
|
||||
details = {}
|
||||
if row_number:
|
||||
details["row_number"] = row_number
|
||||
if errors:
|
||||
details["errors"] = errors
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="PRODUCT_CSV_IMPORT_FAILED",
|
||||
details=details,
|
||||
)
|
||||
@@ -1,101 +0,0 @@
|
||||
# app/exceptions/media.py
|
||||
"""
|
||||
Media/file management exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .base import (
|
||||
BusinessLogicException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class MediaNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a media file is not found."""
|
||||
|
||||
def __init__(self, media_id: int | str):
|
||||
super().__init__(
|
||||
resource_type="MediaFile",
|
||||
identifier=str(media_id),
|
||||
message=f"Media file '{media_id}' not found",
|
||||
error_code="MEDIA_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class MediaUploadException(BusinessLogicException):
|
||||
"""Raised when media upload fails."""
|
||||
|
||||
def __init__(self, message: str, details: dict[str, Any] | None = None):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="MEDIA_UPLOAD_FAILED",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class MediaValidationException(ValidationException):
|
||||
"""Raised when media validation fails (file type, size, etc.)."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Media validation failed",
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(message=message, field=field, details=details)
|
||||
self.error_code = "MEDIA_VALIDATION_FAILED"
|
||||
|
||||
|
||||
class UnsupportedMediaTypeException(ValidationException):
|
||||
"""Raised when media file type is not supported."""
|
||||
|
||||
def __init__(self, file_type: str, allowed_types: list[str] | None = None):
|
||||
details = {"file_type": file_type}
|
||||
if allowed_types:
|
||||
details["allowed_types"] = allowed_types
|
||||
|
||||
super().__init__(
|
||||
message=f"Unsupported media type: {file_type}",
|
||||
field="file",
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "UNSUPPORTED_MEDIA_TYPE"
|
||||
|
||||
|
||||
class MediaFileTooLargeException(ValidationException):
|
||||
"""Raised when media file exceeds size limit."""
|
||||
|
||||
def __init__(self, file_size: int, max_size: int, media_type: str = "file"):
|
||||
super().__init__(
|
||||
message=f"File size ({file_size} bytes) exceeds maximum allowed ({max_size} bytes) for {media_type}",
|
||||
field="file",
|
||||
details={
|
||||
"file_size": file_size,
|
||||
"max_size": max_size,
|
||||
"media_type": media_type,
|
||||
},
|
||||
)
|
||||
self.error_code = "MEDIA_FILE_TOO_LARGE"
|
||||
|
||||
|
||||
class MediaOptimizationException(BusinessLogicException):
|
||||
"""Raised when media optimization fails."""
|
||||
|
||||
def __init__(self, message: str = "Only images can be optimized"):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="MEDIA_OPTIMIZATION_FAILED",
|
||||
)
|
||||
|
||||
|
||||
class MediaDeleteException(BusinessLogicException):
|
||||
"""Raised when media deletion fails."""
|
||||
|
||||
def __init__(self, message: str, details: dict[str, Any] | None = None):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="MEDIA_DELETE_FAILED",
|
||||
details=details,
|
||||
)
|
||||
@@ -1,100 +0,0 @@
|
||||
# app/exceptions/message.py
|
||||
"""
|
||||
Messaging specific exceptions.
|
||||
"""
|
||||
|
||||
from .base import BusinessLogicException, ResourceNotFoundException
|
||||
|
||||
|
||||
class ConversationNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a conversation is not found."""
|
||||
|
||||
def __init__(self, conversation_identifier: str):
|
||||
super().__init__(
|
||||
resource_type="Conversation",
|
||||
identifier=conversation_identifier,
|
||||
message=f"Conversation '{conversation_identifier}' not found",
|
||||
error_code="CONVERSATION_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class MessageNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a message is not found."""
|
||||
|
||||
def __init__(self, message_identifier: str):
|
||||
super().__init__(
|
||||
resource_type="Message",
|
||||
identifier=message_identifier,
|
||||
message=f"Message '{message_identifier}' not found",
|
||||
error_code="MESSAGE_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class ConversationClosedException(BusinessLogicException):
|
||||
"""Raised when trying to send message to a closed conversation."""
|
||||
|
||||
def __init__(self, conversation_id: int):
|
||||
super().__init__(
|
||||
message=f"Cannot send message to closed conversation {conversation_id}",
|
||||
error_code="CONVERSATION_CLOSED",
|
||||
details={"conversation_id": conversation_id},
|
||||
)
|
||||
|
||||
|
||||
class MessageAttachmentException(BusinessLogicException):
|
||||
"""Raised when attachment validation fails."""
|
||||
|
||||
def __init__(self, message: str, details: dict | None = None):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="MESSAGE_ATTACHMENT_INVALID",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class UnauthorizedConversationAccessException(BusinessLogicException):
|
||||
"""Raised when user tries to access a conversation they don't have access to."""
|
||||
|
||||
def __init__(self, conversation_id: int):
|
||||
super().__init__(
|
||||
message=f"You do not have access to conversation {conversation_id}",
|
||||
error_code="CONVERSATION_ACCESS_DENIED",
|
||||
details={"conversation_id": conversation_id},
|
||||
)
|
||||
|
||||
|
||||
class InvalidConversationTypeException(BusinessLogicException):
|
||||
"""Raised when conversation type is not valid for the operation."""
|
||||
|
||||
def __init__(self, message: str, allowed_types: list[str] | None = None):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="INVALID_CONVERSATION_TYPE",
|
||||
details={"allowed_types": allowed_types} if allowed_types else None,
|
||||
)
|
||||
|
||||
|
||||
class InvalidRecipientTypeException(BusinessLogicException):
|
||||
"""Raised when recipient type doesn't match conversation type."""
|
||||
|
||||
def __init__(self, conversation_type: str, expected_recipient_type: str):
|
||||
super().__init__(
|
||||
message=f"{conversation_type} conversations require a {expected_recipient_type} recipient",
|
||||
error_code="INVALID_RECIPIENT_TYPE",
|
||||
details={
|
||||
"conversation_type": conversation_type,
|
||||
"expected_recipient_type": expected_recipient_type,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class AttachmentNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when an attachment is not found."""
|
||||
|
||||
def __init__(self, attachment_id: int | str):
|
||||
super().__init__(
|
||||
resource_type="Attachment",
|
||||
identifier=str(attachment_id),
|
||||
message=f"Attachment '{attachment_id}' not found",
|
||||
error_code="ATTACHMENT_NOT_FOUND",
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
# Monitoring exceptions
|
||||
@@ -1 +0,0 @@
|
||||
# Notification exceptions
|
||||
@@ -1,87 +0,0 @@
|
||||
# app/exceptions/onboarding.py
|
||||
"""
|
||||
Onboarding-specific exceptions.
|
||||
|
||||
Exceptions for the vendor onboarding wizard flow.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .base import BusinessLogicException, ResourceNotFoundException, ValidationException
|
||||
|
||||
|
||||
class OnboardingNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when onboarding record is not found for a vendor."""
|
||||
|
||||
def __init__(self, vendor_id: int):
|
||||
super().__init__(
|
||||
resource_type="VendorOnboarding",
|
||||
identifier=str(vendor_id),
|
||||
)
|
||||
|
||||
|
||||
class OnboardingStepOrderException(ValidationException):
|
||||
"""Raised when trying to access a step out of order."""
|
||||
|
||||
def __init__(self, current_step: str, required_step: str):
|
||||
super().__init__(
|
||||
message=f"Please complete the {required_step} step first",
|
||||
field="step",
|
||||
details={
|
||||
"current_step": current_step,
|
||||
"required_step": required_step,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class OnboardingAlreadyCompletedException(BusinessLogicException):
|
||||
"""Raised when trying to modify a completed onboarding."""
|
||||
|
||||
def __init__(self, vendor_id: int):
|
||||
super().__init__(
|
||||
message="Onboarding has already been completed",
|
||||
error_code="ONBOARDING_ALREADY_COMPLETED",
|
||||
details={"vendor_id": vendor_id},
|
||||
)
|
||||
|
||||
|
||||
class LetzshopConnectionFailedException(BusinessLogicException):
|
||||
"""Raised when Letzshop API connection test fails."""
|
||||
|
||||
def __init__(self, error_message: str):
|
||||
super().__init__(
|
||||
message=f"Letzshop connection failed: {error_message}",
|
||||
error_code="LETZSHOP_CONNECTION_FAILED",
|
||||
details={"error": error_message},
|
||||
)
|
||||
|
||||
|
||||
class OnboardingCsvUrlRequiredException(ValidationException):
|
||||
"""Raised when no CSV URL is provided in product import step."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
message="At least one CSV URL must be provided",
|
||||
field="csv_url",
|
||||
)
|
||||
|
||||
|
||||
class OnboardingSyncJobNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when sync job is not found."""
|
||||
|
||||
def __init__(self, job_id: int):
|
||||
super().__init__(
|
||||
resource_type="LetzshopHistoricalImportJob",
|
||||
identifier=str(job_id),
|
||||
)
|
||||
|
||||
|
||||
class OnboardingSyncNotCompleteException(BusinessLogicException):
|
||||
"""Raised when trying to complete onboarding before sync is done."""
|
||||
|
||||
def __init__(self, job_status: str):
|
||||
super().__init__(
|
||||
message=f"Import job is still {job_status}, please wait",
|
||||
error_code="SYNC_NOT_COMPLETE",
|
||||
details={"job_status": job_status},
|
||||
)
|
||||
@@ -1,60 +0,0 @@
|
||||
# app/exceptions/order.py
|
||||
"""
|
||||
Order management specific exceptions.
|
||||
"""
|
||||
|
||||
from .base import BusinessLogicException, ResourceNotFoundException, ValidationException
|
||||
|
||||
|
||||
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):
|
||||
"""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",
|
||||
error_code="ORDER_ALREADY_EXISTS",
|
||||
details={"order_number": order_number},
|
||||
)
|
||||
|
||||
|
||||
class OrderValidationException(ValidationException):
|
||||
"""Raised when order data validation fails."""
|
||||
|
||||
def __init__(self, message: str, details: dict | None = None):
|
||||
super().__init__(
|
||||
message=message, error_code="ORDER_VALIDATION_FAILED", details=details
|
||||
)
|
||||
|
||||
|
||||
class InvalidOrderStatusException(BusinessLogicException):
|
||||
"""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):
|
||||
"""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},
|
||||
)
|
||||
@@ -1,54 +0,0 @@
|
||||
# app/exceptions/order_item_exception.py
|
||||
"""Order item exception specific exceptions."""
|
||||
|
||||
from .base import BusinessLogicException, ResourceNotFoundException
|
||||
|
||||
|
||||
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},
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
# Payment processing exceptions
|
||||
@@ -1,44 +0,0 @@
|
||||
# app/exceptions/platform.py
|
||||
"""
|
||||
Platform-related exceptions.
|
||||
|
||||
Custom exceptions for platform management operations.
|
||||
"""
|
||||
|
||||
from app.exceptions.base import WizamartException
|
||||
|
||||
|
||||
class PlatformNotFoundException(WizamartException):
|
||||
"""Raised when a platform is not found."""
|
||||
|
||||
def __init__(self, code: str):
|
||||
super().__init__(
|
||||
message=f"Platform not found: {code}",
|
||||
error_code="PLATFORM_NOT_FOUND",
|
||||
status_code=404,
|
||||
details={"platform_code": code},
|
||||
)
|
||||
|
||||
|
||||
class PlatformInactiveException(WizamartException):
|
||||
"""Raised when trying to access an inactive platform."""
|
||||
|
||||
def __init__(self, code: str):
|
||||
super().__init__(
|
||||
message=f"Platform is inactive: {code}",
|
||||
error_code="PLATFORM_INACTIVE",
|
||||
status_code=403,
|
||||
details={"platform_code": code},
|
||||
)
|
||||
|
||||
|
||||
class PlatformUpdateException(WizamartException):
|
||||
"""Raised when platform update fails."""
|
||||
|
||||
def __init__(self, code: str, reason: str):
|
||||
super().__init__(
|
||||
message=f"Failed to update platform {code}: {reason}",
|
||||
error_code="PLATFORM_UPDATE_FAILED",
|
||||
status_code=400,
|
||||
details={"platform_code": code, "reason": reason},
|
||||
)
|
||||
@@ -1,142 +0,0 @@
|
||||
# app/exceptions/product.py
|
||||
"""
|
||||
Product (vendor catalog) specific exceptions.
|
||||
"""
|
||||
|
||||
from .base import (
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class ProductNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a product is not found in vendor catalog."""
|
||||
|
||||
def __init__(self, product_id: int, vendor_id: int | None = None):
|
||||
if vendor_id:
|
||||
message = f"Product with ID '{product_id}' not found in vendor {vendor_id} catalog"
|
||||
else:
|
||||
message = f"Product with ID '{product_id}' not found"
|
||||
|
||||
super().__init__(
|
||||
resource_type="Product",
|
||||
identifier=str(product_id),
|
||||
message=message,
|
||||
error_code="PRODUCT_NOT_FOUND",
|
||||
)
|
||||
# Add extra details after init
|
||||
self.details["product_id"] = product_id
|
||||
if vendor_id:
|
||||
self.details["vendor_id"] = vendor_id
|
||||
|
||||
|
||||
class ProductAlreadyExistsException(ConflictException):
|
||||
"""Raised when trying to add a marketplace product that's already in vendor catalog."""
|
||||
|
||||
def __init__(self, vendor_id: int, marketplace_product_id: int):
|
||||
super().__init__(
|
||||
message=f"Marketplace product {marketplace_product_id} already exists in vendor {vendor_id} catalog",
|
||||
error_code="PRODUCT_ALREADY_EXISTS",
|
||||
details={
|
||||
"vendor_id": vendor_id,
|
||||
"marketplace_product_id": marketplace_product_id,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class ProductNotInCatalogException(ResourceNotFoundException):
|
||||
"""Raised when trying to access a product that's not in vendor's catalog."""
|
||||
|
||||
def __init__(self, product_id: int, vendor_id: int):
|
||||
super().__init__(
|
||||
resource_type="Product",
|
||||
identifier=str(product_id),
|
||||
message=f"Product {product_id} is not in vendor {vendor_id} catalog",
|
||||
error_code="PRODUCT_NOT_IN_CATALOG",
|
||||
details={
|
||||
"product_id": product_id,
|
||||
"vendor_id": vendor_id,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class ProductNotActiveException(BusinessLogicException):
|
||||
"""Raised when trying to perform operations on inactive product."""
|
||||
|
||||
def __init__(self, product_id: int, vendor_id: int):
|
||||
super().__init__(
|
||||
message=f"Product {product_id} in vendor {vendor_id} catalog is not active",
|
||||
error_code="PRODUCT_NOT_ACTIVE",
|
||||
details={
|
||||
"product_id": product_id,
|
||||
"vendor_id": vendor_id,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class InvalidProductDataException(ValidationException):
|
||||
"""Raised when product data is invalid."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid product data",
|
||||
field: str | None = None,
|
||||
details: dict | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "INVALID_PRODUCT_DATA"
|
||||
|
||||
|
||||
class ProductValidationException(ValidationException):
|
||||
"""Raised when product validation fails."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Product validation failed",
|
||||
field: str | None = None,
|
||||
validation_errors: dict | None = None,
|
||||
):
|
||||
details = {}
|
||||
if validation_errors:
|
||||
details["validation_errors"] = validation_errors
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "PRODUCT_VALIDATION_FAILED"
|
||||
|
||||
|
||||
class CannotDeleteProductWithInventoryException(BusinessLogicException):
|
||||
"""Raised when trying to delete a product that has inventory."""
|
||||
|
||||
def __init__(self, product_id: int, inventory_count: int):
|
||||
super().__init__(
|
||||
message=f"Cannot delete product {product_id} - it has {inventory_count} inventory entries",
|
||||
error_code="CANNOT_DELETE_PRODUCT_WITH_INVENTORY",
|
||||
details={
|
||||
"product_id": product_id,
|
||||
"inventory_count": inventory_count,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class CannotDeleteProductWithOrdersException(BusinessLogicException):
|
||||
"""Raised when trying to delete a product that has been ordered."""
|
||||
|
||||
def __init__(self, product_id: int, order_count: int):
|
||||
super().__init__(
|
||||
message=f"Cannot delete product {product_id} - it has {order_count} associated orders",
|
||||
error_code="CANNOT_DELETE_PRODUCT_WITH_ORDERS",
|
||||
details={
|
||||
"product_id": product_id,
|
||||
"order_count": order_count,
|
||||
},
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
# Search exceptions
|
||||
@@ -1,273 +0,0 @@
|
||||
# app/exceptions/team.py
|
||||
"""
|
||||
Team management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .base import (
|
||||
AuthorizationException,
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class TeamMemberNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a team member is not found."""
|
||||
|
||||
def __init__(self, user_id: int, vendor_id: int | None = None):
|
||||
details = {"user_id": user_id}
|
||||
if vendor_id:
|
||||
details["vendor_id"] = vendor_id
|
||||
message = (
|
||||
f"Team member with user ID '{user_id}' not found in vendor {vendor_id}"
|
||||
)
|
||||
else:
|
||||
message = f"Team member with user ID '{user_id}' not found"
|
||||
|
||||
super().__init__(
|
||||
resource_type="TeamMember",
|
||||
identifier=str(user_id),
|
||||
message=message,
|
||||
error_code="TEAM_MEMBER_NOT_FOUND",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class TeamMemberAlreadyExistsException(ConflictException):
|
||||
"""Raised when trying to add a user who is already a team member."""
|
||||
|
||||
def __init__(self, user_id: int, vendor_id: int):
|
||||
super().__init__(
|
||||
message=f"User {user_id} is already a team member of vendor {vendor_id}",
|
||||
error_code="TEAM_MEMBER_ALREADY_EXISTS",
|
||||
details={
|
||||
"user_id": user_id,
|
||||
"vendor_id": vendor_id,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class TeamInvitationNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a team invitation is not found."""
|
||||
|
||||
def __init__(self, invitation_token: str):
|
||||
super().__init__(
|
||||
resource_type="TeamInvitation",
|
||||
identifier=invitation_token,
|
||||
message=f"Team invitation with token '{invitation_token}' not found or expired",
|
||||
error_code="TEAM_INVITATION_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class TeamInvitationExpiredException(BusinessLogicException):
|
||||
"""Raised when trying to accept an expired invitation."""
|
||||
|
||||
def __init__(self, invitation_token: str):
|
||||
super().__init__(
|
||||
message="Team invitation has expired",
|
||||
error_code="TEAM_INVITATION_EXPIRED",
|
||||
details={"invitation_token": invitation_token},
|
||||
)
|
||||
|
||||
|
||||
class TeamInvitationAlreadyAcceptedException(ConflictException):
|
||||
"""Raised when trying to accept an already accepted invitation."""
|
||||
|
||||
def __init__(self, invitation_token: str):
|
||||
super().__init__(
|
||||
message="Team invitation has already been accepted",
|
||||
error_code="TEAM_INVITATION_ALREADY_ACCEPTED",
|
||||
details={"invitation_token": invitation_token},
|
||||
)
|
||||
|
||||
|
||||
class UnauthorizedTeamActionException(AuthorizationException):
|
||||
"""Raised when user tries to perform team action without permission."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
action: str,
|
||||
user_id: int | None = None,
|
||||
required_permission: str | None = None,
|
||||
):
|
||||
details = {"action": action}
|
||||
if user_id:
|
||||
details["user_id"] = user_id
|
||||
if required_permission:
|
||||
details["required_permission"] = required_permission
|
||||
|
||||
super().__init__(
|
||||
message=f"Unauthorized to perform action: {action}",
|
||||
error_code="UNAUTHORIZED_TEAM_ACTION",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class CannotRemoveOwnerException(BusinessLogicException):
|
||||
"""Raised when trying to remove the vendor owner from team."""
|
||||
|
||||
def __init__(self, user_id: int, vendor_id: int):
|
||||
super().__init__(
|
||||
message="Cannot remove vendor owner from team",
|
||||
error_code="CANNOT_REMOVE_OWNER",
|
||||
details={
|
||||
"user_id": user_id,
|
||||
"vendor_id": vendor_id,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class CannotModifyOwnRoleException(BusinessLogicException):
|
||||
"""Raised when user tries to modify their own role."""
|
||||
|
||||
def __init__(self, user_id: int):
|
||||
super().__init__(
|
||||
message="Cannot modify your own role",
|
||||
error_code="CANNOT_MODIFY_OWN_ROLE",
|
||||
details={"user_id": user_id},
|
||||
)
|
||||
|
||||
|
||||
class RoleNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a role is not found."""
|
||||
|
||||
def __init__(self, role_id: int, vendor_id: int | None = None):
|
||||
details = {"role_id": role_id}
|
||||
if vendor_id:
|
||||
details["vendor_id"] = vendor_id
|
||||
message = f"Role with ID '{role_id}' not found in vendor {vendor_id}"
|
||||
else:
|
||||
message = f"Role with ID '{role_id}' not found"
|
||||
|
||||
super().__init__(
|
||||
resource_type="Role",
|
||||
identifier=str(role_id),
|
||||
message=message,
|
||||
error_code="ROLE_NOT_FOUND",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class InvalidRoleException(ValidationException):
|
||||
"""Raised when role data is invalid."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid role data",
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "INVALID_ROLE_DATA"
|
||||
|
||||
|
||||
class InsufficientTeamPermissionsException(AuthorizationException):
|
||||
"""Raised when user lacks required team permissions for an action."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
required_permission: str,
|
||||
user_id: int | None = None,
|
||||
action: str | None = None,
|
||||
):
|
||||
details = {"required_permission": required_permission}
|
||||
if user_id:
|
||||
details["user_id"] = user_id
|
||||
if action:
|
||||
details["action"] = action
|
||||
|
||||
message = f"Insufficient team permissions. Required: {required_permission}"
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="INSUFFICIENT_TEAM_PERMISSIONS",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class MaxTeamMembersReachedException(BusinessLogicException):
|
||||
"""Raised when vendor has reached maximum team members limit."""
|
||||
|
||||
def __init__(self, max_members: int, vendor_id: int):
|
||||
super().__init__(
|
||||
message=f"Maximum number of team members reached ({max_members})",
|
||||
error_code="MAX_TEAM_MEMBERS_REACHED",
|
||||
details={
|
||||
"max_members": max_members,
|
||||
"vendor_id": vendor_id,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class TeamValidationException(ValidationException):
|
||||
"""Raised when team operation validation fails."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Team operation validation failed",
|
||||
field: str | None = None,
|
||||
validation_errors: dict[str, str] | None = None,
|
||||
):
|
||||
details = {}
|
||||
if validation_errors:
|
||||
details["validation_errors"] = validation_errors
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "TEAM_VALIDATION_FAILED"
|
||||
|
||||
|
||||
class InvalidInvitationDataException(ValidationException):
|
||||
"""Raised when team invitation data is invalid."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid invitation data",
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "INVALID_INVITATION_DATA"
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# NEW: Add InvalidInvitationTokenException
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class InvalidInvitationTokenException(ValidationException):
|
||||
"""Raised when invitation token is invalid, expired, or already used.
|
||||
|
||||
This is a general exception for any invitation token validation failure.
|
||||
Use this when checking invitation tokens during the acceptance flow.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid or expired invitation token",
|
||||
invitation_token: str | None = None,
|
||||
):
|
||||
details = {}
|
||||
if invitation_token:
|
||||
details["invitation_token"] = invitation_token
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
field="invitation_token",
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "INVALID_INVITATION_TOKEN"
|
||||
@@ -1,190 +0,0 @@
|
||||
# app/exceptions/vendor.py
|
||||
"""
|
||||
Vendor management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .base import (
|
||||
AuthorizationException,
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class VendorNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a vendor is not found."""
|
||||
|
||||
def __init__(self, vendor_identifier: str, identifier_type: str = "code"):
|
||||
if identifier_type.lower() == "id":
|
||||
message = f"Vendor with ID '{vendor_identifier}' not found"
|
||||
else:
|
||||
message = f"Vendor with code '{vendor_identifier}' not found"
|
||||
|
||||
super().__init__(
|
||||
resource_type="Vendor",
|
||||
identifier=vendor_identifier,
|
||||
message=message,
|
||||
error_code="VENDOR_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class VendorAlreadyExistsException(ConflictException):
|
||||
"""Raised when trying to create a vendor that already exists."""
|
||||
|
||||
def __init__(self, vendor_code: str):
|
||||
super().__init__(
|
||||
message=f"Vendor with code '{vendor_code}' already exists",
|
||||
error_code="VENDOR_ALREADY_EXISTS",
|
||||
details={"vendor_code": vendor_code},
|
||||
)
|
||||
|
||||
|
||||
class VendorNotActiveException(BusinessLogicException):
|
||||
"""Raised when trying to perform operations on inactive vendor."""
|
||||
|
||||
def __init__(self, vendor_code: str):
|
||||
super().__init__(
|
||||
message=f"Vendor '{vendor_code}' is not active",
|
||||
error_code="VENDOR_NOT_ACTIVE",
|
||||
details={"vendor_code": vendor_code},
|
||||
)
|
||||
|
||||
|
||||
class VendorNotVerifiedException(BusinessLogicException):
|
||||
"""Raised when trying to perform operations requiring verified vendor."""
|
||||
|
||||
def __init__(self, vendor_code: str):
|
||||
super().__init__(
|
||||
message=f"Vendor '{vendor_code}' is not verified",
|
||||
error_code="VENDOR_NOT_VERIFIED",
|
||||
details={"vendor_code": vendor_code},
|
||||
)
|
||||
|
||||
|
||||
class UnauthorizedVendorAccessException(AuthorizationException):
|
||||
"""Raised when user tries to access vendor they don't own."""
|
||||
|
||||
def __init__(self, vendor_code: str, user_id: int | None = None):
|
||||
details = {"vendor_code": vendor_code}
|
||||
if user_id:
|
||||
details["user_id"] = user_id
|
||||
|
||||
super().__init__(
|
||||
message=f"Unauthorized access to vendor '{vendor_code}'",
|
||||
error_code="UNAUTHORIZED_VENDOR_ACCESS",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class InvalidVendorDataException(ValidationException):
|
||||
"""Raised when vendor data is invalid or incomplete."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid vendor data",
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "INVALID_VENDOR_DATA"
|
||||
|
||||
|
||||
class VendorValidationException(ValidationException):
|
||||
"""Raised when vendor validation fails."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Vendor validation failed",
|
||||
field: str | None = None,
|
||||
validation_errors: dict[str, str] | None = None,
|
||||
):
|
||||
details = {}
|
||||
if validation_errors:
|
||||
details["validation_errors"] = validation_errors
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "VENDOR_VALIDATION_FAILED"
|
||||
|
||||
|
||||
class IncompleteVendorDataException(ValidationException):
|
||||
"""Raised when vendor data is missing required fields."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vendor_code: str,
|
||||
missing_fields: list,
|
||||
):
|
||||
super().__init__(
|
||||
message=f"Vendor '{vendor_code}' is missing required fields: {', '.join(missing_fields)}",
|
||||
details={
|
||||
"vendor_code": vendor_code,
|
||||
"missing_fields": missing_fields,
|
||||
},
|
||||
)
|
||||
self.error_code = "INCOMPLETE_VENDOR_DATA"
|
||||
|
||||
|
||||
class MaxVendorsReachedException(BusinessLogicException):
|
||||
"""Raised when user tries to create more vendors than allowed."""
|
||||
|
||||
def __init__(self, max_vendors: int, user_id: int | None = None):
|
||||
details = {"max_vendors": max_vendors}
|
||||
if user_id:
|
||||
details["user_id"] = user_id
|
||||
|
||||
super().__init__(
|
||||
message=f"Maximum number of vendors reached ({max_vendors})",
|
||||
error_code="MAX_VENDORS_REACHED",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class VendorAccessDeniedException(AuthorizationException):
|
||||
"""Raised when no vendor context is available for an authenticated endpoint."""
|
||||
|
||||
def __init__(self, message: str = "No vendor context available"):
|
||||
super().__init__(
|
||||
message=message,
|
||||
error_code="VENDOR_ACCESS_DENIED",
|
||||
)
|
||||
|
||||
|
||||
class VendorOwnerOnlyException(AuthorizationException):
|
||||
"""Raised when operation requires vendor owner role."""
|
||||
|
||||
def __init__(self, operation: str, vendor_code: str | None = None):
|
||||
details = {"operation": operation}
|
||||
if vendor_code:
|
||||
details["vendor_code"] = vendor_code
|
||||
|
||||
super().__init__(
|
||||
message=f"Operation '{operation}' requires vendor owner role",
|
||||
error_code="VENDOR_OWNER_ONLY",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class InsufficientVendorPermissionsException(AuthorizationException):
|
||||
"""Raised when user lacks required vendor permission."""
|
||||
|
||||
def __init__(self, required_permission: str, vendor_code: str | None = None):
|
||||
details = {"required_permission": required_permission}
|
||||
if vendor_code:
|
||||
details["vendor_code"] = vendor_code
|
||||
|
||||
super().__init__(
|
||||
message=f"Permission required: {required_permission}",
|
||||
error_code="INSUFFICIENT_VENDOR_PERMISSIONS",
|
||||
details=details,
|
||||
)
|
||||
@@ -1,146 +0,0 @@
|
||||
# app/exceptions/vendor_domain.py
|
||||
"""
|
||||
Vendor domain management specific exceptions.
|
||||
"""
|
||||
|
||||
from .base import (
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ExternalServiceException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class VendorDomainNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a vendor domain is not found."""
|
||||
|
||||
def __init__(self, domain_identifier: str, identifier_type: str = "ID"):
|
||||
if identifier_type.lower() == "domain":
|
||||
message = f"Domain '{domain_identifier}' not found"
|
||||
else:
|
||||
message = f"Domain with ID '{domain_identifier}' not found"
|
||||
|
||||
super().__init__(
|
||||
resource_type="VendorDomain",
|
||||
identifier=domain_identifier,
|
||||
message=message,
|
||||
error_code="VENDOR_DOMAIN_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class VendorDomainAlreadyExistsException(ConflictException):
|
||||
"""Raised when trying to add a domain that already exists."""
|
||||
|
||||
def __init__(self, domain: str, existing_vendor_id: int | None = None):
|
||||
details = {"domain": domain}
|
||||
if existing_vendor_id:
|
||||
details["existing_vendor_id"] = existing_vendor_id
|
||||
|
||||
super().__init__(
|
||||
message=f"Domain '{domain}' is already registered",
|
||||
error_code="VENDOR_DOMAIN_ALREADY_EXISTS",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class InvalidDomainFormatException(ValidationException):
|
||||
"""Raised when domain format is invalid."""
|
||||
|
||||
def __init__(self, domain: str, reason: str = "Invalid domain format"):
|
||||
super().__init__(
|
||||
message=f"{reason}: {domain}",
|
||||
field="domain",
|
||||
details={"domain": domain, "reason": reason},
|
||||
)
|
||||
self.error_code = "INVALID_DOMAIN_FORMAT"
|
||||
|
||||
|
||||
class ReservedDomainException(ValidationException):
|
||||
"""Raised when trying to use a reserved domain."""
|
||||
|
||||
def __init__(self, domain: str, reserved_part: str):
|
||||
super().__init__(
|
||||
message=f"Domain cannot use reserved subdomain: {reserved_part}",
|
||||
field="domain",
|
||||
details={"domain": domain, "reserved_part": reserved_part},
|
||||
)
|
||||
self.error_code = "RESERVED_DOMAIN"
|
||||
|
||||
|
||||
class DomainNotVerifiedException(BusinessLogicException):
|
||||
"""Raised when trying to activate an unverified domain."""
|
||||
|
||||
def __init__(self, domain_id: int, domain: str):
|
||||
super().__init__(
|
||||
message=f"Domain '{domain}' must be verified before activation",
|
||||
error_code="DOMAIN_NOT_VERIFIED",
|
||||
details={"domain_id": domain_id, "domain": domain},
|
||||
)
|
||||
|
||||
|
||||
class DomainVerificationFailedException(BusinessLogicException):
|
||||
"""Raised when domain verification fails."""
|
||||
|
||||
def __init__(self, domain: str, reason: str):
|
||||
super().__init__(
|
||||
message=f"Domain verification failed for '{domain}': {reason}",
|
||||
error_code="DOMAIN_VERIFICATION_FAILED",
|
||||
details={"domain": domain, "reason": reason},
|
||||
)
|
||||
|
||||
|
||||
class DomainAlreadyVerifiedException(BusinessLogicException):
|
||||
"""Raised when trying to verify an already verified domain."""
|
||||
|
||||
def __init__(self, domain_id: int, domain: str):
|
||||
super().__init__(
|
||||
message=f"Domain '{domain}' is already verified",
|
||||
error_code="DOMAIN_ALREADY_VERIFIED",
|
||||
details={"domain_id": domain_id, "domain": domain},
|
||||
)
|
||||
|
||||
|
||||
class MultiplePrimaryDomainsException(BusinessLogicException):
|
||||
"""Raised when trying to set multiple primary domains."""
|
||||
|
||||
def __init__(self, vendor_id: int):
|
||||
super().__init__(
|
||||
message="Vendor can only have one primary domain",
|
||||
error_code="MULTIPLE_PRIMARY_DOMAINS",
|
||||
details={"vendor_id": vendor_id},
|
||||
)
|
||||
|
||||
|
||||
class DNSVerificationException(ExternalServiceException):
|
||||
"""Raised when DNS verification service fails."""
|
||||
|
||||
def __init__(self, domain: str, reason: str):
|
||||
super().__init__(
|
||||
service_name="DNS",
|
||||
message=f"DNS verification failed for '{domain}': {reason}",
|
||||
error_code="DNS_VERIFICATION_ERROR",
|
||||
details={"domain": domain, "reason": reason},
|
||||
)
|
||||
|
||||
|
||||
class MaxDomainsReachedException(BusinessLogicException):
|
||||
"""Raised when vendor tries to add more domains than allowed."""
|
||||
|
||||
def __init__(self, vendor_id: int, max_domains: int):
|
||||
super().__init__(
|
||||
message=f"Maximum number of domains reached ({max_domains})",
|
||||
error_code="MAX_DOMAINS_REACHED",
|
||||
details={"vendor_id": vendor_id, "max_domains": max_domains},
|
||||
)
|
||||
|
||||
|
||||
class UnauthorizedDomainAccessException(BusinessLogicException):
|
||||
"""Raised when trying to access domain that doesn't belong to vendor."""
|
||||
|
||||
def __init__(self, domain_id: int, vendor_id: int):
|
||||
super().__init__(
|
||||
message=f"Unauthorized access to domain {domain_id}",
|
||||
error_code="UNAUTHORIZED_DOMAIN_ACCESS",
|
||||
details={"domain_id": domain_id, "vendor_id": vendor_id},
|
||||
)
|
||||
@@ -1,129 +0,0 @@
|
||||
# app/exceptions/vendor_theme.py
|
||||
"""
|
||||
Vendor theme management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .base import (
|
||||
BusinessLogicException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class VendorThemeNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a vendor theme is not found."""
|
||||
|
||||
def __init__(self, vendor_identifier: str):
|
||||
super().__init__(
|
||||
resource_type="VendorTheme",
|
||||
identifier=vendor_identifier,
|
||||
message=f"Theme for vendor '{vendor_identifier}' not found",
|
||||
error_code="VENDOR_THEME_NOT_FOUND",
|
||||
)
|
||||
|
||||
|
||||
class InvalidThemeDataException(ValidationException):
|
||||
"""Raised when theme data is invalid."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid theme data",
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "INVALID_THEME_DATA"
|
||||
|
||||
|
||||
class ThemePresetNotFoundException(ResourceNotFoundException):
|
||||
"""Raised when a theme preset is not found."""
|
||||
|
||||
def __init__(self, preset_name: str, available_presets: list | None = None):
|
||||
details = {"preset_name": preset_name}
|
||||
if available_presets:
|
||||
details["available_presets"] = available_presets
|
||||
|
||||
super().__init__(
|
||||
resource_type="ThemePreset",
|
||||
identifier=preset_name,
|
||||
message=f"Theme preset '{preset_name}' not found",
|
||||
error_code="THEME_PRESET_NOT_FOUND",
|
||||
)
|
||||
self.details = details
|
||||
|
||||
|
||||
class ThemeValidationException(ValidationException):
|
||||
"""Raised when theme validation fails."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Theme validation failed",
|
||||
field: str | None = None,
|
||||
validation_errors: dict[str, str] | None = None,
|
||||
):
|
||||
details = {}
|
||||
if validation_errors:
|
||||
details["validation_errors"] = validation_errors
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
field=field,
|
||||
details=details,
|
||||
)
|
||||
self.error_code = "THEME_VALIDATION_FAILED"
|
||||
|
||||
|
||||
class ThemePresetAlreadyAppliedException(BusinessLogicException):
|
||||
"""Raised when trying to apply the same preset that's already active."""
|
||||
|
||||
def __init__(self, preset_name: str, vendor_code: str):
|
||||
super().__init__(
|
||||
message=f"Preset '{preset_name}' is already applied to vendor '{vendor_code}'",
|
||||
error_code="THEME_PRESET_ALREADY_APPLIED",
|
||||
details={"preset_name": preset_name, "vendor_code": vendor_code},
|
||||
)
|
||||
|
||||
|
||||
class InvalidColorFormatException(ValidationException):
|
||||
"""Raised when color format is invalid."""
|
||||
|
||||
def __init__(self, color_value: str, field: str):
|
||||
super().__init__(
|
||||
message=f"Invalid color format: {color_value}",
|
||||
field=field,
|
||||
details={"color_value": color_value},
|
||||
)
|
||||
self.error_code = "INVALID_COLOR_FORMAT"
|
||||
|
||||
|
||||
class InvalidFontFamilyException(ValidationException):
|
||||
"""Raised when font family is invalid."""
|
||||
|
||||
def __init__(self, font_value: str, field: str):
|
||||
super().__init__(
|
||||
message=f"Invalid font family: {font_value}",
|
||||
field=field,
|
||||
details={"font_value": font_value},
|
||||
)
|
||||
self.error_code = "INVALID_FONT_FAMILY"
|
||||
|
||||
|
||||
class ThemeOperationException(BusinessLogicException):
|
||||
"""Raised when theme operation fails."""
|
||||
|
||||
def __init__(self, operation: str, vendor_code: str, reason: str):
|
||||
super().__init__(
|
||||
message=f"Theme operation '{operation}' failed for vendor '{vendor_code}': {reason}",
|
||||
error_code="THEME_OPERATION_FAILED",
|
||||
details={
|
||||
"operation": operation,
|
||||
"vendor_code": vendor_code,
|
||||
"reason": reason,
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user