"""Services transactionnels pour la gestion des rôles RBAC. Les fonctions de ce module ne valident ni ne commitent la transaction appelante. Cela permet aux routes d'enregistrer simultanément l'utilisateur et ses accès. """ from ...extensions import db from ..models.rbac import Role, UserRole def replace_user_roles(user, role_ids, assigned_by=None): """Remplace l'ensemble des rôles actifs d'un utilisateur. Les liens déjà sélectionnés sont conservés (et donc leur traçabilité), les liens absents sont retirés et les nouveaux sont ajoutés sans doublon. ``role_ids`` peut être vide. Les rôles inactifs ou inconnus sont ignorés. La fonction travaille dans la transaction courante et ne committe jamais. """ normalized_ids = set() for value in role_ids or (): try: normalized_ids.add(int(value)) except (TypeError, ValueError): continue active_roles = { role.id: role for role in Role.query.filter(Role.id.in_(normalized_ids), Role.is_active.is_(True)).all() } if normalized_ids else {} selected_ids = set(active_roles) existing = {link.role_id: link for link in list(user.role_links)} for role_id, link in existing.items(): if role_id not in selected_ids: db.session.delete(link) for role_id in selected_ids - set(existing): db.session.add(UserRole( user_id=user.id, role_id=role_id, assigned_by_id=getattr(assigned_by, "id", assigned_by), )) return [active_roles[role_id] for role_id in sorted(selected_ids)]