Add full CRUD interface for Showcase management

Implemented complete web interface for managing showcases (display areas for ready-made bouquets) with:

**Backend:**
- ShowcaseForm with validation (unique name per warehouse)
- ShowcaseListView with filtering by warehouse and status
- ShowcaseCreateView, ShowcaseUpdateView with success messages
- ShowcaseDeleteView with active reservation validation
- URL routes: list, create, edit, delete

**Frontend:**
- List page with warehouse grouping, active reservations count
- Responsive table with filters (warehouse, status)
- Create/edit form with Bootstrap styling
- Delete confirmation with active reservations check
- Breadcrumb navigation

**Features:**
 One warehouse can have multiple showcases (ForeignKey relationship)
 Unique showcase names within each warehouse
 Display active reservation counts for each showcase
 Prevent deletion if showcase has active reservations
 Auto-select default warehouse when creating showcase
 Navigation link added to main navbar between "Касса" and "Склад"
 Active state highlighting in navigation

**Files created:**
- inventory/forms_showcase.py (ShowcaseForm)
- inventory/views/showcase.py (4 CBV views)
- inventory/templates/inventory/showcase/ (list, form, delete templates)

**Files modified:**
- inventory/urls.py (added showcase routes)
- inventory/forms.py (added Showcase import)
- templates/navbar.html (added "Витрины" link)

URL structure:
/inventory/showcases/ - list
/inventory/showcases/create/ - create
/inventory/showcases/<id>/edit/ - edit
/inventory/showcases/<id>/delete/ - delete

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-20 10:52:53 +03:00
parent 07c8819936
commit 766ca3c87c
8 changed files with 710 additions and 2 deletions

View File

@@ -0,0 +1,175 @@
# -*- coding: utf-8 -*-
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.shortcuts import render, redirect, get_object_or_404
from django.db.models import Count, Q
from django.views.generic import ListView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from inventory.models import Showcase, Reservation
from inventory.forms_showcase import ShowcaseForm
@method_decorator(login_required, name='dispatch')
class ShowcaseListView(ListView):
"""
Список всех витрин с группировкой по складам.
Отображает информацию о количестве активных резервов на каждой витрине.
"""
model = Showcase
template_name = 'inventory/showcase/list.html'
context_object_name = 'showcases'
def get_queryset(self):
"""
Получаем витрины с аннотацией количества активных резервов.
Сортируем по складу и названию.
"""
queryset = Showcase.objects.select_related('warehouse').annotate(
active_reservations_count=Count(
'reservations',
filter=Q(reservations__status='reserved')
)
).order_by('warehouse__name', 'name')
# Фильтрация по складу, если указан GET-параметр
warehouse_id = self.request.GET.get('warehouse')
if warehouse_id:
queryset = queryset.filter(warehouse_id=warehouse_id)
# Фильтрация по статусу активности
is_active = self.request.GET.get('is_active')
if is_active == '1':
queryset = queryset.filter(is_active=True)
elif is_active == '0':
queryset = queryset.filter(is_active=False)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'Витрины'
# Добавляем информацию для фильтров
from inventory.models import Warehouse
context['warehouses'] = Warehouse.objects.filter(is_active=True).order_by('name')
return context
@method_decorator(login_required, name='dispatch')
class ShowcaseCreateView(CreateView):
"""
Создание новой витрины.
"""
model = Showcase
form_class = ShowcaseForm
template_name = 'inventory/showcase/form.html'
success_url = reverse_lazy('inventory:showcase-list')
def form_valid(self, form):
"""Сохраняем витрину и показываем сообщение об успехе"""
response = super().form_valid(form)
messages.success(
self.request,
f'Витрина "{self.object.name}" успешно создана на складе "{self.object.warehouse.name}"'
)
return response
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'Создание витрины'
context['form_title'] = 'Новая витрина'
context['submit_text'] = 'Создать витрину'
return context
@method_decorator(login_required, name='dispatch')
class ShowcaseUpdateView(UpdateView):
"""
Редактирование существующей витрины.
"""
model = Showcase
form_class = ShowcaseForm
template_name = 'inventory/showcase/form.html'
success_url = reverse_lazy('inventory:showcase-list')
def form_valid(self, form):
"""Сохраняем изменения и показываем сообщение об успехе"""
response = super().form_valid(form)
messages.success(
self.request,
f'Витрина "{self.object.name}" успешно обновлена'
)
return response
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = f'Редактирование витрины: {self.object.name}'
context['form_title'] = f'Редактирование: {self.object.name}'
context['submit_text'] = 'Сохранить изменения'
# Добавляем информацию о резервах на витрине
context['active_reservations_count'] = Reservation.objects.filter(
showcase=self.object,
status='reserved'
).count()
return context
@method_decorator(login_required, name='dispatch')
class ShowcaseDeleteView(DeleteView):
"""
Удаление витрины с подтверждением.
Проверяет наличие активных резервов перед удалением.
"""
model = Showcase
template_name = 'inventory/showcase/delete.html'
success_url = reverse_lazy('inventory:showcase-list')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = f'Удаление витрины: {self.object.name}'
# Проверяем наличие активных резервов
active_reservations = Reservation.objects.filter(
showcase=self.object,
status='reserved'
).select_related('product')
context['active_reservations'] = active_reservations
context['has_active_reservations'] = active_reservations.exists()
return context
def delete(self, request, *args, **kwargs):
"""Проверяем наличие активных резервов перед удалением"""
showcase = self.get_object()
# Проверка активных резервов
active_reservations_count = Reservation.objects.filter(
showcase=showcase,
status='reserved'
).count()
if active_reservations_count > 0:
messages.error(
request,
f'Невозможно удалить витрину "{showcase.name}": '
f'на ней есть {active_reservations_count} активных резервов. '
'Сначала освободите или продайте зарезервированные товары.'
)
return redirect('inventory:showcase-delete', pk=showcase.pk)
# Удаляем витрину
showcase_name = showcase.name
response = super().delete(request, *args, **kwargs)
messages.success(
request,
f'Витрина "{showcase_name}" успешно удалена'
)
return response