kwork-poizonstore/store/views.py
2023-07-03 06:38:55 +04:00

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)