diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index e7a18fe33..2d59d822f 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -113,7 +113,7 @@ def usage(service_id): start=current_financial_year - 1, end=current_financial_year + 1, ), - **calculate_usage(service_api_client.get_service_usage(service_id, year)['data']) + **calculate_usage(service_api_client.get_service_usage(service_id, year)) ) @@ -189,7 +189,7 @@ def get_dashboard_partials(service_id): **calculate_usage(service_api_client.get_service_usage( service_id, get_current_financial_year(), - )['data']) + )) ), } @@ -204,10 +204,11 @@ def get_dashboard_totals(statistics): def calculate_usage(usage): # TODO: Don't hardcode these - get em from the API sms_free_allowance = 250000 - sms_rate = 0.0165 - sms_sent = usage.get('sms_count', 0) - emails_sent = usage.get('email_count', 0) + sms_rate = 0 if len(usage) == 0 else usage[0].get("rate", 0) + sms_sent = get_sum_billing_units(breakdown for breakdown in usage if breakdown['notification_type'] == 'sms') + emails = [breakdown["billing_units"] for breakdown in usage if breakdown['notification_type'] == 'email'] + emails_sent = 0 if len(emails) == 0 else emails[0] return { 'emails_sent': emails_sent, @@ -215,7 +216,7 @@ def calculate_usage(usage): 'sms_sent': sms_sent, 'sms_allowance_remaining': max(0, (sms_free_allowance - sms_sent)), 'sms_chargeable': max(0, sms_sent - sms_free_allowance), - 'sms_rate': sms_rate + 'sms_rate': sms_rate, } @@ -262,14 +263,21 @@ def get_months_for_year(start, end, year): return [datetime(year, month, 1) for month in range(start, end)] -def get_free_paid_breakdown_for_billable_units(year, billable_units): +def get_sum_billing_units(billing_units, month=None): + if month: + return sum(b['billing_units'] * b.get('rate_multiplier', 1) for b in billing_units if b['month'] == month) + return sum(b['billing_units'] * b.get('rate_multiplier', 1) for b in billing_units) + + +def get_free_paid_breakdown_for_billable_units(year, billing_units): cumulative = 0 for month in get_months_for_financial_year(year): previous_cumulative = cumulative - monthly_usage = billable_units.get(month, 0) + monthly_usage = get_sum_billing_units(billing_units, month) cumulative += monthly_usage breakdown = get_free_paid_breakdown_for_month( - cumulative, previous_cumulative, monthly_usage + cumulative, previous_cumulative, + [billing_month for billing_month in billing_units if billing_month['month'] == month] ) yield { 'name': month, @@ -285,20 +293,23 @@ def get_free_paid_breakdown_for_month( ): allowance = 250000 + total_monthly_billing_units = get_sum_billing_units(monthly_usage) + if cumulative < allowance: return { 'paid': 0, - 'free': monthly_usage, + 'free': total_monthly_billing_units, } elif previous_cumulative < allowance: + remaining_allowance = allowance - previous_cumulative return { - 'paid': monthly_usage - (allowance - previous_cumulative), - 'free': allowance - previous_cumulative + 'paid': total_monthly_billing_units - remaining_allowance, + 'free': remaining_allowance, } else: return { - 'paid': monthly_usage, - 'free': 0 + 'paid': total_monthly_billing_units, + 'free': 0, } diff --git a/app/notify_client/service_api_client.py b/app/notify_client/service_api_client.py index 5394ad1a6..dc48d0043 100644 --- a/app/notify_client/service_api_client.py +++ b/app/notify_client/service_api_client.py @@ -217,7 +217,7 @@ class ServiceAPIClient(NotifyAdminAPIClient): def get_service_usage(self, service_id, year=None): return self.get( - '/service/{0}/fragment/aggregate_statistics'.format(service_id), + '/service/{0}/yearly-usage'.format(service_id), params=dict(year=year) ) @@ -231,7 +231,10 @@ class ServiceAPIClient(NotifyAdminAPIClient): return self.put(url='/service/{}/whitelist'.format(service_id), data=data) def get_billable_units(self, service_id, year): - return self.get(url='/service/{}/billable-units?year={}'.format(service_id, year)) + return self.get( + '/service/{0}/monthly-usage'.format(service_id), + params=dict(year=year) + ) class ServicesBrowsableItem(BrowsableItem): diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index 17c51ce59..b6c8b9404 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -223,21 +223,22 @@ def test_usage_page( assert normalize_spaces(nav_links[0].text) == '2010 to 2011 financial year' assert normalize_spaces(nav.find('li', {'aria-selected': 'true'}).text) == '2011 to 2012 financial year' assert normalize_spaces(nav_links[1].text) == '2012 to 2013 financial year' - - assert '123' in cols[0].text - assert 'Emails' in cols[0].text - - assert '456,123' in cols[1].text + assert '252,190' in cols[1].text assert 'Text messages' in cols[1].text table = page.find('table').text.strip() + assert '249,860 free text messages' in table + assert '40 free text messages' in table + assert '960 text messages at 1.65p' in table + assert 'April' in table + assert 'February' in table assert 'March' in table - assert '123 free text messages' in table - assert '£3,403.06' in table - assert '249,877 free text messages' in table - assert '206,246 text messages at 1.65p' in table + assert '£15.84' in table + assert '140 free text messages' in table + assert '£20.30' in table + assert '1,230 text messages at 1.65p' in table def test_usage_page_with_year_argument( @@ -256,6 +257,18 @@ def test_usage_page_for_invalid_year( assert logged_in_client.get(url_for('main.usage', service_id=SERVICE_ONE_ID, year='abcd')).status_code == 404 +@freeze_time("2012-03-31 12:12:12") +def test_future_usage_page( + logged_in_client, + mock_get_future_usage, + mock_get_future_billable_units, +): + assert logged_in_client.get(url_for('main.usage', service_id=SERVICE_ONE_ID, year=2014)).status_code == 200 + + mock_get_future_billable_units.assert_called_once_with(SERVICE_ONE_ID, 2014) + mock_get_future_usage.assert_called_once_with(SERVICE_ONE_ID, 2014) + + def _test_dashboard_menu(mocker, app_, usr, service, permissions): with app_.test_request_context(): with app_.test_client() as client: @@ -552,26 +565,39 @@ def test_aggregate_status_types(dict_in, expected_failed, expected_requested): ) def test_get_free_paid_breakdown_for_billable_units(now, expected_number_of_months): with now: - assert list(get_free_paid_breakdown_for_billable_units( - 2016, { - 'April': 100000, - 'May': 100000, - 'June': 100000, - 'February': 1234 - } - )) == [ - {'name': 'April', 'free': 100000, 'paid': 0}, - {'name': 'May', 'free': 100000, 'paid': 0}, - {'name': 'June', 'free': 50000, 'paid': 50000}, - {'name': 'July', 'free': 0, 'paid': 0}, - {'name': 'August', 'free': 0, 'paid': 0}, - {'name': 'September', 'free': 0, 'paid': 0}, - {'name': 'October', 'free': 0, 'paid': 0}, - {'name': 'November', 'free': 0, 'paid': 0}, - {'name': 'December', 'free': 0, 'paid': 0}, - {'name': 'January', 'free': 0, 'paid': 0}, - {'name': 'February', 'free': 0, 'paid': 1234}, - {'name': 'March', 'free': 0, 'paid': 0} + billing_units = get_free_paid_breakdown_for_billable_units( + 2016, [ + { + 'month': 'April', 'international': False, 'rate_multiplier': 1, + 'notification_type': 'sms', 'rate': 1.65, 'billing_units': 100000 + }, + { + 'month': 'May', 'international': False, 'rate_multiplier': 1, + 'notification_type': 'sms', 'rate': 1.65, 'billing_units': 100000 + }, + { + 'month': 'June', 'international': False, 'rate_multiplier': 1, + 'notification_type': 'sms', 'rate': 1.65, 'billing_units': 100000 + }, + { + 'month': 'February', 'international': False, 'rate_multiplier': 1, + 'notification_type': 'sms', 'rate': 1.65, 'billing_units': 2000 + }, + ] + ) + assert list(billing_units) == [ + {'free': 100000, 'name': 'April', 'paid': 0}, + {'free': 100000, 'name': 'May', 'paid': 0}, + {'free': 50000, 'name': 'June', 'paid': 50000}, + {'free': 0, 'name': 'July', 'paid': 0}, + {'free': 0, 'name': 'August', 'paid': 0}, + {'free': 0, 'name': 'September', 'paid': 0}, + {'free': 0, 'name': 'October', 'paid': 0}, + {'free': 0, 'name': 'November', 'paid': 0}, + {'free': 0, 'name': 'December', 'paid': 0}, + {'free': 0, 'name': 'January', 'paid': 0}, + {'free': 0, 'name': 'February', 'paid': 2000}, + {'free': 0, 'name': 'March', 'paid': 0} ][:expected_number_of_months] diff --git a/tests/conftest.py b/tests/conftest.py index 8631b1061..75b658a3d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1315,10 +1315,18 @@ def mock_get_template_statistics_for_template(mocker, service_one): @pytest.fixture(scope='function') def mock_get_usage(mocker, service_one, fake_uuid): def _get_usage(service_id, year=None): - return {'data': { - "sms_count": 456123, - "email_count": 123 - }} + return [ + {"international": False, "rate": 0.0165, "rate_multiplier": 1, + "notification_type": "sms", "billing_units": 251500}, + {"international": True, "rate": 0.0165, "rate_multiplier": 1, + "notification_type": "sms", "billing_units": 300}, + {"international": True, "rate": 0.0165, "rate_multiplier": 2, + "notification_type": "sms", "billing_units": 150}, + {"international": True, "rate": 0.0165, "rate_multiplier": 3, + "notification_type": "sms", "billing_units": 30}, + {"international": False, "rate": 0.0165, "notification_type": "email", + "rate_multiplier": None, "billing_units": 1000} + ] return mocker.patch( 'app.service_api_client.get_service_usage', side_effect=_get_usage) @@ -1327,10 +1335,116 @@ def mock_get_usage(mocker, service_one, fake_uuid): @pytest.fixture(scope='function') def mock_get_billable_units(mocker): def _get_usage(service_id, year): - return { - "April": 123, - "March": 456123 - } + return [ + { + 'month': 'April', + 'international': False, + 'rate_multiplier': 1, + 'notification_type': 'sms', + 'rate': 1.65, + 'billing_units': 249500 + }, + { + 'month': 'April', + 'international': True, + 'rate_multiplier': 1, + 'notification_type': 'sms', + 'rate': 1.65, + 'billing_units': 100 + }, + { + 'month': 'April', + 'international': True, + 'rate_multiplier': 2, + 'notification_type': 'sms', + 'rate': 1.65, + 'billing_units': 100 + }, + { + 'month': 'April', + 'international': True, + 'rate_multiplier': 3, + 'notification_type': 'sms', + 'rate': 1.65, + 'billing_units': 20 + }, + { + 'month': 'March', + 'international': False, + 'rate_multiplier': 1, + 'notification_type': 'sms', + 'rate': 1.65, + 'billing_units': 1000 + }, + { + 'month': 'March', + 'international': True, + 'rate_multiplier': 1, + 'notification_type': 'sms', + 'rate': 1.65, + 'billing_units': 100 + }, + { + 'month': 'March', + 'international': True, + 'rate_multiplier': 2, + 'notification_type': 'sms', + 'rate': 1.65, + 'billing_units': 50 + }, + { + 'month': 'March', + 'international': True, + 'rate_multiplier': 3, + 'notification_type': 'sms', + 'rate': 1.65, + 'billing_units': 10 + }, + { + 'month': 'February', + 'international': False, + 'rate_multiplier': 1, + 'notification_type': 'sms', + 'rate': 1.65, + 'billing_units': 1000 + }, + { + 'month': 'February', + 'international': True, + 'rate_multiplier': 1, + 'notification_type': 'sms', + 'rate': 1.65, + 'billing_units': 100 + }, + + ] + + return mocker.patch( + 'app.service_api_client.get_billable_units', side_effect=_get_usage) + + +@pytest.fixture(scope='function') +def mock_get_future_usage(mocker, service_one, fake_uuid): + def _get_usage(service_id, year=None): + return [ + { + 'notification_type': 'sms', 'international': False, + 'credits': 0, 'rate_multiplier': 1, 'rate': 1.58, 'billing_units': 0 + }, + { + 'notification_type': 'email', 'international': False, + 'credits': 0, 'rate_multiplier': 1, 'rate': 0, 'billing_units': 0 + } + ] + + return mocker.patch( + 'app.service_api_client.get_service_usage', side_effect=_get_usage) + + +@pytest.fixture(scope='function') +def mock_get_future_billable_units(mocker): + def _get_usage(service_id, year): + return [] return mocker.patch( 'app.service_api_client.get_billable_units', side_effect=_get_usage)