+ PriceSnapshot

* Category commission inside a commission_rub field
This commit is contained in:
Phil Zhitnikov 2023-10-04 06:57:53 +04:00
parent ce59792419
commit 8c0f5ac6fd
3 changed files with 131 additions and 36 deletions

View File

@ -0,0 +1,29 @@
# Generated by Django 4.2.2 on 2023-10-04 02:53
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('store', '0042_oldchecklist_checklist_split_payment_proof_and_more'),
]
operations = [
migrations.CreateModel(
name='PriceSnapshot',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('yuan_rate', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Курс CNY/RUB')),
('delivery_price_CN', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Цена доставки по Китаю')),
('delivery_price_CN_RU', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Цена доставки Китай-РФ')),
('commission_rub', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Комиссия, руб')),
],
),
migrations.AddField(
model_name='checklist',
name='price_snapshot',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checklist', to='store.pricesnapshot', verbose_name='Сохраненные цены'),
),
]

View File

@ -228,17 +228,23 @@ def generate_checklist_id():
class ChecklistQuerySet(models.QuerySet):
def with_base_related(self):
return self.select_related('manager', 'category', 'payment_method', 'promocode')\
return self.select_related('manager', 'category', 'payment_method', 'promocode', 'price_snapshot')\
.prefetch_related(Prefetch('images', to_attr='_images'))
def default_ordering(self):
return self.order_by(F('status_updated_at').desc(nulls_last=True))
def annotate_price_rub(self):
# FIXME: implement price_rub in DB query
return self
yuan_rate = GlobalSettings.load().yuan_rate
return self.annotate(_price_rub=F('price_yuan') * yuan_rate)
def annotate_commission_rub(self):
# FIXME: implement category commission in DB query
return self
commission = GlobalSettings.load().commission_rub
return self.annotate(_commission_rub=Case(
When(GreaterThan(F("_price_rub"), 150_000), then=F("_price_rub") * settings.COMMISSION_OVER_150K),
@ -247,6 +253,13 @@ class ChecklistQuerySet(models.QuerySet):
))
class PriceSnapshot(models.Model):
yuan_rate = models.DecimalField('Курс CNY/RUB', max_digits=10, decimal_places=2, default=0)
delivery_price_CN = models.DecimalField('Цена доставки по Китаю', max_digits=10, decimal_places=2, default=0)
delivery_price_CN_RU = models.DecimalField('Цена доставки Китай-РФ', max_digits=10, decimal_places=2, default=0)
commission_rub = models.DecimalField('Комиссия, руб', max_digits=10, decimal_places=2, default=0)
@cleanup.select
class Checklist(models.Model):
# Statuses
@ -350,6 +363,10 @@ class Checklist(models.Model):
cdek_tracking = models.CharField('Трек-номер СДЭК', max_length=100, null=True, blank=True)
cdek_barcode_pdf = models.FileField('Штрих-код СДЭК в PDF', upload_to='docs', null=True, blank=True)
price_snapshot = models.ForeignKey('PriceSnapshot', verbose_name='Сохраненные цены',
related_name='checklist',
on_delete=models.SET_NULL, null=True, blank=True)
objects = ChecklistQuerySet.as_manager()
class Meta:
@ -368,11 +385,18 @@ class Checklist(models.Model):
@property
def price_rub(self) -> int:
# Prefer annotated field
if hasattr(self, '_price_rub'):
return self._price_rub
# FIXME: implement price_rub in DB query
# # Prefer annotated field for calculation
# if hasattr(self, '_price_rub'):
# return self._price_rub
return math.ceil(GlobalSettings.load().yuan_rate * self.price_yuan)
# Get saved prices
if self.price_snapshot_id:
yuan_rate = self.price_snapshot.yuan_rate
else:
yuan_rate = GlobalSettings.load().yuan_rate
return math.ceil(yuan_rate * self.price_yuan)
@property
def full_price(self) -> int:
@ -392,33 +416,59 @@ class Checklist(models.Model):
no_comission = promocode.no_comission
if not free_delivery:
price += GlobalSettings.load().delivery_price_CN + self.delivery_price_CN_RU
price += self.delivery_price_CN + self.delivery_price_CN_RU
if not no_comission:
price += self.commission_rub
# Add commission of bottom-most category
if self.category:
category = self.category.get_ancestors(ascending=True, include_self=True).first()
category_commission = getattr(category, 'commission', 0)
price += category_commission * self.price_rub / 100
return max(0, math.ceil(price))
@property
def yuan_rate(self) -> Decimal:
# Get saved value if exists
if self.price_snapshot_id:
return self.price_snapshot.yuan_rate
else:
return GlobalSettings.load().yuan_rate
@property
def delivery_price_CN(self) -> Decimal:
# Get saved value if exists
if self.price_snapshot_id:
return self.price_snapshot.delivery_price_CN
else:
return GlobalSettings.load().delivery_price_CN
@property
def delivery_price_CN_RU(self) -> Decimal:
# Get saved value if exists
if self.price_snapshot_id:
return self.price_snapshot.delivery_price_CN_RU
else:
return getattr(self.category, 'delivery_price_CN_RU', Decimal(0))
@property
def commission_rub(self) -> Decimal:
# Prefer annotated field
if hasattr(self, '_commission_rub'):
return self._commission_rub
# FIXME: implement category commission in DB query
# # Prefer annotated field
# if hasattr(self, '_commission_rub'):
# return self._commission_rub
return (self.price_rub * Decimal(settings.COMMISSION_OVER_150K)
# Prefer saved value
if self.price_snapshot_id:
return self.price_snapshot.commission_rub
commission = (self.price_rub * Decimal(settings.COMMISSION_OVER_150K)
if self.price_rub > 150_000
else GlobalSettings.load().commission_rub)
# Add commission of bottom-most category
if self.category_id:
category_commission = getattr(self.category, 'commission', 0)
commission += category_commission * self.price_rub / 100
return commission
@property
def preview_image(self):
# Prefer annotated field
@ -467,9 +517,27 @@ class Checklist(models.Model):
self.images.add(image_obj)
def save_prices(self):
# Temporarily remove snapshot from object
self.price_snapshot = None
snapshot, _ = PriceSnapshot.objects.get_or_create(
checklist__id=self.price_snapshot_id,
defaults={
'yuan_rate': self.yuan_rate,
'delivery_price_CN': self.delivery_price_CN,
'delivery_price_CN_RU': self.delivery_price_CN_RU,
'commission_rub': self.commission_rub,
}
)
# Restore snapshot
self.price_snapshot = snapshot
def save(self, *args, **kwargs):
if self.id:
old_obj = Checklist.objects.filter(id=self.id).first()
# If status was updated, update status_updated_at field
if old_obj and self.status != old_obj.status:
self.status_updated_at = timezone.now()
@ -485,5 +553,15 @@ class Checklist(models.Model):
if not self.preview_image:
self.generate_preview()
# Save price details to snapshot
if self.price_snapshot_id:
# Status updated from other statuses back to DRAFT
if self.status == Checklist.Status.DRAFT:
self.price_snapshot.delete()
self.price_snapshot = None
elif self.status != Checklist.Status.DRAFT:
self.save_prices()
super().save(*args, **kwargs)

View File

@ -75,16 +75,16 @@ class ChecklistSerializer(serializers.ModelSerializer):
promo = serializers.SlugRelatedField(source='promocode', slug_field='name',
queryset=Promocode.objects.active(), required=False, allow_null=True)
currency = serializers.SerializerMethodField('get_yuan_rate')
currency = serializers.DecimalField(source='yuan_rate', read_only=True, max_digits=10, decimal_places=2)
curencycurency2 = serializers.DecimalField(source='price_yuan', required=False, max_digits=10, decimal_places=2)
currency3 = serializers.IntegerField(source='price_rub', read_only=True)
chinadelivery = serializers.SerializerMethodField('get_delivery_price_CN', read_only=True)
chinadelivery2 = serializers.DecimalField(source='delivery_price_CN_RU', read_only=True, max_digits=10,
decimal_places=2)
chinadelivery = serializers.DecimalField(source='delivery_price_CN', read_only=True, max_digits=10, decimal_places=2)
chinadelivery2 = serializers.DecimalField(source='delivery_price_CN_RU', read_only=True,
max_digits=10, decimal_places=2)
fullprice = serializers.IntegerField(source='full_price', read_only=True)
realprice = serializers.DecimalField(source='real_price', required=False, allow_null=True, max_digits=10,
decimal_places=2)
commission = serializers.SerializerMethodField('get_commission', read_only=True)
commission = serializers.DecimalField(source='commission_rub', read_only=True, max_digits=10, decimal_places=2)
buyername = serializers.CharField(source='buyer_name', required=False, allow_null=True)
buyerphone = serializers.CharField(source='buyer_phone', required=False, allow_null=True)
@ -151,22 +151,10 @@ class ChecklistSerializer(serializers.ModelSerializer):
return instance
@staticmethod
def get_yuan_rate(obj: Checklist):
return GlobalSettings.load().yuan_rate
@staticmethod
def get_image(obj: Checklist):
return obj.images.all()
@staticmethod
def get_delivery_price_CN(obj: Checklist):
return GlobalSettings.load().delivery_price_CN
@staticmethod
def get_commission(obj: Checklist):
return GlobalSettings.load().commission_rub
class Meta:
model = Checklist
fields = ('id', 'status', 'managerid', 'link',