From 86ba8b9091f12955c9c69331ae88ab9876763f36 Mon Sep 17 00:00:00 2001 From: Sravan Reddy Date: Sat, 5 Oct 2024 23:50:35 +0530 Subject: [PATCH] Break down by delivery-type --- commcare_connect/reports/tables.py | 24 +++++ commcare_connect/reports/views.py | 143 +++++++++++++++++++---------- 2 files changed, 119 insertions(+), 48 deletions(-) diff --git a/commcare_connect/reports/tables.py b/commcare_connect/reports/tables.py index fc6e9b3b..6cde26de 100644 --- a/commcare_connect/reports/tables.py +++ b/commcare_connect/reports/tables.py @@ -1,8 +1,11 @@ +from django.urls import reverse +from django.utils.html import format_html from django_tables2 import columns, tables class AdminReportTable(tables.Table): quarter = columns.Column(verbose_name="Quarter") + delivery_type = columns.Column(verbose_name="Delivery Type") users = columns.Column(verbose_name="Active Users") services = columns.Column(verbose_name="Verified Services") approved_payments = columns.Column(verbose_name="Acknowledged Payments") @@ -12,3 +15,24 @@ class AdminReportTable(tables.Table): class Meta: empty_text = "No data for this quarter." orderable = False + row_attrs = {"id": lambda record: f"row{record['quarter'][0]}-{record['quarter'][1]}"} + + def render_quarter(self, value): + return f"{value[0]} Q{value[1]}" + + def render_delivery_type(self, record): + if record["delivery_type"] != "All": + return record["delivery_type"] + url = reverse("reports:delivery_stats_report") + return format_html( + """""", + url=url, + year=record["quarter"][0], + quarter=record["quarter"][1], + ) diff --git a/commcare_connect/reports/views.py b/commcare_connect/reports/views.py index 106c41f9..f73bd70c 100644 --- a/commcare_connect/reports/views.py +++ b/commcare_connect/reports/views.py @@ -2,6 +2,7 @@ import django_filters import django_tables2 as tables +from django import forms from django.conf import settings from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin @@ -10,6 +11,7 @@ from django.http import JsonResponse from django.shortcuts import render from django.urls import reverse +from django.utils.functional import cached_property from django.views.decorators.http import require_GET from django_filters.views import FilterView @@ -41,22 +43,29 @@ def _get_quarters_since_start(): return quarters -def _get_table_data_for_quarter(quarter, delivery_type): +def _get_table_data_for_quarter(quarter, delivery_type, group_by_delivery_type=False): if delivery_type: delivery_type_filter = Q(opportunity_access__opportunity__delivery_type__slug=delivery_type) else: delivery_type_filter = Q() - quarter_start = date(quarter[0], (quarter[1] - 1) * 3 + 1, 1) next_quarter = _increment(quarter) quarter_end = date(next_quarter[0], (next_quarter[1] - 1) * 3 + 1, 1) + data = [] + + if group_by_delivery_type: + from collections import defaultdict + + user_set = defaultdict(set) + beneficiary_set = defaultdict(set) + service_count = defaultdict(int) + else: + user_set = set() + beneficiary_set = set() + service_count = 0 - user_set = set() - beneficiary_set = set() - service_count = 0 last_pk = 0 more = True - while more: visit_data = ( CompletedWork.objects.annotate(work_date=Max("uservisit__visit_date")) @@ -68,44 +77,77 @@ def _get_table_data_for_quarter(quarter, delivery_type): work_date__lt=quarter_end, id__gt=last_pk, ) - .select_related("opportunity_access") + .select_related("opportunity_access__opportunity__delivery_type") ).order_by("id")[:100] - if len(visit_data) < 100: more = False for v in visit_data: - user_set.add(v.opportunity_access.user_id) - beneficiary_set.add(v.entity_id) - service_count += v.approved_count + delivery_type_name = v.opportunity_access.opportunity.delivery_type.name + if group_by_delivery_type: + user_set[delivery_type_name].add(v.opportunity_access.user_id) + beneficiary_set[delivery_type_name].add(v.entity_id) + service_count[delivery_type_name] += v.approved_count + else: + user_set.add(v.opportunity_access.user_id) + beneficiary_set.add(v.entity_id) + service_count += v.approved_count + last_pk = v.id - approved_payment_amount = ( - Payment.objects.filter( - delivery_type_filter, - opportunity_access__opportunity__is_test=False, - confirmed=True, - date_paid__gte=quarter_start, - date_paid__lt=quarter_end, - ).aggregate(Sum("amount_usd")) - )["amount_usd__sum"] - - total_payment_amount = ( - Payment.objects.filter( - delivery_type_filter, - opportunity_access__opportunity__is_test=False, - date_paid__gte=quarter_start, - date_paid__lt=quarter_end, - ).aggregate(Sum("amount_usd")) - )["amount_usd__sum"] - - return { - "quarter": f"{quarter[0]} Q{quarter[1]}", - "users": len(user_set), - "services": service_count, - "approved_payments": approved_payment_amount, - "total_payments": total_payment_amount, - "beneficiaries": len(beneficiary_set), - } + payment_query = Payment.objects.filter( + delivery_type_filter, + opportunity_access__opportunity__is_test=False, + date_paid__gte=quarter_start, + date_paid__lt=quarter_end, + ) + + if group_by_delivery_type: + approved_payment_data = ( + payment_query.filter(confirmed=True) + .values("opportunity_access__opportunity__delivery_type__name") + .annotate(approved_sum=Sum("amount_usd")) + ) + total_payment_data = payment_query.values("opportunity_access__opportunity__delivery_type__name").annotate( + total_sum=Sum("amount_usd") + ) + + approved_payment_dict = { + item["opportunity_access__opportunity__delivery_type__name"]: item["approved_sum"] + for item in approved_payment_data + } + total_payment_dict = { + item["opportunity_access__opportunity__delivery_type__name"]: item["total_sum"] + for item in total_payment_data + } + for delivery_type_name in user_set.keys(): + data.append( + { + "delivery_type": delivery_type_name, + "quarter": quarter, + "users": len(user_set[delivery_type_name]), + "services": service_count[delivery_type_name], + "approved_payments": approved_payment_dict.get(delivery_type_name, 0), + "total_payments": total_payment_dict.get(delivery_type_name, 0), + "beneficiaries": len(beneficiary_set[delivery_type_name]), + } + ) + else: + approved_payment_amount = ( + payment_query.filter(confirmed=True).aggregate(Sum("amount_usd"))["amount_usd__sum"] or 0 + ) + total_payment_amount = payment_query.aggregate(Sum("amount_usd"))["amount_usd__sum"] or 0 + data.append( + { + "delivery_type": "All", + "quarter": quarter, + "users": len(user_set), + "services": service_count, + "approved_payments": approved_payment_amount, + "total_payments": total_payment_amount, + "beneficiaries": len(beneficiary_set), + } + ) + return data @login_required @@ -178,9 +220,7 @@ class DeliveryReportFilters(django_filters.FilterSet): choices=[(1, "Q1"), (2, "Q2"), (3, "Q3"), (4, "Q4")], label="Quarter", method="filter_by_ignore" ) by_delivery_type = django_filters.BooleanFilter( - widget=forms.CheckboxInput(), - label='Break up by delivery type', - method='filter_by_ignore' + widget=forms.CheckboxInput(), label="Break up by delivery type", method="filter_by_ignore" ) def __init__(self, *args, **kwargs): @@ -237,16 +277,23 @@ def get_context_data(self, *args, **kwargs): context["report_url"] = reverse("reports:delivery_stats_report") return context + @cached_property + def filter_values(self): + if not self.filterset.form.is_valid(): + return None + else: + return self.filterset.form.cleaned_data + @property def object_list(self): table_data = [] - if not self.filterset.form.is_valid(): + if not self.filter_values: return [] - filter_values = self.filterset.form.cleaned_data - delivery_type = filter_values["delivery_type"] - year = int(filter_values["year"]) - quarter = filter_values["quarter"] + delivery_type = self.filter_values["delivery_type"] + year = int(self.filter_values["year"]) + quarter = self.filter_values["quarter"] + group_by_delivery_type = self.filter_values["by_delivery_type"] if not year: quarters = _get_quarters_since_start() @@ -257,6 +304,6 @@ def object_list(self): quarters = [(year, q) for q in range(1, 5)] for q in quarters: - data = _get_table_data_for_quarter(q, delivery_type) - table_data.append(data) + data = _get_table_data_for_quarter(q, delivery_type, group_by_delivery_type) + table_data += data return table_data