# -*- coding: utf-8 -*- from django.shortcuts import render, get_object_or_404 from django.views.generic import ListView, CreateView, UpdateView, DeleteView, View from django.urls import reverse_lazy from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib import messages from django.http import JsonResponse, HttpResponseRedirect from django.views.decorators.http import require_http_methods from django.utils.decorators import method_decorator from ..models import Warehouse from ..forms import WarehouseForm class WarehouseListView(LoginRequiredMixin, ListView): """ Список всех складов тенанта Сортирует по is_default (по умолчанию первым), потом по названию """ model = Warehouse template_name = 'inventory/warehouse/warehouse_list.html' context_object_name = 'warehouses' paginate_by = 20 def get_queryset(self): # Сортируем: сначала is_default DESC (по умолчанию первый), потом по названию return Warehouse.objects.filter(is_active=True).order_by('-is_default', 'name') class WarehouseCreateView(LoginRequiredMixin, CreateView): """ Создание нового склада """ model = Warehouse form_class = WarehouseForm template_name = 'inventory/warehouse/warehouse_form.html' success_url = reverse_lazy('inventory:warehouse-list') def form_valid(self, form): messages.success(self.request, f'Склад "{form.instance.name}" успешно создан.') return super().form_valid(form) class WarehouseUpdateView(LoginRequiredMixin, UpdateView): """ Редактирование склада """ model = Warehouse form_class = WarehouseForm template_name = 'inventory/warehouse/warehouse_form.html' success_url = reverse_lazy('inventory:warehouse-list') def form_valid(self, form): messages.success(self.request, f'Склад "{form.instance.name}" успешно обновлён.') return super().form_valid(form) class WarehouseDeleteView(LoginRequiredMixin, DeleteView): """ Удаление склада (мягкое удаление - деактивация). Вместо физического удаления из БД, устанавливаем is_active=False """ model = Warehouse template_name = 'inventory/warehouse/warehouse_confirm_delete.html' success_url = reverse_lazy('inventory:warehouse-list') def post(self, request, *args, **kwargs): """ Переопределяем POST метод чтобы использовать мягкое удаление вместо стандартного физического удаления Django. Включает строгую валидацию для защиты от случайного удаления. """ self.object = self.get_object() warehouse_name = self.object.name # 1. Проверка: это последний активный склад? active_warehouses_count = Warehouse.objects.filter(is_active=True).count() if active_warehouses_count <= 1: messages.error( request, 'Невозможно деактивировать последний активный склад. ' 'Система требует наличия хотя бы одного активного склада для работы. ' 'Создайте новый склад перед деактивацией этого.' ) return HttpResponseRedirect(reverse_lazy('inventory:warehouse-list')) # 2. Проверка: есть ли ненулевые остатки товаров? from inventory.models import Stock has_stock = Stock.objects.filter( warehouse=self.object, available__gt=0 ).exists() if has_stock: messages.error( request, f'Невозможно деактивировать склад "{warehouse_name}": ' 'на складе есть товары с ненулевыми остатками. ' 'Переместите все товары на другой склад перед деактивацией.' ) return HttpResponseRedirect(reverse_lazy('inventory:warehouse-list')) # 3. Проверка: есть ли активные резервы? from inventory.models import Reservation active_reservations_count = Reservation.objects.filter( warehouse=self.object, status='reserved' ).count() if active_reservations_count > 0: messages.error( request, f'Невозможно деактивировать склад "{warehouse_name}": ' f'на складе есть {active_reservations_count} активных резервирований. ' 'Завершите все заказы с резервами перед деактивацией склада.' ) return HttpResponseRedirect(reverse_lazy('inventory:warehouse-list')) # 4. Предупреждение: если это дефолтный склад if self.object.is_default: messages.warning( request, f'Внимание: вы деактивируете склад по умолчанию "{warehouse_name}". ' 'Рекомендуется сначала назначить другой склад по умолчанию.' ) # Мягкое удаление - деактивируем склад self.object.is_active = False self.object.save() messages.success( request, f'Склад "{warehouse_name}" успешно архивирован и скрыт из списка. ' 'Все связанные данные сохранены.' ) return HttpResponseRedirect(self.get_success_url()) @method_decorator(require_http_methods(["POST"]), name="dispatch") class SetDefaultWarehouseView(LoginRequiredMixin, View): """ Установка склада по умолчанию Обрабатывает POST запрос от AJAX и возвращает JSON ответ """ def post(self, request, pk): """ Установить склад с заданным pk как склад по умолчанию """ try: warehouse = get_object_or_404(Warehouse, pk=pk, is_active=True) # Установить этот склад как по умолчанию # (метод save() в модели автоматически снимет флаг с других) warehouse.is_default = True warehouse.save() return JsonResponse({ 'status': 'success', 'message': f'Склад "{warehouse.name}" установлен по умолчанию', 'warehouse_id': warehouse.id, 'warehouse_name': warehouse.name }) except Warehouse.DoesNotExist: return JsonResponse({ 'status': 'error', 'message': 'Склад не найден' }, status=404) except Exception as e: return JsonResponse({ 'status': 'error', 'message': str(e) }, status=500)