feat: implement company-based ownership architecture
- Add database migration to make vendor.owner_user_id nullable - Update Vendor model to support company-based ownership (DEPRECATED vendor.owner_user_id) - Implement company_service with singleton pattern (consistent with vendor_service) - Create Company model with proper relationships to vendors and users - Add company exception classes for proper error handling - Refactor companies API to use singleton service pattern Architecture Change: - OLD: Each vendor has its own owner (vendor.owner_user_id) - NEW: Vendors belong to a company, company has one owner (company.owner_user_id) - This allows one company owner to manage multiple vendor brands Technical Details: - Company service uses singleton pattern (not factory) - Company service accepts db: Session as parameter (follows SVC-003) - Uses AuthManager for password hashing (consistent with admin_service) - Added _generate_temp_password() helper method
This commit is contained in:
128
app/exceptions/company.py
Normal file
128
app/exceptions/company.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# 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},
|
||||
)
|
||||
Reference in New Issue
Block a user