fix: Улучшения системы ценообразования комплектов

Исправлены 4 проблемы:
1. Расчёт цены первого товара - улучшена валидация в getProductPrice и calculateFinalPrice
2. Отображение actual_price в Select2 вместо обычной цены
3. Количество по умолчанию = 1 для новых форм компонентов
4. Auto-select текста при клике на поле количества для удобства редактирования

Изменённые файлы:
- products/forms.py: добавлен __init__ в KitItemForm для quantity.initial = 1
- products/templates/includes/select2-product-init.html: обновлена formatSelectResult
- products/templates/productkit_create.html: добавлен focus handler для auto-select
- products/templates/productkit_edit.html: добавлен focus handler для auto-select

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-02 19:04:03 +03:00
parent c84a372f98
commit 6c8af5ab2c
120 changed files with 9035 additions and 3036 deletions

View File

@@ -0,0 +1,198 @@
# Быстрый гид: Динамическая себестоимость товаров
## Как это работает
Себестоимость товара теперь **автоматически рассчитывается** на основе партий товара (StockBatch) по формуле средневзвешенной стоимости:
```
cost_price = Σ(количество × стоимость) / Σ(количество)
```
## Автоматическое обновление
Себестоимость обновляется **автоматически** при:
- ✅ Создании новой партии (поступление товара)
- ✅ Изменении количества в партии
- ✅ Изменении стоимости партии
- ✅ Удалении партии
**Никаких дополнительных действий не требуется!**
## Просмотр деталей
### На странице товара
1. Откройте страницу товара: `http://grach.localhost:8000/products/1/`
2. Найдите строку "Себестоимость"
3. Нажмите кнопку **"Детали расчета"**
4. Увидите:
- Кешированную стоимость (из БД)
- Рассчитанную стоимость (из партий)
- Таблицу с разбивкой по партиям
- Дату создания каждой партии
## Примеры сценариев
### Сценарий 1: Новый товар
```
Товар создан → cost_price = 0.00 (нет партий)
```
### Сценарий 2: Первая поставка
```
Поступление: 10 шт по 100 руб
→ Автоматически: cost_price = 100.00
```
### Сценарий 3: Вторая поставка
```
Текущее: 10 шт по 100 руб (cost_price = 100.00)
Поступление: 10 шт по 120 руб
→ Автоматически: cost_price = 110.00
Расчет: (10×100 + 10×120) / 20 = 110.00
```
### Сценарий 4: Товар закончился
```
Продажа: весь товар продан
→ Автоматически: cost_price = 0.00
```
### Сценарий 5: Новая поставка после опустошения
```
Поступление: 15 шт по 130 руб
→ Автоматически: cost_price = 130.00
```
## Ручной пересчет (если нужно)
Если по какой-то причине себестоимость "слетела", можно пересчитать вручную:
```bash
# Пересчитать для тенанта grach
python manage.py recalculate_product_costs --schema=grach
# С подробным выводом
python manage.py recalculate_product_costs --schema=grach --verbose
# Предварительный просмотр без сохранения
python manage.py recalculate_product_costs --schema=grach --dry-run --verbose
# Показать только изменившиеся товары
python manage.py recalculate_product_costs --schema=grach --only-changed
```
## Влияние на комплекты (ProductKit)
Стоимость комплектов теперь автоматически учитывает актуальную себестоимость компонентов!
```python
# Раньше: использовалась статическая стоимость
# Теперь: использует динамическую стоимость из партий
kit_cost = sum(component.cost_price × quantity)
```
## Проверка синхронизации
На странице товара в секции "Детали расчета":
- 🟢 **Зеленый статус** - все синхронизировано
- 🟡 **Желтый статус** - требуется синхронизация (запустите команду пересчета)
## API для разработчиков
### Получить детали расчета
```python
from products.models import Product
product = Product.objects.get(id=1)
# Получить детали
details = product.cost_price_details
print(f"Кешированная стоимость: {details['cached_cost']}")
print(f"Рассчитанная стоимость: {details['calculated_cost']}")
print(f"Синхронизировано: {details['is_synced']}")
print(f"Всего в партиях: {details['total_quantity']}")
# Перебрать партии
for batch in details['batches']:
print(f"Склад: {batch['warehouse_name']}")
print(f"Количество: {batch['quantity']}")
print(f"Стоимость: {batch['cost_price']}")
```
### Ручное обновление стоимости
```python
from products.services.cost_calculator import ProductCostCalculator
# Рассчитать новую стоимость
new_cost = ProductCostCalculator.calculate_weighted_average_cost(product)
# Обновить в БД
old_cost, new_cost, was_updated = ProductCostCalculator.update_product_cost(product)
if was_updated:
print(f"Стоимость обновлена: {old_cost}{new_cost}")
```
## Логирование
Все операции логируются в стандартный Django logger:
```python
import logging
logger = logging.getLogger('products.services.cost_calculator')
```
Примеры сообщений:
- `INFO: Обновлена себестоимость товара SKU-001: 100.00 -> 110.00`
- `ERROR: Ошибка при расчете себестоимости для товара SKU-001: ...`
## Производительность
### Чтение cost_price
- **0 дополнительных запросов** - значение читается из БД
### Создание/изменение партии
- **1 дополнительный UPDATE** - автоматическое обновление cost_price
### Просмотр деталей (cost_price_details)
- **1 SELECT** - запрос партий товара
## FAQ
**Q: Нужно ли что-то делать после создания партии?**
A: Нет! Себестоимость обновляется автоматически через Django signals.
**Q: Что если у товара нет партий?**
A: cost_price = 0.00 (автоматически)
**Q: Можно ли вручную установить себестоимость?**
A: Можно, но при следующем изменении партий значение пересчитается автоматически.
**Q: Как проверить правильность расчета?**
A: Откройте "Детали расчета" на странице товара - там видна вся математика.
**Q: Влияет ли это на ProductKit?**
A: Да! Стоимость комплектов теперь использует актуальную себестоимость компонентов.
**Q: Что если синхронизация нарушилась?**
A: Запустите `python manage.py recalculate_product_costs --schema=grach`
## Техническая документация
Подробная техническая документация доступна в файле:
`DYNAMIC_COST_PRICE_IMPLEMENTATION.md`
## Контакты и поддержка
При возникновении проблем проверьте:
1. Логи Django (ошибки при расчете)
2. Страницу товара (секция "Детали расчета")
3. Запустите команду с --dry-run для проверки
---
Версия: 1.0
Дата: 2025-01-01