fix(tenancy): fix team CRUD bugs + add member integration tests
Store team page: - Fix undefined user_id (API returns `id`, JS used `user_id`) - Fix wrong URL path in updateMember (remove redundant storeCode) - Fix update_member_role route passing wrong kwarg (new_role_id → new_role_name) - Add update_member() service method for role_id + is_active updates - Add :selected binding for role pre-selection in edit modal Merchant team page: - Add missing db.commit() on invite, update, and remove endpoints - Fix forward-reference string type annotation on MerchantTeamInvite - Add :selected binding for role pre-selection in edit modal Shared fixes: - Replace removed subscription_service.check_team_limit with usage_service - Replace removed subscription_service.get_current_tier in email service - Fix email config bool settings crashing on .lower() (value_type=boolean) Tests: 15 new integration tests for store team member API endpoints. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -76,10 +76,22 @@ class StoreTeamService:
|
||||
Dict with invitation details
|
||||
"""
|
||||
try:
|
||||
# Check team size limit from subscription
|
||||
from app.modules.billing.services import subscription_service
|
||||
# Check team size limit from subscription (skip if no subscription)
|
||||
try:
|
||||
from app.modules.billing.services.usage_service import usage_service
|
||||
|
||||
subscription_service.check_team_limit(db, store.id)
|
||||
limit_check = usage_service.check_limit(db, store.id, "team_members")
|
||||
if limit_check.limit is not None and not limit_check.can_proceed:
|
||||
raise TierLimitExceededException(
|
||||
message=limit_check.message or "Team member limit reached",
|
||||
limit_type="team_members",
|
||||
current=limit_check.current,
|
||||
limit=limit_check.limit,
|
||||
)
|
||||
except TierLimitExceededException:
|
||||
raise
|
||||
except Exception as e: # noqa: EXC003
|
||||
logger.warning(f"Could not check team limit (proceeding): {e}")
|
||||
|
||||
# Check if user already exists
|
||||
user = db.query(User).filter(User.email == email).first()
|
||||
@@ -331,8 +343,9 @@ class StoreTeamService:
|
||||
if store_user.is_owner:
|
||||
raise CannotRemoveOwnerException(user_id, store.id)
|
||||
|
||||
# Soft delete - just deactivate
|
||||
store_user.is_active = False
|
||||
from app.core.soft_delete import soft_delete
|
||||
|
||||
soft_delete(db, store_user, deleted_by_id=actor_user_id)
|
||||
|
||||
logger.info(f"Removed user {user_id} from store {store.store_code}")
|
||||
|
||||
@@ -438,6 +451,60 @@ class StoreTeamService:
|
||||
logger.error(f"Error updating member role: {str(e)}")
|
||||
raise
|
||||
|
||||
def update_member(
|
||||
self,
|
||||
db: Session,
|
||||
store: Store,
|
||||
user_id: int,
|
||||
role_id: int | None = None,
|
||||
is_active: bool | None = None,
|
||||
actor_user_id: int | None = None,
|
||||
) -> StoreUser:
|
||||
"""
|
||||
Update a team member's role (by ID) and/or active status.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
store: Store
|
||||
user_id: User ID
|
||||
role_id: New role ID (must belong to this store)
|
||||
is_active: New active status
|
||||
actor_user_id: Actor performing the update
|
||||
|
||||
Returns:
|
||||
Updated StoreUser
|
||||
"""
|
||||
store_user = (
|
||||
db.query(StoreUser)
|
||||
.filter(StoreUser.store_id == store.id, StoreUser.user_id == user_id)
|
||||
.first()
|
||||
)
|
||||
if not store_user:
|
||||
raise UserNotFoundException(str(user_id))
|
||||
|
||||
if role_id is not None:
|
||||
role = (
|
||||
db.query(Role)
|
||||
.filter(Role.id == role_id, Role.store_id == store.id)
|
||||
.first()
|
||||
)
|
||||
if not role:
|
||||
raise InvalidRoleException(f"Role {role_id} not found in store {store.id}")
|
||||
self.update_member_role(
|
||||
db=db,
|
||||
store=store,
|
||||
user_id=user_id,
|
||||
new_role_name=role.name,
|
||||
actor_user_id=actor_user_id,
|
||||
)
|
||||
|
||||
if is_active is not None:
|
||||
store_user.is_active = is_active
|
||||
|
||||
db.flush()
|
||||
db.refresh(store_user)
|
||||
return store_user
|
||||
|
||||
def get_team_members(
|
||||
self,
|
||||
db: Session,
|
||||
|
||||
Reference in New Issue
Block a user