diff --git a/cloud/endagaweb/models.py b/cloud/endagaweb/models.py index 65b0780f..8b4679b0 100644 --- a/cloud/endagaweb/models.py +++ b/cloud/endagaweb/models.py @@ -1468,6 +1468,10 @@ class NetworkDenomination(models.Model): start_amount = models.BigIntegerField() end_amount = models.BigIntegerField() validity_days = models.PositiveIntegerField(blank=True, default=0) + status = models.CharField(max_length=10, default='pending', + choices=[('pending', 'Pending'), + ('deleted', 'Deleted'), + ('done', 'Done')]) # The denomination group associated with the network network = models.ForeignKey('Network', null=True, on_delete=models.CASCADE) @@ -1482,6 +1486,7 @@ def __unicode__(self): class Meta: ordering = ('start_amount',) + default_permissions = () class ConfigurationKey(models.Model): diff --git a/cloud/endagaweb/templates/dashboard/network_detail/denomination.html b/cloud/endagaweb/templates/dashboard/network_detail/denomination.html index 3e7582f0..eb14c826 100644 --- a/cloud/endagaweb/templates/dashboard/network_detail/denomination.html +++ b/cloud/endagaweb/templates/dashboard/network_detail/denomination.html @@ -37,87 +37,90 @@ {% endblock %} {% block content %} - {% include "dashboard/network_detail/header.html" with network=network %} + {% include "dashboard/network_detail/header.html" with network=network active_tab='network-denominations' %}
{% include "dashboard/network_detail/nav.html" with active_tab='network-denominations' %} -
+
+ {% if invalid_ranges and user_profile.user.is_staff %} +
+ × +

+ + Error! There is a missing range in denomination brackets. Add/edit denominations to fill the below missing ranges. Changes will be lost until you confirm the final submit. +

+

Missing range are {% for range in invalid_ranges %}{{ range.start }} - {{ range.end }} {% endfor %}

+
+ {% elif sync_status and user_profile.user.is_staff %} +
+

+ + Warning! Changes are not saved yet. Please confirm to submit changes. + Submit Changes +

+
+ {% endif %} {% for message in messages %}
× {{ message }}
{% endfor %} +
{% if denomination %} {% render_table denominations_table %} + {% if user_profile.user.is_staff %} + + {% endif %} {% else %}

There are currently no denominations associated with this network.

{% endif %} +

"Denomination" facilitates admin to create various top-up brackets along with limited validity period + which can be achieved using “Add Denomination” Button. "Add denomination" button is used to create various denomination brackets and their validity period for Recharges. + For error free creation of denomination brackets, Created denomination bracket must be continuous in terms of Amount. + ( Ex:- Start Amount for 1st denomination is 1 & End Amount is 10 , for 2nd Denomination Start Amount must start from 11)

+
- {% if user_profile.user.is_staff %} -
-
-
Create Denomination
-
-
-
{% csrf_token %} -
-
- -
- -
-
-
-
-
- -
- -
-
-
- -
-
- -
- -
-
-
+
-
-
-
- - - -
-
-
- -
+ +
+ {% endblock %} {% block js %} @@ -151,50 +215,23 @@

src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.10.0/jquery.validate.min.js"> diff --git a/cloud/endagaweb/templates/dashboard/network_detail/header.html b/cloud/endagaweb/templates/dashboard/network_detail/header.html index 4e9d3965..60b2640f 100644 --- a/cloud/endagaweb/templates/dashboard/network_detail/header.html +++ b/cloud/endagaweb/templates/dashboard/network_detail/header.html @@ -13,6 +13,11 @@

{% if network.name %} "{{ network.name }}" {% endif %} + {% if user_profile.user.is_staff %} + {% if active_tab == 'network-denominations' %} + Add Denomination + {% endif %} + {% endif %}

diff --git a/cloud/endagaweb/urls.py b/cloud/endagaweb/urls.py index d2b8d10c..8a19d167 100644 --- a/cloud/endagaweb/urls.py +++ b/cloud/endagaweb/urls.py @@ -145,6 +145,9 @@ url(r'^dashboard/network/denominations$', endagaweb.views.network.NetworkDenomination.as_view(), name='network-denominations'), + url(r'^dashboard/network/denominations/manage$', + endagaweb.views.network.NetworkDenominationEdit.as_view(), + name='network-denominations-manage'), 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 fe8daf18..5f3f0772 100644 --- a/cloud/endagaweb/views/django_tables.py +++ b/cloud/endagaweb/views/django_tables.py @@ -342,13 +342,14 @@ class DenominationTable(tables.Table): class Meta: model = models.NetworkDenomination - fields = ('start_amount', 'end_amount', 'validity_days') + fields = ('id', 'start_amount', 'end_amount', 'validity_days') attrs = {'class': 'table table-hover'} + id = tables.CheckBoxColumn( + accessor="pk", attrs={"th__input": {"onclick": "toggle(this)"}}) 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, @@ -357,11 +358,3 @@ def render_start_amount(self, record): 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) diff --git a/cloud/endagaweb/views/network.py b/cloud/endagaweb/views/network.py index 318335c4..d4fab672 100644 --- a/cloud/endagaweb/views/network.py +++ b/cloud/endagaweb/views/network.py @@ -446,6 +446,8 @@ def get(self, request, network_id): user_profile = models.UserProfile.objects.get(user=request.user) try: network = models.Network.objects.get(pk=network_id) + if 'sync_status' in request.session: + del request.session['sync_status'] except models.Network.DoesNotExist: return http.HttpResponseBadRequest() @@ -457,8 +459,33 @@ def get(self, request, network_id): return http.HttpResponseRedirect(request.META.get('HTTP_REFERER', '/dashboard')) +def sync_denomination(network_id, status): + """ Rebase denomination table remove pending changes. """ + if status == 'apply': + with transaction.atomic(): + models.NetworkDenomination.objects.filter( + network=network_id, + status__in=['pending']).update(status='done') + deleted_denom = models.NetworkDenomination.objects.filter( + status__in=['deleted']) + for denomination in deleted_denom: + denomination.delete() + if status == 'discard': + with transaction.atomic(): + new_denom = models.NetworkDenomination.objects.filter( + status__in=['pending']) + for denomination in new_denom: + denomination.delete() + deleted_denom = models.NetworkDenomination.objects.filter( + status__in=['deleted']) + for denomination in deleted_denom: + denomination.status = 'done' + denomination.save() + + class NetworkDenomination(ProtectedView): """Assign denominations bracket for recharge/adjust-credit in network.""" + permission_required = 'view_denomination' def get(self, request): """Handles GET requests.""" @@ -466,8 +493,16 @@ def get(self, request): network = user_profile.network currency = network.subscriber_currency + sync_status = False + if 'sync_status' in request.session: + sync_status = request.session['sync_status'] + else: + sync_denomination(network.id, 'discard') + request.session['sync_status'] = sync_status + # Count the associated denomination with selected network. - denom = models.NetworkDenomination.objects.filter(network=network) + denom = models.NetworkDenomination.objects.filter( + network=network, status__in=['done', 'pending']) denom_count = denom.count() dnm_id = request.GET.get('id', None) @@ -490,6 +525,20 @@ def get(self, request): return http.HttpResponse(json.dumps(response), content_type="application/json") + invalid_ranges = [] + max_denominations = 100000 + for denomination in denom: + if denomination.start_amount > (max_denominations): + start_range = humanize_credits((max_denominations), + CURRENCIES[currency]).money_str() + end_range = humanize_credits((denomination.start_amount), + CURRENCIES[currency]).money_str() + invalid_ranges.append({"start": start_range, + "end": end_range}) + max_denominations = denomination.end_amount + next_start_amount = humanize_credits(max_denominations, + CURRENCIES[currency]).amount + # 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: @@ -512,6 +561,9 @@ def get(self, request): 'number_country': NUMBER_COUNTRIES[network.number_country], 'denomination': denom_count, 'denominations_table': denom_table, + 'invalid_ranges': invalid_ranges, + 'next_start_amount': next_start_amount, + 'sync_status': sync_status } # Render template. info_template = template.loader.get_template( @@ -519,6 +571,11 @@ def get(self, request): html = info_template.render(context, request) return http.HttpResponse(html) + +class NetworkDenominationEdit(ProtectedView): + + permission_required = 'change_denomination' + def post(self, request): """Operators can use this API to add denomination to a network. @@ -528,6 +585,16 @@ def post(self, request): user_profile = models.UserProfile.objects.get(user=request.user) network = user_profile.network try: + sync = request.GET.get('sync', False) + if sync: + sync_denomination(network.id, 'apply') + request.session['sync_status'] = False + messages.success( + request, 'New denomination changes applied successfully.', + extra_tags='alert alert-success') + return http.HttpResponse(json.dumps({'status': 'ok'}), + content_type="application/json") + currency = network.subscriber_currency start_amount_raw = request.POST.get('start_amount') start_amount = parse_credits(start_amount_raw, @@ -546,9 +613,9 @@ def post(self, request): request, message, extra_tags='alert alert-danger') return redirect(urlresolvers.reverse('network-denominations')) - elif start_amount <= 0 or end_amount <= 0: + elif start_amount < 1 or end_amount <= 1: messages.error(request, - 'Enter value >0 for start/end amount.', + 'Enter value >= 1 for start amount.', extra_tags='alert alert-danger') return redirect(urlresolvers.reverse('network-denominations')) elif validity_days <= 0: @@ -567,13 +634,14 @@ def post(self, request): if dnm_id > 0: try: denom = models.NetworkDenomination.objects.get( - id=dnm_id) + id=dnm_id, status__in=['done', 'pending']) # 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( + end_amount__gte=start_amount+1, + start_amount__lte=end_amount-1, + network=user_profile.network, + status__in=['done', 'pending']).exclude( id=dnm_id).count() if denom_exists: messages.error( @@ -581,11 +649,18 @@ def post(self, request): 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.status = 'deleted' denom.save() + # Create new denomination for updated record + new_denom = models.NetworkDenomination( + network=user_profile.network) + new_denom.network = user_profile.network + new_denom.start_amount = start_amount + new_denom.end_amount = end_amount + new_denom.validity_days = validity_days + new_denom.status = 'pending' + new_denom.save() + request.session['sync_status'] = True messages.success( request, 'Denomination is updated successfully.', extra_tags='alert alert-success') @@ -598,9 +673,10 @@ def post(self, request): 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() + end_amount__gte=start_amount+1, + start_amount__lte=end_amount-1, + network=user_profile.network, + status__in=['done', 'pending']).count() if denom_exists: messages.error( request, 'Denomination range already exists.', @@ -614,7 +690,9 @@ def post(self, request): denom.start_amount = start_amount denom.end_amount = end_amount denom.validity_days = validity_days + denom.status = 'pending' denom.save() + request.session['sync_status'] = True messages.success( request, 'Denomination is created successfully.', extra_tags='alert alert-success') @@ -626,28 +704,28 @@ def post(self, request): return redirect(urlresolvers.reverse('network-denominations')) def delete(self, request): - """Handles delete requests.""" + """soft delete denominations, this can be commit/rollback by + sync_denomination() as per request.""" response = { 'status': 'ok', 'messages': [], } - dnm_id = request.GET.get('id') or False - if dnm_id: + dnm_ids = request.GET.getlist('ids[]') or False + if dnm_ids: try: - denom = models.NetworkDenomination.objects.get(id=dnm_id) - denom.delete() + models.NetworkDenomination.objects.filter( + id__in=dnm_ids).update(status='deleted') + request.session['sync_status'] = True 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.', + messages.error( request, 'Invalid denomination ID.', extra_tags='alert alert-danger') else: response['status'] = 'failed' - messages.error( - request, 'Invalid request data.', + messages.error(request, 'Invalid request data.', extra_tags='alert alert-danger') return http.HttpResponse(json.dumps(response), content_type="application/json")