loko/loko/stock/permissions.py

338 lines
10 KiB
Python

from functools import wraps
from django.shortcuts import get_object_or_404, redirect
from django.contrib import messages
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import PermissionDenied
from .models import Warehouse, WarehouseLocation, Product
from common.models import UserConfig
def get_user_config(user):
"""
Récupère ou crée la configuration utilisateur.
"""
if not user.is_authenticated or not user.pk:
return None
try:
return user.config
except UserConfig.DoesNotExist:
return UserConfig.objects.create(user=user)
def user_can_view_warehouse(user, warehouse):
"""
Vérifie si l'utilisateur peut visualiser un entrepôt.
"""
if not user.is_authenticated:
return False
config = get_user_config(user)
if not config:
return False
return config.can_view_warehouse(warehouse)
def user_can_manage_warehouse(user, warehouse):
"""
Vérifie si l'utilisateur peut gérer un entrepôt.
"""
if not user.is_authenticated:
return False
config = get_user_config(user)
if not config:
return False
return config.can_manage_warehouse(warehouse)
def get_user_accessible_warehouses(user, can_manage=False):
"""
Retourne la liste des entrepôts accessibles par l'utilisateur.
"""
if not user.is_authenticated:
return Warehouse.objects.none()
config = get_user_config(user)
if not config:
return Warehouse.objects.none()
return config.get_accessible_warehouses(can_manage=can_manage)
def warehouse_view_required(view_func):
"""
Décorateur pour vérifier que l'utilisateur peut visualiser l'entrepôt.
L'ID de l'entrepôt doit être passé en paramètre 'warehouse_id'.
"""
@wraps(view_func)
def wrapper(request, warehouse_id, *args, **kwargs):
warehouse = get_object_or_404(Warehouse, id=warehouse_id)
if not user_can_view_warehouse(request.user, warehouse):
messages.error(
request,
_("Vous n'avez pas la permission de visualiser cet entrepôt.")
)
return redirect('stock:warehouses')
return view_func(request, warehouse_id, *args, **kwargs)
return wrapper
def warehouse_manage_required(view_func):
"""
Décorateur pour vérifier que l'utilisateur peut gérer l'entrepôt.
L'ID de l'entrepôt doit être passé en paramètre 'warehouse_id'.
"""
@wraps(view_func)
def wrapper(request, warehouse_id, *args, **kwargs):
warehouse = get_object_or_404(Warehouse, id=warehouse_id)
if not user_can_manage_warehouse(request.user, warehouse):
messages.error(
request,
_("Vous n'avez pas la permission de gérer cet entrepôt.")
)
return redirect('stock:warehouses')
return view_func(request, warehouse_id, *args, **kwargs)
return wrapper
def location_view_required(view_func):
"""
Décorateur pour vérifier que l'utilisateur peut visualiser l'emplacement d'entrepôt.
L'ID de l'emplacement doit être passé en paramètre 'location_id'.
"""
@wraps(view_func)
def wrapper(request, location_id, *args, **kwargs):
location = get_object_or_404(WarehouseLocation, id=location_id)
if not user_can_view_warehouse(request.user, location.warehouse):
messages.error(
request,
_("Vous n'avez pas la permission de visualiser cet emplacement.")
)
return redirect('stock:warehouses')
return view_func(request, location_id, *args, **kwargs)
return wrapper
def location_manage_required(view_func):
"""
Décorateur pour vérifier que l'utilisateur peut gérer l'emplacement d'entrepôt.
L'ID de l'emplacement doit être passé en paramètre 'location_id'.
"""
@wraps(view_func)
def wrapper(request, location_id, *args, **kwargs):
location = get_object_or_404(WarehouseLocation, id=location_id)
if not user_can_manage_warehouse(request.user, location.warehouse):
messages.error(
request,
_("Vous n'avez pas la permission de gérer cet emplacement.")
)
return redirect('stock:warehouses')
return view_func(request, location_id, *args, **kwargs)
return wrapper
def product_view_required(view_func):
"""
Décorateur pour vérifier que l'utilisateur peut visualiser un produit.
Vérifie que l'utilisateur a accès à au moins un entrepôt contenant ce produit.
L'ID du produit doit être passé en paramètre 'product_id'.
"""
@wraps(view_func)
def wrapper(request, product_id, *args, **kwargs):
product = get_object_or_404(Product, id=product_id)
# Bypasser pour les superutilisateurs, le staff ou les admins
config = get_user_config(request.user)
if request.user.is_superuser or request.user.is_staff or (config and config.has_role('admin')):
return view_func(request, product_id, *args, **kwargs)
# Bypasser si le produit n'a aucun stock physique n'importe où
if not product.stocks.exists():
return view_func(request, product_id, *args, **kwargs)
accessible_warehouses = get_user_accessible_warehouses(request.user)
# Vérifier si le produit est dans un entrepôt accessible
product_warehouses = Warehouse.objects.filter(
locations__stocks__product=product
).distinct()
if not product_warehouses.filter(id__in=accessible_warehouses).exists():
messages.error(
request,
_("Vous n'avez pas la permission de visualiser ce produit.")
)
return redirect('stock:warehouses')
return view_func(request, product_id, *args, **kwargs)
return wrapper
def filter_warehouses_by_permission(queryset, user, can_manage=False):
"""
Filtre un queryset de Warehouse en fonction des permissions de l'utilisateur.
"""
accessible_warehouses = get_user_accessible_warehouses(user, can_manage=can_manage)
return queryset.filter(id__in=accessible_warehouses)
def filter_locations_by_permission(queryset, user, can_manage=False):
"""
Filtre un queryset de WarehouseLocation en fonction des permissions de l'utilisateur.
"""
accessible_warehouses = get_user_accessible_warehouses(user, can_manage=can_manage)
return queryset.filter(warehouse__id__in=accessible_warehouses)
def filter_products_by_permission(queryset, user):
"""
Filtre un queryset de Product en fonction des permissions de l'utilisateur.
Retourne les produits présents dans les entrepôts accessibles, ainsi que
les produits qui ne sont pas encore associés à un emplacement (sans stock).
"""
from django.db.models import Q
accessible_warehouses = get_user_accessible_warehouses(user)
return queryset.filter(
Q(stocks__isnull=True) | Q(stocks__location__warehouse__id__in=accessible_warehouses)
).distinct()
def user_can_validate_inventory(user, warehouse):
"""
Vérifie si l'utilisateur peut valider les inventaires d'un entrepôt.
"""
if not user.is_authenticated:
return False
config = get_user_config(user)
if not config:
return False
return config.can_validate_inventory_for(warehouse)
def user_can_perform_inventory_on(user, location):
"""
Vérifie si l'utilisateur peut effectuer un inventaire sur un emplacement spécifique.
"""
if not user.is_authenticated:
return False
config = get_user_config(user)
if not config:
return False
return config.can_perform_inventory_on(location)
def user_can_validate_inventory_on(user, location):
"""
Vérifie si l'utilisateur peut valider les inventaires d'un emplacement spécifique.
"""
if not user.is_authenticated:
return False
config = get_user_config(user)
if not config:
return False
return config.can_validate_inventory_on(location)
def user_can_administer_warehouse(user, warehouse):
"""
Vérifie si l'utilisateur peut administrer un entrepôt spécifique.
"""
if not user.is_authenticated:
return False
config = get_user_config(user)
if not config:
return False
return config.can_administer_warehouse(warehouse)
def user_can_administer_any_warehouse(user):
"""
Vérifie si l'utilisateur peut administrer au moins un entrepôt.
"""
if not user.is_authenticated:
return False
config = get_user_config(user)
if not config:
return False
return config.can_administer_any_warehouse()
def user_can_manage_products(user):
"""
Vérifie si l'utilisateur peut gérer les produits (créer, modifier, supprimer).
Autorisé pour les rôles admin et pour les utilisateurs
ayant can_administer sur au moins un entrepôt.
"""
if not user.is_authenticated:
return False
if user.is_superuser:
return True
config = get_user_config(user)
if not config:
return False
return (
config.has_role('admin')
or config.can_administer_any_warehouse()
)
def user_can_manage_preparation_orders(user, warehouse=None):
"""
Vérifie si l'utilisateur peut gérer des bons de préparation (création, traitement, annulation).
Autorisé pour les superutilisateurs, le rôle admin, le rôle stock_manager (magasinier),
ou les utilisateurs ayant la permission can_manage_preparation_orders.
"""
if not user.is_authenticated:
return False
if user.is_superuser:
return True
config = get_user_config(user)
if not config:
return False
if config.has_role('admin') or config.has_role('stock_manager'):
return True
if warehouse:
return config.can_manage_preparation_orders_for(warehouse)
return config.can_manage_preparation_orders_any()
def user_can_edit_preparation_order(user, preparation_order=None):
"""
Vérifie si l'utilisateur peut modifier un bon de préparation.
La modification est possible pour les gestionnaires et magasiniers,
même lorsque le bon est au statut 'traité' (processed).
Seul le statut 'annulé' (cancelled) est non modifiable.
"""
if preparation_order and preparation_order.status == 'cancelled':
return False
return user_can_manage_preparation_orders(user)