diff --git a/app/modules/messaging/services/email_service.py b/app/modules/messaging/services/email_service.py index e63f413c..91f1d586 100644 --- a/app/modules/messaging/services/email_service.py +++ b/app/modules/messaging/services/email_service.py @@ -404,10 +404,10 @@ def get_platform_email_config(db: Session) -> dict: config["smtp_password"] = db_smtp_password if db_smtp_password else settings.smtp_password db_smtp_use_tls = get_db_setting("smtp_use_tls") - config["smtp_use_tls"] = db_smtp_use_tls.lower() in ("true", "1", "yes") if db_smtp_use_tls else settings.smtp_use_tls + config["smtp_use_tls"] = bool(db_smtp_use_tls) if db_smtp_use_tls is not None else settings.smtp_use_tls db_smtp_use_ssl = get_db_setting("smtp_use_ssl") - config["smtp_use_ssl"] = db_smtp_use_ssl.lower() in ("true", "1", "yes") if db_smtp_use_ssl else settings.smtp_use_ssl + config["smtp_use_ssl"] = bool(db_smtp_use_ssl) if db_smtp_use_ssl is not None else settings.smtp_use_ssl # SendGrid db_sendgrid_key = get_db_setting("sendgrid_api_key") @@ -432,10 +432,10 @@ def get_platform_email_config(db: Session) -> dict: # Behavior db_enabled = get_db_setting("email_enabled") - config["enabled"] = db_enabled.lower() in ("true", "1", "yes") if db_enabled else settings.email_enabled + config["enabled"] = bool(db_enabled) if db_enabled is not None else settings.email_enabled db_debug = get_db_setting("email_debug") - config["debug"] = db_debug.lower() in ("true", "1", "yes") if db_debug else settings.email_debug + config["debug"] = bool(db_debug) if db_debug is not None else settings.email_debug return config @@ -1038,8 +1038,8 @@ class EmailService: subscription_service, ) - tier = subscription_service.get_current_tier(self.db, store_id) - self._store_tier_cache[store_id] = tier.value if tier else None + sub = subscription_service.get_subscription_for_store(self.db, store_id) + self._store_tier_cache[store_id] = sub.tier.code if sub and sub.tier else None return self._store_tier_cache[store_id] def _should_add_powered_by_footer(self, store_id: int | None) -> bool: diff --git a/app/modules/tenancy/routes/api/merchant.py b/app/modules/tenancy/routes/api/merchant.py index 4377a710..295308bd 100644 --- a/app/modules/tenancy/routes/api/merchant.py +++ b/app/modules/tenancy/routes/api/merchant.py @@ -25,6 +25,7 @@ from app.modules.tenancy.schemas import ( MerchantStoreUpdate, ) from app.modules.tenancy.schemas.auth import UserContext +from app.modules.tenancy.schemas.team import MerchantTeamInvite from app.modules.tenancy.services.merchant_service import merchant_service from app.modules.tenancy.services.merchant_store_service import merchant_store_service @@ -192,7 +193,7 @@ async def merchant_team_store_roles( @_account_router.post("/team/invite") async def merchant_team_invite( - data: "MerchantTeamInvite", + data: MerchantTeamInvite, current_user: UserContext = Depends(get_current_merchant_api), merchant=Depends(get_merchant_for_current_user), db: Session = Depends(get_db), @@ -233,6 +234,7 @@ async def merchant_team_invite( error=str(e), )) + db.commit() success_count = sum(1 for r in results if r.success) if success_count == len(results): message = f"Invitation sent to {data.email} for {success_count} store(s)" @@ -268,6 +270,7 @@ async def merchant_team_update_role( new_role_name=role_name, actor_user_id=current_user.id, ) + db.commit() return {"message": "Role updated successfully"} @@ -289,6 +292,7 @@ async def merchant_team_remove_member( user_id=user_id, actor_user_id=current_user.id, ) + db.commit() @_account_router.get("/profile", response_model=MerchantPortalProfileResponse) diff --git a/app/modules/tenancy/routes/api/store_team.py b/app/modules/tenancy/routes/api/store_team.py index 4bfc0001..9f6aed79 100644 --- a/app/modules/tenancy/routes/api/store_team.py +++ b/app/modules/tenancy/routes/api/store_team.py @@ -277,12 +277,13 @@ def update_team_member( """ store = request.state.store - store_team_service.update_member_role( + store_team_service.update_member( db=db, store=store, user_id=user_id, - new_role_id=update_data.role_id, + role_id=update_data.role_id, is_active=update_data.is_active, + actor_user_id=current_user.id, ) db.commit() diff --git a/app/modules/tenancy/routes/pages/store.py b/app/modules/tenancy/routes/pages/store.py index 2644dca2..909bbf3b 100644 --- a/app/modules/tenancy/routes/pages/store.py +++ b/app/modules/tenancy/routes/pages/store.py @@ -94,6 +94,7 @@ async def store_login_page( "request": request, "store_code": store_code, "platform_code": platform_code, + "frontend_type": "store", **get_jinja2_globals(language), }, ) diff --git a/app/modules/tenancy/services/store_team_service.py b/app/modules/tenancy/services/store_team_service.py index 99a5098f..5a73533a 100644 --- a/app/modules/tenancy/services/store_team_service.py +++ b/app/modules/tenancy/services/store_team_service.py @@ -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, diff --git a/app/modules/tenancy/static/store/js/team.js b/app/modules/tenancy/static/store/js/team.js index cb78f211..a4a0a6db 100644 --- a/app/modules/tenancy/static/store/js/team.js +++ b/app/modules/tenancy/static/store/js/team.js @@ -194,7 +194,7 @@ function storeTeam() { this.saving = true; try { await apiClient.put( - `/store/${this.storeCode}/team/members/${this.selectedMember.user_id}`, + `/store/team/members/${this.selectedMember.id}`, this.editForm ); @@ -228,7 +228,7 @@ function storeTeam() { this.saving = true; try { - await apiClient.delete(`/store/team/members/${this.selectedMember.user_id}`); + await apiClient.delete(`/store/team/members/${this.selectedMember.id}`); Utils.showToast(I18n.t('tenancy.messages.team_member_removed'), 'success'); storeTeamLog.info('Removed team member:', this.selectedMember.user_id); diff --git a/app/modules/tenancy/templates/tenancy/merchant/team.html b/app/modules/tenancy/templates/tenancy/merchant/team.html index a59879ad..a2369b41 100644 --- a/app/modules/tenancy/templates/tenancy/merchant/team.html +++ b/app/modules/tenancy/templates/tenancy/merchant/team.html @@ -299,7 +299,7 @@