# Быстрая справка: Наличие товаров и цены вариантов
## В Python коде
### Product (товар)
```python
from products.models import Product
product = Product.objects.get(id=1)
# Проверить есть ли в наличии
if product.in_stock:
print(f"{product.name} - в наличии")
# Получить цену
print(product.sale_price)
# Фильтровать товары в наличии
in_stock = Product.objects.filter(in_stock=True)
out_of_stock = Product.objects.filter(in_stock=False)
```
### ProductVariantGroup (группа вариантов)
```python
from products.models import ProductVariantGroup
group = ProductVariantGroup.objects.prefetch_related('items__product').get(id=1)
# Проверить есть ли в наличии хотя бы один вариант
if group.in_stock:
print(f"Группа '{group.name}' - есть в наличии")
# Получить цену группы
price = group.price # Decimal('50.00')
# Перебрать товары по приоритету
for item in group.items.all().order_by('priority'):
print(f"{item.priority}. {item.product.name}")
if item.product.in_stock:
print(f" -> В наличии ({item.product.sale_price} руб)")
else:
print(f" -> Не в наличии")
```
---
## В шаблонах (HTML)
### Проверка наличия товара
```html
{% if product.in_stock %}
В наличии
{% else %}
Нет в наличии
{% endif %}
```
### Отображение цены товара
```html
{{ product.sale_price }} руб
```
### Группа вариантов - статус наличия
```html
{% if variant_group.in_stock %}
Доступно - есть варианты в наличии
{% else %}
Недоступно - все варианты закончились
{% endif %}
```
### Группа вариантов - цена
```html
Цена: {{ variant_group.price }} руб
```
### Список товаров в группе
```html
Приоритет
Товар
Цена
Статус
{% for item in variant_group.items.all %}
{{ item.priority }}
{{ item.product.name }}
{{ item.product.sale_price }} руб
{% if item.product.in_stock %}
В наличии
{% else %}
Нет
{% endif %}
{% endfor %}
```
### Полный пример - карточка варианта
```html
{{ variant_group.name }}
{% if variant_group.in_stock %}
✓ В наличии
{% else %}
✗ Нет в наличии
{% endif %}
{{ variant_group.price }} руб
Доступные варианты:
{% for item in variant_group.items.all|slice:":3" %}
{% if variant_group.in_stock %}
{% else %}
{% endif %}
```
---
## В View (запросы к БД)
### Оптимизация запросов
```python
from django.shortcuts import render
from products.models import ProductVariantGroup
def variant_groups_list(request):
# ПРАВИЛЬНО: используем prefetch_related для оптимизации
groups = ProductVariantGroup.objects.prefetch_related(
'items__product'
)
return render(request, 'variants.html', {
'variant_groups': groups
})
```
### Фильтрация товаров в наличии
```python
from products.models import Product
# Получить только товары в наличии
in_stock = Product.objects.filter(in_stock=True)
# Получить только товары без наличия
out_of_stock = Product.objects.filter(in_stock=False)
# Комбинированный фильтр
available = Product.objects.filter(
is_active=True,
in_stock=True
).order_by('name')
```
---
## Логика наличия
### Когда товар считается "в наличии"?
Товар в наличии (Product.in_stock = True) когда:
- Существует запись в Stock с `quantity_available > 0`
- Это может быть на любом из складов
- quantity_available = quantity - reserved (свободный остаток)
### Когда он перестаёт быть в наличии?
- Все Stock записи удалены
- Или всем Stock записям quantity_available = 0 (все проданы или зарезервированы)
### Как это обновляется автоматически?
1. При создании приходного документа (Incoming)
2. При продаже товара (Sale)
3. При списании товара (WriteOff)
4. При изменении резервирования (Reservation)
**Вы не должны вручную обновлять Product.in_stock!**
---
## Цена варианта - логика
### Порядок определения цены ProductVariantGroup:
1. **Есть товары в наличии?**
- Да → берём цену товара с **наименьшим приоритетом** среди доступных
- Пример: приоритет 1 доступен → его цена
2. **Нет товаров в наличии?**
- Все недоступны → берём **максимальную цену** из всех товаров
- Пример: цены 50, 60, 70 → показываем 70 (самая дорогая)
### Пример расчёта:
```
Группа "Роза красная Freedom"
├─ Приоритет 1: Роза 50см, цена 50 руб, в наличии ✓
├─ Приоритет 2: Роза 60см, цена 60 руб, в наличии ✓
└─ Приоритет 3: Роза 70см, цена 70 руб, нет в наличии ✗
Цена группы = 50 руб (первый в наличии)
```
```
Группа "Роза красная Freedom"
├─ Приоритет 1: Роза 50см, цена 50 руб, нет ✗
├─ Приоритет 2: Роза 60см, цена 60 руб, нет ✗
└─ Приоритет 3: Роза 70см, цена 70 руб, нет ✗
Цена группы = 70 руб (максимальная из всех)
```
---
## Типичные ошибки
❌ **Ошибка 1: Попытка обновить Product.in_stock вручную**
```python
# НЕПРАВИЛЬНО!
product.in_stock = True
product.save()
# Это будет перезаписано при следующем изменении Stock
```
✅ **Правильно:**
Система сама обновит Product.in_stock при изменении остатков.
---
❌ **Ошибка 2: Не использовать prefetch_related для вариантов**
```python
# НЕПРАВИЛЬНО (N+1 query problem)!
for group in groups:
price = group.price # Это выполнит запрос для каждой группы!
```
✅ **Правильно:**
```python
groups = ProductVariantGroup.objects.prefetch_related('items__product')
for group in groups:
price = group.price # Всего 2 запроса вместо N+1
```
---
❌ **Ошибка 3: Фильтровать по in_stock на ProductVariantGroup**
```python
# НЕПРАВИЛЬНО!
groups = ProductVariantGroup.objects.filter(in_stock=True)
# in_stock это свойство, а не поле БД
```
✅ **Правильно:**
```python
# Если нужны группы где есть хотя бы один товар в наличии
groups = ProductVariantGroup.objects.filter(
items__product__in_stock=True
).distinct()
# Или отфильтровать в Python
groups = [g for g in groups if g.in_stock]
```
---
## Дополнительные полезные запросы
### Все товары без наличия
```python
from products.models import Product
out_of_stock = Product.objects.filter(in_stock=False)
```
### Группы вариантов где нет ни одного товара в наличии
```python
from django.db.models import Exists, OuterRef
ProductVariantGroup.objects.filter(
~Exists(ProductVariantGroupItem.objects.filter(
variant_group=OuterRef('pk'),
product__in_stock=True
))
)
```
### Товары которые изменили статус наличия за последний час
```python
from django.utils import timezone
from datetime import timedelta
Product.objects.filter(
updated_at__gte=timezone.now() - timedelta(hours=1)
)
```
---
## Помощь и контакты
Если что-то не работает:
1. Проверьте что миграция `0003_add_product_in_stock` применена
2. Убедитесь что сигналы зарегистрированы в `inventory/apps.py`
3. Проверьте логи: есть ли ошибки в сигналах при обновлении Stock
4. Запустите тестовый скрипт: `python test_variant_stock.py`