Добавлен расчёт и отображение доступного количества комплектов

- Добавлен метод calculate_available_quantity() в модель ProductKit для точного расчёта максимального количества комплектов на основе свободных остатков компонентов
- Обновлён метод check_availability() для использования нового расчёта (обратная совместимость)
- Удалён устаревший сервис kit_availability.py

Исправлено отображение остатков комплектов:
- products_list.html: вместо прочерка показывается количество комплектов
- catalog.html: добавлено отображение доступного количества комплектов с цветовой индикацией
- POS terminal.js: в карточке товара показывается конкретное количество вместо общего 'В наличии'

Обновлены представления:
- ProductsListView: аннотирует комплекты атрибутом total_free
- CatalogView: рассчитывает доступное количество для каждого комплекта
- POS get_products(): убран хардкод, используется реальный расчёт по складу
This commit is contained in:
2026-01-06 01:02:28 +03:00
parent 2aba3d2404
commit d44ae0b598
12 changed files with 130 additions and 176 deletions

View File

@@ -13,7 +13,6 @@ from .categories import ProductCategory, ProductTag
from .variants import ProductVariantGroup
from .products import Product
from ..utils.sku_generator import generate_kit_sku
from ..services.kit_availability import KitAvailabilityChecker
class ProductKit(BaseProductEntity):
@@ -225,10 +224,69 @@ class ProductKit(BaseProductEntity):
def check_availability(self, stock_manager=None):
"""
Проверяет доступность всего комплекта.
Делегирует проверку в сервис.
Проверяет доступность всего комплекта (возвращает True/False).
Для обратной совместимости. Использует calculate_available_quantity().
"""
return KitAvailabilityChecker.check_availability(self, stock_manager)
return self.calculate_available_quantity() > 0
def calculate_available_quantity(self, warehouse=None):
"""
Рассчитывает максимальное количество комплектов, которое можно собрать
на основе свободных остатков компонентов на складе.
Args:
warehouse: Склад для проверки остатков. Если None, суммируются остатки по всем складам.
Returns:
Decimal: Максимальное количество комплектов (0 если хоть один компонент недоступен)
"""
from inventory.models import Stock
if not self.kit_items.exists():
return Decimal('0')
min_available = None
for kit_item in self.kit_items.select_related('product', 'variant_group'):
# Определяем товар для проверки
product = None
if kit_item.product:
product = kit_item.product
elif kit_item.variant_group:
# Берём первый активный товар из группы вариантов
available_products = kit_item.get_available_products()
product = available_products[0] if available_products else None
if not product:
# Если товар не найден - комплект недоступен
return Decimal('0')
# Получаем остатки на складе
stock_filter = {'product': product}
if warehouse:
stock_filter['warehouse'] = warehouse
stocks = Stock.objects.filter(**stock_filter)
# Суммируем свободное количество (available - reserved)
total_free = Decimal('0')
for stock in stocks:
free_qty = stock.quantity_available - stock.quantity_reserved
total_free += free_qty
# Вычисляем сколько комплектов можно собрать из этого компонента
component_quantity = kit_item.quantity or Decimal('1')
if component_quantity <= 0:
return Decimal('0')
kits_from_this_component = total_free / component_quantity
# Ищем минимум (узкое место)
if min_available is None or kits_from_this_component < min_available:
min_available = kits_from_this_component
# Возвращаем целую часть (нельзя собрать половину комплекта)
return Decimal(int(min_available)) if min_available is not None else Decimal('0')
def make_permanent(self):
"""