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

@@ -7,16 +7,30 @@
{% block inventory_content %}
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h4 class="mb-0">Активные резервирования</h4>
<span class="badge bg-info">Только для просмотра</span>
<h4 class="mb-0">
Активные резервирования
{% if filtered_product %}
<small class="text-muted">— {{ filtered_product.name }}</small>
{% endif %}
</h4>
<div>
{% if filtered_product %}
<a href="{% url 'inventory:reservation-list' %}" class="btn btn-sm btn-outline-secondary me-2">
<i class="bi bi-x-circle"></i> Сбросить фильтр
</a>
{% endif %}
<span class="badge bg-info">Только для просмотра</span>
</div>
</div>
<div class="card-body">
{% if not filtered_product %}
<div class="alert alert-info mb-3">
<i class="bi bi-info-circle me-2"></i>
<strong>Информация:</strong> Резервы создаются автоматически через POS (витринные комплекты) и заказы.
Прямое управление резервами отключено для предотвращения несогласованности данных.
</div>
{% endif %}
{% if reservations %}
<div class="table-responsive">
@@ -36,7 +50,9 @@
{% for r in reservations %}
<tr>
<td>
<strong>{{ r.product.name }}</strong>
<a href="{% url 'products:product-detail' r.product.pk %}" class="text-decoration-none" target="_blank">
<strong>{{ r.product.name }}</strong>
</a>
</td>
<td>
<span class="badge bg-secondary">{{ r.quantity|smart_quantity }}</span>
@@ -78,10 +94,10 @@
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1">Первая</a>
<a class="page-link" href="?page=1{% if filtered_product %}&product={{ filtered_product.pk }}{% endif %}">Первая</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">Предыдущая</a>
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if filtered_product %}&product={{ filtered_product.pk }}{% endif %}">Предыдущая</a>
</li>
{% endif %}
@@ -93,10 +109,10 @@
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">Следующая</a>
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if filtered_product %}&product={{ filtered_product.pk }}{% endif %}">Следующая</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">Последняя</a>
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if filtered_product %}&product={{ filtered_product.pk }}{% endif %}">Последняя</a>
</li>
{% endif %}
</ul>

View File

@@ -6,6 +6,7 @@ Reservation (Резервирование товара) views - READ ONLY
from django.views.generic import ListView
from django.contrib.auth.mixins import LoginRequiredMixin
from ..models import Reservation
from products.models import Product
class ReservationListView(LoginRequiredMixin, ListView):
@@ -19,7 +20,25 @@ class ReservationListView(LoginRequiredMixin, ListView):
paginate_by = 20
def get_queryset(self):
"""Показываем все резервы со статусом 'reserved'"""
return Reservation.objects.filter(
"""Показываем все резервы со статусом 'reserved', с опциональной фильтрацией по товару"""
queryset = Reservation.objects.filter(
status='reserved'
).select_related('product', 'warehouse', 'order_item', 'showcase').order_by('-reserved_at')
).select_related('product', 'warehouse', 'order_item', 'showcase', 'product_kit').order_by('-reserved_at')
# Фильтрация по товару
product_id = self.request.GET.get('product')
if product_id:
queryset = queryset.filter(product_id=product_id)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Если фильтруем по товару, добавляем его в контекст
product_id = self.request.GET.get('product')
if product_id:
try:
context['filtered_product'] = Product.objects.get(pk=product_id)
except Product.DoesNotExist:
pass
return context