|
|
from django.db import models
|
|
|
from io import BytesIO
|
|
|
from reportlab.lib.pagesizes import letter
|
|
|
from reportlab.pdfgen import canvas
|
|
|
|
|
|
# Справочник товаров
|
|
|
class Product(models.Model):
|
|
|
name = models.CharField(max_length=255, verbose_name="Наименование товара")
|
|
|
manufacturer_name = models.CharField(max_length=255, verbose_name="Производитель")
|
|
|
manufacturer_country = models.CharField(max_length=255, verbose_name="Страна производителя")
|
|
|
manufacturer_code = models.CharField(max_length=50, verbose_name="Код производителя", blank=True, null=True)
|
|
|
dimensions = models.CharField(max_length=255, verbose_name="Размеры", blank=True, null=True)
|
|
|
unit_of_measure = models.CharField(max_length=50, verbose_name="Единица измерения")
|
|
|
shelf_life_days = models.IntegerField(verbose_name="Срок годности (дни)")
|
|
|
barcode = models.CharField(max_length=50, unique=True, verbose_name="Штрихкод")
|
|
|
|
|
|
def __str__(self):
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
# Типы цен (например, регулярная, скидочная, акционная)
|
|
|
class PriceType(models.Model):
|
|
|
name = models.CharField(max_length=50, verbose_name="Тип цены", choices=[
|
|
|
('regular', 'Регулярная'),
|
|
|
('discount', 'Скидочная'),
|
|
|
('promotional', 'Акционная'),
|
|
|
])
|
|
|
|
|
|
def __str__(self):
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
# Прайс-листы (со всеми ценами)
|
|
|
class PriceList(models.Model):
|
|
|
product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name="Товар")
|
|
|
price_type = models.ForeignKey(PriceType, on_delete=models.CASCADE, verbose_name="Тип цены")
|
|
|
entry_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Входная цена")
|
|
|
final_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Итоговая цена")
|
|
|
date_effective = models.DateField(verbose_name="Дата вступления в силу")
|
|
|
constraint_percent_limit = models.DecimalField(max_digits=5, decimal_places=2, default=1000, verbose_name="Лимит на наценку (%)")
|
|
|
constraint_price_change = models.DecimalField(max_digits=5, decimal_places=2, default=90, verbose_name="Лимит на изменение цены (%)")
|
|
|
|
|
|
def save(self, *args, **kwargs):
|
|
|
if self.pk:
|
|
|
old_price = PriceList.objects.get(pk=self.pk).final_price
|
|
|
price_change_percent = abs((self.final_price - old_price) / old_price) * 100
|
|
|
if price_change_percent > self.constraint_price_change:
|
|
|
raise ValueError(f"Цена не может измениться более чем на {self.constraint_price_change}%")
|
|
|
|
|
|
super().save(*args, **kwargs)
|
|
|
|
|
|
def __str__(self):
|
|
|
return f"{self.product.name} - {self.price_type.name} - {self.final_price} руб."
|
|
|
|
|
|
|
|
|
# Скидки и акции (могут применяться к товарам)
|
|
|
class Discount(models.Model):
|
|
|
product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name="Товар")
|
|
|
discount_percentage = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="Процент скидки")
|
|
|
start_date = models.DateField(verbose_name="Дата начала")
|
|
|
end_date = models.DateField(verbose_name="Дата окончания")
|
|
|
description = models.TextField(verbose_name="Описание акции", blank=True, null=True)
|
|
|
|
|
|
def __str__(self):
|
|
|
return f"Скидка {self.discount_percentage}% на {self.product.name} с {self.start_date} по {self.end_date}"
|
|
|
|
|
|
|
|
|
# История изменений цен
|
|
|
class PriceChangeHistory(models.Model):
|
|
|
product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name="Товар")
|
|
|
old_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Старая цена")
|
|
|
new_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Новая цена")
|
|
|
change_date = models.DateTimeField(auto_now_add=True, verbose_name="Дата изменения цены")
|
|
|
reason = models.TextField(verbose_name="Причина изменения цены", blank=True, null=True)
|
|
|
|
|
|
def __str__(self):
|
|
|
return f"{self.product.name} - изменение цены с {self.old_price} на {self.new_price} - {self.change_date}"
|
|
|
|
|
|
|
|
|
# Пример использования ценников для печати
|
|
|
class PriceTag(models.Model):
|
|
|
product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name="Товар")
|
|
|
price_list = models.ForeignKey(PriceList, on_delete=models.CASCADE, verbose_name="Прайс-лист")
|
|
|
tag_image = models.ImageField(upload_to='price_tags/', verbose_name="Изображение ценника", blank=True, null=True)
|
|
|
price_effective_date = models.DateField(verbose_name="Дата вступления в силу ценника")
|
|
|
|
|
|
def generate_pdf(self):
|
|
|
"""Генерация ценника в формате PDF."""
|
|
|
buffer = BytesIO()
|
|
|
c = canvas.Canvas(buffer, pagesize=letter)
|
|
|
c.drawString(100, 750, f"Продукт: {self.product.name}")
|
|
|
c.drawString(100, 730, f"Тип цены: {self.price_list.price_type.name}")
|
|
|
c.drawString(100, 710, f"Входная цена: {self.price_list.entry_price} руб.")
|
|
|
c.drawString(100, 690, f"Итоговая цена: {self.price_list.final_price} руб.")
|
|
|
c.showPage()
|
|
|
c.save()
|
|
|
buffer.seek(0)
|
|
|
return buffer
|
|
|
|
|
|
def __str__(self):
|
|
|
return f"Ценник для {self.product.name} - {self.price_effective_date}"
|
|
|
|
|
|
|
|
|
# Связь с историей акций
|
|
|
class DiscountHistory(models.Model):
|
|
|
discount = models.ForeignKey(Discount, on_delete=models.CASCADE, verbose_name="Акция/Скидка")
|
|
|
old_discount = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="Старая скидка")
|
|
|
new_discount = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="Новая скидка")
|
|
|
change_date = models.DateTimeField(auto_now_add=True, verbose_name="Дата изменения скидки")
|
|
|
reason = models.TextField(verbose_name="Причина изменения скидки", blank=True, null=True)
|
|
|
|
|
|
def __str__(self):
|
|
|
return f"Изменение скидки с {self.old_discount}% на {self.new_discount}% для {self.discount.product.name}"
|
|
|
|
|
|
|
|
|
# Пример применения скидки к прайс-листу
|
|
|
class PriceListWithDiscount(models.Model):
|
|
|
price_list = models.ForeignKey(PriceList, on_delete=models.CASCADE, verbose_name="Прайс-лист")
|
|
|
discount = models.ForeignKey(Discount, on_delete=models.CASCADE, verbose_name="Скидка")
|
|
|
final_price_after_discount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Итоговая цена после скидки")
|
|
|
|
|
|
def save(self, *args, **kwargs):
|
|
|
# Расчёт итоговой цены с учётом скидки
|
|
|
self.final_price_after_discount = self.price_list.final_price - (self.price_list.final_price * self.discount.discount_percentage / 100)
|
|
|
super().save(*args, **kwargs)
|
|
|
|
|
|
def __str__(self):
|
|
|
return f"{self.price_list.product.name} - {self.final_price_after_discount} руб. (с учетом скидки)"
|