diff --git a/app/dao/notification_usage_dao.py b/app/dao/notification_usage_dao.py index 7dffaca7a..7d9d5e1d0 100644 --- a/app/dao/notification_usage_dao.py +++ b/app/dao/notification_usage_dao.py @@ -1,6 +1,4 @@ from datetime import datetime -from decimal import Decimal - from sqlalchemy import func from app import db @@ -23,9 +21,9 @@ def get_yearly_billing_data(service_id, year): result = [] for r, n in zip(rates, rates[1:]): result.append( - sms_billing_data_query(str(r.rate), service_id, r.valid_from, n.valid_from)) + sms_billing_data_query(r.rate, service_id, r.valid_from, n.valid_from)) - result.append(sms_billing_data_query(str(rates[-1].rate), service_id, rates[-1].valid_from, end_date)) + result.append(sms_billing_data_query(rates[-1].rate, service_id, rates[-1].valid_from, end_date)) result.append(email_billing_data_query(service_id, start_date, end_date)) @@ -47,7 +45,6 @@ def email_billing_data_query(service_id, start_date, end_date): result = db.session.query( func.count(NotificationHistory.id), NotificationHistory.notification_type, - "0", NotificationHistory.rate_multiplier, NotificationHistory.international, NotificationHistory.phone_prefix @@ -59,17 +56,14 @@ def email_billing_data_query(service_id, start_date, end_date): NotificationHistory.international, NotificationHistory.phone_prefix ).first() - if not result: - return 0, EMAIL_TYPE, Decimal("0"), None, False, None - else: - return result + + return tuple(result) + (0,) def sms_billing_data_query(rate, service_id, start_date, end_date): result = db.session.query( func.sum(NotificationHistory.billable_units), NotificationHistory.notification_type, - rate, NotificationHistory.rate_multiplier, NotificationHistory.international, NotificationHistory.phone_prefix @@ -82,9 +76,9 @@ def sms_billing_data_query(rate, service_id, start_date, end_date): NotificationHistory.phone_prefix ).first() if not result: - return 0, SMS_TYPE, Decimal("0"), None, False, None + return 0, SMS_TYPE, None, False, None, 0 else: - return result + return tuple(result) + (rate,) def get_rates_for_year(start_date, end_date, notification_type): @@ -94,11 +88,10 @@ def get_rates_for_year(start_date, end_date, notification_type): def sms_billing_data_per_month_query(rate, service_id, start_date, end_date): month = get_london_month_from_utc_column(NotificationHistory.created_at) - return db.session.query( + return [result + (rate,) for result in db.session.query( month, func.sum(NotificationHistory.billable_units), NotificationHistory.notification_type, - rate, NotificationHistory.rate_multiplier, NotificationHistory.international, NotificationHistory.phone_prefix @@ -112,15 +105,15 @@ def sms_billing_data_per_month_query(rate, service_id, start_date, end_date): ).order_by( month ).all() + ] def email_billing_data_per_month_query(rate, service_id, start_date, end_date): month = get_london_month_from_utc_column(NotificationHistory.created_at) - return db.session.query( + return [result + (rate,) for result in db.session.query( month, func.count(NotificationHistory.id), NotificationHistory.notification_type, - rate, NotificationHistory.rate_multiplier, NotificationHistory.international, NotificationHistory.phone_prefix @@ -134,18 +127,19 @@ def email_billing_data_per_month_query(rate, service_id, start_date, end_date): ).order_by( month ).all() + ] @statsd(namespace="dao") -def get_notification_billing_data_per_month(service_id, year): +def get_monthly_billing_data(service_id, year): start_date, end_date = get_financial_year(year) rates = get_rates_for_year(start_date, end_date, SMS_TYPE) result = [] for r, n in zip(rates, rates[1:]): - result.extend(sms_billing_data_per_month_query(str(r.rate), service_id, r.valid_from, n.valid_from)) - result.extend(sms_billing_data_per_month_query(str(rates[-1].rate), service_id, rates[-1].valid_from, end_date)) + result.extend(sms_billing_data_per_month_query(r.rate, service_id, r.valid_from, n.valid_from)) + result.extend(sms_billing_data_per_month_query(rates[-1].rate, service_id, rates[-1].valid_from, end_date)) - result.extend(email_billing_data_per_month_query("0", service_id, start_date, end_date)) + result.extend(email_billing_data_per_month_query(0, service_id, start_date, end_date)) return [(datetime.strftime(x[0], "%B"), x[1], x[2], x[3], x[4], x[5], x[6]) for x in result] diff --git a/app/models.py b/app/models.py index 82d9efb43..b6af23aac 100644 --- a/app/models.py +++ b/app/models.py @@ -968,5 +968,5 @@ class Rate(db.Model): id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) valid_from = db.Column(db.DateTime, nullable=False) - rate = db.Column(db.Numeric(), nullable=False) + rate = db.Column(db.Numeric(asdecimal=False), nullable=False) notification_type = db.Column(notification_types, index=True, nullable=False) diff --git a/app/service/rest.py b/app/service/rest.py index 26722373c..8027125bd 100644 --- a/app/service/rest.py +++ b/app/service/rest.py @@ -1,4 +1,5 @@ import itertools +import json from datetime import datetime from flask import ( @@ -8,6 +9,7 @@ from flask import ( ) from sqlalchemy.orm.exc import NoResultFound +from app.dao import notification_usage_dao from app.dao.dao_utils import dao_rollback from app.dao.api_key_dao import ( save_model_api_key, @@ -411,3 +413,39 @@ def get_monthly_template_stats(service_id): )) except ValueError: raise InvalidRequest('Year must be a number', status_code=400) + + +@service_blueprint.route('//yearly-usage') +def get_yearly_billing_usage(service_id): + try: + year = int(request.args.get('year')) + results = notification_usage_dao.get_yearly_billing_data(service_id, year) + json_result = [{"billing_units": x[0], + "notification_type": x[1], + "rate_multiplier": x[2], + "international": x[3], + "phone_prefix": x[4], + "rate": x[5] + } for x in results] + return json.dumps(json_result) + + except TypeError: + return jsonify(result='error', message='No valid year provided'), 400 + + +@service_blueprint.route('//monthly-usage') +def get_yearly_monthly_usage(service_id): + try: + year = int(request.args.get('year')) + results = notification_usage_dao.get_monthly_billing_data(service_id, year) + json_results = [{"month": x[0], + "billing_units": x[1], + "notification_type": x[2], + "rate_multiplier": x[3], + "international": x[4], + "phone_prefix": x[5], + "rate": x[6] + } for x in results] + return json.dumps(json_results) + except TypeError: + return jsonify(result='error', message='No valid year provided'), 400 diff --git a/tests/app/dao/test_notification_usage_dao.py b/tests/app/dao/test_notification_usage_dao.py index 7b53ab6f1..75942f15a 100644 --- a/tests/app/dao/test_notification_usage_dao.py +++ b/tests/app/dao/test_notification_usage_dao.py @@ -1,10 +1,8 @@ import uuid from datetime import datetime -from decimal import Decimal - from app.dao.notification_usage_dao import (get_rates_for_year, get_yearly_billing_data, - get_notification_billing_data_per_month) + get_monthly_billing_data) from app.models import Rate from tests.app.db import create_notification @@ -15,11 +13,11 @@ def test_get_rates_for_year(notify_db, notify_db_session): rates = get_rates_for_year(datetime(2016, 4, 1), datetime(2017, 3, 31), 'sms') assert len(rates) == 1 assert datetime.strftime(rates[0].valid_from, '%Y-%m-%d %H:%M:%S') == "2016-04-01 00:00:00" - assert rates[0].rate == Decimal("1.50") + assert rates[0].rate == 1.50 rates = get_rates_for_year(datetime(2017, 4, 1), datetime(2018, 3, 31), 'sms') assert len(rates) == 1 assert datetime.strftime(rates[0].valid_from, '%Y-%m-%d %H:%M:%S') == "2017-06-01 00:00:00" - assert rates[0].rate == Decimal("1.75") + assert rates[0].rate == 1.75 def test_get_yearly_billing_data(notify_db, notify_db_session, sample_template, sample_email_template): @@ -48,9 +46,9 @@ def test_get_yearly_billing_data(notify_db, notify_db_session, sample_template, status='sending', billable_units=6) results = get_yearly_billing_data(sample_template.service_id, 2016) assert len(results) == 3 - assert results[0] == (3, 'sms', Decimal('1.4'), None, False, None) - assert results[1] == (12, 'sms', Decimal('1.58'), None, False, None) - assert results[2] == (2, 'email', Decimal("0"), None, False, None) + assert results[0] == (3, 'sms', None, False, None, 1.40) + assert results[1] == (12, 'sms', None, False, None, 1.58) + assert results[2] == (2, 'email', None, False, None, 0) def test_get_yearly_billing_data_with_one_rate(notify_db, notify_db_session, sample_template): @@ -76,8 +74,8 @@ def test_get_yearly_billing_data_with_one_rate(notify_db, notify_db_session, sam status='sending', billable_units=7) results = get_yearly_billing_data(sample_template.service_id, 2016) assert len(results) == 2 - assert results[0] == (15, 'sms', Decimal('1.4'), None, False, None) - assert results[1] == (0, 'email', Decimal('0'), None, False, None) + assert results[0] == (15, 'sms', None, False, None, 1.4) + assert results[1] == (0, 'email', None, False, None, 0) def test_get_yearly_billing_data_with_no_sms_notifications(notify_db, notify_db_session, sample_email_template): @@ -89,11 +87,11 @@ def test_get_yearly_billing_data_with_no_sms_notifications(notify_db, notify_db_ results = get_yearly_billing_data(sample_email_template.service_id, 2016) assert len(results) == 2 - assert results[0] == (0, 'sms', Decimal('0'), None, False, None) - assert results[1] == (2, 'email', Decimal('0'), None, False, None) + assert results[0] == (0, 'sms', None, False, None, 0) + assert results[1] == (2, 'email', None, False, None, 0) -def test_get_notification_billing_data_per_month(notify_db, notify_db_session, sample_template, sample_email_template): +def test_get_monthly_billing_data(notify_db, notify_db_session, sample_template, sample_email_template): set_up_rate(notify_db, datetime(2016, 4, 1), 1.40) # previous year create_notification(template=sample_template, created_at=datetime(2016, 3, 31), sent_at=datetime(2016, 3, 31), @@ -114,16 +112,16 @@ def test_get_notification_billing_data_per_month(notify_db, notify_db_session, s # next year create_notification(template=sample_template, created_at=datetime(2017, 3, 31, 23, 00, 00), sent_at=datetime(2017, 3, 31), status='sending', billable_units=6) - results = get_notification_billing_data_per_month(sample_template.service_id, 2016) + results = get_monthly_billing_data(sample_template.service_id, 2016) assert len(results) == 4 - assert results[0] == ('April', 1, 'sms', Decimal('1.4'), None, False, None) - assert results[1] == ('May', 2, 'sms', Decimal('1.4'), None, False, None) - assert results[2] == ('July', 7, 'sms', Decimal('1.4'), None, False, None) - assert results[3] == ('August', 2, 'email', Decimal('0'), None, False, None) + assert results[0] == ('April', 1, 'sms', None, False, None, 1.4) + assert results[1] == ('May', 2, 'sms', None, False, None, 1.4) + assert results[2] == ('July', 7, 'sms', None, False, None, 1.4) + assert results[3] == ('August', 2, 'email', None, False, None, 0) -def test_get_notification_billing_data_per_month_with_multiple_rates(notify_db, notify_db_session, sample_template, - sample_email_template): +def test_get_monthly_billing_data_with_multiple_rates(notify_db, notify_db_session, sample_template, + sample_email_template): set_up_rate(notify_db, datetime(2016, 4, 1), 1.40) set_up_rate(notify_db, datetime(2016, 6, 5), 1.75) # previous year @@ -147,13 +145,13 @@ def test_get_notification_billing_data_per_month_with_multiple_rates(notify_db, # next year create_notification(template=sample_template, created_at=datetime(2017, 3, 31, 23, 00, 00), sent_at=datetime(2017, 3, 31), status='sending', billable_units=6) - results = get_notification_billing_data_per_month(sample_template.service_id, 2016) + results = get_monthly_billing_data(sample_template.service_id, 2016) assert len(results) == 5 - assert results[0] == ('April', 1, 'sms', Decimal('1.4'), None, False, None) - assert results[1] == ('May', 2, 'sms', Decimal('1.4'), None, False, None) - assert results[2] == ('June', 3, 'sms', Decimal('1.4'), None, False, None) - assert results[3] == ('June', 4, 'sms', Decimal('1.75'), None, False, None) - assert results[4] == ('August', 2, 'email', Decimal('0'), None, False, None) + assert results[0] == ('April', 1, 'sms', None, False, None, 1.4) + assert results[1] == ('May', 2, 'sms', None, False, None, 1.4) + assert results[2] == ('June', 3, 'sms', None, False, None, 1.4) + assert results[3] == ('June', 4, 'sms', None, False, None, 1.75) + assert results[4] == ('August', 2, 'email', None, False, None, 0) def set_up_rate(notify_db, start_date, value): diff --git a/tests/app/job/test_rest.py b/tests/app/job/test_rest.py index e5a3cc453..e05d52b3a 100644 --- a/tests/app/job/test_rest.py +++ b/tests/app/job/test_rest.py @@ -205,7 +205,6 @@ def test_should_not_create_scheduled_job_more_then_24_hours_hence(notify_api, sa auth_header = create_authorization_header() headers = [('Content-Type', 'application/json'), auth_header] - print(json.dumps(data)) response = client.post( path, data=json.dumps(data), @@ -240,7 +239,6 @@ def test_should_not_create_scheduled_job_in_the_past(notify_api, sample_template auth_header = create_authorization_header() headers = [('Content-Type', 'application/json'), auth_header] - print(json.dumps(data)) response = client.post( path, data=json.dumps(data), diff --git a/tests/app/service/test_rest.py b/tests/app/service/test_rest.py index 2cf666be4..37331b182 100644 --- a/tests/app/service/test_rest.py +++ b/tests/app/service/test_rest.py @@ -10,7 +10,7 @@ from freezegun import freeze_time from app.dao.users_dao import save_model_user from app.dao.services_dao import dao_remove_user_from_service -from app.models import User, Organisation, DVLA_ORG_LAND_REGISTRY +from app.models import User, Organisation, DVLA_ORG_LAND_REGISTRY, Rate from tests import create_authorization_header from tests.app.conftest import ( sample_service as create_service, @@ -1509,3 +1509,69 @@ def test_get_template_stats_by_month_returns_error_for_incorrect_year( ) assert response.status_code == expected_status assert json.loads(response.get_data(as_text=True)) == expected_json + + +def test_get_yearly_billing_usage(client, notify_db, notify_db_session): + rate = Rate(id=uuid.uuid4(), valid_from=datetime(2016, 3, 31, 23, 00), rate=1.58, notification_type='sms') + notify_db.session.add(rate) + notification = create_sample_notification(notify_db, notify_db_session, created_at=datetime(2016, 6, 5), + sent_at=datetime(2016, 6, 5), + status='sending') + response = client.get( + '/service/{}/yearly-usage?year=2016'.format(notification.service_id), + headers=[create_authorization_header()] + ) + assert response.status_code == 200 + + assert json.loads(response.get_data(as_text=True)) == [{'billing_units': 1, 'notification_type': 'sms', + 'phone_prefix': None, 'international': False, + 'rate_multiplier': None, 'rate': 1.58}, + {'billing_units': 0, 'notification_type': 'email', + 'phone_prefix': None, 'international': False, + 'rate_multiplier': None, 'rate': 0}] + + +def test_get_yearly_billing_usage_returns_400_if_missing_year(client, sample_service): + response = client.get( + '/service/{}/yearly-usage'.format(sample_service.id), + headers=[create_authorization_header()] + ) + assert response.status_code == 400 + assert json.loads(response.get_data(as_text=True)) == { + 'message': 'No valid year provided', 'result': 'error' + } + + +def test_get_monthly_billing_usage(client, notify_db, notify_db_session): + rate = Rate(id=uuid.uuid4(), valid_from=datetime(2016, 3, 31, 23, 00), rate=1.58, notification_type='sms') + notify_db.session.add(rate) + notification = create_sample_notification(notify_db, notify_db_session, created_at=datetime(2016, 6, 5), + sent_at=datetime(2016, 6, 5), + status='sending') + create_sample_notification(notify_db, notify_db_session, created_at=datetime(2016, 7, 5), + sent_at=datetime(2016, 7, 5), + status='sending') + response = client.get( + '/service/{}/monthly-usage?year=2016'.format(notification.service_id), + headers=[create_authorization_header()] + ) + assert response.status_code == 200 + actual = json.loads(response.get_data(as_text=True)) + assert len(actual) == 2 + assert actual == [{'month': 'June', 'billing_units': 1, 'notification_type': 'sms', + 'phone_prefix': None, 'international': False, + 'rate_multiplier': None, 'rate': 1.58}, + {'month': 'July', 'billing_units': 1, 'notification_type': 'sms', + 'phone_prefix': None, 'international': False, + 'rate_multiplier': None, 'rate': 1.58}] + + +def test_get_monthly_billing_usage_returns_400_if_missing_year(client, sample_service): + response = client.get( + '/service/{}/monthly-usage'.format(sample_service.id), + headers=[create_authorization_header()] + ) + assert response.status_code == 400 + assert json.loads(response.get_data(as_text=True)) == { + 'message': 'No valid year provided', 'result': 'error' + } diff --git a/tests/app/v2/test_errors.py b/tests/app/v2/test_errors.py index 5f0ed0f81..8345faacc 100644 --- a/tests/app/v2/test_errors.py +++ b/tests/app/v2/test_errors.py @@ -88,7 +88,6 @@ def test_validation_error(app_for_test): response = client.get(url_for('v2_under_test.raising_validation_error')) assert response.status_code == 400 error = json.loads(response.get_data(as_text=True)) - print(error) assert len(error.keys()) == 2 assert error['status_code'] == 400 assert len(error['errors']) == 2