diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index 02292b400..4efd8272f 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -222,10 +222,10 @@ def get_dashboard_partials(service_id): 'has_jobs': bool(immediate_jobs), 'usage': render_template( 'views/dashboard/_usage.html', - **calculate_usage(service_api_client.get_service_usage( + **calculate_free_tier_usage(service_api_client.get_yearly_sms_unit_count_and_cost( service_id, get_current_financial_year(), - )) + ), service) ), } @@ -237,6 +237,16 @@ def get_dashboard_totals(statistics): return statistics +def calculate_free_tier_usage(usage, service): + sms_free_allowance = service['data']['free_sms_fragment_limit'] + return({ + 'sms_chargeable': max(0, usage['billable_sms_units'] - sms_free_allowance), + 'total_sms_bill': usage['billable_sms_units'], + 'total_sms_cost': usage['total_cost'], + 'sms_allowance_remaining': sms_free_allowance - int(usage['billable_sms_units']) + }) + + def calculate_usage(usage): # TODO: Don't hardcode these - get em from the API sms_free_allowance = 250000 diff --git a/app/notify_client/service_api_client.py b/app/notify_client/service_api_client.py index b5c82656c..da9bb9d34 100644 --- a/app/notify_client/service_api_client.py +++ b/app/notify_client/service_api_client.py @@ -223,6 +223,12 @@ class ServiceAPIClient(NotifyAdminAPIClient): params=dict(year=year) ) + def get_yearly_sms_unit_count_and_cost(self, service_id, year=None): + return self.get( + '/service/{0}/yearly-sms-billable-units'.format(service_id), + params=dict(year=year) + ) + def get_monthly_notification_stats(self, service_id, year): return self.get(url='/service/{}/notifications/monthly?year={}'.format(service_id, year)) diff --git a/app/templates/views/dashboard/_usage.html b/app/templates/views/dashboard/_usage.html index ff0530549..7a908d53c 100644 --- a/app/templates/views/dashboard/_usage.html +++ b/app/templates/views/dashboard/_usage.html @@ -10,7 +10,7 @@
{% if sms_chargeable %} {{ big_number( - (sms_chargeable * sms_rate), + total_sms_cost, 'spent on text messages', currency="£", smaller=True diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index b05299f01..28c29a63d 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -50,6 +50,7 @@ def test_get_started( mock_get_detailed_service, mock_get_usage, mock_get_inbound_sms_summary, + mock_get_yearly_sms_unit_count_and_cost ): mock_template_stats = mocker.patch('app.template_statistics_client.get_template_statistics_for_service', return_value=copy.deepcopy(stub_template_stats)) @@ -69,6 +70,7 @@ def test_get_started_is_hidden_once_templates_exist( mock_get_detailed_service, mock_get_usage, mock_get_inbound_sms_summary, + mock_get_yearly_sms_unit_count_and_cost ): mock_template_stats = mocker.patch('app.template_statistics_client.get_template_statistics_for_service', return_value=copy.deepcopy(stub_template_stats)) @@ -88,6 +90,7 @@ def test_inbound_messages_not_visible_to_service_without_permissions( mock_get_template_statistics, mock_get_usage, mock_get_inbound_sms_summary, + mock_get_yearly_sms_unit_count_and_cost ): service_one['permissions'] = [] @@ -115,6 +118,7 @@ def test_inbound_messages_shows_count_of_messages( mock_get_usage, inbound_summary_mock, expected_text, + mock_get_yearly_sms_unit_count_and_cost ): service_one['permissions'] = ['inbound_sms'] @@ -204,6 +208,7 @@ def test_should_show_recent_templates_on_dashboard( mock_get_detailed_service, mock_get_usage, mock_get_inbound_sms_summary, + mock_get_yearly_sms_unit_count_and_cost ): mock_template_stats = mocker.patch('app.template_statistics_client.get_template_statistics_for_service', return_value=copy.deepcopy(stub_template_stats)) @@ -270,6 +275,7 @@ def test_should_show_upcoming_jobs_on_dashboard( mock_get_jobs, mock_get_usage, mock_get_inbound_sms_summary, + mock_get_yearly_sms_unit_count_and_cost ): response = logged_in_client.get(url_for('main.service_dashboard', service_id=SERVICE_ONE_ID)) @@ -301,6 +307,7 @@ def test_should_show_recent_jobs_on_dashboard( mock_get_jobs, mock_get_usage, mock_get_inbound_sms_summary, + mock_get_yearly_sms_unit_count_and_cost ): response = logged_in_client.get(url_for('main.service_dashboard', service_id=SERVICE_ONE_ID)) @@ -420,6 +427,7 @@ def test_menu_send_messages( mock_get_detailed_service, mock_get_usage, mock_get_inbound_sms_summary, + mock_get_yearly_sms_unit_count_and_cost ): with app_.test_request_context(): resp = _test_dashboard_menu( @@ -451,6 +459,7 @@ def test_menu_manage_service( mock_get_detailed_service, mock_get_usage, mock_get_inbound_sms_summary, + mock_get_yearly_sms_unit_count_and_cost ): with app_.test_request_context(): resp = _test_dashboard_menu( @@ -481,6 +490,7 @@ def test_menu_manage_api_keys( mock_get_detailed_service, mock_get_usage, mock_get_inbound_sms_summary, + mock_get_yearly_sms_unit_count_and_cost ): with app_.test_request_context(): resp = _test_dashboard_menu( @@ -511,6 +521,7 @@ def test_menu_all_services_for_platform_admin_user( mock_get_detailed_service, mock_get_usage, mock_get_inbound_sms_summary, + mock_get_yearly_sms_unit_count_and_cost ): with app_.test_request_context(): resp = _test_dashboard_menu( @@ -541,6 +552,7 @@ def test_route_for_service_permissions( mock_get_detailed_service, mock_get_usage, mock_get_inbound_sms_summary, + mock_get_yearly_sms_unit_count_and_cost ): with app_.test_request_context(): validate_route_permission( @@ -578,6 +590,7 @@ def test_service_dashboard_updates_gets_dashboard_totals( mock_get_jobs, mock_get_usage, mock_get_inbound_sms_summary, + mock_get_yearly_sms_unit_count_and_cost ): mocker.patch('app.main.views.dashboard.get_dashboard_totals', return_value={ 'email': {'requested': 123, 'delivered': 0, 'failed': 0}, @@ -803,6 +816,7 @@ def test_should_show_all_jobs_with_valid_statuses( mock_get_jobs, mock_get_usage, mock_get_inbound_sms_summary, + mock_get_yearly_sms_unit_count_and_cost ): logged_in_client.get(url_for('main.service_dashboard', service_id=SERVICE_ONE_ID)) @@ -819,3 +833,45 @@ def test_should_show_all_jobs_with_valid_statuses( 'ready to send', 'sent to dvla' }) + + +def test_should_show_remaining_free_tier_count( + logged_in_client, + mock_get_service_templates, + mock_get_template_statistics, + mock_get_detailed_service, + mock_get_jobs, + mock_get_usage, + mocker +): + mocker.patch( + 'app.service_api_client.get_yearly_sms_unit_count_and_cost', + return_value={"billable_sms_units": 100, "total_cost": 200.0} + ) + + response = logged_in_client.get(url_for('main.service_dashboard', service_id=SERVICE_ONE_ID)) + + assert response.status_code == 200 + assert '249,900' in response.get_data(as_text=True) + assert 'free text messages left' in response.get_data(as_text=True) + + +def test_should_show_cost_if_exceeded_free_tier_count( + logged_in_client, + mock_get_service_templates, + mock_get_template_statistics, + mock_get_detailed_service, + mock_get_jobs, + mock_get_usage, + mocker +): + mocker.patch( + 'app.service_api_client.get_yearly_sms_unit_count_and_cost', + return_value={"billable_sms_units": 300000, "total_cost": 1500.50} + ) + + response = logged_in_client.get(url_for('main.service_dashboard', service_id=SERVICE_ONE_ID)) + + assert response.status_code == 200 + assert '£1,500.50' in response.get_data(as_text=True) + assert 'spent on text messages' in response.get_data(as_text=True) diff --git a/tests/app/main/views/test_sign_out.py b/tests/app/main/views/test_sign_out.py index 3b550f377..c17f5fa4a 100644 --- a/tests/app/main/views/test_sign_out.py +++ b/tests/app/main/views/test_sign_out.py @@ -23,6 +23,7 @@ def test_sign_out_user( mock_has_permissions, mock_get_template_statistics, mock_get_detailed_service, + mock_get_yearly_sms_unit_count_and_cost, mock_get_usage, mock_get_inbound_sms_summary, ): diff --git a/tests/conftest.py b/tests/conftest.py index 71d6e8588..e8dffef64 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -92,6 +92,7 @@ def mock_get_detailed_service(mocker, api_user_active): return { 'data': { 'id': service_id, + 'free_sms_fragment_limit': 250000, 'statistics': { 'email': {'requested': 0, 'delivered': 0, 'failed': 0}, 'sms': {'requested': 0, 'delivered': 0, 'failed': 0} @@ -109,6 +110,7 @@ def mock_get_detailed_service_for_today(mocker, api_user_active): return { 'data': { 'id': service_id, + 'free_sms_fragment_limit': 250000, 'statistics': { 'email': {'requested': 0, 'delivered': 0, 'failed': 0}, 'sms': {'requested': 0, 'delivered': 0, 'failed': 0} @@ -1398,6 +1400,15 @@ def mock_get_usage(mocker, service_one, fake_uuid): 'app.service_api_client.get_service_usage', side_effect=_get_usage) +@pytest.fixture(scope='function') +def mock_get_yearly_sms_unit_count_and_cost(mocker, service_one, fake_uuid): + def _get_usage(service_id, year=None): + return {"billable_sms_units": 100, "total_cost": 200.0} + + return mocker.patch( + 'app.service_api_client.get_yearly_sms_unit_count_and_cost', side_effect=_get_usage) + + @pytest.fixture(scope='function') def mock_get_billable_units(mocker): def _get_usage(service_id, year):