From d328cb7980f6127a8792d78e7628fa8338bd476d Mon Sep 17 00:00:00 2001 From: Shiv K Sah Date: Fri, 2 Jun 2017 21:34:47 +0530 Subject: [PATCH 1/5] Denomination bracket --- cloud/endagaweb/forms/dashboard_forms.py | 2 +- cloud/endagaweb/models.py | 20 ++ .../network_detail/denomination.html | 305 ++++++++++++++++++ .../dashboard/network_detail/nav.html | 7 +- cloud/endagaweb/tests/test_denomination.py | 107 ++++++ cloud/endagaweb/urls.py | 3 + cloud/endagaweb/views/django_tables.py | 51 +++ cloud/endagaweb/views/network.py | 194 ++++++++++- 8 files changed, 686 insertions(+), 3 deletions(-) create mode 100644 cloud/endagaweb/templates/dashboard/network_detail/denomination.html create mode 100644 cloud/endagaweb/tests/test_denomination.py diff --git a/cloud/endagaweb/forms/dashboard_forms.py b/cloud/endagaweb/forms/dashboard_forms.py index 2f9a6ddc..963616cc 100644 --- a/cloud/endagaweb/forms/dashboard_forms.py +++ b/cloud/endagaweb/forms/dashboard_forms.py @@ -367,4 +367,4 @@ def __init__(self, *args, **kwargs): self.helper.form_method = 'post' self.helper.form_action = '/dashboard/staff/tower-monitoring' self.helper.add_input(Submit('submit', 'Select')) - self.helper.layout = Layout('tower') + self.helper.layout = Layout('tower') \ No newline at end of file diff --git a/cloud/endagaweb/models.py b/cloud/endagaweb/models.py index d2429f9d..141ad542 100644 --- a/cloud/endagaweb/models.py +++ b/cloud/endagaweb/models.py @@ -1460,6 +1460,26 @@ def create_ledger(sender, instance, created, **kwargs): post_save.connect(Network.create_billing_tiers, sender=Network) +class NetworkDenomination(models.Model): + """Network has its own denomination bracket for rechange and validity + + Subscriber status depends on recharge under denomination bracket + """ + start_amount = models.BigIntegerField() + end_amount = models.BigIntegerField() + validity_days = models.PositiveIntegerField(blank=True, default=0) + + # The denomination group associated with the network + network = models.ForeignKey('Network', null=True, on_delete=models.CASCADE) + + def __unicode__(self): + return "Amount %s - %d for %s(days)" % ( + self.start_amount, self.end_amount, self.validity_days) + + class Meta: + ordering = ('start_amount',) + + class ConfigurationKey(models.Model): """A key->value mapping for storing settings. diff --git a/cloud/endagaweb/templates/dashboard/network_detail/denomination.html b/cloud/endagaweb/templates/dashboard/network_detail/denomination.html new file mode 100644 index 00000000..2101bf1a --- /dev/null +++ b/cloud/endagaweb/templates/dashboard/network_detail/denomination.html @@ -0,0 +1,305 @@ +{% extends "dashboard/layout.html" %} +{% comment %} +Copyright (c) 2016-present, Facebook, Inc. +All rights reserved. + +This source code is licensed under the BSD-style license found in the +LICENSE file in the root directory of this source tree. An additional grant +of patent rights can be found in the PATENTS file in the same directory. +{% endcomment %} +{% load apptags %} +{% load humanize %} +{% load crispy_forms_tags %} +{% load render_table from django_tables2 %} + + +{% block title %} + {% if network.name %} + {% tmpl_const "SITENAME" %} | "{{ network.name }}" + {% else %} + {% tmpl_const "SITENAME" %} | Network + {% endif %} +{% endblock %} + +{% block pagestyle %} + +{% endblock %} + +{% block content %} + {% include "dashboard/network_detail/header.html" with network=network %} + +
+ {% include "dashboard/network_detail/nav.html" with active_tab='network-denominations' %} + +
+ {% for message in messages %} +
+ × + {{ message }} +
+ {% endfor %} +
+ {% if denomination %} + {% render_table denominations_table %} + {% else %} +

There are currently no denominations associated with this network.

+ {% endif %} +
+
+ {% if user_profile.user.is_staff %} +
+
+
Create Denomination
+
+
+
{% csrf_token %} +
+
+ +
+ +
+
+
+ +
+
+ +
+ +
+
+
+ +
+
+ +
+ +
+
+
+ +
+
+
+ + + +
+
+
+
+
+
+
+
+ {% endif %} + +
+ + + +{% endblock %} +{% block js %} + + + + +{% endblock %} diff --git a/cloud/endagaweb/templates/dashboard/network_detail/nav.html b/cloud/endagaweb/templates/dashboard/network_detail/nav.html index 80a0705a..d2c2d396 100644 --- a/cloud/endagaweb/templates/dashboard/network_detail/nav.html +++ b/cloud/endagaweb/templates/dashboard/network_detail/nav.html @@ -23,6 +23,12 @@ {% endif %} ">Prices {% endcomment %} + - diff --git a/cloud/endagaweb/tests/test_denomination.py b/cloud/endagaweb/tests/test_denomination.py new file mode 100644 index 00000000..d797824f --- /dev/null +++ b/cloud/endagaweb/tests/test_denomination.py @@ -0,0 +1,107 @@ +"""Tests for models.Users. + +Copyright (c) 2016-present, Facebook, Inc. +All rights reserved. + +This source code is licensed under the BSD-style license found in the +LICENSE file in the root directory of this source tree. An additional grant +of patent rights can be found in the PATENTS file in the same directory. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from datetime import datetime +from random import randrange +import uuid +from django import test +import json + +import pytz + +from django.test import TestCase + +from ccm.common import crdt +from endagaweb import models + + +class TestBase(TestCase): + + @classmethod + def setUpClass(cls): + cls.username = 'y' + cls.password = 'pw' + cls.user = models.User(username=cls.username, email='y@l.com') + cls.user.set_password(cls.password) + cls.user.save() + cls.user_profile = models.UserProfile.objects.get(user=cls.user) + + cls.uuid = "59216199-d664-4b7a-a2db-6f26e9a5d208" + + # Create a test client. + cls.client = test.Client() + + @classmethod + def tearDownClass(cls): + cls.user.delete() + cls.user_profile.delete() + + def tearDown(self): + self.logout() + + def login(self): + """Log the client in.""" + data = { + 'email': self.username, + 'password': self.password, + } + self.client.post('/auth/', data) + + def logout(self): + """Log the client out.""" + self.client.get('/logout') + + +class DenominationUITest(TestBase): + """Testing that we can add User in the UI.""" + + def test_add_denominaton(self): + self.logout() + response = self.client.get('/dashboard/network/denominations') + # Anonymous User can not see this page so returning permission denied. + self.assertEqual(302, response.status_code) + + def test_add_denominaton_auth(self): + self.login() + response = self.client.get('/dashboard/network/denominations') + self.assertEqual(200, response.status_code) + + def test_delete_denominaton(self): + self.logout() + response = self.client.delete('/dashboard/network/denominations') + # Anonymous User can not see this page so returning permission denied. + self.assertEqual(302, response.status_code) + + def test_delete_denominaton_auth(self): + self.login() + response = self.client.delete('/dashboard/network/denominations') + self.assertEqual(200, response.status_code) + + def test_post_add_denominaton(self): + self.logout() + data = {} + response = self.client.post('/dashboard/network/denominations', data) + # Anonymous User can not see this page so returning permission denied. + self.assertEqual(302, response.status_code) + + def test_post_add_denominaton_auth(self): + self.login() + data = { + 'start_amount': 1, + 'end_amount': 2, + 'validity_days': 3 + } + response = self.client.post('/dashboard/network/denominations', data) + self.assertEqual(302, response.status_code) diff --git a/cloud/endagaweb/urls.py b/cloud/endagaweb/urls.py index 4c7d2469..a44fe086 100644 --- a/cloud/endagaweb/urls.py +++ b/cloud/endagaweb/urls.py @@ -142,6 +142,9 @@ url(r'^dashboard/network/prices$', endagaweb.views.network.NetworkPrices.as_view(), name='network-prices'), + url(r'^dashboard/network/denominations$', + endagaweb.views.network.NetworkDenomination.as_view(), + name='network-denominations'), url(r'^dashboard/network/inactive-subscribers$', endagaweb.views.network.NetworkInactiveSubscribers.as_view(), name='network-inactive-subscribers'), diff --git a/cloud/endagaweb/views/django_tables.py b/cloud/endagaweb/views/django_tables.py index 7c02bca2..83484d97 100644 --- a/cloud/endagaweb/views/django_tables.py +++ b/cloud/endagaweb/views/django_tables.py @@ -314,3 +314,54 @@ def render_actions(self, record): " id='%s'>release") element = template % record.number return safestring.mark_safe(element) + + +class DenominationListTable(tables.Table): + """A django-tables2 Table definition for the table list.""" + + class Meta: + model = models.NetworkDenomination + fields = ('start_amount', 'end_amount', 'validity_days') + attrs = {'class': 'table table-hover'} + + start_amount = tables.Column(empty_values=(), verbose_name='Start Amount') + end_amount = tables.Column(empty_values=(), verbose_name='End Amount') + validity_days = tables.Column(empty_values=(), verbose_name='Validity(Days)') + + def render_start_amount(self, record): + return humanize_credits(record.start_amount, + CURRENCIES[record.network.subscriber_currency]) + + def render_end_amount(self, record): + return humanize_credits(record.end_amount, + CURRENCIES[record.network.subscriber_currency]) + + +class DenominationTable(tables.Table): + """A django-tables2 Table definition for the table list.""" + + class Meta: + model = models.NetworkDenomination + fields = ('start_amount', 'end_amount', 'validity_days') + attrs = {'class': 'table table-hover'} + + start_amount = tables.Column(empty_values=(), verbose_name='Start Amount') + end_amount = tables.Column(empty_values=(), verbose_name='End Amount') + validity_days = tables.Column(empty_values=(), verbose_name='Validity(Days)') + action = tables.Column(empty_values=(), verbose_name='Action', orderable=False) + + def render_start_amount(self, record): + return humanize_credits(record.start_amount, + CURRENCIES[record.network.subscriber_currency]) + + def render_end_amount(self, record): + return humanize_credits(record.end_amount, + CURRENCIES[record.network.subscriber_currency]) + + def render_action(self, record): + """Shows the edit and delete button.""" + element = "Edit   " % (record.id, record.id) + element += "Delete" % (record.id) + return safestring.mark_safe(element) \ No newline at end of file diff --git a/cloud/endagaweb/views/network.py b/cloud/endagaweb/views/network.py index bf0db230..4b42123c 100644 --- a/cloud/endagaweb/views/network.py +++ b/cloud/endagaweb/views/network.py @@ -10,6 +10,7 @@ import datetime import time +import json from django import http from django import template @@ -21,7 +22,8 @@ import django_tables2 as tables from guardian.shortcuts import get_objects_for_user -from ccm.common.currency import parse_credits, CURRENCIES, DEFAULT_CURRENCY +from ccm.common.currency import parse_credits, humanize_credits, \ + CURRENCIES, DEFAULT_CURRENCY from endagaweb import models from endagaweb.forms import dashboard_forms from endagaweb.views.dashboard import ProtectedView @@ -453,3 +455,193 @@ def get(self, request, network_id): user_profile.network = network user_profile.save() return http.HttpResponseRedirect(request.META.get('HTTP_REFERER', '/dashboard')) + + +class NetworkDenomination(ProtectedView): + """Assign denominations bracket for recharge/adjust-credit in network.""" + + def get(self, request): + """Handles GET requests.""" + user_profile = models.UserProfile.objects.get(user=request.user) + network = user_profile.network + currency = network.subscriber_currency + + # Count the associated denomination with selected network. + denom = models.NetworkDenomination.objects.filter(network=network) + denom_count = denom.count() + + dnm_id = request.GET.get('id', None) + if dnm_id: + response = { + 'status': 'ok', + 'messages': [], + 'data': {} + } + denom = models.NetworkDenomination.objects.get(id=dnm_id) + denom_data = { + 'id': denom.id, + 'start_amount': humanize_credits(denom.start_amount, + CURRENCIES[currency]).amount, + 'end_amount': humanize_credits(denom.end_amount, + CURRENCIES[currency]).amount, + 'validity_days': denom.validity_days + } + response["data"] = denom_data + return http.HttpResponse(json.dumps(response), + content_type="application/json") + + # Configure the table of denominations. Do not show any pagination + # controls if the total number of donominations is small. + if not user_profile.user.is_staff: + denom_table = django_tables.DenominationListTable(list(denom)) + else: + denom_table = django_tables.DenominationTable(list(denom)) + towers_per_page = 8 + paginate = False + if denom > towers_per_page: + paginate = {'per_page': towers_per_page} + tables.RequestConfig(request, paginate=paginate).configure(denom_table) + + # Set the context with various stats. + context = { + 'networks': get_objects_for_user(request.user, 'view_network', + klass=models.Network), + 'currency': CURRENCIES[user_profile.network.subscriber_currency], + 'user_profile': user_profile, + 'network': network, + 'number_country': NUMBER_COUNTRIES[network.number_country], + 'denomination': denom_count, + 'denominations_table': denom_table, + } + # Render template. + info_template = template.loader.get_template( + 'dashboard/network_detail/denomination.html') + html = info_template.render(context, request) + return http.HttpResponse(html) + + def post(self, request): + """Operators can use this API to add denomination to a network. + + These denomination bracket will be used to recharge subscriber, + set balance validity and status + """ + user_profile = models.UserProfile.objects.get(user=request.user) + network = user_profile.network + try: + currency = network.subscriber_currency + start_amount_raw = request.POST.get('start_amount') + start_amount = parse_credits(start_amount_raw, + CURRENCIES[currency]).amount_raw + end_amount_raw = request.POST.get('end_amount') + end_amount = parse_credits(end_amount_raw, + CURRENCIES[currency]).amount_raw + validity_days = int(request.POST.get('validity_days')) or 0 + if validity_days > 10000: + validity_days = 10000 + dnm_id = int(request.POST.get('dnm_id')) or 0 + if start_amount <= 0 or end_amount <= 0: + messages.error(request, 'Enter positive and non-zero value ' \ + 'for start/end amount.', + extra_tags='alert alert-danger') + return redirect(urlresolvers.reverse('network-denominations')) + elif validity_days <= 0: + messages.error( + request, 'Validity can not be 0 day.', + extra_tags='alert alert-danger') + return redirect(urlresolvers.reverse('network-denominations')) + elif end_amount <= start_amount: + messages.error( + request, 'End amount should be greater than start amount.', + extra_tags='alert alert-danger') + return redirect(urlresolvers.reverse('network-denominations')) + + user_profile = models.UserProfile.objects.get(user=request.user) + with transaction.atomic(): + if dnm_id > 0: + try: + denom = models.NetworkDenomination.objects.get( + id=dnm_id) + # Check for existing denomination range exist. + denom_exists = models.NetworkDenomination.objects.\ + filter( + end_amount__gte=start_amount, + start_amount__lte=end_amount, + network=user_profile.network).\ + exclude(id=dnm_id).count() + if denom_exists: + messages.error( + request, 'Denomination range already exists.', + extra_tags='alert alert-danger') + return redirect( + urlresolvers.reverse('network-denominations')) + denom.network = user_profile.network + denom.start_amount = start_amount + denom.end_amount = end_amount + denom.validity_days = validity_days + denom.save() + messages.success( + request, 'Denomination is updated successfully.', + extra_tags='alert alert-success') + except models.NetworkDenomination.DoesNotExist: + messages.error( + request, 'Invalid denomination ID.', + extra_tags='alert alert-danger') + return redirect( + urlresolvers.reverse('network-denominations')) + else: + # Check for existing denomination range exist. + denom_exists = models.NetworkDenomination.objects.filter( + end_amount__gte=start_amount, + start_amount__lte=end_amount, + network=user_profile.network).count() + if denom_exists: + messages.error( + request, 'Denomination range already exists.', + extra_tags='alert alert-danger') + return redirect( + urlresolvers.reverse('network-denominations')) + # Create new denomination for selected network + denom = models.NetworkDenomination( + network=user_profile.network) + denom.network = user_profile.network + denom.start_amount = start_amount + denom.end_amount = end_amount + denom.validity_days = validity_days + denom.save() + messages.success( + request, 'Denomination is created successfully.', + extra_tags='alert alert-success') + except Exception: + messages.error( + request, 'Invalid validity value. Enter greater than ' \ + '0 digit value', + extra_tags='alert alert-danger') + return redirect(urlresolvers.reverse('network-denominations')) + + def delete(self, request): + """Handles delete requests.""" + response = { + 'status': 'ok', + 'messages': [], + } + dnm_id = request.GET.get('id') or False + if dnm_id: + try: + denom = models.NetworkDenomination.objects.get(id=dnm_id) + denom.delete() + response['status'] = 'success' + messages.success(request, + 'Denomination deleted successfully.', + extra_tags='alert alert-success') + except models.NetworkDenomination.DoesNotExist: + response['status'] = 'failed' + messages.error( + request, 'Invalid denomination ID.', + extra_tags='alert alert-danger') + else: + response['status'] = 'failed' + messages.error( + request, 'Invalid request data.', + extra_tags='alert alert-danger') + return http.HttpResponse(json.dumps(response), + content_type="application/json") \ No newline at end of file From a546b8a73f4c80f8844deb2215502a4a9e89e16b Mon Sep 17 00:00:00 2001 From: Shiv K Sah Date: Mon, 5 Jun 2017 18:38:53 +0530 Subject: [PATCH 2/5] subscriber adjust credit - validate denomination, max credit limit etc --- cloud/endagaweb/models.py | 4 + .../subscriber_detail/adjust_credit.html | 4 +- cloud/endagaweb/views/dashboard.py | 92 ++++++++++++------- 3 files changed, 68 insertions(+), 32 deletions(-) diff --git a/cloud/endagaweb/models.py b/cloud/endagaweb/models.py index 141ad542..90f99f83 100644 --- a/cloud/endagaweb/models.py +++ b/cloud/endagaweb/models.py @@ -883,6 +883,7 @@ class PendingCreditUpdate(models.Model): date = models.DateTimeField(auto_now_add=True) subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE) amount = models.BigIntegerField() + valid_through = models.DateTimeField(null=True, blank=True) uuid = models.TextField() def __unicode__(self): @@ -978,6 +979,9 @@ class Network(models.Model): # Network environments let you specify things like "prod", "test", "dev", # etc so they can be filtered out of alerts. For internal use. environment = models.TextField(default="default") + # Added for Network Balance Limit + max_account_limit = models.BigIntegerField(default=10000) + max_failure_transaction = models.IntegerField(default=3) class Meta: permissions = ( diff --git a/cloud/endagaweb/templates/dashboard/subscriber_detail/adjust_credit.html b/cloud/endagaweb/templates/dashboard/subscriber_detail/adjust_credit.html index b2078ef7..ad5e2b57 100644 --- a/cloud/endagaweb/templates/dashboard/subscriber_detail/adjust_credit.html +++ b/cloud/endagaweb/templates/dashboard/subscriber_detail/adjust_credit.html @@ -29,7 +29,9 @@
{% for message in messages %} -
{{ message }}
+
+ ×{{ message }} +
{% endfor %}
diff --git a/cloud/endagaweb/views/dashboard.py b/cloud/endagaweb/views/dashboard.py index fad4b84e..7df897c2 100644 --- a/cloud/endagaweb/views/dashboard.py +++ b/cloud/endagaweb/views/dashboard.py @@ -28,6 +28,7 @@ from django.views.generic import View from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.db.models import Q +from endagaweb.models import NetworkDenomination import django_tables2 as tables import csv @@ -510,28 +511,30 @@ def get(self, request, imsi=None): try: subscriber = Subscriber.objects.get(imsi=imsi, network=network) + # Set the response context. + pending_updates = subscriber.pendingcreditupdate_set.all().\ + order_by('date') + initial_form_data = { + 'imsi': subscriber.imsi, + } + context = { + 'networks': get_objects_for_user(request.user, + 'view_network', + klass=Network), + 'currency': CURRENCIES[network.subscriber_currency], + 'user_profile': user_profile, + 'subscriber': subscriber, + 'pending_updates': pending_updates, + 'credit_update_form': dform.SubscriberCreditUpdateForm( + initial=initial_form_data), + } + # Render template. + template = get_template( + 'dashboard/subscriber_detail/adjust_credit.html') + html = template.render(context, request) + return HttpResponse(html) except Subscriber.DoesNotExist: - return HttpResponseBadRequest() - # Set the response context. - pending_updates = subscriber.pendingcreditupdate_set.all().order_by( - 'date') - initial_form_data = { - 'imsi': subscriber.imsi, - } - context = { - 'networks': get_objects_for_user(request.user, 'view_network', klass=Network), - 'currency': CURRENCIES[network.subscriber_currency], - 'user_profile': user_profile, - 'subscriber': subscriber, - 'pending_updates': pending_updates, - 'credit_update_form': dform.SubscriberCreditUpdateForm( - initial=initial_form_data), - } - # Render template. - template = get_template( - 'dashboard/subscriber_detail/adjust_credit.html') - html = template.render(context, request) - return HttpResponse(html) + return dashboard_view(request) def post(self, request, imsi=None): """Operators can use this API to add credit to a subscriber. @@ -553,23 +556,50 @@ def post(self, request, imsi=None): # Validate the input. if 'amount' not in request.POST: return HttpResponseBadRequest() - error_text = 'Error: credit value must be between -10M and 10M.' + error_text = 'Credit value must be between -10M and 10M.' + try: currency = network.subscriber_currency amount = parse_credits(request.POST['amount'], - CURRENCIES[currency]).amount_raw + CURRENCIES[currency]).amount_raw if abs(amount) > 2147483647: + error_text = 'Credit value must be between -10M and 10M.' + raise ValueError(error_text) + if sub.balance + amount > network.max_account_limit: + error_text = 'Error : Crossed Credit Limit.' + raise ValueError(error_text) + try: + # Check for existing denomination range exist. + denom_exists = NetworkDenomination.objects.get( + start_amount__lte=amount, + end_amount__gte=amount, + network=network) + # Update user validity for recharge denomination amount + if denom_exists.validity_days > 0: + now = datetime.datetime.now(pytz.UTC) + expiry_date = now + datetime.timedelta( + days=denom_exists.validity_days) + try: + # Validation suceeded, create a PCU and start the + # update credit task. + msgid = str(uuid.uuid4()) + credit_update = PendingCreditUpdate(subscriber=sub, + uuid=msgid, + amount=amount) + credit_update.valid_through = expiry_date + credit_update.save() + tasks.update_credit.delay(sub.imsi, msgid) + return adjust_credit_redirect + except Number.DoesNotExist: + error_text = 'Subscriber has no number assigned.' + raise ValueError(error_text) + except NetworkDenomination.DoesNotExist: + error_text = 'Credit value must be in denomination range.' raise ValueError(error_text) except ValueError: - messages.error(request, error_text) + messages.error(request, error_text, + extra_tags="alert alert-danger") return adjust_credit_redirect - # Validation suceeded, create a PCU and start the update credit task. - msgid = str(uuid.uuid4()) - credit_update = PendingCreditUpdate(subscriber=sub, uuid=msgid, - amount=amount) - credit_update.save() - tasks.update_credit.delay(sub.imsi, msgid) - return adjust_credit_redirect def delete(self, request, imsi=None): """Handle the deletion of Pending Credit Updates.""" From a46c0ea4bc7d42db3709eed2046e781b600b2908 Mon Sep 17 00:00:00 2001 From: Shiv K Sah Date: Mon, 5 Jun 2017 19:00:36 +0530 Subject: [PATCH 3/5] Update subscriber balance,status on client and cloud --- cloud/endagaweb/tasks.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/cloud/endagaweb/tasks.py b/cloud/endagaweb/tasks.py index 405ca698..425542b6 100644 --- a/cloud/endagaweb/tasks.py +++ b/cloud/endagaweb/tasks.py @@ -30,6 +30,7 @@ from django.core.mail import send_mail from django.template.loader import render_to_string from django.db.models import Avg, Count +from django.db import transaction import django.utils.timezone import requests @@ -213,9 +214,23 @@ def update_credit(self, imsi, update_id): if request.status_code >= 200 and request.status_code < 300: print "update_credit SUCCESS. id=%s, imsi=%s, amount=%s. (%d)" % ( update_id, imsi, update.amount, request.status_code) - update.delete() - bts.mark_active() - bts.save() + with transaction.atomic(): + expiry_date = update.valid_through + update.subscriber.state = 'active' + # Get subscriber's 1st number from some admin number. + num = Number.objects.filter( + subscriber__imsi=update.subscriber.imsi)[0:1].get() + if num.valid_through is None: + num.valid_through = expiry_date + num.save() + elif expiry_date >= num.valid_through: + num.valid_through = expiry_date + num.save() + print "update_credit SUCCESS. id=%s, imsi=%s, amount=%s. (%d)" % ( + update_id, imsi, update.amount, request.status_code) + update.delete() + bts.mark_active() + bts.save() else: message = ("update_credit FAIL. id=%s, imsi=%s, (bts=%s), " "amount=%s. (%d)") From 7de014631ae7dc3bacba3559ff22fb2c782e5137 Mon Sep 17 00:00:00 2001 From: Shiv K Sah Date: Wed, 7 Jun 2017 13:13:31 +0530 Subject: [PATCH 4/5] code review incorporated - Update subscriber validity from denomination --- cloud/endagaweb/models.py | 2 +- cloud/endagaweb/tasks.py | 35 ++++++++++++++++++------------ cloud/endagaweb/views/dashboard.py | 4 ---- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/cloud/endagaweb/models.py b/cloud/endagaweb/models.py index 90f99f83..f1aeb729 100644 --- a/cloud/endagaweb/models.py +++ b/cloud/endagaweb/models.py @@ -525,6 +525,7 @@ class Subscriber(models.Model): # When toggled, this will protect a subsriber from getting "vacuumed." You # can still delete subs with the usual "deactivate" button. prevent_automatic_deactivation = models.BooleanField(default=False) + valid_through = models.DateTimeField(null=True, auto_now_add=True) @classmethod def update_balance(cls, imsi, other_bal): @@ -883,7 +884,6 @@ class PendingCreditUpdate(models.Model): date = models.DateTimeField(auto_now_add=True) subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE) amount = models.BigIntegerField() - valid_through = models.DateTimeField(null=True, blank=True) uuid = models.TextField() def __unicode__(self): diff --git a/cloud/endagaweb/tasks.py b/cloud/endagaweb/tasks.py index 425542b6..281f8d8c 100644 --- a/cloud/endagaweb/tasks.py +++ b/cloud/endagaweb/tasks.py @@ -17,6 +17,7 @@ import os import paramiko import zipfile +import pytz try: # we only import zlib here to check that it is available # (why would it not be?), so we have to disable the 'unused' warning @@ -43,6 +44,7 @@ from endagaweb.models import UsageEvent from endagaweb.models import SystemEvent from endagaweb.models import TimeseriesStat +from endagaweb.models import NetworkDenomination from endagaweb.ic_providers.nexmo import NexmoProvider @@ -212,22 +214,27 @@ def update_credit(self, imsi, update_id): url, params={'jwt': jwt}, timeout=settings.ENDAGA['BTS_REQUEST_TIMEOUT_SECS']) if request.status_code >= 200 and request.status_code < 300: - print "update_credit SUCCESS. id=%s, imsi=%s, amount=%s. (%d)" % ( - update_id, imsi, update.amount, request.status_code) with transaction.atomic(): - expiry_date = update.valid_through + # Check for existing denomination range exist. + denom = NetworkDenomination.objects.get( + start_amount__lte=update.amount, + end_amount__gte=update.amount, + network=update.subscriber.network) + expiry_date = datetime.datetime.now(pytz.UTC) + \ + datetime.timedelta(days=denom.validity_days) + if update.subscriber.valid_through: + # Check if existing validity is greater than new validity + # then dont update new validity + if expiry_date >= update.subscriber.valid_through: + update.subscriber.valid_through = expiry_date + else: + # Check if subscriber has no validity set + update.subscriber.valid_through = expiry_date + update.subscriber.state = 'active' - # Get subscriber's 1st number from some admin number. - num = Number.objects.filter( - subscriber__imsi=update.subscriber.imsi)[0:1].get() - if num.valid_through is None: - num.valid_through = expiry_date - num.save() - elif expiry_date >= num.valid_through: - num.valid_through = expiry_date - num.save() - print "update_credit SUCCESS. id=%s, imsi=%s, amount=%s. (%d)" % ( - update_id, imsi, update.amount, request.status_code) + update.subscriber.save() + print "update_credit SUCCESS. id=%s, imsi=%s, amount=%s. (%d)"\ + % (update_id, imsi, update.amount, request.status_code) update.delete() bts.mark_active() bts.save() diff --git a/cloud/endagaweb/views/dashboard.py b/cloud/endagaweb/views/dashboard.py index 7df897c2..15acea16 100644 --- a/cloud/endagaweb/views/dashboard.py +++ b/cloud/endagaweb/views/dashboard.py @@ -576,9 +576,6 @@ def post(self, request, imsi=None): network=network) # Update user validity for recharge denomination amount if denom_exists.validity_days > 0: - now = datetime.datetime.now(pytz.UTC) - expiry_date = now + datetime.timedelta( - days=denom_exists.validity_days) try: # Validation suceeded, create a PCU and start the # update credit task. @@ -586,7 +583,6 @@ def post(self, request, imsi=None): credit_update = PendingCreditUpdate(subscriber=sub, uuid=msgid, amount=amount) - credit_update.valid_through = expiry_date credit_update.save() tasks.update_credit.delay(sub.imsi, msgid) return adjust_credit_redirect From 899aee923f8169e8dc03f5d451d4fced2d043f04 Mon Sep 17 00:00:00 2001 From: Shiv K Sah Date: Fri, 28 Jul 2017 16:11:18 +0530 Subject: [PATCH 5/5] adjust credit - if amount 10 in exist in 2 denomination, recharge with larger denomination --- cloud/endagaweb/tasks.py | 6 ++++-- cloud/endagaweb/views/dashboard.py | 9 ++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cloud/endagaweb/tasks.py b/cloud/endagaweb/tasks.py index 281f8d8c..95774494 100644 --- a/cloud/endagaweb/tasks.py +++ b/cloud/endagaweb/tasks.py @@ -216,10 +216,12 @@ def update_credit(self, imsi, update_id): if request.status_code >= 200 and request.status_code < 300: with transaction.atomic(): # Check for existing denomination range exist. - denom = NetworkDenomination.objects.get( + denom = NetworkDenomination.objects.filter( start_amount__lte=update.amount, end_amount__gte=update.amount, - network=update.subscriber.network) + network=update.subscriber.network).order_by('-end_amount') + if len(denom): + denom = denom[0] expiry_date = datetime.datetime.now(pytz.UTC) + \ datetime.timedelta(days=denom.validity_days) if update.subscriber.valid_through: diff --git a/cloud/endagaweb/views/dashboard.py b/cloud/endagaweb/views/dashboard.py index 15acea16..f05b4185 100644 --- a/cloud/endagaweb/views/dashboard.py +++ b/cloud/endagaweb/views/dashboard.py @@ -570,12 +570,12 @@ def post(self, request, imsi=None): raise ValueError(error_text) try: # Check for existing denomination range exist. - denom_exists = NetworkDenomination.objects.get( + denom_exists = NetworkDenomination.objects.filter( start_amount__lte=amount, end_amount__gte=amount, - network=network) + network=network).exists() # Update user validity for recharge denomination amount - if denom_exists.validity_days > 0: + if denom_exists: try: # Validation suceeded, create a PCU and start the # update credit task. @@ -589,6 +589,9 @@ def post(self, request, imsi=None): except Number.DoesNotExist: error_text = 'Subscriber has no number assigned.' raise ValueError(error_text) + else: + error_text = 'Credit value must be in denomination range.' + raise ValueError(error_text) except NetworkDenomination.DoesNotExist: error_text = 'Credit value must be in denomination range.' raise ValueError(error_text)