refactor: update services for company-centric ownership
- Update admin_service: remove vendor owner methods, fix get_all_vendors query - Update company_service: add transfer_ownership method - Update vendor_service: check ownership via company relationship - Remove backwards compatibility code for Vendor.owner_user_id 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,6 @@ from sqlalchemy.orm import Session
|
||||
from app.exceptions import (
|
||||
InvalidVendorDataException,
|
||||
MarketplaceProductNotFoundException,
|
||||
MaxVendorsReachedException,
|
||||
ProductAlreadyExistsException,
|
||||
UnauthorizedVendorAccessException,
|
||||
ValidationException,
|
||||
@@ -41,27 +40,50 @@ class VendorService:
|
||||
self, db: Session, vendor_data: VendorCreate, current_user: User
|
||||
) -> Vendor:
|
||||
"""
|
||||
Create a new vendor.
|
||||
Create a new vendor under a company.
|
||||
|
||||
DEPRECATED: This method is for self-service vendor creation by company owners.
|
||||
For admin operations, use admin_service.create_vendor() instead.
|
||||
|
||||
The new architecture:
|
||||
- Companies are the business entities with owners and contact info
|
||||
- Vendors are storefronts/brands under companies
|
||||
- The company_id is required in vendor_data
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
vendor_data: Vendor creation data
|
||||
current_user: User creating the vendor
|
||||
vendor_data: Vendor creation data (must include company_id)
|
||||
current_user: User creating the vendor (must be company owner or admin)
|
||||
|
||||
Returns:
|
||||
Created vendor object
|
||||
|
||||
Raises:
|
||||
VendorAlreadyExistsException: If vendor code already exists
|
||||
MaxVendorsReachedException: If user has reached maximum vendors
|
||||
UnauthorizedVendorAccessException: If user is not company owner
|
||||
InvalidVendorDataException: If vendor data is invalid
|
||||
"""
|
||||
try:
|
||||
# Validate vendor data
|
||||
self._validate_vendor_data(vendor_data)
|
||||
from models.database.company import Company
|
||||
|
||||
# Check user's vendor limit (if applicable)
|
||||
self._check_vendor_limit(db, current_user)
|
||||
try:
|
||||
# Validate company_id is provided
|
||||
if not hasattr(vendor_data, 'company_id') or not vendor_data.company_id:
|
||||
raise InvalidVendorDataException(
|
||||
"company_id is required to create a vendor", field="company_id"
|
||||
)
|
||||
|
||||
# Get company and verify ownership
|
||||
company = db.query(Company).filter(Company.id == vendor_data.company_id).first()
|
||||
if not company:
|
||||
raise InvalidVendorDataException(
|
||||
f"Company with ID {vendor_data.company_id} not found", field="company_id"
|
||||
)
|
||||
|
||||
# Check if user is company owner or admin
|
||||
if current_user.role != "admin" and company.owner_user_id != current_user.id:
|
||||
raise UnauthorizedVendorAccessException(
|
||||
f"company-{vendor_data.company_id}", current_user.id
|
||||
)
|
||||
|
||||
# Normalize vendor code to uppercase
|
||||
normalized_vendor_code = vendor_data.vendor_code.upper()
|
||||
@@ -70,13 +92,16 @@ class VendorService:
|
||||
if self._vendor_code_exists(db, normalized_vendor_code):
|
||||
raise VendorAlreadyExistsException(normalized_vendor_code)
|
||||
|
||||
# Create vendor with uppercase code
|
||||
vendor_dict = vendor_data.model_dump()
|
||||
vendor_dict["vendor_code"] = normalized_vendor_code # Store as uppercase
|
||||
|
||||
# Create vendor linked to company
|
||||
new_vendor = Vendor(
|
||||
**vendor_dict,
|
||||
owner_user_id=current_user.id,
|
||||
company_id=company.id,
|
||||
vendor_code=normalized_vendor_code,
|
||||
subdomain=vendor_data.subdomain.lower(),
|
||||
name=vendor_data.name,
|
||||
description=vendor_data.description,
|
||||
letzshop_csv_url_fr=vendor_data.letzshop_csv_url_fr,
|
||||
letzshop_csv_url_en=vendor_data.letzshop_csv_url_en,
|
||||
letzshop_csv_url_de=vendor_data.letzshop_csv_url_de,
|
||||
is_active=True,
|
||||
is_verified=(current_user.role == "admin"),
|
||||
)
|
||||
@@ -86,21 +111,21 @@ class VendorService:
|
||||
db.refresh(new_vendor)
|
||||
|
||||
logger.info(
|
||||
f"New vendor created: {new_vendor.vendor_code} by {current_user.username}"
|
||||
f"New vendor created: {new_vendor.vendor_code} under company {company.name} by {current_user.username}"
|
||||
)
|
||||
return new_vendor
|
||||
|
||||
except (
|
||||
VendorAlreadyExistsException,
|
||||
MaxVendorsReachedException,
|
||||
UnauthorizedVendorAccessException,
|
||||
InvalidVendorDataException,
|
||||
):
|
||||
db.rollback()
|
||||
raise # Re-raise custom exceptions
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error creating vendor : {str(e)}")
|
||||
raise ValidationException("Failed to create vendor ")
|
||||
logger.error(f"Error creating vendor: {str(e)}")
|
||||
raise ValidationException("Failed to create vendor")
|
||||
|
||||
def get_vendors(
|
||||
self,
|
||||
@@ -130,11 +155,19 @@ class VendorService:
|
||||
|
||||
# Non-admin users can only see active and verified vendors, plus their own
|
||||
if current_user.role != "admin":
|
||||
# Get vendor IDs the user owns through companies
|
||||
from models.database.company import Company
|
||||
|
||||
owned_vendor_ids = (
|
||||
db.query(Vendor.id)
|
||||
.join(Company)
|
||||
.filter(Company.owner_user_id == current_user.id)
|
||||
.subquery()
|
||||
)
|
||||
query = query.filter(
|
||||
(Vendor.is_active == True)
|
||||
& (
|
||||
(Vendor.is_verified == True)
|
||||
| (Vendor.owner_user_id == current_user.id)
|
||||
(Vendor.is_verified == True) | (Vendor.id.in_(owned_vendor_ids))
|
||||
)
|
||||
)
|
||||
else:
|
||||
@@ -305,38 +338,6 @@ class VendorService:
|
||||
raise ValidationException("Failed to retrieve vendor products")
|
||||
|
||||
# Private helper methods
|
||||
def _validate_vendor_data(self, vendor_data: VendorCreate) -> None:
|
||||
"""Validate vendor creation data."""
|
||||
if not vendor_data.vendor_code or not vendor_data.vendor_code.strip():
|
||||
raise InvalidVendorDataException(
|
||||
"Vendor code is required", field="vendor_code"
|
||||
)
|
||||
|
||||
if not vendor_data.vendor_name or not vendor_data.vendor_name.strip():
|
||||
raise InvalidVendorDataException("Vendor name is required", field="name")
|
||||
|
||||
# Validate vendor code format (alphanumeric, underscores, hyphens)
|
||||
import re
|
||||
|
||||
if not re.match(r"^[A-Za-z0-9_-]+$", vendor_data.vendor_code):
|
||||
raise InvalidVendorDataException(
|
||||
"Vendor code can only contain letters, numbers, underscores, and hyphens",
|
||||
field="vendor_code",
|
||||
)
|
||||
|
||||
def _check_vendor_limit(self, db: Session, user: User) -> None:
|
||||
"""Check if user has reached maximum vendor limit."""
|
||||
if user.role == "admin":
|
||||
return # Admins have no limit
|
||||
|
||||
user_vendor_count = (
|
||||
db.query(Vendor).filter(Vendor.owner_user_id == user.id).count()
|
||||
)
|
||||
max_vendors = 5 # Configure this as needed
|
||||
|
||||
if user_vendor_count >= max_vendors:
|
||||
raise MaxVendorsReachedException(max_vendors, user.id)
|
||||
|
||||
def _vendor_code_exists(self, db: Session, vendor_code: str) -> bool:
|
||||
"""Check if vendor code already exists (case-insensitive)."""
|
||||
return (
|
||||
@@ -375,16 +376,20 @@ class VendorService:
|
||||
|
||||
def _can_access_vendor(self, vendor: Vendor, user: User) -> bool:
|
||||
"""Check if user can access vendor."""
|
||||
# Admins and owners can always access
|
||||
if user.role == "admin" or vendor.owner_user_id == user.id:
|
||||
# Admins can always access
|
||||
if user.role == "admin":
|
||||
return True
|
||||
|
||||
# Company owners can access their vendors
|
||||
if vendor.company and vendor.company.owner_user_id == user.id:
|
||||
return True
|
||||
|
||||
# Others can only access active and verified vendors
|
||||
return vendor.is_active and vendor.is_verified
|
||||
|
||||
def _is_vendor_owner(self, vendor: Vendor, user: User) -> bool:
|
||||
"""Check if user is vendor owner."""
|
||||
return vendor.owner_user_id == user.id
|
||||
"""Check if user is vendor owner (via company ownership)."""
|
||||
return vendor.company and vendor.company.owner_user_id == user.id
|
||||
|
||||
|
||||
# Create service instance following the same pattern as other services
|
||||
|
||||
Reference in New Issue
Block a user