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:
198
myproject/COST_PRICE_QUICK_GUIDE.md
Normal file
198
myproject/COST_PRICE_QUICK_GUIDE.md
Normal 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
|
||||
Reference in New Issue
Block a user