358 lines
12 KiB
Python
358 lines
12 KiB
Python
import calendar
|
|
import json
|
|
from collections import OrderedDict, defaultdict
|
|
|
|
from django.contrib.auth import login
|
|
from django.db.models import F, Count, Q, Sum
|
|
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.response import Response
|
|
|
|
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)
|
|
|
|
|
|
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(serializer.data)
|
|
|
|
|
|
class ChecklistAPI(mixins.ListModelMixin, mixins.RetrieveModelMixin, generics.GenericAPIView):
|
|
serializer_class = ChecklistSerializer
|
|
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 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})
|
|
|
|
# FIXME: use PATCH method for updates
|
|
def post(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,
|
|
})
|
|
|
|
# FIXME: use PATCH method for updates
|
|
def post(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
|
|
|
|
# FIXME: use PATCH method for updates
|
|
def post(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
|
|
|
|
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)
|
|
|
|
# FIXME: use PATCH method for updates
|
|
def post(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 = get_object_or_404(self.get_queryset(), name=data['name'])
|
|
instance.delete()
|
|
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
|
|
|
|
completed_filter = Q(status=Checklist.Status.COMPLETED)
|
|
|
|
# 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)
|
|
|
|
# TODO: implement stats_by_clients
|
|
@action(url_path='clients', detail=False, methods=['get'])
|
|
def stats_by_clients(self, request, *args, **kwargs):
|
|
def _create_stats(data: dict):
|
|
return {
|
|
"moreone": data.get('moreone', 0),
|
|
"moretwo": data.get('moretwo', 0),
|
|
"morethree": data.get('morethree', 0),
|
|
"morefour": data.get('morefour', 0),
|
|
"morefive": data.get('morefive', 0),
|
|
"moreten": data.get('moreten', 0),
|
|
"moretwentyfive": data.get('moretwentyfive', 0),
|
|
"morefifty": data.get('morefifty', 0),
|
|
}
|
|
|
|
def _filter_for_count(count):
|
|
return Count('buyer_phone',
|
|
filter=Q(buyer_phone__in=Checklist.objects.values('buyer_phone')
|
|
.annotate(total_orders=Count('id'))
|
|
.filter(total_orders__gt=count)
|
|
.values('buyer_phone')
|
|
))
|
|
|
|
qs = self.get_queryset() \
|
|
.values('month') \
|
|
.annotate(
|
|
moreone=_filter_for_count(1),
|
|
moretwo=_filter_for_count(2),
|
|
morethree=_filter_for_count(3),
|
|
morefour=_filter_for_count(4),
|
|
morefive=_filter_for_count(5),
|
|
moreten=_filter_for_count(10),
|
|
moretwentyfive=_filter_for_count(25),
|
|
morefifty=_filter_for_count(50),
|
|
)
|
|
|
|
result = {}
|
|
# Add empty stats
|
|
for i in range(1, 13):
|
|
month = calendar.month_name[i]
|
|
result[month] = _create_stats(dict())
|
|
|
|
# Add actual stats
|
|
for stat in qs:
|
|
month = calendar.month_name[stat['month']]
|
|
result[month] = _create_stats(stat)
|
|
|
|
return Response(result)
|