From 971cb745c9e6ba04edc9da07c77c6444a0d78960 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Mon, 7 Mar 2022 14:30:11 +0000 Subject: [PATCH] Report for total notifications sent per day for each channel. Daily volumes report: total volumes across the platform aggregated by whole business day (bst_date) Volumes by service report: total volumes per service aggregated by the date range given. NB: start and end dates are inclusive --- app/main/views/platform_admin.py | 68 +++++++++++++++++++ app/navigation.py | 2 + app/notify_client/billing_api_client.py | 14 ++++ .../platform-admin/daily-volumes-report.html | 48 +++++++++++++ .../views/platform-admin/reports.html | 6 ++ .../volumes-by-service-report.html | 46 +++++++++++++ .../app/notify_client/test_billing_client.py | 18 +++++ tests/app/test_navigation.py | 2 + 8 files changed, 204 insertions(+) create mode 100644 app/templates/views/platform-admin/daily-volumes-report.html create mode 100644 app/templates/views/platform-admin/volumes-by-service-report.html diff --git a/app/main/views/platform_admin.py b/app/main/views/platform_admin.py index 10e43fb4b..303a1fea5 100644 --- a/app/main/views/platform_admin.py +++ b/app/main/views/platform_admin.py @@ -313,6 +313,74 @@ def get_billing_report(): return render_template('views/platform-admin/get-billing-report.html', form=form) +@main.route("/platform-admin/reports/volumes-by-service", methods=['GET', 'POST']) +@user_is_platform_admin +def get_volumes_by_service(): + form = BillingReportDateFilterForm() + + if form.validate_on_submit(): + start_date = form.start_date.data + end_date = form.end_date.data + headers = [ + "organisation_id", "organisation_name", "service_id", "service_name", + "free_allowance", "sms_notifications", "sms chargeable units", "email_totals", + "letter_totals", "letter_cost", "letter_sheet_totals" + ] + result = billing_api_client.get_data_for_volumes_by_service_report(start_date, end_date) + + rows = [ + [ + r["organisation_id"], r["organisation_name"], r["service_id"], r["service_name"], + r["free_allowance"], r["sms_notifications"], r["sms_chargeable_units"], r["email_totals"], + r["letter_totals"], r["letter_cost"], r["letter_sheet_totals"] + ] + for r in result + ] + if rows: + return Spreadsheet.from_rows([headers] + rows).as_csv_data, 200, { + 'Content-Type': 'text/csv; charset=utf-8', + 'Content-Disposition': 'attachment; filename="Volumes by service report from {} to {}.csv"'.format( + start_date, end_date + ) + } + else: + flash('No results for dates') + return render_template('views/platform-admin/volumes-by-service-report.html', form=form) + + +@main.route("/platform-admin/reports/daily-volumes-report", methods=['GET', 'POST']) +@user_is_platform_admin +def get_daily_volumes(): + form = BillingReportDateFilterForm() + + if form.validate_on_submit(): + start_date = form.start_date.data + end_date = form.end_date.data + headers = [ + "day", "sms totals", "sms fragment totals", "sms chargeable units", + "email totals", "letter totals", "letter sheet totals" + ] + result = billing_api_client.get_data_for_daily_volumes_report(start_date, end_date) + + rows = [ + [ + r["day"], r["sms_totals"], r["sms_fragment_totals"], r["sms_chargeable_units"], + r["email_totals"], r["letter_totals"], r["letter_sheet_totals"] + ] + for r in result + ] + if rows: + return Spreadsheet.from_rows([headers] + rows).as_csv_data, 200, { + 'Content-Type': 'text/csv; charset=utf-8', + 'Content-Disposition': 'attachment; filename="Daily volumes report from {} to {}.csv"'.format( + start_date, end_date + ) + } + else: + flash('No results for dates') + return render_template('views/platform-admin/daily-volumes-report.html', form=form) + + @main.route("/platform-admin/complaints") @user_is_platform_admin def platform_admin_list_complaints(): diff --git a/app/navigation.py b/app/navigation.py index 7a72749c6..110a1557a 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -92,6 +92,8 @@ class HeaderNavigation(Navigation): 'live_services_csv', 'notifications_sent_by_service', 'get_billing_report', + 'get_daily_volumes', + 'get_volumes_by_service', 'organisations', 'platform_admin', 'platform_admin_list_complaints', diff --git a/app/notify_client/billing_api_client.py b/app/notify_client/billing_api_client.py index ed083692e..08b5ce52c 100644 --- a/app/notify_client/billing_api_client.py +++ b/app/notify_client/billing_api_client.py @@ -41,5 +41,19 @@ class BillingAPIClient(NotifyAdminAPIClient): 'end_date': str(end_date), }) + def get_data_for_volumes_by_service_report(self, start_date, end_date): + return self.get(url='/platform-stats/volumes-by-service', + params={ + 'start_date': str(start_date), + 'end_date': str(end_date), + }) + + def get_data_for_daily_volumes_report(self, start_date, end_date): + return self.get(url='/platform-stats/daily-volumes-report', + params={ + 'start_date': str(start_date), + 'end_date': str(end_date), + }) + billing_api_client = BillingAPIClient() diff --git a/app/templates/views/platform-admin/daily-volumes-report.html b/app/templates/views/platform-admin/daily-volumes-report.html new file mode 100644 index 000000000..dbe24918b --- /dev/null +++ b/app/templates/views/platform-admin/daily-volumes-report.html @@ -0,0 +1,48 @@ +{% extends "views/platform-admin/_base_template.html" %} +{% from "components/form.html" import form_wrapper %} +{% from "components/table.html" import mapping_table, row, text_field %} + +{% block per_page_title %} + Daily volumes report +{% endblock %} + +{% block platform_admin_content %} + +

+ Daily volumes Report +

+ + {% call form_wrapper() %} + {{ form.start_date(param_extensions={"hint": {"text": "Use the format YYYY-MM-DD"}}) }} + {{ form.end_date(param_extensions={"hint": {"text": "Use the format YYYY-MM-DD"}}) }} + {{ page_footer('Download report') }} + {% endcall %} + +

+ Data included in the report +

+
+ {% call mapping_table( + caption='Descriptions of daily volumes data', + field_headings=['Name', 'Description'], + field_headings_visible=True, + caption_visible=False + ) %} + {% for message_length, charge in [ + ('day', 'The whole business day in BST.'), + ('sms totals', 'The number of text messages sent'), + ('sms chargeable units', 'The number of text message fragments sent'), + ('sms_fragments_times_multiplier', 'The number of text message fragments sent times the rate multiplier'), + ('email totals', 'The number of emails sent'), + ('letter totals', 'The number of letters sent'), + ('letter sheet totals', The number of sheets sent) + ] %} + {% call row() %} + {{ text_field(message_length) }} + {{ text_field(charge | safe) }} + {% endcall %} + {% endfor %} + {% endcall %} +
+ +{% endblock %} diff --git a/app/templates/views/platform-admin/reports.html b/app/templates/views/platform-admin/reports.html index 8d56460a9..dda3f18ff 100644 --- a/app/templates/views/platform-admin/reports.html +++ b/app/templates/views/platform-admin/reports.html @@ -19,4 +19,10 @@

Billing Report

+

+ Volumes by service Report +

+

+ Daily volumes Report +

{% endblock %} diff --git a/app/templates/views/platform-admin/volumes-by-service-report.html b/app/templates/views/platform-admin/volumes-by-service-report.html new file mode 100644 index 000000000..1ae33d8b2 --- /dev/null +++ b/app/templates/views/platform-admin/volumes-by-service-report.html @@ -0,0 +1,46 @@ +{% extends "views/platform-admin/_base_template.html" %} +{% from "components/form.html" import form_wrapper %} +{% from "components/table.html" import mapping_table, row, text_field %} + +{% block per_page_title %} + Volumes by service report +{% endblock %} + +{% block platform_admin_content %} + +

+ Volumes by service Report +

+ + {% call form_wrapper() %} + {{ form.start_date(param_extensions={"hint": {"text": "Use the format YYYY-MM-DD"}}) }} + {{ form.end_date(param_extensions={"hint": {"text": "Use the format YYYY-MM-DD"}}) }} + {{ page_footer('Download report') }} + {% endcall %} + +

+ Data included in the report +

+
+ {% call mapping_table( + caption='Descriptions of volumes by service data', + field_headings=['Name', 'Description'], + field_headings_visible=True, + caption_visible=False + ) %} + {% for message_length, charge in [ + ('free allowance', 'Free allowance set for the service. This is the latest free allowance for the date range given'), + ('sms notifications', 'The number of text messages sent by the service.'), + ('sms volume', 'The number of text message fragments times the rate multiplier sent by the service.'), + ('email totals', 'The number of emails sent by a service'), + ('letter totals', 'The number of letters sent by a service'), + ] %} + {% call row() %} + {{ text_field(message_length) }} + {{ text_field(charge | safe) }} + {% endcall %} + {% endfor %} + {% endcall %} +
+ +{% endblock %} diff --git a/tests/app/notify_client/test_billing_client.py b/tests/app/notify_client/test_billing_client.py index e29a80bdc..b3c29509b 100644 --- a/tests/app/notify_client/test_billing_client.py +++ b/tests/app/notify_client/test_billing_client.py @@ -41,3 +41,21 @@ def test_post_free_sms_fragment_limit_for_year_endpoint(mocker, api_user_active) url='/service/{}/billing/free-sms-fragment-limit'.format(service_id), data=sms_limit_data ) + + +def test_get_data_for_volumes_by_service_report(mocker, api_user_active): + mock_get = mocker.patch('app.notify_client.billing_api_client.BillingAPIClient.get') + client = BillingAPIClient() + + client.get_data_for_volumes_by_service_report('2022-03-01', '2022-03-31') + mock_get.assert_called_once_with(url='/platform-stats/volumes-by-service', + params={'start_date': '2022-03-01', 'end_date': '2022-03-31'}) + + +def test_get_data_for_daily_volumes_report(mocker, api_user_active): + mock_get = mocker.patch('app.notify_client.billing_api_client.BillingAPIClient.get') + client = BillingAPIClient() + + client.get_data_for_daily_volumes_report('2022-03-01', '2022-03-31') + mock_get.assert_called_once_with(url='/platform-stats/daily-volumes-report', + params={'start_date': '2022-03-01', 'end_date': '2022-03-31'}) diff --git a/tests/app/test_navigation.py b/tests/app/test_navigation.py index 4cb615f34..8477a9581 100644 --- a/tests/app/test_navigation.py +++ b/tests/app/test_navigation.py @@ -131,6 +131,8 @@ EXCLUDED_ENDPOINTS = tuple(map(Navigation.get_endpoint_with_blueprint, { 'find_users_by_email', 'forgot_password', 'get_billing_report', + 'get_daily_volumes', + 'get_volumes_by_service', 'get_example_csv', 'get_notifications_as_json', 'get_started',