You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

339 lines
13 KiB

from django.utils.timezone import now, timedelta
import uuid
from django.core.validators import MinValueValidator, MaxValueValidator, RegexValidator
from django.db import models
from django.contrib.auth import get_user_model
from django.db.models import Avg
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from autoslug import AutoSlugField
# Create your models here.
from core.models import (
AbstractStatusModel, AbstractDateTimeModel,
ActiveOnlyManager,
City, Currency)
from products.models import Product
# -----------------------------------------Client ------------------------------------------------------#
class Client(AbstractStatusModel):
def upload_file_to(self, filename):
return "clients/{name}/{filename}".format(**{
'name': self.name,
'filename': filename
})
name = models.CharField(_('Название'), max_length=255)
image = models.FileField(_('Изображение'), upload_to=upload_file_to)
preview = models.FileField(_('Миниатюрка'), upload_to=upload_file_to)
def __str__(self):
return self.name
class Meta:
verbose_name = _('Клиент')
verbose_name_plural = _('Клиенты')
# -------------------------------------- Supply type dimensions --------------------------------------#
SUPPLY_TYPE_HOUR_DIMENSION = 0
SUPPLY_TYPE_DAY_DIMENSION = 0
SUPPLY_TYPE_WEEK_DIMENSION = 0
SUPPLY_TYPE_MONTH_DIMENSION = 0
SUPPLY_TYPE_DIMENSION_CHOICES = (
(SUPPLY_TYPE_HOUR_DIMENSION, _('Час')),
(SUPPLY_TYPE_DAY_DIMENSION, _('День')),
(SUPPLY_TYPE_WEEK_DIMENSION, _('Неделя')),
(SUPPLY_TYPE_MONTH_DIMENSION, _('Месяц'))
)
SUPPLY_TYPE_DEFAULT_DIMENSION = SUPPLY_TYPE_HOUR_DIMENSION
class SupplyType(AbstractDateTimeModel):
name = models.CharField(_('Тип'), max_length=255)
slug = AutoSlugField(populate_from='name', unique=True)
min_term = models.IntegerField(_('от'), help_text=_('Минимальный срок поставки'))
max_term = models.IntegerField(_('до'), help_text=_('Максимальный срок поставки'))
term_dimension = models.SmallIntegerField(
_('размерность'),
choices=SUPPLY_TYPE_DIMENSION_CHOICES,
default=SUPPLY_TYPE_DEFAULT_DIMENSION
)
def get_formatted_desc(self):
return _(" от {from} до {to} {dimension}".format(**{
'from': self.min_term,
'to': self.max_term,
'dimension': SUPPLY_TYPE_DIMENSION_CHOICES[self.term_dimension][1]
}))
def __str__(self):
return self.name
class Meta:
verbose_name = _('Тип поставки')
verbose_name_plural = _('Тип поставки')
class SupplyTarget(AbstractDateTimeModel):
name = models.CharField(_('Назначение'), max_length=255)
slug = AutoSlugField(populate_from='name', unique=True)
status = models.PositiveSmallIntegerField(_('статус'), help_text=_('Необходимо указать числовой код статус'))
def __str__(self):
return self.name
class Meta:
verbose_name = _('Лицензия')
verbose_name_plural = _('Лицензии')
class Discount(AbstractDateTimeModel):
def upload_file_to(self, filename):
return "discounts/{name}/{filename}".format(**{
'name': self.name,
'filename': filename
})
name = models.CharField(_('Имя'), max_length=255)
image = models.FileField(_('Изображение'), upload_to=upload_file_to, blank=True, null=True)
code = models.CharField(_('Код'), max_length=50, blank=True, unique=True, default=str(uuid.uuid4()))
valid_from = models.DateTimeField(_('Начало'), auto_now_add=True, blank=True)
valid_to = models.DateTimeField(_('Конец'), default=now() + timedelta(days=7), blank=True)
value = models.IntegerField(_('Процент'), validators=[MinValueValidator(0), MaxValueValidator(100)], default=0,
help_text=_('Указываем целым числом. Пример: 30 = 30%'))
active = models.BooleanField(_('Активная'), default=True)
def __str__(self):
return self.code
class Meta:
verbose_name = _('Дисконт')
verbose_name_plural = _('Дисконт')
# -------------------------------------- Offer status list -------------------------------------_#
OFFER_STATUS_INACTIVE = 0
OFFER_STATUS_ACTIVE = 25
OFFER_STATUS_DELETED = 50
OFFER_STATUS_CHOICES = (
(OFFER_STATUS_ACTIVE, _('Активный')),
(OFFER_STATUS_INACTIVE, _('Неактивный')),
(OFFER_STATUS_DELETED, _('Удаленный'))
)
OFFER_DEFAULT_CHOICE = OFFER_STATUS_INACTIVE
class Offer(AbstractStatusModel):
product = models.OneToOneField(Product, on_delete=models.CASCADE, primary_key=True, verbose_name=_('Продукт'))
vendor_code = models.CharField(_('Артикул'), max_length=255, unique=True, help_text=_('Должен быть уникальным'))
price = models.DecimalField(_('цена'), max_digits=10, decimal_places=2, validators=[MinValueValidator(1.00)],
help_text=_('Цена за продукт'))
currency = models.ForeignKey(Currency, verbose_name=_('Валюта'), on_delete=models.PROTECT,
help_text=_('Цена по умолчанию в рублях'))
supply_type = models.ForeignKey(SupplyType, verbose_name=_('Поставка'), on_delete=models.SET_NULL, blank=True,
null=True)
supply_target = models.ForeignKey(SupplyTarget, on_delete=models.SET_NULL, verbose_name=_('Лицензия'), blank=True,
null=True, default=None)
discount = models.ForeignKey(Discount, on_delete=models.SET_NULL, verbose_name=_('Дисконт'), blank=True, null=True)
amount = models.IntegerField(_('Колличество'), default=1, validators=[MinValueValidator(1)])
cashback = models.DecimalField(_('Кешбек'), max_digits=6, decimal_places=2, default=0,
help_text=_('Указаная сумма будет отображаться в выбранной валюте позиции'))
note = models.TextField(_('Пометка'), blank=True, null=True)
account_nds = models.BooleanField(_('с учетом НДС'), default=False)
def get_price_with_discount(self):
return self.price - ((self.discount.value * self.price) if self.discount else 0)
def __str__(self):
return self.product.name
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
if self.product:
self.status = self.product.status
super().save(force_insert, force_update, using, update_fields)
class Meta:
verbose_name = _('Позиция')
verbose_name_plural = _('Позиции')
STATUS_GAINED = 0
STATUS_SPENT = 100
CASHBACK_STATUS_CHOICES = (
(STATUS_GAINED, _('заработанный')),
(STATUS_SPENT, _('потраченный')),
)
STATUS_DEFAULT = STATUS_GAINED
class CashBackManager(ActiveOnlyManager):
def get_gained_cashback_sum(self, user):
return self.get_queryset().filter(user=user, status=STATUS_GAINED).aggregate(Avg('amount'))
def get_spent_cashback_sum(self, user):
return self.get_queryset().filter(user=user, status=STATUS_SPENT).aggregate(Avg('amount'))
class Cashback(AbstractDateTimeModel):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
amount = models.DecimalField(_('Сумма'), 'cashback', 7, 2, default=0)
status = models.SmallIntegerField(_('статус'), default=STATUS_DEFAULT, choices=CASHBACK_STATUS_CHOICES)
objects = CashBackManager()
@property
def is_spent(self):
return self.status == STATUS_SPENT
class Meta:
verbose_name_plural = _('cashback')
verbose_name = _('cashback')
ORDER_STATUS_NEW = 0
ORDER_STATUS_PENDING = 50
ORDER_STATUS_PAID = 100
ORDER_STATUS_CHOICES = (
(ORDER_STATUS_NEW, _('Новый')),
(ORDER_STATUS_PENDING, _('Обрабатывается')),
(ORDER_STATUS_PAID, _('Оплаченно'))
)
ORDER_STATUS_DEFAULT = ORDER_STATUS_NEW
class Order(AbstractStatusModel):
order_code = models.CharField(_('код заказа'), max_length=255, default=str(uuid.uuid4()))
customer_name = models.CharField(_('имя'), max_length=255)
customer_email = models.EmailField(_('email'), blank=True, null=True, default=None)
customer_user = models.ForeignKey(
get_user_model(), on_delete=models.SET_NULL,
verbose_name=_('пользователь'),
blank=True, null=True
)
phone_regex = RegexValidator(
regex=r'^\((+7)|8)?\d{10}$',
message="Phone number must be entered in the format: '+99999999999'. Up to 12 digits allowed."
)
phone = models.CharField(_('телефон'), validators=[phone_regex], max_length=12)
customer_address = models.TextField(_('адрес'))
city = models.ForeignKey(City, on_delete=models.PROTECT, verbose_name=_('Город'))
total_price = models.DecimalField(_('стоимость'), max_digits=10, decimal_places=2, default=0)
comment = models.TextField(_('комментарий'), blank=True, null=True, default=None)
status = models.SmallIntegerField(_('статус'), default=ORDER_STATUS_CHOICES, choices=ORDER_STATUS_DEFAULT)
def __str__(self):
return self.order_code
class Meta:
ordering = ('-create_at',)
verbose_name = _('Заказ')
verbose_name_plural = _('Заказы')
# ------------------------------------------ Buying status --------------------------------------------------- #
BUYING_STATUS_IN_CART = 25
BUYING_STATUS_PENDING = 50
BUYING_STATUS_PAID = 75
BUYING_STATUS_CHOICES = (
(BUYING_STATUS_IN_CART, _('В корзине')),
(BUYING_STATUS_PENDING, _('Обрабатываеться')),
(BUYING_STATUS_PAID, _('Оплаченно'))
)
BUYING_DEFAULT_CHOICE = BUYING_STATUS_IN_CART
class BuyingManager(ActiveOnlyManager, models.Manager):
def get_user_buyings(self, user):
qs = self.get_queryset()
return qs.filter(user=user).all()
def get_buying_total_price(self, user=None):
qs = self.get_user_buyings(user) if user else self.get_queryset()
return qs.aggregate(Sum('total_price'))
def get_buying_total_bonus_points(self, user=None):
qs = self.get_user_buyings(user) if user else self.get_queryset()
return qs.aggregate(Sum('bonus_points'))
def get_buying_total_cashback(self, user=None):
qs = self.get_user_buyings(user) if user else self.get_queryset()
return qs.select_related('buying_cashback').aggregate(Sum('amount'))
class Buying(AbstractStatusModel):
order = models.ForeignKey(Order, verbose_name=_('пользователь'), on_delete=models.CASCADE)
user = models.ForeignKey(get_user_model(), verbose_name=_('пользователь'), on_delete=models.CASCADE)
offer = models.ForeignKey(Offer, verbose_name=_('позиция'), on_delete=models.CASCADE)
bonus_points = models.IntegerField(_('бонусы'), validators=(MinValueValidator(0),))
status = models.SmallIntegerField(_('статус'), default=BUYING_DEFAULT_CHOICE, choices=BUYING_STATUS_CHOICES)
amount = models.SmallIntegerField(_('колличество'), default=0)
total_price = models.DecimalField(_('цена'), max_digits=10, decimal_places=2)
active = BuyingManager()
@property
def is_in_cart(self):
return self.status == BUYING_STATUS_IN_CART
@property
def is_pending(self):
return self.status == BUYING_STATUS_PENDING
@property
def is_paid(self):
return self.status == BUYING_STATUS_PAID
def __str__(self):
return "{product_name}({product_amount}) - {price}".format(**{
'product_name': self.offer.product.name,
'product_amount': self.amount,
'price': self.total_price
})
class Meta:
verbose_name = _('Покупка')
verbose_name_plural = _('Покупки')
@receiver(post_save, sender=Order)
def product_in_order_post_save(sender, instance, created, **kwargs):
order = instance.order
all_products_in_order = Buying.objects.filter(order=instance, status=ORDER_STATUS_NEW)
order_total_price = sum(item.total_price for item in all_products_in_order)
# if order.discount:
# order.total_price = order_total_price * (order.discount_value / Decimal('100'))
if order.points_quant:
order.total_price = order_total_price - order.points_quant
else:
order.total_price = order_total_price
order.save(force_update=True)