Compare commits
4 Commits
b31272511c
...
cfe2f68305
| Author | SHA1 | Date | |
|---|---|---|---|
| cfe2f68305 | |||
| 8da934bae7 | |||
| 1761f57fde | |||
| 4e5360553a |
|
|
@ -22,3 +22,6 @@ FLOWER_BASIC_AUTH="login:pwd"
|
|||
|
||||
# Logging
|
||||
SENTRY_DSN=""
|
||||
|
||||
# production/stage
|
||||
SENTRY_ENVIRONMENT=""
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import User, BonusProgramTransaction
|
||||
from .models import User
|
||||
|
||||
|
||||
@admin.register(User)
|
||||
|
|
@ -11,14 +11,3 @@ class UserAdmin(admin.ModelAdmin):
|
|||
return User.objects.with_base_related()
|
||||
|
||||
|
||||
@admin.register(BonusProgramTransaction)
|
||||
class BonusProgramTransactionAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'type', 'user', 'date', 'amount', 'comment', 'order', 'was_cancelled')
|
||||
|
||||
def get_queryset(self, request):
|
||||
return BonusProgramTransaction.objects.with_base_related()
|
||||
|
||||
def delete_queryset(self, request, queryset):
|
||||
for obj in queryset:
|
||||
obj.cancel()
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from django.db import migrations, models
|
|||
import django.utils.timezone
|
||||
|
||||
import account.models
|
||||
import bonus_program.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
@ -64,7 +65,7 @@ class Migration(migrations.Migration):
|
|||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('balance', models.PositiveSmallIntegerField(default=0, verbose_name='Баланс, руб')),
|
||||
('referral_code', models.CharField(default=account.models.generate_referral_code, editable=False, max_length=9)),
|
||||
('referral_code', models.CharField(default=bonus_program.models.generate_referral_code, editable=False, max_length=9)),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
# Generated by Django 4.2.2 on 2024-04-07 17:36
|
||||
|
||||
import account.models
|
||||
from django.db import migrations, models
|
||||
|
||||
import account.models
|
||||
import bonus_program.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
@ -25,6 +24,6 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='referral_code',
|
||||
field=models.CharField(default=account.models.generate_referral_code, editable=False, max_length=10),
|
||||
field=models.CharField(default=bonus_program.models.generate_referral_code, editable=False, max_length=10),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,5 +1 @@
|
|||
from .bonus import (generate_referral_code, BonusType, BonusProgramMixin, BonusProgram,
|
||||
BonusProgramTransaction, BonusProgramTransactionQuerySet)
|
||||
from .user import User, UserManager, UserQuerySet, ReferralRelationship
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
from phonenumber_field.phonenumber import PhoneNumber
|
||||
|
||||
from account.models import BonusProgramMixin
|
||||
from account.models.bonus import BonusProgram
|
||||
from bonus_program.models import BonusProgramMixin, BonusProgram
|
||||
from store.utils import concat_not_null_values
|
||||
from tg_bot.tasks import send_tg_message
|
||||
|
||||
|
|
@ -179,6 +178,11 @@ class User(BonusProgramMixin, AbstractUser):
|
|||
.annotate(_orders_count=Count('customer_orders'))
|
||||
.filter(_orders_count__gt=0))
|
||||
|
||||
@property
|
||||
def completed_orders_count(self):
|
||||
from store.models import Checklist
|
||||
return Checklist.objects.filter(customer_id=self.id, status=Checklist.Status.COMPLETED).count()
|
||||
|
||||
@property
|
||||
def inviter(self):
|
||||
return User.objects.filter(user_invited__invited=self.id).first()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ from djoser.conf import settings as djoser_settings
|
|||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import AuthenticationFailed
|
||||
|
||||
from .models import User, BonusProgramTransaction, BonusType
|
||||
from bonus_program.serializers import BonusProgramTransactionSerializer
|
||||
from .models import User
|
||||
from bonus_program.models import BonusType
|
||||
from .utils import verify_telegram_authentication
|
||||
|
||||
|
||||
|
|
@ -24,15 +26,6 @@ class UserSerializer(serializers.ModelSerializer):
|
|||
return obj.invited_users_with_orders.count()
|
||||
|
||||
|
||||
class BonusProgramTransactionSerializer(serializers.ModelSerializer):
|
||||
order_id = serializers.StringRelatedField(source='order.id', allow_null=True)
|
||||
type = serializers.CharField(source='get_type_display')
|
||||
|
||||
class Meta:
|
||||
model = BonusProgramTransaction
|
||||
fields = ('id', 'type', 'date', 'amount', 'order_id', 'comment', 'was_cancelled')
|
||||
|
||||
|
||||
def non_zero_validator(value):
|
||||
if value == 0:
|
||||
raise serializers.ValidationError("Value cannot be zero")
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import logging
|
|||
from django.db.models.signals import post_save, post_delete
|
||||
from django.dispatch import receiver
|
||||
|
||||
from account.models import User, ReferralRelationship, BonusProgramTransaction
|
||||
from account.models import User, ReferralRelationship
|
||||
from bonus_program.models import BonusProgramTransaction
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ from rest_framework.renderers import StaticHTMLRenderer
|
|||
from rest_framework.response import Response
|
||||
|
||||
from account.models import User
|
||||
from account.serializers import SetInitialPasswordSerializer, BonusProgramTransactionSerializer, \
|
||||
UserBalanceUpdateSerializer, TelegramCallbackSerializer
|
||||
from account.serializers import SetInitialPasswordSerializer, UserBalanceUpdateSerializer, TelegramCallbackSerializer
|
||||
from bonus_program.serializers import BonusProgramTransactionSerializer
|
||||
from tg_bot.handlers.start import request_phone_sync
|
||||
from tg_bot.messages import TGCoreMessage
|
||||
|
||||
|
|
|
|||
0
bonus_program/__init__.py
Normal file
0
bonus_program/__init__.py
Normal file
16
bonus_program/admin.py
Normal file
16
bonus_program/admin.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from bonus_program.models import BonusProgramTransaction
|
||||
|
||||
|
||||
# Register your models here.
|
||||
@admin.register(BonusProgramTransaction)
|
||||
class BonusProgramTransactionAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'type', 'user', 'date', 'amount', 'comment', 'order', 'was_cancelled')
|
||||
|
||||
def get_queryset(self, request):
|
||||
return BonusProgramTransaction.objects.with_base_related()
|
||||
|
||||
def delete_queryset(self, request, queryset):
|
||||
for obj in queryset:
|
||||
obj.cancel()
|
||||
6
bonus_program/apps.py
Normal file
6
bonus_program/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BonusProgramConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'bonus_program'
|
||||
0
bonus_program/migrations/__init__.py
Normal file
0
bonus_program/migrations/__init__.py
Normal file
|
|
@ -10,6 +10,7 @@ from django.utils.formats import localize
|
|||
from django.utils.functional import cached_property
|
||||
|
||||
from core.models import BonusProgramConfig
|
||||
from store.models import Checklist
|
||||
from tg_bot.messages import TGBonusMessage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -76,7 +77,7 @@ class BonusProgramTransaction(models.Model):
|
|||
""" Represents the history of all bonus program transactions """
|
||||
|
||||
type = models.PositiveSmallIntegerField('Тип транзакции', choices=BonusType.CHOICES)
|
||||
user = models.ForeignKey('User', verbose_name='Пользователь транзакции', on_delete=models.CASCADE, related_name='bonus_transactions')
|
||||
user = models.ForeignKey('account.User', verbose_name='Пользователь транзакции', on_delete=models.CASCADE, related_name='bonus_transactions')
|
||||
date = models.DateTimeField('Дата транзакции', auto_now_add=True)
|
||||
amount = models.SmallIntegerField('Количество, руб')
|
||||
comment = models.CharField('Комментарий', max_length=200, null=True, blank=True)
|
||||
|
|
@ -273,9 +274,8 @@ class BonusProgram:
|
|||
user.update_balance(amount, bonus_type)
|
||||
|
||||
@staticmethod
|
||||
def add_order_bonus(order: 'Checklist'):
|
||||
def add_order_bonus(order: Checklist):
|
||||
bonus_type = BonusType.DEFAULT_PURCHASE
|
||||
amount = BonusProgramConfig.load().amount_default_purchase
|
||||
|
||||
# Check if data is sufficient
|
||||
if order is None or order.customer_id is None:
|
||||
|
|
@ -285,11 +285,14 @@ class BonusProgram:
|
|||
if order.status != settings.BONUS_ELIGIBILITY_STATUS:
|
||||
return
|
||||
|
||||
level = BonusProgramLevel.objects.level_for_order_count(order.customer.completed_orders_count)
|
||||
amount = getattr(level, 'amount_default_purchase', 0)
|
||||
|
||||
# Add bonuses
|
||||
order.customer.update_balance(amount, bonus_type, order=order)
|
||||
|
||||
@staticmethod
|
||||
def add_referral_bonus(order: 'Checklist', for_inviter: bool):
|
||||
def add_referral_bonus(order: Checklist, for_inviter: bool):
|
||||
amount = BonusProgramConfig.load().amount_referral
|
||||
|
||||
# Check if data is sufficient
|
||||
|
|
@ -306,3 +309,17 @@ class BonusProgram:
|
|||
|
||||
# Add bonuses
|
||||
user.update_balance(amount, bonus_type, order=order)
|
||||
|
||||
|
||||
class BonusProgramLevelQuerySet(models.QuerySet):
|
||||
def level_for_order_count(self, count):
|
||||
return self.filter(orders_count__lt=count).order_by('-orders_count').first()
|
||||
|
||||
|
||||
class BonusProgramLevel(models.Model):
|
||||
slug = models.SlugField('Идентификатор', unique=True)
|
||||
name = models.CharField('Название', max_length=30)
|
||||
orders_count = models.PositiveSmallIntegerField('Минимальное количество заказов', unique=True)
|
||||
amount_default_purchase = models.PositiveSmallIntegerField('Бонус за обычную покупку')
|
||||
|
||||
objects = BonusProgramLevelQuerySet.as_manager()
|
||||
12
bonus_program/serializers.py
Normal file
12
bonus_program/serializers.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from bonus_program.models import BonusProgramTransaction
|
||||
|
||||
|
||||
class BonusProgramTransactionSerializer(serializers.ModelSerializer):
|
||||
order_id = serializers.StringRelatedField(source='order.id', allow_null=True)
|
||||
type = serializers.CharField(source='get_type_display')
|
||||
|
||||
class Meta:
|
||||
model = BonusProgramTransaction
|
||||
fields = ('id', 'type', 'date', 'amount', 'order_id', 'comment', 'was_cancelled')
|
||||
3
bonus_program/tests.py
Normal file
3
bonus_program/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
bonus_program/views.py
Normal file
3
bonus_program/views.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
|
|
@ -37,8 +37,6 @@ DEFAULT_CONFIG = settings.BONUS_PROGRAM_DEFAULT_CONFIG
|
|||
class BonusProgramConfig(models.Model):
|
||||
amount_signup = models.PositiveSmallIntegerField(
|
||||
'Бонус за регистрацию', default=DEFAULT_CONFIG['amounts']['signup'])
|
||||
amount_default_purchase = models.PositiveSmallIntegerField(
|
||||
'Бонус за обычную покупку', default=DEFAULT_CONFIG['amounts']['default_purchase'])
|
||||
amount_referral = models.PositiveSmallIntegerField(
|
||||
'Реферальный бонус', default=DEFAULT_CONFIG['amounts']['referral'])
|
||||
|
||||
|
|
|
|||
|
|
@ -105,7 +105,8 @@ INSTALLED_APPS = [
|
|||
'account',
|
||||
'store',
|
||||
'tg_bot',
|
||||
'core'
|
||||
'core',
|
||||
'bonus_program'
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
|
@ -250,6 +251,7 @@ if SENTRY_DSN:
|
|||
# of sampled transactions.
|
||||
# We recommend adjusting this value in production.
|
||||
profiles_sample_rate=1.0,
|
||||
environment=get_secret("SENTRY_ENVIRONMENT"),
|
||||
)
|
||||
|
||||
# Celery
|
||||
|
|
@ -267,7 +269,15 @@ BONUS_ELIGIBILITY_STATUS = 'completed'
|
|||
BONUS_PROGRAM_DEFAULT_CONFIG = {
|
||||
"amounts": {
|
||||
"signup": 150,
|
||||
"default_purchase": 50,
|
||||
"referral": 500,
|
||||
}
|
||||
},
|
||||
|
||||
"levels": [
|
||||
# slug, name, orders_count, amount_default_purchase
|
||||
("new", "Новичок", 0, 50),
|
||||
("fashion", "Модник", 3, 150),
|
||||
("pro", "Профессионал", 10, 250),
|
||||
("shopaholic", "Шопоголик", 20, 350),
|
||||
("killer", "Фэшн Киллер", 30, 500),
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
from django.core.management import BaseCommand
|
||||
from django.conf import settings
|
||||
from tqdm import tqdm
|
||||
|
||||
from bonus_program.models import BonusProgramLevel
|
||||
from store.models import Category, PaymentMethod
|
||||
|
||||
|
||||
|
|
@ -134,10 +136,23 @@ def create_payment_types():
|
|||
PaymentMethod.objects.get_or_create(slug=slug, defaults=data)
|
||||
|
||||
|
||||
def create_bonus_program_levels():
|
||||
for cfg in settings.BONUS_PROGRAM_DEFAULT_CONFIG['levels']:
|
||||
slug, name, order_count, amount_default_purchase = cfg
|
||||
BonusProgramLevel.objects.get_or_create(
|
||||
slug=slug,
|
||||
defaults={
|
||||
'name': name,
|
||||
'orders_count': order_count,
|
||||
'amount_default_purchase': amount_default_purchase
|
||||
})
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = ''' Create root categories '''
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
create_categories()
|
||||
create_payment_types()
|
||||
create_bonus_program_levels()
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ from django_cleanup import cleanup
|
|||
from mptt.fields import TreeForeignKey
|
||||
from mptt.models import MPTTModel
|
||||
|
||||
from account.models import BonusProgram
|
||||
from core.models import GlobalSettings
|
||||
from store.utils import create_preview
|
||||
|
||||
|
|
@ -166,7 +165,7 @@ class ChecklistQuerySet(models.QuerySet):
|
|||
.prefetch_related(Prefetch('images', to_attr='_images'))
|
||||
|
||||
def annotate_bonus_used(self):
|
||||
from account.models import BonusProgramTransaction, BonusType
|
||||
from bonus_program.models import BonusProgramTransaction, BonusType
|
||||
|
||||
amount_subquery = Subquery(
|
||||
BonusProgramTransaction.objects.all()
|
||||
|
|
@ -606,7 +605,7 @@ class Checklist(models.Model):
|
|||
return
|
||||
|
||||
# Check if any BonusProgramTransaction bound to current order exists
|
||||
from account.models import BonusProgramTransaction
|
||||
from bonus_program.models import BonusProgramTransaction, BonusProgram
|
||||
if BonusProgramTransaction.objects.filter(order_id=self.id).exists():
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from django.db.transaction import atomic
|
|||
from drf_extra_fields.fields import Base64ImageField
|
||||
from rest_framework import serializers
|
||||
|
||||
from account.models.bonus import BonusProgram
|
||||
from bonus_program.models import BonusProgram
|
||||
from account.serializers import UserSerializer
|
||||
from utils.exceptions import CRMException
|
||||
from store.models import Checklist, Category, PaymentMethod, Promocode, Image, Gift
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ router.register(r'gifts', views.GiftAPI, basename='gifts')
|
|||
router.register(r'poizon', views.PoizonAPI, basename='poizon')
|
||||
router.register(r'promo', views.PromoCodeAPI, basename='promo')
|
||||
router.register(r'category', views.CategoryAPI, basename='category')
|
||||
router.register(r'settings/payment', views.PaymentMethodsAPI, basename='payment')
|
||||
router.register(r'payment', views.PaymentMethodsAPI, basename='payment')
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user