From 355e98102884453c4d512c185f46d886d95f24f0 Mon Sep 17 00:00:00 2001 From: Katie Smith Date: Thu, 9 Jul 2020 18:44:03 +0100 Subject: [PATCH] Show international letters on the usage page The api returns letter details split by postage, so international letters are returned with a postage of `europe` or `rest-of-world` not `international` and these rows need to be added together when the rate is the same before they are displayed on the usage page. To do this, we need to replace the postage of `europe` and `rest-of-world` with `international`. The data then needs to be sorted by postage and rate before the letter units for rows which are international and have the same rate are added together. --- app/main/views/dashboard.py | 56 +++++++++++++++++++++----- app/templates/views/usage.html | 4 +- tests/app/main/views/test_dashboard.py | 44 ++++++++++++++++++-- tests/conftest.py | 16 +++++++- 4 files changed, 104 insertions(+), 16 deletions(-) diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index 6001660df..d169de9d2 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -3,6 +3,7 @@ from collections import namedtuple from datetime import datetime from functools import partial from itertools import groupby +from operator import itemgetter from flask import ( Response, @@ -418,16 +419,8 @@ def get_free_paid_breakdown_for_billable_units(year, free_sms_fragment_limit, bi [billing_month for billing_month in sms_units if billing_month['month'] == month] ) - LetterDetails = namedtuple('LetterDetails', ['billing_units', 'rate', 'cost', 'postage']) - - letter_billing = [LetterDetails(billing_units=x['billing_units'], - rate=x['rate'], - cost=(x['billing_units'] * x['rate']), - postage=x['postage']) - for x in letter_units if x['month'] == month] - - if letter_billing: - letter_billing.sort(key=lambda x: (x.postage, x.rate)) + letter_units_for_month = [x for x in letter_units if x['month'] == month] + letter_billing = format_letter_details_for_month(letter_units_for_month) letter_total = 0 for x in letter_billing: @@ -443,6 +436,49 @@ def get_free_paid_breakdown_for_billable_units(year, free_sms_fragment_limit, bi } +def format_letter_details_for_month(letter_units_for_month): + # Format postage descriptions in letter units e.g. to 'international' not 'europe' + for month in letter_units_for_month: + for k, v in month.items(): + if k == 'postage': + month[k] = get_postage_description(v) + + # letter_units_for_month must be sorted before international postage values can be aggregated + postage_order = {'first class': 0, 'second class': 1, 'international': 2} + letter_units_for_month.sort(key=lambda x: (postage_order[x['postage']], x['rate'])) + + LetterDetails = namedtuple('LetterDetails', ['billing_units', 'rate', 'cost', 'postage_description']) + + # Aggregate the rows for international letters which have the same rate + result = [] + for _key, rate_group in groupby(letter_units_for_month, key=itemgetter('postage', 'rate')): + rate_group = list(rate_group) + + letter_details = LetterDetails( + billing_units=sum(x['billing_units'] for x in rate_group), + rate=format_letter_rate(rate_group[0]['rate']), + cost=(sum(x['billing_units'] for x in rate_group) * rate_group[0]['rate']), + postage_description=rate_group[0]['postage'] + ) + + result.append(letter_details) + + return result + + +def get_postage_description(postage): + if postage in ('first', 'second'): + return f'{postage} class' + return 'international' + + +def format_letter_rate(number): + if number >= 1: + return f"£{number:,.2f}" + + return f"{number * 100:.0f}p" + + def get_free_paid_breakdown_for_month( free_sms_fragment_limit, cumulative, diff --git a/app/templates/views/usage.html b/app/templates/views/usage.html index 2ffefa85c..a78c10f79 100644 --- a/app/templates/views/usage.html +++ b/app/templates/views/usage.html @@ -109,8 +109,8 @@ {% endif %} {% for letter in month.letters%} {% if letter.billing_units %} -
  • {{ "{:,} {}".format(letter.billing_units, letter.postage)}} class {{ message_count_label(letter.billing_units, 'letter', '') }}at - {{ '{:.0f}p'.format(letter.rate * 100) }}
  • +
  • {{ "{:,} {}".format(letter.billing_units, letter.postage_description) }} {{ message_count_label(letter.billing_units, 'letter', '') }}at + {{ letter.rate }}
  • {% endif %} {% endfor %} {% if not (month.free or month.paid or month.letters) %} diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index e4633273f..c748f9b92 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -967,7 +967,7 @@ def test_usage_page( assert 'April' in table assert 'February' in table assert 'March' in table - assert '£20.59' in table + assert '£28.99' in table assert '140 free text messages' in table assert '£20.30' in table assert '1,230 text messages at 1.65p' in table @@ -1009,12 +1009,13 @@ def test_usage_page_with_letters( assert 'April' in table assert 'February' in table assert 'March' in table - assert '£20.59' in table + assert '£28.99' in table assert '140 free text messages' in table assert '£20.30' in table assert '1,230 text messages at 1.65p' in table assert '10 second class letters at 31p' in normalize_spaces(table) assert '5 first class letters at 33p' in normalize_spaces(table) + assert '10 international letters at 84p' in normalize_spaces(table) @freeze_time("2012-04-30 12:12:12") @@ -1027,6 +1028,9 @@ def test_usage_page_displays_letters_ordered_by_postage( ): billable_units_resp = [ {'month': 'April', 'notification_type': 'letter', 'rate': 0.5, 'billing_units': 1, 'postage': 'second'}, + {'month': 'April', 'notification_type': 'letter', 'rate': 1, 'billing_units': 1, 'postage': 'europe'}, + {'month': 'April', 'notification_type': 'letter', 'rate': 1, 'billing_units': 2, 'postage': 'rest-of-word'}, + {'month': 'April', 'notification_type': 'letter', 'rate': 1.5, 'billing_units': 7, 'postage': 'europe'}, {'month': 'April', 'notification_type': 'letter', 'rate': 0.3, 'billing_units': 3, 'postage': 'second'}, {'month': 'April', 'notification_type': 'letter', 'rate': 0.5, 'billing_units': 1, 'postage': 'first'}, ] @@ -1040,10 +1044,44 @@ def test_usage_page_displays_letters_ordered_by_postage( row_for_april = page.find('table').find('tr', class_='table-row') postage_details = row_for_april.find_all('li', class_='tabular-numbers') - assert len(postage_details) == 3 + assert len(postage_details) == 5 assert normalize_spaces(postage_details[0].text) == '1 first class letter at 50p' assert normalize_spaces(postage_details[1].text) == '3 second class letters at 30p' assert normalize_spaces(postage_details[2].text) == '1 second class letter at 50p' + assert normalize_spaces(postage_details[3].text) == '3 international letters at £1.00' + assert normalize_spaces(postage_details[4].text) == '7 international letters at £1.50' + + +@freeze_time("2012-07-30 12:12:12") +def test_usage_page_displays_letters_split_by_month_and_postage( + mocker, + client_request, + service_one, + mock_get_usage, + mock_get_free_sms_fragment_limit +): + billable_units_resp = [ + {'month': 'April', 'notification_type': 'letter', 'rate': 0.5, 'billing_units': 1, 'postage': 'second'}, + {'month': 'April', 'notification_type': 'letter', 'rate': 1, 'billing_units': 1, 'postage': 'europe'}, + {'month': 'May', 'notification_type': 'letter', 'rate': 1, 'billing_units': 7, 'postage': 'europe'}, + {'month': 'May', 'notification_type': 'letter', 'rate': 0.5, 'billing_units': 3, 'postage': 'second'}, + {'month': 'May', 'notification_type': 'letter', 'rate': 0.7, 'billing_units': 1, 'postage': 'first'}, + ] + mocker.patch('app.billing_api_client.get_billable_units', return_value=billable_units_resp) + service_one['permissions'].append('letter') + page = client_request.get( + 'main.usage', + service_id=SERVICE_ONE_ID, + ) + + april_row = normalize_spaces(page.find('table').find_all('tr')[1].text) + may_row = normalize_spaces(page.find('table').find_all('tr')[2].text) + + assert '1 second class letter at 50p' in april_row + assert '1 international letter at £1.00' in april_row + assert '1 first class letter at 70p' in may_row + assert '3 second class letters at 50p' in may_row + assert '7 international letters at £1.00' in may_row def test_usage_page_with_year_argument( diff --git a/tests/conftest.py b/tests/conftest.py index 66c5c08e6..88a96f8d4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2627,7 +2627,21 @@ def mock_get_billable_units(mocker): 'rate': 0.33, 'billing_units': 5, 'postage': 'first', - } + }, + { + 'month': 'February', + 'notification_type': 'letter', + 'rate': 0.84, + 'billing_units': 3, + 'postage': 'europe', + }, + { + 'month': 'February', + 'notification_type': 'letter', + 'rate': 0.84, + 'billing_units': 7, + 'postage': 'rest-of-world', + }, ] return mocker.patch(