diff --git a/app/main/views/organisations.py b/app/main/views/organisations.py index c1d247b5b..92d56fd10 100644 --- a/app/main/views/organisations.py +++ b/app/main/views/organisations.py @@ -1,4 +1,5 @@ from collections import OrderedDict +from datetime import datetime from functools import partial from flask import flash, redirect, render_template, request, session, url_for @@ -43,6 +44,7 @@ from app.main.views.dashboard import ( from app.main.views.service_settings import get_branding_as_value_and_label from app.models.organisation import Organisation, Organisations from app.models.user import InvitedOrgUser, User +from app.utils.csv import Spreadsheet from app.utils.user import user_has_permissions, user_is_platform_admin @@ -158,16 +160,46 @@ def organisation_dashboard(org_id): for key in ('emails_sent', 'sms_cost', 'letter_cost') }, download_link=url_for( - '.download_services_report_for_org', + '.download_organisation_usage_report', org_id=org_id, + selected_year=year ) ) -@main.route("/organisations/", methods=['GET']) +@main.route("/organisations//download-usage-report.csv", methods=['GET']) @user_has_permissions() -def download_services_report_for_org(org_id): - pass +def download_organisation_usage_report(org_id): + selected_year = request.args.get('selected_year') + services_usage = current_organisation.services_and_usage( + financial_year=selected_year + )['services'] + + column_names = OrderedDict([ + ('service_id', 'Service ID'), + ('service_name', 'Service Name'), + ('emails_sent', 'Emails sent'), + ('sms_remainder', 'Free text message allowance remaining'), + ('sms_cost', 'Spent on text messages (£)'), + ('letter_cost', 'Spent on letters (£)') + ]) + + org_usage_data = [[x for x in column_names.values()]] + + for service in services_usage: + org_usage_data.append([service[attribute] for attribute in column_names.keys()]) + + return Spreadsheet.from_rows(org_usage_data).as_csv_data, 200, { + 'Content-Type': 'text/csv; charset=utf-8', + 'Content-Disposition': ( + 'inline;' + 'filename="{} organisation usage report for year {}' + ' - generated on {}.csv"'.format( + current_organisation.name, + selected_year, + datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ") + )) + } @main.route("/organisations//trial-services", methods=['GET']) diff --git a/app/templates/views/organisations/organisation/index.html b/app/templates/views/organisations/organisation/index.html index 5309ac813..ae9c27b48 100644 --- a/app/templates/views/organisations/organisation/index.html +++ b/app/templates/views/organisations/organisation/index.html @@ -113,7 +113,7 @@ {% endif %} diff --git a/tests/app/main/views/organisations/test_organisations.py b/tests/app/main/views/organisations/test_organisations.py index 6cd22e0b9..56e94e2b4 100644 --- a/tests/app/main/views/organisations/test_organisations.py +++ b/tests/app/main/views/organisations/test_organisations.py @@ -624,6 +624,7 @@ def test_organisation_services_hides_search_bar_for_7_or_fewer_services( assert not page.select_one('.live-search') +@freeze_time("2021-11-12 11:09:00.061258") def test_organisation_services_links_to_downloadable_report( client_request, mock_get_organisation, @@ -651,7 +652,62 @@ def test_organisation_services_links_to_downloadable_report( page = client_request.get('.organisation_dashboard', org_id=ORGANISATION_ID) link_to_report = page.find('a', text="Download this report") - assert link_to_report.attrs["href"] == url_for('.download_services_report_for_org', org_id=ORGANISATION_ID) + assert link_to_report.attrs["href"] == url_for( + '.download_organisation_usage_report', + org_id=ORGANISATION_ID, + selected_year=2021 + ) + + +@freeze_time("2021-11-12 11:09:00.061258") +def test_download_organisation_usage_report( + client_request, + mock_get_organisation, + mocker, + active_user_with_permissions, + fake_uuid, +): + mocker.patch( + 'app.organisations_client.get_services_and_usage', + return_value={"services": [ + { + 'service_id': SERVICE_ONE_ID, + 'service_name': 'Service 1', + 'chargeable_billable_sms': 22, + 'emails_sent': 13000, + 'free_sms_limit': 100, + 'letter_cost': 30.50, + 'sms_billable_units': 122, + 'sms_cost': 1.93, + 'sms_remainder': None + }, + { + 'service_id': SERVICE_TWO_ID, + 'service_name': 'Service 1', + 'chargeable_billable_sms': 222, + 'emails_sent': 23000, + 'free_sms_limit': 250000, + 'letter_cost': 60.50, + 'sms_billable_units': 322, + 'sms_cost': 3.93, + 'sms_remainder': None + }, + ]} + ) + client_request.login(active_user_with_permissions) + csv_report = client_request.get( + '.download_organisation_usage_report', + org_id=ORGANISATION_ID, + selected_year=2021, + _test_page_title=False + ) + + assert csv_report.string == ( + "Service ID,Service Name,Emails sent,Free text message allowance remaining," + "Spent on text messages (£),Spent on letters (£)" + "\r\n596364a0-858e-42c8-9062-a8fe822260eb,Service 1,13000,,1.93,30.5" + "\r\n147ad62a-2951-4fa1-9ca0-093cd1a52c52,Service 1,23000,,3.93,60.5\r\n" + ) def test_organisation_trial_mode_services_shows_all_non_live_services( diff --git a/tests/app/test_navigation.py b/tests/app/test_navigation.py index 0f6e23116..fad19a5db 100644 --- a/tests/app/test_navigation.py +++ b/tests/app/test_navigation.py @@ -89,6 +89,7 @@ EXCLUDED_ENDPOINTS = tuple(map(Navigation.get_endpoint_with_blueprint, { 'documentation', 'download_contact_list', 'download_notifications_csv', + 'download_organisation_usage_report', 'edit_and_format_messages', 'edit_data_retention', 'edit_organisation_agreement',