382 lines
13 KiB
Python
382 lines
13 KiB
Python
import calendar
|
|
import json
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth import login
|
|
from django.db.models import F, Count, Q, Sum, Value, Subquery
|
|
from django.utils import timezone
|
|
from rest_framework import generics, permissions, mixins, status, viewsets
|
|
from rest_framework.decorators import action
|
|
from rest_framework.generics import get_object_or_404
|
|
from rest_framework.permissions import IsAuthenticated
|
|
from rest_framework.response import Response
|
|
|
|
from cdek.api import CDEKClient
|
|
from store.exceptions import CRMException
|
|
from store.models import User, Checklist, GlobalSettings, Category, PaymentMethod, Promocode
|
|
from store.serializers import (UserSerializer, LoginSerializer, ChecklistSerializer, GlobalSettingsYuanRateSerializer,
|
|
CategorySerializer, GlobalSettingsPriceSerializer, PaymentMethodSerializer,
|
|
PromocodeSerializer, GlobalSettingsPickupSerializer)
|
|
from utils.permissions import ReadOnly
|
|
|
|
|
|
class UserAPI(mixins.ListModelMixin, mixins.RetrieveModelMixin, generics.GenericAPIView):
|
|
serializer_class = UserSerializer
|
|
|
|
def get_queryset(self):
|
|
return User.objects.all()
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
if 'pk' in kwargs:
|
|
return self.retrieve(request, *args, **kwargs)
|
|
return self.list(request, *args, **kwargs)
|
|
|
|
# Update some data on current user
|
|
def patch(self, request, *args, **kwargs):
|
|
data = json.loads(request.body)
|
|
instance = self.request.user
|
|
serializer = self.get_serializer(instance, data=data, partial=True)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
class LoginAPI(generics.GenericAPIView):
|
|
serializer_class = LoginSerializer
|
|
permission_classes = (permissions.AllowAny,)
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
data = json.loads(request.body)
|
|
|
|
serializer = self.get_serializer(data=data)
|
|
serializer.is_valid(raise_exception=True)
|
|
user = serializer.validated_data['user']
|
|
login(request, user)
|
|
return Response(UserSerializer(user).data)
|
|
|
|
|
|
class ChecklistAPI(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, generics.GenericAPIView):
|
|
serializer_class = ChecklistSerializer
|
|
permission_classes = [IsAuthenticated | ReadOnly]
|
|
lookup_field = 'id'
|
|
filterset_fields = ['status', ]
|
|
search_fields = ['id', 'track_id', 'buyer_phone', 'full_price']
|
|
|
|
def get_queryset(self):
|
|
return Checklist.objects.all().with_base_related() \
|
|
.annotate_price_rub().annotate_commission_rub() \
|
|
.default_ordering()
|
|
|
|
def get_object(self):
|
|
obj: Checklist = super().get_object()
|
|
|
|
# 3 hours maximum in 'neworder' status -> move to drafts
|
|
if obj.status == Checklist.Status.NEW:
|
|
diff_hours = (timezone.now() - obj.status_updated_at).seconds / 3600
|
|
if diff_hours > 3:
|
|
obj.status = Checklist.Status.DRAFT
|
|
obj.save()
|
|
|
|
return obj
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
return self.create(request, *args, **kwargs)
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
if 'id' in kwargs:
|
|
return self.retrieve(request, *args, **kwargs)
|
|
return self.list(request, *args, **kwargs)
|
|
|
|
# Update some data on current user
|
|
def patch(self, request, *args, **kwargs):
|
|
data = json.loads(request.body)
|
|
instance = get_object_or_404(self.get_queryset(), id=self.kwargs['id'])
|
|
serializer = self.get_serializer(instance, data=data, partial=True)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
class YuanRateAPI(generics.GenericAPIView):
|
|
serializer_class = GlobalSettingsYuanRateSerializer
|
|
|
|
def get_object(self):
|
|
return GlobalSettings.load()
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
yuan_rate = GlobalSettings.load().yuan_rate
|
|
return Response(data={'currency': yuan_rate})
|
|
|
|
def patch(self, request, *args, **kwargs):
|
|
data = json.loads(request.body)
|
|
instance = self.get_object()
|
|
|
|
serializer = self.get_serializer(instance, data=data, partial=True)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
class CategoryAPI(generics.GenericAPIView):
|
|
serializer_class = CategorySerializer
|
|
|
|
def get_queryset(self):
|
|
return Category.objects.all()
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
categories_qs = self.get_queryset()
|
|
global_settings = GlobalSettings.load()
|
|
|
|
return Response({
|
|
'categories': CategorySerializer(categories_qs, many=True).data,
|
|
'prices': GlobalSettingsPriceSerializer(global_settings).data,
|
|
})
|
|
|
|
def patch(self, request, *args, **kwargs):
|
|
data = json.loads(request.body)
|
|
if not all(k in data for k in ("category", "chinarush")):
|
|
raise CRMException('category and chinarush is required')
|
|
|
|
instance = get_object_or_404(self.get_queryset(), slug=data['category'])
|
|
serializer = self.get_serializer(instance, data=data, partial=True)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
class PricesAPI(generics.GenericAPIView):
|
|
serializer_class = GlobalSettingsPriceSerializer
|
|
|
|
def patch(self, request, *args, **kwargs):
|
|
data = json.loads(request.body)
|
|
|
|
instance = GlobalSettings.load()
|
|
serializer = self.get_serializer(instance, data=data, partial=True)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
class PickupAPI(generics.GenericAPIView):
|
|
serializer_class = GlobalSettingsPickupSerializer
|
|
permission_classes = [IsAuthenticated | ReadOnly]
|
|
|
|
def get_object(self):
|
|
return GlobalSettings.load()
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
instance = self.get_object()
|
|
return Response(self.get_serializer(instance).data)
|
|
|
|
def patch(self, request, *args, **kwargs):
|
|
data = json.loads(request.body)
|
|
|
|
instance = GlobalSettings.load()
|
|
serializer = self.get_serializer(instance, data=data, partial=True)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
class PaymentMethodsAPI(generics.GenericAPIView):
|
|
serializer_class = PaymentMethodSerializer
|
|
|
|
def get_queryset(self):
|
|
return PaymentMethod.objects.all()
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
qs = self.get_queryset()
|
|
data = {}
|
|
for obj in qs:
|
|
data[obj.slug] = self.get_serializer(obj).data
|
|
|
|
return Response(data)
|
|
|
|
def patch(self, request, *args, **kwargs):
|
|
data = json.loads(request.body)
|
|
if 'type' not in data:
|
|
raise CRMException('type is required')
|
|
|
|
instance = get_object_or_404(self.get_queryset(), slug=data['type'])
|
|
serializer = self.get_serializer(instance, data=data, partial=True)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
class PromoCodeAPI(mixins.CreateModelMixin, generics.GenericAPIView):
|
|
serializer_class = PromocodeSerializer
|
|
lookup_field = 'name'
|
|
|
|
def get_queryset(self):
|
|
return Promocode.objects.all()
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
qs = self.get_queryset()
|
|
return Response(
|
|
{'promo': self.get_serializer(qs, many=True).data}
|
|
)
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
self.create(request, *args, **kwargs)
|
|
return self.get(request, *args, **kwargs)
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
data = json.loads(request.body)
|
|
if 'name' not in data:
|
|
raise CRMException('name is required')
|
|
|
|
instance: Promocode = get_object_or_404(self.get_queryset(), name=data['name'])
|
|
instance.is_active = False
|
|
instance.save()
|
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
|
|
|
|
class StatisticsAPI(viewsets.GenericViewSet):
|
|
def get_queryset(self):
|
|
return Checklist.objects.all() \
|
|
.filter(status=Checklist.Status.COMPLETED) \
|
|
.annotate(month=F('status_updated_at__month'))
|
|
|
|
@action(url_path='orders', detail=False, methods=['get'])
|
|
def stats_by_orders(self, request, *args, **kwargs):
|
|
global_settings = GlobalSettings.load()
|
|
yuan_rate = global_settings.yuan_rate
|
|
|
|
# Prepare query to collect the stats
|
|
qs = self.get_queryset() \
|
|
.annotate_price_rub() \
|
|
.annotate_commission_rub() \
|
|
.values('month') \
|
|
.annotate(total=Count('id'),
|
|
total_completed=Count('id'),
|
|
total_bought_yuan=Sum('real_price', default=0),
|
|
total_bought_rub=F('total_bought_yuan') * yuan_rate,
|
|
total_commission_rub=Sum('_commission_rub', default=0)
|
|
)
|
|
|
|
def _create_stats(data: dict):
|
|
return {
|
|
"CountOrders": data.get('total', 0),
|
|
"CountComplete": data.get('total_completed', 0),
|
|
"SumCommission": data.get('total_commission_rub', 0),
|
|
"SumOrders1": data.get('total_bought_yuan', 0),
|
|
"SumOrders2": data.get('total_bought_rub', 0),
|
|
}
|
|
|
|
result = {}
|
|
# Add empty stats
|
|
for i in range(1, 13):
|
|
month_name = calendar.month_name[i]
|
|
result[month_name] = _create_stats(dict())
|
|
|
|
# Add actual stats
|
|
for stat in qs:
|
|
month_name = calendar.month_name[stat['month']]
|
|
result[month_name] = _create_stats(stat)
|
|
|
|
return Response(result)
|
|
|
|
@action(url_path='categories', detail=False, methods=['get'])
|
|
def stats_by_categories(self, request, *args, **kwargs):
|
|
all_categories = Category.objects.values_list('slug', flat=True)
|
|
categories_dict = {c: 0 for c in all_categories}
|
|
|
|
qs = self.get_queryset() \
|
|
.select_related('category') \
|
|
.values('month', 'category__slug') \
|
|
.annotate(total_orders=Count('id'))
|
|
|
|
result = {}
|
|
# Add empty stats
|
|
for i in range(1, 13):
|
|
month = calendar.month_name[i]
|
|
result[month] = categories_dict.copy()
|
|
|
|
# Add actual stats
|
|
for stat in qs:
|
|
month = calendar.month_name[stat['month']]
|
|
category_slug = stat['category__slug']
|
|
total_orders = stat['total_orders']
|
|
|
|
result[month][category_slug] = total_orders
|
|
|
|
return Response(result)
|
|
|
|
@action(url_path='clients', detail=False, methods=['get'])
|
|
def stats_by_clients(self, request, *args, **kwargs):
|
|
options = {
|
|
"moreone": 1,
|
|
"moretwo": 2,
|
|
"morethree": 3,
|
|
"morefour": 4,
|
|
"morefive": 5,
|
|
"moreten": 10,
|
|
"moretwentyfive": 25,
|
|
"morefifty": 50,
|
|
}
|
|
|
|
def _create_empty_stats():
|
|
return {k: set() for k in options.keys()}
|
|
|
|
qs = self.get_queryset() \
|
|
.filter(buyer_phone__isnull=False) \
|
|
.values('month', 'buyer_phone') \
|
|
.annotate(order_count=Count('id')) \
|
|
.filter(order_count__gt=1) \
|
|
.order_by('month')
|
|
|
|
result = {}
|
|
# Add empty stats
|
|
for i in range(1, 13):
|
|
month_name = calendar.month_name[i]
|
|
result[month_name] = _create_empty_stats()
|
|
|
|
# Add actual stats
|
|
for stat in qs:
|
|
month_name = calendar.month_name[stat['month']]
|
|
|
|
for key, size in reversed(options.items()):
|
|
if stat['order_count'] > size:
|
|
result[month_name][key].add(stat['buyer_phone'])
|
|
break
|
|
|
|
return Response(result)
|
|
|
|
|
|
class CDEKAPI(viewsets.GenericViewSet):
|
|
client = CDEKClient(settings.CDEK_CLIENT_ID, settings.CDEK_CLIENT_SECRET)
|
|
|
|
@action(url_path='orders', detail=False, methods=['get'])
|
|
def get_order_info(self, request, *args, **kwargs):
|
|
im_number = request.query_params.get('im_number')
|
|
if not im_number:
|
|
raise CRMException('im_number is required')
|
|
|
|
r = self.client.get_order_info(im_number)
|
|
return Response(r.json())
|
|
|
|
@action(url_path='orders', detail=False, methods=['post'])
|
|
def create_order(self, request, *args, **kwargs):
|
|
order_data = request.data
|
|
if not order_data:
|
|
raise CRMException('json data is required')
|
|
|
|
r = self.client.create_order(order_data)
|
|
return Response(r.json())
|
|
|
|
@action(url_path='calculator/tariff', detail=False, methods=['post'])
|
|
def calculate_tariff(self, request, *args, **kwargs):
|
|
data = request.data
|
|
if not data:
|
|
raise CRMException('json data is required')
|
|
r = self.client.calculate_tariff(data)
|
|
return Response(r.json())
|