Feat: Автоматическая себестоимость товара (read-only)

- Удалено ручное редактирование себестоимости из формы товара
- Себестоимость теперь рассчитывается автоматически из партий (FIFO)
- Добавлена модель CostPriceHistory для логирования изменений
- Добавлен signal для автоматического логирования изменений cost_price
- Админ-панель: себестоимость read-only с детальной информацией о партиях
- Фронтенд: цены перемещены под название, теги под категории
- Поле cost_price сделано опциональным (default=0) для создания товаров

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-23 23:22:45 +03:00
parent 493b6c212d
commit addc5e0962
9 changed files with 374 additions and 71 deletions

View File

@@ -0,0 +1,50 @@
"""
Signals для приложения products.
Логирует изменения себестоимости товара через CostPriceHistory.
"""
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.db import models
from .models import Product, CostPriceHistory
@receiver(post_save, sender=Product)
def log_cost_price_changes(sender, instance, created, **kwargs):
"""
Логирует изменения себестоимости товара.
Срабатывает при создании или обновлении товара.
Создает запись в CostPriceHistory если себестоимость изменилась.
"""
if created:
# При создании товара себестоимость обычно 0, логируем только если не 0
if instance.cost_price != 0:
CostPriceHistory.objects.create(
product=instance,
old_cost_price=0,
new_cost_price=instance.cost_price,
reason='system',
notes='Начальная себестоимость при создании товара'
)
return
# Получаем предыдущее значение себестоимости
try:
previous = Product.objects.get(pk=instance.pk)
old_cost_price = previous.cost_price
except Product.DoesNotExist:
# Товар был удален, не логируем
return
# Если себестоимость изменилась, логируем
if old_cost_price != instance.cost_price:
CostPriceHistory.objects.create(
product=instance,
old_cost_price=old_cost_price,
new_cost_price=instance.cost_price,
reason='recalculation',
notes='Себестоимость пересчитана на основе партий товара'
)