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.
 
 
 
 

321 lines
12 KiB

import uuid
from decimal import Decimal
from autoslug import AutoSlugField
from django.contrib.auth import get_user_model
from django.core.validators import MinValueValidator, MaxValueValidator, RegexValidator
from django.db import models
from django.db.models import Avg
from django.utils.timezone import now, timedelta
from django.utils.translation import ugettext_lazy as _
# 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):
discount = (self.discount.value / 100.0) if self.discount else 0
return round(self.price - (Decimal(discount) * self.price), 2)
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)
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(_('статус'), choices=ORDER_STATUS_CHOICES, default=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 = _('Покупки')