Add stock availability display to product list and detail views

- Add total_available, total_reserved, total_free annotations to product queries
- Display free stock (green/red) with reserved count in product list
- Show detailed stock info in product detail page (moved to top)
- Make reservation count clickable to view filtered reservations
- Add product filter support to ReservationListView
- Add product link in reservation list for easy navigation

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-23 00:31:38 +03:00
parent 856e1ca4c1
commit d3d3c23695
5 changed files with 76 additions and 29 deletions

View File

@@ -5,7 +5,8 @@ from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView
from django.urls import reverse_lazy
from django.db.models import Q
from django.db.models import Q, Sum, Value, DecimalField
from django.db.models.functions import Coalesce
from itertools import chain
from ..models import Product, ProductCategory, ProductTag, ProductKit
@@ -162,8 +163,14 @@ class ProductDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView)
permission_required = 'products.view_product'
def get_queryset(self):
# Предзагрузка фотографий для избежания N+1 запросов
return super().get_queryset().prefetch_related('photos')
# Предзагрузка фотографий и аннотация остатков
total_available = Coalesce(Sum('stocks__quantity_available'), Value(0), output_field=DecimalField())
total_reserved = Coalesce(Sum('stocks__quantity_reserved'), Value(0), output_field=DecimalField())
return super().get_queryset().prefetch_related('photos').annotate(
total_available=total_available,
total_reserved=total_reserved,
total_free=total_available - total_reserved,
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -253,7 +260,14 @@ class CombinedProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListV
type_filter = self.request.GET.get('type', 'all')
# Получаем товары и комплекты (только постоянные комплекты)
products = Product.objects.prefetch_related('categories', 'photos', 'tags')
# Аннотируем товары данными об остатках из агрегированной таблицы Stock
total_available = Coalesce(Sum('stocks__quantity_available'), Value(0), output_field=DecimalField())
total_reserved = Coalesce(Sum('stocks__quantity_reserved'), Value(0), output_field=DecimalField())
products = Product.objects.prefetch_related('categories', 'photos', 'tags').annotate(
total_available=total_available,
total_reserved=total_reserved,
total_free=total_available - total_reserved,
)
kits = ProductKit.objects.filter(is_temporary=False).prefetch_related('categories', 'photos', 'tags')
# Применяем фильтры