from django.core.exceptions import PermissionDenied from django.utils.translation import gettext as _ from functools import wraps from common.models import UserContractAccess, UserContractAccess, UserConfig, UserThematics from contracts.models import Contract, ContractOrder, ContractOrderDocument, ClaimDeclarationAttachment ALLOWED_ROLES_FOR_CONTRACT_ACCESS = {"admin", "manager", "external_manager", "controller", "top_manager"} def get_allowed_contracts_for_user(user): """ Retourne une queryset des contrats accessibles à un utilisateur s'il a le droit de voir les interventions ET un rôle autorisé. """ try: config = user.config except AttributeError: return Contract.objects.none() # Vérifie les rôles user_roles = set(config.roles.values_list("name", flat=True)) if not user_roles.intersection(ALLOWED_ROLES_FOR_CONTRACT_ACCESS): return Contract.objects.none() if "admin" in user_roles or "top_manager" in user_roles: return Contract.objects.all() # Filtre les UserContractAccess valides allowed_contract_ids = UserContractAccess.objects.filter( user_config=config, can_view_interventions=True ).values_list("contract_id", flat=True) return Contract.objects.filter(id__in=allowed_contract_ids) def user_has_access_to_contract(user, contract_id): """ Vérifie si un utilisateur peut accéder à un contrat donné : - A un rôle autorisé - A un accès UserContractAccess avec can_view_interventions=True """ try: config = user.config except AttributeError: return False user_roles = set(config.roles.values_list("name", flat=True)) if not user_roles.intersection(ALLOWED_ROLES_FOR_CONTRACT_ACCESS): return False if "admin" in user_roles or "top_manager" in user_roles: return True return UserContractAccess.objects.filter( user_config=config, contract_id=contract_id, can_view_interventions=True ).exists() def user_can_view_contract_prices(user, contract_id): """Vrai si l'utilisateur est admin ou dispose du droit can_view_prices sur le contrat.""" try: config = user.config except AttributeError: return False user_roles = set(config.roles.values_list("name", flat=True)) if not user_roles.intersection(ALLOWED_ROLES_FOR_CONTRACT_ACCESS): return False if "admin" in user_roles or "top_manager" in user_roles: return True return UserContractAccess.objects.filter( user_config=config, contract_id=contract_id, can_view_prices=True ).exists() def contract_view_required(view_func): """ Décorateur basé sur user_has_access_to_contract. La vue doit avoir un paramètre `contract_id` (en keyword arg). """ @wraps(view_func) def _wrapped_view(request, *args, **kwargs): contract_id = kwargs.get("contract_id") if contract_id is None: raise PermissionDenied(_("Paramètre 'contract_id' manquant.")) from .permissions import user_has_access_to_contract # éviter import circulaire if not user_has_access_to_contract(request.user, contract_id): raise PermissionDenied(_("Accès au contrat refusé.")) return view_func(request, *args, **kwargs) return _wrapped_view def user_has_access_to_contract_order(user, order_id): """ Vérifie que l'utilisateur a accès au contrat lié à une ContractOrder. """ try: order = ContractOrder.objects.select_related('contract').get(id=order_id) except ContractOrder.DoesNotExist: return False return user_has_access_to_contract(user, order.contract.id) def order_view_required(view_func): """ Décorateur pour restreindre l'accès à une vue de ContractOrder. Nécessite un paramètre keyword `order_id`. """ @wraps(view_func) def _wrapped_view(request, *args, **kwargs): order_id = kwargs.get("order_id") if order_id is None: raise PermissionDenied(_("Paramètre 'order_id' manquant.")) from .permissions import user_has_access_to_contract_order if not user_has_access_to_contract_order(request.user, order_id): raise PermissionDenied(_("Accès à la commande refusé.")) return view_func(request, *args, **kwargs) return _wrapped_view ALLOWED_ROLES_FOR_ORDER_CREATION = {"admin", "manager"} def user_can_add_order(user): try: config = user.config except AttributeError: return False user_roles = set(config.roles.values_list("name", flat=True)) if user_roles.intersection(ALLOWED_ROLES_FOR_ORDER_CREATION): return True return False def user_can_add_order_for_contract(user, contract_id): """ Vérifie que l'utilisateur a le droit d'ajouter une commande à un contrat donné : - Il doit avoir un rôle 'admin' ou 'manager' - Il doit avoir un accès UserContractAccess avec can_view_interventions=True """ if not user.is_authenticated: return False try: config = user.config except AttributeError: return False user_roles = set(config.roles.values_list("name", flat=True)) if not user_roles.intersection(ALLOWED_ROLES_FOR_ORDER_CREATION): return False return UserContractAccess.objects.filter( user_config=config, contract_id=contract_id, can_view_interventions=True ).exists() def user_has_access_to_contract_posts(user, contract_id): """ Vérifie que l'utilisateur a accès aux postes du contrat. """ try: config = user.config except AttributeError: return False user_roles = set(config.roles.values_list("name", flat=True)) if not user_roles.intersection(ALLOWED_ROLES_FOR_CONTRACT_ACCESS): return False if "admin" in user_roles or "top_manager" in user_roles: return True return UserContractAccess.objects.filter( user_config=config, contract_id=contract_id, can_view_posts=True ).exists() ALLOWED_ROLES_FOR_QUOTE_MANAGEMENT = {"admin", "external_manager"} def user_can_manage_quote_for_contract(user, contract_id): """ Vérifie que l'utilisateur a le droit de gérer le devis d'une commande : - Il doit avoir un rôle 'admin' ou 'external_manager' - Il doit avoir un accès UserContractAccess avec can_view_interventions=True """ if not user.is_authenticated: return False try: config = user.config except AttributeError: return False user_roles = set(config.roles.values_list("name", flat=True)) if not user_roles.intersection(ALLOWED_ROLES_FOR_QUOTE_MANAGEMENT): return False return UserContractAccess.objects.filter( user_config=config, contract_id=contract_id, can_view_interventions=True ).exists() def user_can_manage_direct_order_posts(user, contract_id): """ Vérifie que l'utilisateur peut créer/modifier/supprimer des commandes directes : - Il doit avoir le rôle 'external_manager' - Il doit avoir un accès UserContractAccess avec can_view_interventions=True """ if not user.is_authenticated: return False try: config = user.config except AttributeError: return False if not config.roles.filter(name='external_manager').exists(): return False return UserContractAccess.objects.filter( user_config=config, contract_id=contract_id, can_view_interventions=True ).exists() ALLOWED_ROLES_FOR_DIRECT_POST_VALIDATION = {"admin", "manager", "controller"} def user_can_validate_direct_order_posts(user, contract_id): """ Vérifie que l'utilisateur peut valider/refuser des commandes directes : - Il doit avoir un rôle 'admin', 'manager' ou 'controller' - Il doit avoir un accès UserContractAccess avec can_view_interventions=True """ if not user.is_authenticated: return False try: config = user.config except AttributeError: return False user_roles = set(config.roles.values_list("name", flat=True)) if not user_roles.intersection(ALLOWED_ROLES_FOR_DIRECT_POST_VALIDATION): return False return UserContractAccess.objects.filter( user_config=config, contract_id=contract_id, can_view_interventions=True ).exists() def user_can_generate_order_documents(user, order): """ Vérifie que l'utilisateur a le droit de générer des documents pour une commande donnée : - Il doit avoir un rôle 'admin' ou 'external_manager' - Il doit avoir un accès UserContractAccess avec can_view_interventions=True """ if not user.is_authenticated: return False try: config = user.config except AttributeError: return False user_roles = set(config.roles.values_list("name", flat=True)) if not user_roles.intersection({"admin", "external_manager"}): return False return UserContractAccess.objects.filter( user_config=config, contract_id=order.contract.id, can_view_interventions=True ).exists() # --------------------------------------------------------------------------- # Permissions équipes (CompanyTeam / CompanyMember) # --------------------------------------------------------------------------- def get_viewable_teams_for_user(user): """ Retourne le queryset des équipes que l'utilisateur peut consulter. Les admins voient toutes les équipes ; les autres ont besoin d'un UserCompanyTeamAccess avec can_view=True. """ from contracts.models import CompanyTeam, UserCompanyTeamAccess if not user.is_authenticated: return CompanyTeam.objects.none() try: config = user.config except AttributeError: return CompanyTeam.objects.none() user_roles = set(config.roles.values_list("name", flat=True)) if "admin" in user_roles: return CompanyTeam.objects.all() return CompanyTeam.objects.filter( user_accesses__user_config=config, user_accesses__can_view=True, ) def user_can_view_teams(user): """Vérifie si l'utilisateur peut accéder à au moins une équipe.""" from contracts.models import UserCompanyTeamAccess if not user.is_authenticated: return False try: config = user.config except AttributeError: return False user_roles = set(config.roles.values_list("name", flat=True)) if "admin" in user_roles: return True return UserCompanyTeamAccess.objects.filter( user_config=config, can_view=True, ).exists() def user_can_edit_teams(user): """ Vérifie si l'utilisateur a le droit d'éditer des équipes. Les admins ont toujours le droit ; les autres ont besoin d'un UserCompanyTeamAccess avec can_edit=True. """ from contracts.models import UserCompanyTeamAccess if not user.is_authenticated: return False try: config = user.config except AttributeError: return False user_roles = set(config.roles.values_list("name", flat=True)) if "admin" in user_roles: return True return UserCompanyTeamAccess.objects.filter( user_config=config, can_edit=True, ).exists() def user_can_edit_specific_team(user, team_id): """ Vérifie si l'utilisateur peut éditer une équipe précise (créer/supprimer des membres, déplacer des membres vers/depuis cette équipe). """ from contracts.models import UserCompanyTeamAccess if not user.is_authenticated: return False try: config = user.config except AttributeError: return False user_roles = set(config.roles.values_list("name", flat=True)) if "admin" in user_roles: return True return UserCompanyTeamAccess.objects.filter( user_config=config, team_id=team_id, can_edit=True, ).exists() def guard_contract_order_file(obj: ContractOrder, user) -> bool: # Accès si l’utilisateur a accès à la commande return user_has_access_to_contract_order(user, obj.id) def guard_contract_file(obj: Contract, user) -> bool: # Si un jour tu ajoutes un FileField sur Contract return user_has_access_to_contract(user, obj.id) def guard_order_document_file(obj: ContractOrderDocument, user) -> bool: # Document rattaché à une commande → on réutilise la même permission return user_has_access_to_contract_order(user, obj.order_id) def guard_claim_attachment_file(obj: ClaimDeclarationAttachment, user) -> bool: # Pièce jointe de déclaration de créance → accès via la commande du document parent return user_has_access_to_contract_order(user, obj.claim_document.order_id) def guard_order_attachment_file(obj, user) -> bool: # Pièce jointe générique d'une commande (photos, PDF, …) return user_has_access_to_contract_order(user, obj.order_id)