# -*- coding: utf-8 -*- """ Views для управления скидками и промокодами во фронтенде. Права доступа: - owner/manager/superuser - полный CRUD - florist - только просмотр - courier - нет доступа """ from django.views.generic import ListView, CreateView, UpdateView, DeleteView, TemplateView from django.shortcuts import redirect, get_object_or_404 from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import reverse_lazy from django.utils.decorators import method_decorator from django.core.exceptions import PermissionDenied from django.db.models import Q, Count from user_roles.mixins import RoleRequiredMixin from user_roles.services import RoleService from .models import Discount, PromoCode class DiscountAccessMixin(RoleRequiredMixin): """ Миксин для контроля доступа к скидкам. - owner/manager/superuser - полный CRUD - florist - только просмотр - courier - нет доступа """ required_roles = ['owner', 'manager', 'florist'] def dispatch(self, request, *args, **kwargs): # Superuser имеет полный доступ if request.user.is_superuser: return super().dispatch(request, *args, **kwargs) # Проверяем базовый доступ к роли if not RoleService.user_has_role(request.user, *self.required_roles): raise PermissionDenied("У вас нет прав для доступа к этой странице") # Florist - только просмотр списков if RoleService.user_has_role(request.user, 'florist'): # Разрешаем только ListView if not isinstance(self, ListView): raise PermissionDenied("Флористы могут только просматривать скидки") return super().dispatch(request, *args, **kwargs) class DiscountListView(DiscountAccessMixin, ListView): """Список скидок""" model = Discount template_name = 'discounts/discount_list.html' context_object_name = 'discounts' paginate_by = 20 def get_queryset(self): queryset = Discount.objects.select_related( 'created_by' ).prefetch_related( 'products', 'categories' ).annotate( promo_count=Count('promo_codes') ).order_by('-created_at') # Фильтры discount_type = self.request.GET.get('type') scope = self.request.GET.get('scope') is_active = self.request.GET.get('is_active') search = self.request.GET.get('search') if discount_type: queryset = queryset.filter(discount_type=discount_type) if scope: queryset = queryset.filter(scope=scope) if is_active: queryset = queryset.filter(is_active=(is_active == 'active')) if search: queryset = queryset.filter( Q(name__icontains=search) | Q(description__icontains=search) ) return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['current_filters'] = { 'type': self.request.GET.get('type', ''), 'scope': self.request.GET.get('scope', ''), 'is_active': self.request.GET.get('is_active', ''), 'search': self.request.GET.get('search', ''), } context['can_edit'] = self._can_edit() return context def _can_edit(self): """Проверка прав на редактирование""" if self.request.user.is_superuser: return True return RoleService.user_has_role(self.request.user, 'owner', 'manager') class DiscountCreateView(LoginRequiredMixin, RoleRequiredMixin, CreateView): """Создание скидки (только owner/manager)""" model = Discount template_name = 'discounts/discount_form.html' fields = [ 'name', 'description', 'discount_type', 'value', 'scope', 'is_auto', 'is_active', 'priority', 'start_date', 'end_date', 'min_order_amount', 'max_usage_count', 'products', 'categories', 'excluded_products', ] required_roles = ['owner', 'manager'] success_url = reverse_lazy('system_settings:discounts:list') def form_valid(self, form): form.instance.created_by = self.request.user messages.success(self.request, f'Скидка "{form.instance.name}" создана') return super().form_valid(form) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['is_edit'] = False # Передаем товары и категории для формы from products.models import Product, ProductCategory context['all_products'] = Product.objects.filter(is_active=True).order_by('name') context['all_categories'] = ProductCategory.objects.filter(is_active=True).order_by('name') context['selected_products'] = [] context['selected_categories'] = [] context['excluded_products'] = [] return context class DiscountUpdateView(LoginRequiredMixin, RoleRequiredMixin, UpdateView): """Редактирование скидки (только owner/manager)""" model = Discount template_name = 'discounts/discount_form.html' fields = [ 'name', 'description', 'discount_type', 'value', 'scope', 'is_auto', 'is_active', 'priority', 'start_date', 'end_date', 'min_order_amount', 'max_usage_count', 'products', 'categories', 'excluded_products', ] required_roles = ['owner', 'manager'] success_url = reverse_lazy('system_settings:discounts:list') def form_valid(self, form): messages.success(self.request, f'Скидка "{form.instance.name}" обновлена') return super().form_valid(form) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['is_edit'] = True # Передаем товары и категории для формы from products.models import Product, ProductCategory context['all_products'] = Product.objects.filter(is_active=True).order_by('name') context['all_categories'] = ProductCategory.objects.filter(is_active=True).order_by('name') context['selected_products'] = list(self.object.products.values_list('id', flat=True)) context['selected_categories'] = list(self.object.categories.values_list('id', flat=True)) context['excluded_products'] = list(self.object.excluded_products.values_list('id', flat=True)) return context class DiscountDeleteView(LoginRequiredMixin, RoleRequiredMixin, DeleteView): """Удаление скидки (только owner/manager)""" model = Discount template_name = 'discounts/discount_confirm_delete.html' required_roles = ['owner', 'manager'] success_url = reverse_lazy('system_settings:discounts:list') def delete(self, request, *args, **kwargs): obj = self.get_object() messages.success(self.request, f'Скидка "{obj.name}" удалена') return super().delete(request, *args, **kwargs) @login_required def discount_toggle(request, pk): """Переключение активности скидки (только owner/manager)""" if not request.user.is_superuser and not RoleService.user_has_role(request.user, 'owner', 'manager'): raise PermissionDenied("У вас нет прав для этого действия") discount = get_object_or_404(Discount, pk=pk) discount.is_active = not discount.is_active discount.save(update_fields=['is_active']) status = "активирована" if discount.is_active else "деактивирована" messages.success(request, f'Скидка "{discount.name}" {status}') return redirect('system_settings:discounts:list') # ============== Промокоды ============== class PromoCodeAccessMixin(RoleRequiredMixin): """Миксин для контроля доступа к промокодам""" required_roles = ['owner', 'manager', 'florist'] def dispatch(self, request, *args, **kwargs): if request.user.is_superuser: return super().dispatch(request, *args, **kwargs) if not RoleService.user_has_role(request.user, *self.required_roles): raise PermissionDenied("У вас нет прав для доступа к этой странице") # Florist - только просмотр if RoleService.user_has_role(request.user, 'florist'): if not isinstance(self, ListView): raise PermissionDenied("Флористы могут только просматривать промокоды") return super().dispatch(request, *args, **kwargs) class PromoCodeListView(PromoCodeAccessMixin, ListView): """Список промокодов""" model = PromoCode template_name = 'discounts/promocode_list.html' context_object_name = 'promocodes' paginate_by = 20 def get_queryset(self): queryset = PromoCode.objects.select_related( 'discount', 'created_by' ).order_by('-created_at') # Фильтры is_active = self.request.GET.get('is_active') search = self.request.GET.get('search') if is_active: queryset = queryset.filter(is_active=(is_active == 'active')) if search: queryset = queryset.filter(code__icontains=search) return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['current_filters'] = { 'is_active': self.request.GET.get('is_active', ''), 'search': self.request.GET.get('search', ''), } context['can_edit'] = self._can_edit() return context def _can_edit(self): if self.request.user.is_superuser: return True return RoleService.user_has_role(self.request.user, 'owner', 'manager') class PromoCodeCreateView(LoginRequiredMixin, RoleRequiredMixin, CreateView): """Создание промокода (только owner/manager)""" model = PromoCode template_name = 'discounts/promocode_form.html' fields = [ 'code', 'discount', 'is_active', 'start_date', 'end_date', 'max_uses_per_user', 'max_total_uses', ] required_roles = ['owner', 'manager'] success_url = reverse_lazy('system_settings:discounts:promo-list') def form_valid(self, form): form.instance.created_by = self.request.user messages.success(self.request, f'Промокод "{form.instance.code}" создан') return super().form_valid(form) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['is_edit'] = False # Передаем активные скидки для выбора context['all_discounts'] = Discount.objects.filter(is_active=True).order_by('name') return context class PromoCodeUpdateView(LoginRequiredMixin, RoleRequiredMixin, UpdateView): """Редактирование промокода (только owner/manager)""" model = PromoCode template_name = 'discounts/promocode_form.html' fields = [ 'code', 'discount', 'is_active', 'start_date', 'end_date', 'max_uses_per_user', 'max_total_uses', ] required_roles = ['owner', 'manager'] success_url = reverse_lazy('system_settings:discounts:promo-list') def form_valid(self, form): messages.success(self.request, f'Промокод "{form.instance.code}" обновлён') return super().form_valid(form) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['is_edit'] = True # Передаем все скидки для выбора (включая неактивные, если они выбраны) context['all_discounts'] = Discount.objects.all().order_by('name') return context class PromoCodeDeleteView(LoginRequiredMixin, RoleRequiredMixin, DeleteView): """Удаление промокода (только owner/manager)""" model = PromoCode template_name = 'discounts/promocode_confirm_delete.html' required_roles = ['owner', 'manager'] success_url = reverse_lazy('system_settings:discounts:promo-list') def delete(self, request, *args, **kwargs): obj = self.get_object() messages.success(self.request, f'Промокод "{obj.code}" удалён') return super().delete(request, *args, **kwargs) @login_required def promocode_toggle(request, pk): """Переключение активности промокода (только owner/manager)""" if not request.user.is_superuser and not RoleService.user_has_role(request.user, 'owner', 'manager'): raise PermissionDenied("У вас нет прав для этого действия") promocode = get_object_or_404(PromoCode, pk=pk) promocode.is_active = not promocode.is_active promocode.save(update_fields=['is_active']) status = "активирован" if promocode.is_active else "деактивирован" messages.success(request, f'Промокод "{promocode.code}" {status}') return redirect('system_settings:discounts:promo-list')