FEATURE(promotions): adding promotion is used status for user
adding promotion is used status for user
This commit is contained in:
parent
ab348d5134
commit
3cef84a346
6 changed files with 213 additions and 5 deletions
|
|
@ -135,6 +135,18 @@ class Plan(BaseModel):
|
|||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def get_configured_promotion_amount(self):
|
||||
amount = 0
|
||||
for recipient in self.recipients.filter(recipient_uuid_field='->event:user'): #TODO: instead of hard code use better soloution
|
||||
base_amount, promotion_amount = recipient.get_promotion_amount()
|
||||
amount += promotion_amount
|
||||
return amount
|
||||
|
||||
def is_promotion_used_by(self, user_uuid):
|
||||
return self.promotions.filter(user_uuid=user_uuid).exclude(
|
||||
state__in=[PaymentStateChoices.FAILED, PaymentStateChoices.EXPECTED_FAILURE]
|
||||
).exists()
|
||||
|
||||
def process_event(self, event=None):
|
||||
# TODO: processor
|
||||
return self.promote_all(event=event)
|
||||
|
|
@ -343,6 +355,23 @@ class PromotionQuerySet(models.QuerySet):
|
|||
def used(self, user):
|
||||
return self.filter(user=user, state__in=[Promotion.PaymentStateChoices.CREATED])
|
||||
|
||||
def is_processed_for_user(self, user_uuid, event_label):
|
||||
return self.filter(user_uuid=user_uuid, event__label=event_label).exists()
|
||||
|
||||
|
||||
def get_event_status_for_user(user_uuid, event_label):
|
||||
processed = Promotion.objects.is_processed_for_user(user_uuid, event_label)
|
||||
promotion_amount = None
|
||||
if not processed:
|
||||
plan = Plan.objects.filter(event_list__icontains=event_label).first()
|
||||
if plan:
|
||||
promotion_amount = plan.get_configured_promotion_amount()
|
||||
return {
|
||||
'event_label': event_label,
|
||||
'processed': processed,
|
||||
'promotion_amount': promotion_amount,
|
||||
}
|
||||
|
||||
|
||||
class Promotion(BaseModel):
|
||||
user_uuid = models.UUIDField() # user_uuid
|
||||
|
|
|
|||
|
|
@ -84,6 +84,12 @@ class UserRecipientSerializer(serializers.ModelSerializer):
|
|||
"base_amount_field"]
|
||||
|
||||
|
||||
class PromotionStatusSerializer(serializers.Serializer):
|
||||
event_label = serializers.CharField(read_only=True)
|
||||
processed = serializers.BooleanField(read_only=True)
|
||||
promotion_amount = serializers.IntegerField(read_only=True, allow_null=True)
|
||||
|
||||
|
||||
class UserPlanSerializer(serializers.ModelSerializer):
|
||||
recipients = UserRecipientSerializer(many=True, read_only=True)
|
||||
class Meta:
|
||||
|
|
|
|||
|
|
@ -719,3 +719,111 @@ class ApplicationApiFlowsTests(APITestCase):
|
|||
response = self.client.post(url_promote, event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
||||
print(response.json())
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def _create_first_ad_create_plan(self):
|
||||
event_label = 'ads::first-ad-create'
|
||||
plan_title = 'first-ad-create'
|
||||
promotion_amount = 30000
|
||||
|
||||
EventSaver.objects.get_or_create(
|
||||
event_label=event_label,
|
||||
defaults={
|
||||
'title': plan_title,
|
||||
'save_once': True,
|
||||
},
|
||||
)
|
||||
|
||||
plan, _ = Plan.objects.get_or_create(
|
||||
title=plan_title,
|
||||
defaults={
|
||||
'balance': promotion_amount * 1000,
|
||||
'user': self.user_1,
|
||||
'application': self.application,
|
||||
'event_list': [event_label],
|
||||
'processor': ProcessorTypeChoices.OTHERS,
|
||||
},
|
||||
)
|
||||
|
||||
Recipient.objects.update_or_create(
|
||||
label=plan_title,
|
||||
plan=plan,
|
||||
defaults={
|
||||
'recipient_uuid_field': '->event:user',
|
||||
'base_amount_field': str(promotion_amount),
|
||||
},
|
||||
)
|
||||
|
||||
return plan, event_label, promotion_amount
|
||||
|
||||
def test_first_ad_create_event_status_not_processed(self):
|
||||
plan, event_label, promotion_amount = self._create_first_ad_create_plan()
|
||||
auth = self._create_authorization_header(self.user_access_token.token)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('promotions:event-status', kwargs={'event_label': event_label}),
|
||||
HTTP_AUTHORIZATION=auth,
|
||||
format='json',
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {
|
||||
'event_label': event_label,
|
||||
'processed': False,
|
||||
'promotion_amount': promotion_amount,
|
||||
})
|
||||
|
||||
def test_first_ad_create_event_status_processed(self):
|
||||
plan, event_label, promotion_amount = self._create_first_ad_create_plan()
|
||||
auth = self._create_authorization_header(self.user_access_token.token)
|
||||
event_create_data = {
|
||||
'label': event_label,
|
||||
'data': {
|
||||
'user': str(self.user_1_uuid),
|
||||
},
|
||||
}
|
||||
|
||||
response = self.client.post(
|
||||
reverse('promotions:promotion-create', kwargs={'plan': plan.uuid}),
|
||||
event_create_data,
|
||||
HTTP_AUTHORIZATION=auth,
|
||||
format='json',
|
||||
)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('promotions:event-status', kwargs={'event_label': event_label}),
|
||||
HTTP_AUTHORIZATION=auth,
|
||||
format='json',
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {
|
||||
'event_label': event_label,
|
||||
'processed': True,
|
||||
'promotion_amount': None,
|
||||
})
|
||||
plan.refresh_from_db()
|
||||
self.assertEqual(plan.balance, promotion_amount * 1000 - promotion_amount)
|
||||
|
||||
def test_first_ad_create_event_status_not_processed_when_only_event_exists(self):
|
||||
plan, event_label, promotion_amount = self._create_first_ad_create_plan()
|
||||
auth = self._create_authorization_header(self.user_access_token.token)
|
||||
Event.objects.create(
|
||||
user=self.user_1_uuid,
|
||||
application=self.application,
|
||||
label=event_label,
|
||||
data={'user': str(self.user_1_uuid)},
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('promotions:event-status', kwargs={'event_label': event_label}),
|
||||
HTTP_AUTHORIZATION=auth,
|
||||
format='json',
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {
|
||||
'event_label': event_label,
|
||||
'processed': False,
|
||||
'promotion_amount': promotion_amount,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@ from utils.router import ProfileRouter
|
|||
from . import views_user
|
||||
from django.urls import NoReverseMatch, path, re_path, include
|
||||
|
||||
from .views_user import ApplicationPromoteUserApiView, ApplicationEventSubmitAPIView, ApplicationPromotionListApiView
|
||||
from .views_user import (
|
||||
ApplicationPromoteUserApiView,
|
||||
ApplicationEventSubmitAPIView,
|
||||
ApplicationPromotionListApiView,
|
||||
ApplicationEventStatusApiView,
|
||||
)
|
||||
|
||||
app_name = 'promotions'
|
||||
|
||||
|
|
@ -19,6 +24,7 @@ router.register(r'plans', views_user.UserPlanViewSet, basename='user-plans')
|
|||
urlpatterns = [
|
||||
path('api/v1/plans/<plan>/', ApplicationPromoteUserApiView.as_view(), name='promotion-create'),
|
||||
path('api/v1/plans/<plan>/promotins', ApplicationPromotionListApiView.as_view(), name='promotion-list'),
|
||||
path('api/v1/events/<path:event_label>/status/', ApplicationEventStatusApiView.as_view(), name='event-status'),
|
||||
path('api/v1/events/submit', ApplicationEventSubmitAPIView.as_view(), name='event-create'),
|
||||
# path('api/v1/events/get', ApplicationEventRetrieveAPIView.as_view(), name='event-retrieve'),
|
||||
path('api/v2/', include(router.urls)),
|
||||
|
|
|
|||
|
|
@ -12,8 +12,15 @@ from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRe
|
|||
from apps.gooyal_oauth2.utils import get_application
|
||||
from utils.clients.accounts_client import get_user_info
|
||||
from utils.exceptions import UnprocessableEntity
|
||||
from .models import Plan, Promotion, EventSaver
|
||||
from .serializers import PlanSerializer, PromotionSerializer, EventSerializer, PromoteSerializer, UserPlanSerializer
|
||||
from .models import Plan, Promotion, EventSaver, get_event_status_for_user
|
||||
from .serializers import (
|
||||
PlanSerializer,
|
||||
PromotionSerializer,
|
||||
EventSerializer,
|
||||
PromoteSerializer,
|
||||
UserPlanSerializer,
|
||||
PromotionStatusSerializer,
|
||||
)
|
||||
from .tasks import analyze_event_task
|
||||
from ..users.models import User
|
||||
from django.utils.translation import gettext as _
|
||||
|
|
@ -105,8 +112,38 @@ class ApplicationEventViewSet(
|
|||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||
required_alternate_scopes = {
|
||||
"POST": [["promotions.application.event:submit"]],
|
||||
"GET": [["promotions.application.event:submit"]],
|
||||
}
|
||||
|
||||
def _resolve_user(self):
|
||||
user = getattr(self.request, 'user', None)
|
||||
if user and getattr(user, 'uuid', None):
|
||||
return user
|
||||
|
||||
user_uuid = self.kwargs.get('user_uuid')
|
||||
user = User.objects.filter(uuid=user_uuid).first()
|
||||
if not user:
|
||||
user_info = get_user_info(user_uuid)
|
||||
if user_info:
|
||||
user, _ = User.objects.get_or_create(uuid=user_uuid)
|
||||
|
||||
if not user:
|
||||
raise NotFound(_('user {user_uuid} not found').format(user_uuid=user_uuid))
|
||||
|
||||
self.request.user = user
|
||||
return user
|
||||
|
||||
@action(
|
||||
detail=False,
|
||||
methods=['GET'],
|
||||
url_path=r'(?P<event_label>.+)/status',
|
||||
serializer_class=PromotionStatusSerializer,
|
||||
)
|
||||
def status(self, request, user_uuid=None, event_label=None):
|
||||
user = self._resolve_user()
|
||||
serializer = self.get_serializer(get_event_status_for_user(user.uuid, event_label))
|
||||
return Response(serializer.data)
|
||||
|
||||
def perform_create(self, serializer: EventSerializer):
|
||||
if hasattr(self.request, 'user'):
|
||||
user = self.request.user
|
||||
|
|
|
|||
|
|
@ -9,8 +9,15 @@ from rest_framework.viewsets import GenericViewSet
|
|||
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
||||
from apps.gooyal_oauth2.utils import get_application
|
||||
from utils.exceptions import UnprocessableEntity
|
||||
from .models import Plan, Promotion, EventSaver
|
||||
from .serializers import PlanSerializer, PromotionSerializer, EventSerializer, PromoteSerializer, UserPlanSerializer
|
||||
from .models import Plan, Promotion, EventSaver, get_event_status_for_user
|
||||
from .serializers import (
|
||||
PlanSerializer,
|
||||
PromotionSerializer,
|
||||
EventSerializer,
|
||||
PromoteSerializer,
|
||||
UserPlanSerializer,
|
||||
PromotionStatusSerializer,
|
||||
)
|
||||
from .tasks import analyze_event_task
|
||||
from ..users.models import User
|
||||
|
||||
|
|
@ -107,6 +114,21 @@ class ApplicationPromoteUserApiView(CreateAPIView):
|
|||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||
|
||||
class ApplicationEventStatusApiView(RetrieveAPIView):
|
||||
serializer_class = PromotionStatusSerializer
|
||||
|
||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||
required_alternate_scopes = {
|
||||
"GET": [[]],
|
||||
}
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
event_label = kwargs['event_label']
|
||||
user_uuid = request.user.uuid
|
||||
serializer = self.get_serializer(get_event_status_for_user(user_uuid, event_label))
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class ApplicationPromotionListApiView(ListAPIView):
|
||||
model = Promotion
|
||||
serializer_class = PromotionSerializer
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue