diff --git a/source/RewardsService/rewardsservice/handlers/customer_rewards_handler.py b/source/RewardsService/rewardsservice/handlers/customer_rewards_handler.py new file mode 100644 index 00000000..724796ca --- /dev/null +++ b/source/RewardsService/rewardsservice/handlers/customer_rewards_handler.py @@ -0,0 +1,158 @@ +import json +from bson import ObjectId +from pymongo import ASCENDING, DESCENDING, MongoClient + +from tornado.escape import json_encode, json_decode +from tornado.gen import coroutine +import tornado.web + + +class CustomerRewardsHandler(tornado.web.RequestHandler): + + def initialize(self): + self.client = MongoClient("mongodb", 27017) + self.db = self.client['Rewards'] + + @staticmethod + def _calculate_points(amount, available_points=0): + """ + Method to return calculated points based on user available points and amount i.e. $1 = 1 points. + + Args: + amount (float): order total amount. + available_points (int): user available points + + Returns: + It returns calculated reward points based on user available points and amount. + """ + return available_points + int(amount or 0) + + def _calculate_progress(self, points): + """ + Method to calculate user reward program progress status. It will return completed percent of user reward program + between previous and next reward program. + + Args: + points (int): user aggregated rewards points + + Returns: + It returns user reward program completed percentage. + """ + top_reward_prg = self.get_top_reward() + if points > top_reward_prg.get("points"): + return 100 + curr_reward_prg = self.get_current_reward_program(points) + nxt_reward_prg = self.get_next_reward_program(points) + diff = nxt_reward_prg.get("points") - curr_reward_prg.get("points", 0) + rem = points % 100 if points > 100 else points + return round((rem/diff) * 100, 2) + + def get_customer_available_points(self, email): + """ + Method to get customer available points. It will do aggregation query to get customer total earned reward points. + + Args: + email (char): aggregate customer reward points based on unique email id. + + Returns: + It returns customer available reward points. + """ + customer = self.db.customers.aggregate([ + { + "$group": { + "_id": "$emailId", + "totalPoints": {"$sum": "$earnedPoints"}, + }, + }, + { + "$match": {"_id": email} + } + ]) + customer = list(customer) + return customer[0].get("totalPoints") if customer else 0 + + def get_top_reward(self): + """ + Method to get top reward program from rewards collection based on highest points document. + + Returns: + It return reward program document in dict format. + """ + return self.db.rewards.find_one({}, sort=[("points", DESCENDING)]) or {} + + def get_current_reward_program(self, points): + """ + Method to return current reward program document based on the customer accumulated points. + + Returns: + It return reward program document in dict format. + """ + top_reward_prg = self.get_top_reward() + if points > top_reward_prg.get("points"): + return top_reward_prg + lower_bound = points - 100 + return self.db.rewards.find_one({ + "$and": [{"points": {"$gt": lower_bound}}, {"points": {"$lte": points}}] + }) or {} + + def get_next_reward_program(self, points): + """ + Method to return next available reward program document based on the customer accumulated points. + + Returns: + It return reward program document in dict format. + """ + top_reward_prg = self.get_top_reward() + if points > top_reward_prg.get("points"): + return top_reward_prg + upper_bound = points + 100 + return self.db.rewards.find_one({ + "$and": [{"points": {"$gt": points}}, {"points": {"$lte": upper_bound}}] + }) or {} + + @coroutine + def post(self): + try: + customer = json_decode(self.request.body) + available_points = self.get_customer_available_points(customer.get("emailId")) + top_reward_prg = self.get_top_reward() + # Once customer has reached top rewards tier, there are no more rewards the customer can earn. + if available_points < top_reward_prg.get("points"): + points = self._calculate_points(customer.get("orderTotal"), available_points) + curr_reward_prg = self.get_current_reward_program(points) + nxt_reward_prg = self.get_next_reward_program(points) + customer.update({ + "_id": str(ObjectId()), + "earnedPoints": int(customer.get("orderTotal") or 0), + "points": points, + "tier": curr_reward_prg.get("tier"), + "rewardName": curr_reward_prg.get("rewardName"), + "nextTier": nxt_reward_prg.get("tier"), + "nextRewardName": nxt_reward_prg.get("rewardName"), + "nextRewardTierAwayPercentage": (100 - self._calculate_progress(points)), # Away from next program + }) + + del customer["orderTotal"] + self.db.customers.insert_one(customer) + self.set_status(201) + self.write(json_encode({"message": "Customer rewards created successfully!"})) + else: + self.set_status(200) + self.write(json_encode({"message": "You cann't earn more rewards as you reach to the top."})) + except Exception as e: + err_msg = "Error while creating customer rewards: {}".format(e) + self.set_status(500) + self.write(json_encode({"message": err_msg})) + + @coroutine + def get(self): + try: + email = self.get_argument("email", None, True) + condition = {"emailId": email} if email else {} + customers = list(self.db.customers.find(condition).sort([("emailId", ASCENDING), ("points", ASCENDING)])) + self.set_status(200) + self.write(json.dumps(customers)) + except Exception as e: + err_msg = "Error while getting customer rewards: {}".format(e) + self.set_status(500) + self.write(json_encode({"message": err_msg})) diff --git a/source/RewardsService/rewardsservice/url_patterns.py b/source/RewardsService/rewardsservice/url_patterns.py index 55e471d6..2e877f34 100644 --- a/source/RewardsService/rewardsservice/url_patterns.py +++ b/source/RewardsService/rewardsservice/url_patterns.py @@ -1,5 +1,7 @@ +from handlers.customer_rewards_handler import CustomerRewardsHandler from handlers.rewards_handler import RewardsHandler url_patterns = [ (r'/rewards', RewardsHandler), + (r'/customers', CustomerRewardsHandler), ] diff --git a/source/RewardsUI/rewards/clients/rewards_service_client.py b/source/RewardsUI/rewards/clients/rewards_service_client.py index 70b56545..6043f927 100644 --- a/source/RewardsUI/rewards/clients/rewards_service_client.py +++ b/source/RewardsUI/rewards/clients/rewards_service_client.py @@ -1,3 +1,4 @@ +import json import requests @@ -5,7 +6,19 @@ class RewardsServiceClient: def __init__(self): self.rewards_url = "http://rewardsservice:7050/rewards" + self.customer_rewards_url = "http://rewardsservice:7050/customers" def get_rewards(self): response = requests.get(self.rewards_url) return response.json() + + def add_order(self, payload): + response = requests.post(self.customer_rewards_url, data=json.dumps(payload)) + return response.json() + + def get_customer_rewards(self, email=None): + url = self.customer_rewards_url + if email: + url = url + "?email={}".format(email) + response = requests.get(url) + return response.json() diff --git a/source/RewardsUI/rewards/forms.py b/source/RewardsUI/rewards/forms.py new file mode 100644 index 00000000..06fbd407 --- /dev/null +++ b/source/RewardsUI/rewards/forms.py @@ -0,0 +1,6 @@ +from django import forms + + +class AddRewardsForm(forms.Form): + email = forms.EmailField() + total = forms.FloatField() diff --git a/source/RewardsUI/rewards/index.html b/source/RewardsUI/rewards/index.html index cdab1ee4..072eba0d 100644 --- a/source/RewardsUI/rewards/index.html +++ b/source/RewardsUI/rewards/index.html @@ -23,16 +23,23 @@

Reward Tiers

Add orders

-
-
- + + {% csrf_token %} + +
+ + +

User Rewards

-
- + + {% csrf_token %} + + +
@@ -47,15 +54,17 @@

User Rewards

- - - - - - - - - + {% for customer in customers_data %} + + + + + + + + + + {% endfor %}
customer01@gmail.com100A5% off purchaseB10% off purchase50%
{{customer.emailId}}{{customer.points}}{{customer.tier}}{{customer.rewardName}}{{customer.nextTier}}{{customer.nextRewardName}}{{customer.nextRewardTierAwayPercentage}}
diff --git a/source/RewardsUI/rewards/views.py b/source/RewardsUI/rewards/views.py index 1d5dd537..4993646f 100644 --- a/source/RewardsUI/rewards/views.py +++ b/source/RewardsUI/rewards/views.py @@ -1,8 +1,12 @@ import logging +from django.utils.http import urlencode +from django.urls import reverse +from django.shortcuts import redirect from django.template.response import TemplateResponse from django.views.generic.base import TemplateView +from rewards.forms import AddRewardsForm from rewards.clients.rewards_service_client import RewardsServiceClient @@ -17,10 +21,31 @@ def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) rewards_data = self.rewards_service_client.get_rewards() - context['rewards_data'] = rewards_data + email = request.GET.get("email", "") + customers_data = self.rewards_service_client.get_customer_rewards(email=email) + context.update({ + "rewards_data": rewards_data, + "customers_data": customers_data, + "user_email": email, + }) return TemplateResponse( request, self.template_name, context - ) \ No newline at end of file + ) + + def post(self, request, *args, **kwargs): + email = request.POST.get("user_email") + if email: + parameters = urlencode({"email": email}) + return redirect("{}?{}".format(reverse("rewards"), parameters)) + form = AddRewardsForm(request.POST) + if form.is_valid(): + data = { + "emailId": form.cleaned_data["email"], + "orderTotal": form.cleaned_data["total"], + } + self.rewards_service_client.add_order(data) + return redirect(reverse("rewards")) + return redirect(reverse("rewards"))