From 6dc336ad6c3834729bc78474a980bd3b12627726 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Wed, 26 Apr 2017 14:16:47 +0100 Subject: [PATCH 1/8] Created new queries to return the rate with the sum of billable units for the year totals. Once we have the new columns in notifications table, the query will need to include the rate multiplier and if the number is international. The monthly billing query will be built next. --- app/dao/date_util.py | 18 ++++ app/dao/notification_usage_dao.py | 69 ++++++++++++++ app/dao/notifications_dao.py | 21 +---- app/dao/rates_dao.py | 11 --- app/dao/services_dao.py | 9 +- tests/app/dao/test_date_utils.py | 13 +++ tests/app/dao/test_notification_dao.py | 18 +--- tests/app/dao/test_notification_usage_dao.py | 97 ++++++++++++++++++++ tests/app/dao/test_rates_dao.py | 18 ---- tests/conftest.py | 3 +- 10 files changed, 210 insertions(+), 67 deletions(-) create mode 100644 app/dao/date_util.py create mode 100644 app/dao/notification_usage_dao.py delete mode 100644 app/dao/rates_dao.py create mode 100644 tests/app/dao/test_date_utils.py create mode 100644 tests/app/dao/test_notification_usage_dao.py delete mode 100644 tests/app/dao/test_rates_dao.py diff --git a/app/dao/date_util.py b/app/dao/date_util.py new file mode 100644 index 000000000..92932bb4c --- /dev/null +++ b/app/dao/date_util.py @@ -0,0 +1,18 @@ +from datetime import datetime + +import pytz + + +def get_financial_year(year): + return get_april_fools(year), get_april_fools(year + 1) + + +def get_april_fools(year): + """ + This function converts the start of the financial year April 1, 00:00 as BST (British Standard Time) to UTC, + the tzinfo is lastly removed from the datetime becasue the database stores the timestamps without timezone. + :param year: the year to calculate the April 1, 00:00 BST for + :return: the datetime of April 1 for the given year, for example 2016 = 2016-03-31 23:00:00 + """ + return pytz.timezone('Europe/London').localize(datetime(year, 4, 1, 0, 0, 0)).astimezone(pytz.UTC).replace( + tzinfo=None) diff --git a/app/dao/notification_usage_dao.py b/app/dao/notification_usage_dao.py new file mode 100644 index 000000000..fbd53681c --- /dev/null +++ b/app/dao/notification_usage_dao.py @@ -0,0 +1,69 @@ +from decimal import Decimal + +from sqlalchemy import func + +from app import db +from app.dao.date_util import get_financial_year +from app.models import (NotificationHistory, + Rate, + NOTIFICATION_STATUS_TYPES_BILLABLE, + KEY_TYPE_TEST, + SMS_TYPE, + EMAIL_TYPE) +from app.statsd_decorators import statsd + + +@statsd(namespace="dao") +def get_yearly_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.append( + sms_billing_data_query(str(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(email_billing_data_query(service_id, start_date, end_date)) + + return result + + +def billing_data_filter(notification_type, start_date, end_date, service_id): + return [NotificationHistory.notification_type == notification_type, + NotificationHistory.created_at >= start_date, + NotificationHistory.created_at < end_date, + NotificationHistory.service_id == service_id, + NotificationHistory.status.in_(NOTIFICATION_STATUS_TYPES_BILLABLE), + NotificationHistory.key_type != KEY_TYPE_TEST + ] + + +def email_billing_data_query(service_id, start_date, end_date): + result = db.session.query(func.count(NotificationHistory.id), + NotificationHistory.notification_type, + "0" + ).filter(*billing_data_filter(EMAIL_TYPE, start_date, end_date, service_id) + ).group_by(NotificationHistory.notification_type).first() + if not result: + return 0, EMAIL_TYPE, Decimal("0") + else: + return result + + +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 + ).filter(*billing_data_filter(SMS_TYPE, start_date, end_date, service_id) + ).group_by(NotificationHistory.notification_type).first() + if not result: + return 0, SMS_TYPE, Decimal("0") + else: + return result + + +def get_rates_for_year(start_date, end_date, notification_type): + return Rate.query.filter(Rate.valid_from >= start_date, Rate.valid_from < end_date, + Rate.notification_type == notification_type).order_by(Rate.valid_from).all() diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index 377b47e3c..6bb5337f8 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -1,5 +1,4 @@ import functools -import pytz from datetime import ( datetime, timedelta, @@ -12,6 +11,7 @@ from sqlalchemy.orm import joinedload from app import db, create_uuid from app.dao import days_ago +from app.dao.date_util import get_financial_year from app.models import ( Service, Notification, @@ -243,13 +243,15 @@ def get_notifications_for_job(service_id, job_id, filter_dict=None, page=1, page def get_notification_billable_unit_count_per_month(service_id, year): month = get_london_month_from_utc_column(NotificationHistory.created_at) + start_date, end_date = get_financial_year(year) notifications = db.session.query( month, func.sum(NotificationHistory.billable_units) ).filter( NotificationHistory.billable_units != 0, NotificationHistory.service_id == service_id, - NotificationHistory.created_at.between(*get_financial_year(year)), + NotificationHistory.created_at >= start_date, + NotificationHistory.created_at < end_date ).group_by( month ).order_by( @@ -410,21 +412,6 @@ def dao_timeout_notifications(timeout_period_in_seconds): return updated -def get_financial_year(year): - return get_april_fools(year), get_april_fools(year + 1) - - -def get_april_fools(year): - """ - This function converts the start of the financial year April 1, 00:00 as BST (British Standard Time) to UTC, - the tzinfo is lastly removed from the datetime becasue the database stores the timestamps without timezone. - :param year: the year to calculate the April 1, 00:00 BST for - :return: the datetime of April 1 for the given year, for example 2016 = 2016-03-31 23:00:00 - """ - return pytz.timezone('Europe/London').localize(datetime(year, 4, 1, 0, 0, 0)).astimezone(pytz.UTC).replace( - tzinfo=None) - - def get_total_sent_notifications_in_date_range(start_date, end_date, notification_type): result = db.session.query( func.count(NotificationHistory.id).label('count') diff --git a/app/dao/rates_dao.py b/app/dao/rates_dao.py deleted file mode 100644 index 47ed83a4e..000000000 --- a/app/dao/rates_dao.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import desc - -from app import db -from app.models import Rate - - -def get_rate_for_type_and_date(notification_type, date_sent): - return db.session.query(Rate).filter(Rate.notification_type == notification_type, - Rate.valid_from <= date_sent - ).order_by(Rate.valid_from.desc() - ).limit(1).first() diff --git a/app/dao/services_dao.py b/app/dao/services_dao.py index 928b3188b..fe25469da 100644 --- a/app/dao/services_dao.py +++ b/app/dao/services_dao.py @@ -229,6 +229,7 @@ def _stats_for_service_query(service_id): def dao_fetch_monthly_historical_stats_by_template_for_service(service_id, year): month = get_london_month_from_utc_column(NotificationHistory.created_at) + start_date, end_date = get_financial_year(year) sq = db.session.query( NotificationHistory.template_id, NotificationHistory.status, @@ -236,7 +237,9 @@ def dao_fetch_monthly_historical_stats_by_template_for_service(service_id, year) func.count().label('count') ).filter( NotificationHistory.service_id == service_id, - NotificationHistory.created_at.between(*get_financial_year(year)) + NotificationHistory.created_at >= start_date, + NotificationHistory.created_at < end_date + ).group_by( month, NotificationHistory.template_id, @@ -262,6 +265,7 @@ def dao_fetch_monthly_historical_stats_by_template_for_service(service_id, year) def dao_fetch_monthly_historical_stats_for_service(service_id, year): month = get_london_month_from_utc_column(NotificationHistory.created_at) + start_date, end_date = get_financial_year(year) rows = db.session.query( NotificationHistory.notification_type, NotificationHistory.status, @@ -269,7 +273,8 @@ def dao_fetch_monthly_historical_stats_for_service(service_id, year): func.count(NotificationHistory.id).label('count') ).filter( NotificationHistory.service_id == service_id, - NotificationHistory.created_at.between(*get_financial_year(year)), + NotificationHistory.created_at >= start_date, + NotificationHistory.created_at < end_date ).group_by( NotificationHistory.notification_type, NotificationHistory.status, diff --git a/tests/app/dao/test_date_utils.py b/tests/app/dao/test_date_utils.py new file mode 100644 index 000000000..d6be85da2 --- /dev/null +++ b/tests/app/dao/test_date_utils.py @@ -0,0 +1,13 @@ +from app.dao.date_util import get_financial_year, get_april_fools + + +def test_get_financial_year(): + start, end = get_financial_year(2000) + assert str(start) == '2000-03-31 23:00:00' + assert str(end) == '2001-03-31 23:00:00' + + +def test_get_april_fools(): + april_fools = get_april_fools(2016) + assert str(april_fools) == '2016-03-31 23:00:00' + assert april_fools.tzinfo is None diff --git a/tests/app/dao/test_notification_dao.py b/tests/app/dao/test_notification_dao.py index 8f67d2335..b5461e036 100644 --- a/tests/app/dao/test_notification_dao.py +++ b/tests/app/dao/test_notification_dao.py @@ -3,7 +3,6 @@ import uuid from functools import partial import pytest - from freezegun import freeze_time from sqlalchemy.exc import SQLAlchemyError, IntegrityError @@ -18,8 +17,7 @@ from app.models import ( NOTIFICATION_SENT, KEY_TYPE_NORMAL, KEY_TYPE_TEAM, - KEY_TYPE_TEST -) + KEY_TYPE_TEST) from app.dao.notifications_dao import ( dao_create_notification, @@ -40,8 +38,6 @@ from app.dao.notifications_dao import ( update_notification_status_by_reference, dao_delete_notifications_and_history_by_id, dao_timeout_notifications, - get_financial_year, - get_april_fools, is_delivery_slow_for_provider, dao_update_notifications_sent_to_dvla) @@ -1359,18 +1355,6 @@ def test_should_exclude_test_key_notifications_by_default( assert len(all_notifications) == 1 -def test_get_financial_year(): - start, end = get_financial_year(2000) - assert str(start) == '2000-03-31 23:00:00' - assert str(end) == '2001-03-31 23:00:00' - - -def test_get_april_fools(): - april_fools = get_april_fools(2016) - assert str(april_fools) == '2016-03-31 23:00:00' - assert april_fools.tzinfo is None - - @pytest.mark.parametrize('notification_type', ['sms', 'email']) def test_get_total_sent_notifications_in_date_range_returns_only_in_date_range( notify_db, diff --git a/tests/app/dao/test_notification_usage_dao.py b/tests/app/dao/test_notification_usage_dao.py new file mode 100644 index 000000000..39525338b --- /dev/null +++ b/tests/app/dao/test_notification_usage_dao.py @@ -0,0 +1,97 @@ +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) +from app.models import Rate +from tests.app.db import create_notification + + +def test_get_rates_for_year(notify_db, notify_db_session): + set_up_rate(notify_db, datetime(2016, 4, 1), 1.50) + set_up_rate(notify_db, datetime(2017, 6, 1), 1.75) + 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") + 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") + + +def test_get_yearly_billing_data(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, 1), 1.58) + # previous year + create_notification(template=sample_template, created_at=datetime(2016, 3, 31), sent_at=datetime(2016, 3, 31), + status='sending', billable_units=1) + # current year + create_notification(template=sample_template, created_at=datetime(2016, 4, 2), sent_at=datetime(2016, 4, 2), + status='sending', billable_units=1) + create_notification(template=sample_template, created_at=datetime(2016, 5, 18), sent_at=datetime(2016, 5, 18), + status='sending', billable_units=2) + create_notification(template=sample_template, created_at=datetime(2016, 7, 22), sent_at=datetime(2016, 7, 22), + status='sending', billable_units=3) + create_notification(template=sample_template, created_at=datetime(2016, 9, 15), sent_at=datetime(2016, 9, 15), + status='sending', billable_units=4) + create_notification(template=sample_template, created_at=datetime(2017, 3, 31), sent_at=datetime(2017, 3, 31), + status='sending', billable_units=5) + create_notification(template=sample_email_template, created_at=datetime(2016, 9, 15), sent_at=datetime(2016, 9, 15), + status='sending', billable_units=0) + create_notification(template=sample_email_template, created_at=datetime(2017, 3, 31), sent_at=datetime(2017, 3, 31), + status='sending', billable_units=0) + # next year + create_notification(template=sample_template, created_at=datetime(2017, 4, 1), sent_at=datetime(2017, 4, 1), + 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')) + assert results[1] == (12, 'sms', Decimal('1.58')) + assert results[2] == (2, 'email', Decimal("0")) + + +def test_get_yearly_billing_data_with_one_rate(notify_db, notify_db_session, sample_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), + status='sending', billable_units=1) + # current year + create_notification(template=sample_template, created_at=datetime(2016, 4, 2), sent_at=datetime(2016, 4, 2), + status='sending', billable_units=1) + create_notification(template=sample_template, created_at=datetime(2016, 5, 18), sent_at=datetime(2016, 5, 18), + status='sending', billable_units=2) + create_notification(template=sample_template, created_at=datetime(2016, 7, 22), sent_at=datetime(2016, 7, 22), + status='sending', billable_units=3) + create_notification(template=sample_template, created_at=datetime(2016, 9, 15), sent_at=datetime(2016, 9, 15), + status='sending', billable_units=4) + create_notification(template=sample_template, created_at=datetime(2017, 3, 31, 22, 59, 59), + sent_at=datetime(2017, 3, 31), status='sending', billable_units=5) + # 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) + create_notification(template=sample_template, created_at=datetime(2017, 4, 1), sent_at=datetime(2017, 4, 1), + 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')) + assert results[1] == (0, 'email', Decimal('0')) + + +def test_get_yearly_billing_data_with_no_sms_notifications(notify_db, notify_db_session, sample_email_template): + set_up_rate(notify_db, datetime(2016, 4, 1), 1.40) + create_notification(template=sample_email_template, created_at=datetime(2016, 7, 31), sent_at=datetime(2016, 3, 31), + status='sending', billable_units=0) + create_notification(template=sample_email_template, created_at=datetime(2016, 10, 2), sent_at=datetime(2016, 4, 2), + status='sending', billable_units=0) + + results = get_yearly_billing_data(sample_email_template.service_id, 2016) + assert len(results) == 2 + assert results[0] == (0, 'sms', Decimal('0')) + assert results[1] == (2, 'email', Decimal('0')) + + +def set_up_rate(notify_db, start_date, value): + rate = Rate(id=uuid.uuid4(), valid_from=start_date, rate=value, notification_type='sms') + notify_db.session.add(rate) diff --git a/tests/app/dao/test_rates_dao.py b/tests/app/dao/test_rates_dao.py deleted file mode 100644 index 33f68a671..000000000 --- a/tests/app/dao/test_rates_dao.py +++ /dev/null @@ -1,18 +0,0 @@ -from datetime import datetime - -from decimal import Decimal - -from app.dao.rates_dao import get_rate_for_type_and_date - - -def test_get_rate_for_type_and_date(notify_db): - rate = get_rate_for_type_and_date('sms', datetime.utcnow()) - assert rate.rate == Decimal("1.58") - - rate = get_rate_for_type_and_date('sms', datetime(2016, 6, 1)) - assert rate.rate == Decimal("1.65") - - -def test_get_rate_for_type_and_date_early_date(notify_db): - rate = get_rate_for_type_and_date('sms', datetime(2014, 6, 1)) - assert not rate diff --git a/tests/conftest.py b/tests/conftest.py index c7ec7ee3b..b08059725 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -76,8 +76,7 @@ def notify_db_session(notify_db): "job_status", "provider_details_history", "template_process_type", - "dvla_organisation", - "rates"]: + "dvla_organisation"]: notify_db.engine.execute(tbl.delete()) notify_db.session.commit() From 4c37c8bdbb2ae74f0ae9b5b44d5b39930e785dd9 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Wed, 26 Apr 2017 15:31:25 +0100 Subject: [PATCH 2/8] New query to get billing data per month. --- app/dao/notification_usage_dao.py | 92 ++++++++++++++++---- tests/app/dao/test_notification_usage_dao.py | 66 +++++++++++++- 2 files changed, 140 insertions(+), 18 deletions(-) diff --git a/app/dao/notification_usage_dao.py b/app/dao/notification_usage_dao.py index fbd53681c..b1db43329 100644 --- a/app/dao/notification_usage_dao.py +++ b/app/dao/notification_usage_dao.py @@ -1,3 +1,4 @@ +from datetime import datetime from decimal import Decimal from sqlalchemy import func @@ -11,6 +12,7 @@ from app.models import (NotificationHistory, SMS_TYPE, EMAIL_TYPE) from app.statsd_decorators import statsd +from app.utils import get_london_month_from_utc_column @statsd(namespace="dao") @@ -31,21 +33,26 @@ def get_yearly_billing_data(service_id, year): def billing_data_filter(notification_type, start_date, end_date, service_id): - return [NotificationHistory.notification_type == notification_type, - NotificationHistory.created_at >= start_date, - NotificationHistory.created_at < end_date, - NotificationHistory.service_id == service_id, - NotificationHistory.status.in_(NOTIFICATION_STATUS_TYPES_BILLABLE), - NotificationHistory.key_type != KEY_TYPE_TEST - ] + return [ + NotificationHistory.notification_type == notification_type, + NotificationHistory.created_at >= start_date, + NotificationHistory.created_at < end_date, + NotificationHistory.service_id == service_id, + NotificationHistory.status.in_(NOTIFICATION_STATUS_TYPES_BILLABLE), + NotificationHistory.key_type != KEY_TYPE_TEST + ] def email_billing_data_query(service_id, start_date, end_date): - result = db.session.query(func.count(NotificationHistory.id), - NotificationHistory.notification_type, - "0" - ).filter(*billing_data_filter(EMAIL_TYPE, start_date, end_date, service_id) - ).group_by(NotificationHistory.notification_type).first() + result = db.session.query( + func.count(NotificationHistory.id), + NotificationHistory.notification_type, + "0" + ).filter( + *billing_data_filter(EMAIL_TYPE, start_date, end_date, service_id) + ).group_by( + NotificationHistory.notification_type + ).first() if not result: return 0, EMAIL_TYPE, Decimal("0") else: @@ -53,11 +60,15 @@ def email_billing_data_query(service_id, start_date, end_date): 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 - ).filter(*billing_data_filter(SMS_TYPE, start_date, end_date, service_id) - ).group_by(NotificationHistory.notification_type).first() + result = db.session.query( + func.sum(NotificationHistory.billable_units), + NotificationHistory.notification_type, + rate + ).filter( + *billing_data_filter(SMS_TYPE, start_date, end_date, service_id) + ).group_by( + NotificationHistory.notification_type + ).first() if not result: return 0, SMS_TYPE, Decimal("0") else: @@ -67,3 +78,50 @@ def sms_billing_data_query(rate, service_id, start_date, end_date): def get_rates_for_year(start_date, end_date, notification_type): return Rate.query.filter(Rate.valid_from >= start_date, Rate.valid_from < end_date, Rate.notification_type == notification_type).order_by(Rate.valid_from).all() + + +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( + month, + func.sum(NotificationHistory.billable_units), + NotificationHistory.notification_type, + rate + ).filter( + *billing_data_filter(SMS_TYPE, start_date, end_date, service_id) + ).group_by( + NotificationHistory.notification_type, month + ).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( + month, + func.count(NotificationHistory.id), + NotificationHistory.notification_type, + rate + ).filter( + *billing_data_filter(EMAIL_TYPE, start_date, end_date, service_id) + ).group_by( + NotificationHistory.notification_type, month + ).order_by( + month + ).all() + + +@statsd(namespace="dao") +def get_notification_billing_data_per_month(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(email_billing_data_per_month_query("0", service_id, start_date, end_date)) + + return [(datetime.strftime(x[0], "%B"), x[1:]) for x in result] diff --git a/tests/app/dao/test_notification_usage_dao.py b/tests/app/dao/test_notification_usage_dao.py index 39525338b..0386d5f85 100644 --- a/tests/app/dao/test_notification_usage_dao.py +++ b/tests/app/dao/test_notification_usage_dao.py @@ -3,7 +3,8 @@ from datetime import datetime from decimal import Decimal -from app.dao.notification_usage_dao import (get_rates_for_year, get_yearly_billing_data) +from app.dao.notification_usage_dao import (get_rates_for_year, get_yearly_billing_data, + get_notification_billing_data_per_month) from app.models import Rate from tests.app.db import create_notification @@ -92,6 +93,69 @@ def test_get_yearly_billing_data_with_no_sms_notifications(notify_db, notify_db_ assert results[1] == (2, 'email', Decimal('0')) +def test_get_notification_billing_data_per_month(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), + status='sending', billable_units=1) + # current year + create_notification(template=sample_template, created_at=datetime(2016, 4, 2), sent_at=datetime(2016, 4, 2), + status='sending', billable_units=1) + create_notification(template=sample_template, created_at=datetime(2016, 5, 18), sent_at=datetime(2016, 5, 18), + status='sending', billable_units=2) + create_notification(template=sample_template, created_at=datetime(2016, 7, 22), sent_at=datetime(2016, 7, 22), + status='sending', billable_units=3) + create_notification(template=sample_template, created_at=datetime(2016, 7, 30), sent_at=datetime(2016, 7, 22), + status='sending', billable_units=4) + create_notification(template=sample_email_template, created_at=datetime(2016, 8, 22), sent_at=datetime(2016, 7, 22), + status='sending', billable_units=0) + create_notification(template=sample_email_template, created_at=datetime(2016, 8, 30), sent_at=datetime(2016, 7, 22), + status='sending', billable_units=0) + # 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) + assert len(results) == 4 + assert results[0] == ('April', (1, 'sms', Decimal('1.4'))) + assert results[1] == ('May', (2, 'sms', Decimal('1.4'))) + assert results[2] == ('July', (7, 'sms', Decimal('1.4'))) + assert results[3] == ('August', (2, 'email', Decimal('0'))) + + +def test_get_notification_billing_data_per_month_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 + create_notification(template=sample_template, created_at=datetime(2016, 3, 31), sent_at=datetime(2016, 3, 31), + status='sending', billable_units=1) + # current year + create_notification(template=sample_template, created_at=datetime(2016, 4, 2), sent_at=datetime(2016, 4, 2), + status='sending', billable_units=1) + create_notification(template=sample_template, created_at=datetime(2016, 5, 18), sent_at=datetime(2016, 5, 18), + status='sending', billable_units=2) + create_notification(template=sample_template, created_at=datetime(2016, 6, 1), sent_at=datetime(2016, 6, 1), + status='sending', billable_units=3) + create_notification(template=sample_template, created_at=datetime(2016, 6, 15), sent_at=datetime(2016, 6, 15), + status='sending', billable_units=4) + create_notification(template=sample_email_template, created_at=datetime(2016, 8, 22), + sent_at=datetime(2016, 7, 22), + status='sending', billable_units=0) + create_notification(template=sample_email_template, created_at=datetime(2016, 8, 30), + sent_at=datetime(2016, 7, 22), + status='sending', billable_units=0) + # 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) + assert len(results) == 5 + assert results[0] == ('April', (1, 'sms', Decimal('1.4'))) + assert results[1] == ('May', (2, 'sms', Decimal('1.4'))) + assert results[2] == ('June', (3, 'sms', Decimal('1.4'))) + assert results[3] == ('June', (4, 'sms', Decimal('1.75'))) + assert results[4] == ('August', (2, 'email', Decimal('0'))) + + def set_up_rate(notify_db, start_date, value): rate = Rate(id=uuid.uuid4(), valid_from=start_date, rate=value, notification_type='sms') notify_db.session.add(rate) From e1e55edd9cae767b2dfa7c3eaedc075263240d37 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Wed, 26 Apr 2017 15:57:11 +0100 Subject: [PATCH 3/8] Add new fields to the usage queries: rate_multiplier, international, phone_prefix. --- app/dao/notification_usage_dao.py | 46 +++++++++++++++----- tests/app/dao/test_notification_usage_dao.py | 32 +++++++------- 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/app/dao/notification_usage_dao.py b/app/dao/notification_usage_dao.py index b1db43329..7dffaca7a 100644 --- a/app/dao/notification_usage_dao.py +++ b/app/dao/notification_usage_dao.py @@ -47,14 +47,20 @@ def email_billing_data_query(service_id, start_date, end_date): result = db.session.query( func.count(NotificationHistory.id), NotificationHistory.notification_type, - "0" + "0", + NotificationHistory.rate_multiplier, + NotificationHistory.international, + NotificationHistory.phone_prefix ).filter( *billing_data_filter(EMAIL_TYPE, start_date, end_date, service_id) ).group_by( - NotificationHistory.notification_type + NotificationHistory.notification_type, + NotificationHistory.rate_multiplier, + NotificationHistory.international, + NotificationHistory.phone_prefix ).first() if not result: - return 0, EMAIL_TYPE, Decimal("0") + return 0, EMAIL_TYPE, Decimal("0"), None, False, None else: return result @@ -63,14 +69,20 @@ 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 + rate, + NotificationHistory.rate_multiplier, + NotificationHistory.international, + NotificationHistory.phone_prefix ).filter( *billing_data_filter(SMS_TYPE, start_date, end_date, service_id) ).group_by( - NotificationHistory.notification_type + NotificationHistory.notification_type, + NotificationHistory.rate_multiplier, + NotificationHistory.international, + NotificationHistory.phone_prefix ).first() if not result: - return 0, SMS_TYPE, Decimal("0") + return 0, SMS_TYPE, Decimal("0"), None, False, None else: return result @@ -86,11 +98,17 @@ def sms_billing_data_per_month_query(rate, service_id, start_date, end_date): month, func.sum(NotificationHistory.billable_units), NotificationHistory.notification_type, - rate + rate, + NotificationHistory.rate_multiplier, + NotificationHistory.international, + NotificationHistory.phone_prefix ).filter( *billing_data_filter(SMS_TYPE, start_date, end_date, service_id) ).group_by( - NotificationHistory.notification_type, month + NotificationHistory.notification_type, month, + NotificationHistory.rate_multiplier, + NotificationHistory.international, + NotificationHistory.phone_prefix ).order_by( month ).all() @@ -102,11 +120,17 @@ def email_billing_data_per_month_query(rate, service_id, start_date, end_date): month, func.count(NotificationHistory.id), NotificationHistory.notification_type, - rate + rate, + NotificationHistory.rate_multiplier, + NotificationHistory.international, + NotificationHistory.phone_prefix ).filter( *billing_data_filter(EMAIL_TYPE, start_date, end_date, service_id) ).group_by( - NotificationHistory.notification_type, month + NotificationHistory.notification_type, month, + NotificationHistory.rate_multiplier, + NotificationHistory.international, + NotificationHistory.phone_prefix ).order_by( month ).all() @@ -124,4 +148,4 @@ def get_notification_billing_data_per_month(service_id, year): result.extend(email_billing_data_per_month_query("0", service_id, start_date, end_date)) - return [(datetime.strftime(x[0], "%B"), x[1:]) for x in result] + 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/tests/app/dao/test_notification_usage_dao.py b/tests/app/dao/test_notification_usage_dao.py index 0386d5f85..7b53ab6f1 100644 --- a/tests/app/dao/test_notification_usage_dao.py +++ b/tests/app/dao/test_notification_usage_dao.py @@ -48,9 +48,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')) - assert results[1] == (12, 'sms', Decimal('1.58')) - assert results[2] == (2, 'email', Decimal("0")) + 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) def test_get_yearly_billing_data_with_one_rate(notify_db, notify_db_session, sample_template): @@ -76,8 +76,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')) - assert results[1] == (0, 'email', Decimal('0')) + assert results[0] == (15, 'sms', Decimal('1.4'), None, False, None) + assert results[1] == (0, 'email', Decimal('0'), None, False, None) def test_get_yearly_billing_data_with_no_sms_notifications(notify_db, notify_db_session, sample_email_template): @@ -89,8 +89,8 @@ 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')) - assert results[1] == (2, 'email', Decimal('0')) + assert results[0] == (0, 'sms', Decimal('0'), None, False, None) + assert results[1] == (2, 'email', Decimal('0'), None, False, None) def test_get_notification_billing_data_per_month(notify_db, notify_db_session, sample_template, sample_email_template): @@ -116,10 +116,10 @@ def test_get_notification_billing_data_per_month(notify_db, notify_db_session, s sent_at=datetime(2017, 3, 31), status='sending', billable_units=6) results = get_notification_billing_data_per_month(sample_template.service_id, 2016) assert len(results) == 4 - assert results[0] == ('April', (1, 'sms', Decimal('1.4'))) - assert results[1] == ('May', (2, 'sms', Decimal('1.4'))) - assert results[2] == ('July', (7, 'sms', Decimal('1.4'))) - assert results[3] == ('August', (2, 'email', Decimal('0'))) + 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) def test_get_notification_billing_data_per_month_with_multiple_rates(notify_db, notify_db_session, sample_template, @@ -149,11 +149,11 @@ def test_get_notification_billing_data_per_month_with_multiple_rates(notify_db, sent_at=datetime(2017, 3, 31), status='sending', billable_units=6) results = get_notification_billing_data_per_month(sample_template.service_id, 2016) assert len(results) == 5 - assert results[0] == ('April', (1, 'sms', Decimal('1.4'))) - assert results[1] == ('May', (2, 'sms', Decimal('1.4'))) - assert results[2] == ('June', (3, 'sms', Decimal('1.4'))) - assert results[3] == ('June', (4, 'sms', Decimal('1.75'))) - assert results[4] == ('August', (2, 'email', Decimal('0'))) + 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) def set_up_rate(notify_db, start_date, value): From a186fc95be918e891344b04d517a37eff413baff Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Thu, 27 Apr 2017 10:00:09 +0100 Subject: [PATCH 4/8] Added new endpoints to return the yearly-usage and monthly-usage for a given financial year and service id. Since the response has changed I have created new endpoints so that the deployments for Admin are more managable. Removed print statements from some tests. --- app/dao/notification_usage_dao.py | 34 ++++------ app/models.py | 2 +- app/service/rest.py | 38 +++++++++++ tests/app/dao/test_notification_usage_dao.py | 50 +++++++------- tests/app/job/test_rest.py | 2 - tests/app/service/test_rest.py | 68 +++++++++++++++++++- tests/app/v2/test_errors.py | 1 - 7 files changed, 144 insertions(+), 51 deletions(-) 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 From fdbadf967e4541a8933aeb3fc264b354ea89212b Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Thu, 27 Apr 2017 10:16:29 +0100 Subject: [PATCH 5/8] Fix the email billing data when there is no results. --- app/dao/notification_usage_dao.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/dao/notification_usage_dao.py b/app/dao/notification_usage_dao.py index 7d9d5e1d0..e07f575e0 100644 --- a/app/dao/notification_usage_dao.py +++ b/app/dao/notification_usage_dao.py @@ -56,8 +56,10 @@ def email_billing_data_query(service_id, start_date, end_date): NotificationHistory.international, NotificationHistory.phone_prefix ).first() - - return tuple(result) + (0,) + if not result: + return 0, EMAIL_TYPE, None, False, None, 0 + else: + return tuple(result) + (0,) def sms_billing_data_query(rate, service_id, start_date, end_date): From 1a64509186410c40f6da9daaea3fa1e56b948761 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Thu, 27 Apr 2017 15:43:57 +0100 Subject: [PATCH 6/8] Change the resultset from the yearly and monthly billing data queries. Fix some formatting of the return objects. --- app/dao/notification_usage_dao.py | 119 +++++++++---------- app/models.py | 4 +- app/service/rest.py | 17 ++- tests/app/dao/test_notification_usage_dao.py | 44 ++++--- tests/app/db.py | 8 +- tests/app/service/test_rest.py | 37 ++++-- 6 files changed, 121 insertions(+), 108 deletions(-) diff --git a/app/dao/notification_usage_dao.py b/app/dao/notification_usage_dao.py index e07f575e0..fb412d849 100644 --- a/app/dao/notification_usage_dao.py +++ b/app/dao/notification_usage_dao.py @@ -1,5 +1,8 @@ from datetime import datetime -from sqlalchemy import func + +from sqlalchemy import Float, Integer +from sqlalchemy import func, case, cast +from sqlalchemy import literal_column from app import db from app.dao.date_util import get_financial_year @@ -21,13 +24,26 @@ def get_yearly_billing_data(service_id, year): result = [] for r, n in zip(rates, rates[1:]): result.append( - sms_billing_data_query(r.rate, service_id, r.valid_from, n.valid_from)) + sms_yearly_billing_data_query(r.rate, service_id, r.valid_from, n.valid_from)) - result.append(sms_billing_data_query(rates[-1].rate, service_id, rates[-1].valid_from, end_date)) + result.append(sms_yearly_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)) + result.append(email_yearly_billing_data_query(service_id, start_date, end_date)) - return result + return sum(result, []) + + +@statsd(namespace="dao") +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(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)) + + return [(datetime.strftime(x[0], "%B"), x[1], x[2], x[3], x[4], x[5]) for x in result] def billing_data_filter(notification_type, start_date, end_date, service_id): @@ -41,46 +57,49 @@ def billing_data_filter(notification_type, start_date, end_date, service_id): ] -def email_billing_data_query(service_id, start_date, end_date): +def email_yearly_billing_data_query(service_id, start_date, end_date, rate=0): result = db.session.query( func.count(NotificationHistory.id), + func.count(NotificationHistory.id), + rate_multiplier(), NotificationHistory.notification_type, - NotificationHistory.rate_multiplier, NotificationHistory.international, - NotificationHistory.phone_prefix + cast(rate, Integer()) ).filter( *billing_data_filter(EMAIL_TYPE, start_date, end_date, service_id) ).group_by( NotificationHistory.notification_type, - NotificationHistory.rate_multiplier, - NotificationHistory.international, - NotificationHistory.phone_prefix + rate_multiplier(), + NotificationHistory.international ).first() if not result: - return 0, EMAIL_TYPE, None, False, None, 0 + return [(0, 0, 1, EMAIL_TYPE, False, 0)] else: - return tuple(result) + (0,) + return [result] -def sms_billing_data_query(rate, service_id, start_date, end_date): +def sms_yearly_billing_data_query(rate, service_id, start_date, end_date): result = db.session.query( + cast(func.sum(NotificationHistory.billable_units * rate_multiplier()), Integer()), func.sum(NotificationHistory.billable_units), + rate_multiplier(), NotificationHistory.notification_type, - NotificationHistory.rate_multiplier, NotificationHistory.international, - NotificationHistory.phone_prefix + cast(rate, Float()) ).filter( *billing_data_filter(SMS_TYPE, start_date, end_date, service_id) ).group_by( NotificationHistory.notification_type, - NotificationHistory.rate_multiplier, NotificationHistory.international, - NotificationHistory.phone_prefix - ).first() + rate_multiplier() + ).order_by( + rate_multiplier() + ).all() + if not result: - return 0, SMS_TYPE, None, False, None, 0 + return [(0, 0, 1, SMS_TYPE, False, rate)] else: - return tuple(result) + (rate,) + return result def get_rates_for_year(start_date, end_date, notification_type): @@ -90,58 +109,30 @@ 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 [result + (rate,) for result in db.session.query( + result = db.session.query( month, func.sum(NotificationHistory.billable_units), - NotificationHistory.notification_type, - NotificationHistory.rate_multiplier, + rate_multiplier(), NotificationHistory.international, - NotificationHistory.phone_prefix + NotificationHistory.notification_type, + cast(rate, Float()) ).filter( *billing_data_filter(SMS_TYPE, start_date, end_date, service_id) ).group_by( - NotificationHistory.notification_type, month, - NotificationHistory.rate_multiplier, - NotificationHistory.international, - NotificationHistory.phone_prefix - ).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 [result + (rate,) for result in db.session.query( - month, - func.count(NotificationHistory.id), NotificationHistory.notification_type, + month, NotificationHistory.rate_multiplier, - NotificationHistory.international, - NotificationHistory.phone_prefix - ).filter( - *billing_data_filter(EMAIL_TYPE, start_date, end_date, service_id) - ).group_by( - NotificationHistory.notification_type, month, - NotificationHistory.rate_multiplier, - NotificationHistory.international, - NotificationHistory.phone_prefix + NotificationHistory.international ).order_by( - month + month, + rate_multiplier() ).all() - ] + + return result -@statsd(namespace="dao") -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(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)) - - return [(datetime.strftime(x[0], "%B"), x[1], x[2], x[3], x[4], x[5], x[6]) for x in result] +def rate_multiplier(): + return cast(case([ + (NotificationHistory.rate_multiplier == None, literal_column("'1'")), # noqa + (NotificationHistory.rate_multiplier != None, NotificationHistory.rate_multiplier), # noqa + ]), Integer()) diff --git a/app/models.py b/app/models.py index b6af23aac..59e6f7a91 100644 --- a/app/models.py +++ b/app/models.py @@ -847,7 +847,7 @@ class NotificationHistory(db.Model, HistoryModel): international = db.Column(db.Boolean, nullable=False, default=False) phone_prefix = db.Column(db.String, nullable=True) - rate_multiplier = db.Column(db.Float(), nullable=True) + rate_multiplier = db.Column(db.Float(asdecimal=False), nullable=True) @classmethod def from_original(cls, notification): @@ -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(asdecimal=False), nullable=False) + rate = db.Column(db.Float(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 8027125bd..25991f30d 100644 --- a/app/service/rest.py +++ b/app/service/rest.py @@ -420,11 +420,11 @@ 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], + json_result = [{"credits": x[0], + "billing_units": x[1], "rate_multiplier": x[2], - "international": x[3], - "phone_prefix": x[4], + "notification_type": x[3], + "international": x[4], "rate": x[5] } for x in results] return json.dumps(json_result) @@ -440,11 +440,10 @@ def get_yearly_monthly_usage(service_id): 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] + "rate_multiplier": x[2], + "international": x[3], + "notification_type": x[4], + "rate": x[5] } for x in results] return json.dumps(json_results) except TypeError: diff --git a/tests/app/dao/test_notification_usage_dao.py b/tests/app/dao/test_notification_usage_dao.py index 75942f15a..72891d46e 100644 --- a/tests/app/dao/test_notification_usage_dao.py +++ b/tests/app/dao/test_notification_usage_dao.py @@ -32,7 +32,7 @@ def test_get_yearly_billing_data(notify_db, notify_db_session, sample_template, create_notification(template=sample_template, created_at=datetime(2016, 5, 18), sent_at=datetime(2016, 5, 18), status='sending', billable_units=2) create_notification(template=sample_template, created_at=datetime(2016, 7, 22), sent_at=datetime(2016, 7, 22), - status='sending', billable_units=3) + status='sending', billable_units=3, rate_multiplier=2, international=True, phone_prefix="1") create_notification(template=sample_template, created_at=datetime(2016, 9, 15), sent_at=datetime(2016, 9, 15), status='sending', billable_units=4) create_notification(template=sample_template, created_at=datetime(2017, 3, 31), sent_at=datetime(2017, 3, 31), @@ -45,10 +45,11 @@ def test_get_yearly_billing_data(notify_db, notify_db_session, sample_template, create_notification(template=sample_template, created_at=datetime(2017, 4, 1), sent_at=datetime(2017, 4, 1), status='sending', billable_units=6) results = get_yearly_billing_data(sample_template.service_id, 2016) - assert len(results) == 3 - 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) + assert len(results) == 4 + assert results[0] == (3, 3, 1, 'sms', False, 1.4) + assert results[1] == (9, 9, 1, 'sms', False, 1.58) + assert results[2] == (6, 3, 2, 'sms', True, 1.58) + assert results[3] == (2, 2, 1, 'email', False, 0) def test_get_yearly_billing_data_with_one_rate(notify_db, notify_db_session, sample_template): @@ -74,8 +75,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', None, False, None, 1.4) - assert results[1] == (0, 'email', None, False, None, 0) + assert results[0] == (15, 15, 1, 'sms', False, 1.4) + assert results[1] == (0, 0, 1, 'email', False, 0) def test_get_yearly_billing_data_with_no_sms_notifications(notify_db, notify_db_session, sample_email_template): @@ -87,8 +88,8 @@ 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', None, False, None, 0) - assert results[1] == (2, 'email', None, False, None, 0) + assert results[0] == (0, 0, 1, 'sms', False, 1.4) + assert results[1] == (2, 2, 1, 'email', False, 0) def test_get_monthly_billing_data(notify_db, notify_db_session, sample_template, sample_email_template): @@ -103,8 +104,13 @@ def test_get_monthly_billing_data(notify_db, notify_db_session, sample_template, status='sending', billable_units=2) create_notification(template=sample_template, created_at=datetime(2016, 7, 22), sent_at=datetime(2016, 7, 22), status='sending', billable_units=3) + create_notification(template=sample_template, created_at=datetime(2016, 7, 22), sent_at=datetime(2016, 7, 22), + status='sending', billable_units=3, rate_multiplier=2) + create_notification(template=sample_template, created_at=datetime(2016, 7, 22), sent_at=datetime(2016, 7, 22), + status='sending', billable_units=3, rate_multiplier=2) create_notification(template=sample_template, created_at=datetime(2016, 7, 30), sent_at=datetime(2016, 7, 22), status='sending', billable_units=4) + create_notification(template=sample_email_template, created_at=datetime(2016, 8, 22), sent_at=datetime(2016, 7, 22), status='sending', billable_units=0) create_notification(template=sample_email_template, created_at=datetime(2016, 8, 30), sent_at=datetime(2016, 7, 22), @@ -114,10 +120,11 @@ def test_get_monthly_billing_data(notify_db, notify_db_session, sample_template, sent_at=datetime(2017, 3, 31), status='sending', billable_units=6) results = get_monthly_billing_data(sample_template.service_id, 2016) assert len(results) == 4 - 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) + # (billable_units, rate_multiplier, international, type, rate) + assert results[0] == ('April', 1, 1, False, 'sms', 1.4) + assert results[1] == ('May', 2, 1, False, 'sms', 1.4) + assert results[2] == ('July', 7, 1, False, 'sms', 1.4) + assert results[3] == ('July', 6, 2, False, 'sms', 1.4) def test_get_monthly_billing_data_with_multiple_rates(notify_db, notify_db_session, sample_template, @@ -146,12 +153,11 @@ def test_get_monthly_billing_data_with_multiple_rates(notify_db, notify_db_sessi 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_monthly_billing_data(sample_template.service_id, 2016) - assert len(results) == 5 - 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) + assert len(results) == 4 + assert results[0] == ('April', 1, 1, False, 'sms', 1.4) + assert results[1] == ('May', 2, 1, False, 'sms', 1.4) + assert results[2] == ('June', 3, 1, False, 'sms', 1.4) + assert results[3] == ('June', 4, 1, False, 'sms', 1.75) def set_up_rate(notify_db, start_date, value): diff --git a/tests/app/db.py b/tests/app/db.py index 38d5eca9d..b5838d693 100644 --- a/tests/app/db.py +++ b/tests/app/db.py @@ -73,7 +73,9 @@ def create_notification( key_type=KEY_TYPE_NORMAL, sent_by=None, client_reference=None, - international=False + rate_multiplier=None, + international=False, + phone_prefix=None ): if created_at is None: created_at = datetime.utcnow() @@ -105,7 +107,9 @@ def create_notification( 'updated_at': updated_at, 'client_reference': client_reference, 'job_row_number': job_row_number, - 'international': international + 'rate_multiplier': rate_multiplier, + 'international': international, + 'phone_prefix': phone_prefix } notification = Notification(**data) dao_create_notification(notification) diff --git a/tests/app/service/test_rest.py b/tests/app/service/test_rest.py index 37331b182..89cfc27f1 100644 --- a/tests/app/service/test_rest.py +++ b/tests/app/service/test_rest.py @@ -1523,12 +1523,18 @@ def test_get_yearly_billing_usage(client, notify_db, notify_db_session): ) 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}] + assert json.loads(response.get_data(as_text=True)) == [{'credits': 1, + 'billing_units': 1, + 'rate_multiplier': 1, + 'notification_type': 'sms', + 'international': False, + 'rate': 1.58}, + {'credits': 0, + 'billing_units': 0, + 'rate_multiplier': 1, + 'notification_type': 'email', + 'international': False, + 'rate': 0}] def test_get_yearly_billing_usage_returns_400_if_missing_year(client, sample_service): @@ -1558,12 +1564,19 @@ def test_get_monthly_billing_usage(client, notify_db, notify_db_session): 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}] + print(actual) + assert actual == [{'month': 'June', + 'international': False, + 'rate_multiplier': 1, + 'notification_type': 'sms', + 'rate': 1.58, + 'billing_units': 1}, + {'month': 'July', + 'international': False, + 'rate_multiplier': 1, + 'notification_type': 'sms', + 'rate': 1.58, + 'billing_units': 1}] def test_get_monthly_billing_usage_returns_400_if_missing_year(client, sample_service): From 3b41478a0aa6471be5413b7d175c54aedbf6e733 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Thu, 27 Apr 2017 16:40:00 +0100 Subject: [PATCH 7/8] Updated Notification model to use Float(asdecimal=False) for rate_mutliplier. Added test with multiple rows for a month. --- app/models.py | 2 +- tests/app/conftest.py | 6 +++-- tests/app/dao/test_notification_usage_dao.py | 7 ++++++ tests/app/service/test_rest.py | 23 ++++++++++++++++++-- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/app/models.py b/app/models.py index 59e6f7a91..0d7f7236d 100644 --- a/app/models.py +++ b/app/models.py @@ -670,7 +670,7 @@ class Notification(db.Model): international = db.Column(db.Boolean, nullable=False, default=False) phone_prefix = db.Column(db.String, nullable=True) - rate_multiplier = db.Column(db.Float(), nullable=True) + rate_multiplier = db.Column(db.Float(asdecimal=False), nullable=True) @property def personalisation(self): diff --git a/tests/app/conftest.py b/tests/app/conftest.py index de69e9240..fc9748870 100644 --- a/tests/app/conftest.py +++ b/tests/app/conftest.py @@ -444,7 +444,8 @@ def sample_notification( api_key_id=None, key_type=KEY_TYPE_NORMAL, sent_by=None, - client_reference=None + client_reference=None, + rate_multiplier=1.0 ): if created_at is None: created_at = datetime.utcnow() @@ -481,7 +482,8 @@ def sample_notification( 'key_type': key_type, 'sent_by': sent_by, 'updated_at': created_at if status in NOTIFICATION_STATUS_TYPES_COMPLETED else None, - 'client_reference': client_reference + 'client_reference': client_reference, + 'rate_multiplier': rate_multiplier } if job_row_number is not None: data['job_row_number'] = job_row_number diff --git a/tests/app/dao/test_notification_usage_dao.py b/tests/app/dao/test_notification_usage_dao.py index 72891d46e..0c3ddc952 100644 --- a/tests/app/dao/test_notification_usage_dao.py +++ b/tests/app/dao/test_notification_usage_dao.py @@ -160,6 +160,13 @@ def test_get_monthly_billing_data_with_multiple_rates(notify_db, notify_db_sessi assert results[3] == ('June', 4, 1, False, 'sms', 1.75) +def test_get_monthly_billing_data_with_no_notifications_for_year(notify_db, notify_db_session, sample_template, + sample_email_template): + set_up_rate(notify_db, datetime(2016, 4, 1), 1.40) + results = get_monthly_billing_data(sample_template.service_id, 2016) + assert len(results) == 0 + + def set_up_rate(notify_db, start_date, value): rate = Rate(id=uuid.uuid4(), valid_from=start_date, rate=value, notification_type='sms') notify_db.session.add(rate) diff --git a/tests/app/service/test_rest.py b/tests/app/service/test_rest.py index 89cfc27f1..f289960a9 100644 --- a/tests/app/service/test_rest.py +++ b/tests/app/service/test_rest.py @@ -1554,6 +1554,9 @@ def test_get_monthly_billing_usage(client, notify_db, notify_db_session): 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, 6, 5), + sent_at=datetime(2016, 6, 5), + status='sending', rate_multiplier=2) create_sample_notification(notify_db, notify_db_session, created_at=datetime(2016, 7, 5), sent_at=datetime(2016, 7, 5), status='sending') @@ -1563,14 +1566,19 @@ def test_get_monthly_billing_usage(client, notify_db, notify_db_session): ) assert response.status_code == 200 actual = json.loads(response.get_data(as_text=True)) - assert len(actual) == 2 - print(actual) + assert len(actual) == 3 assert actual == [{'month': 'June', 'international': False, 'rate_multiplier': 1, 'notification_type': 'sms', 'rate': 1.58, 'billing_units': 1}, + {'month': 'June', + 'international': False, + 'rate_multiplier': 2, + 'notification_type': 'sms', + 'rate': 1.58, + 'billing_units': 1}, {'month': 'July', 'international': False, 'rate_multiplier': 1, @@ -1588,3 +1596,14 @@ def test_get_monthly_billing_usage_returns_400_if_missing_year(client, sample_se assert json.loads(response.get_data(as_text=True)) == { 'message': 'No valid year provided', 'result': 'error' } + + +def test_get_monthly_billing_usage_returns_empty_list_if_no_notifications(client, notify_db, sample_service): + rate = Rate(id=uuid.uuid4(), valid_from=datetime(2016, 3, 31, 23, 00), rate=1.58, notification_type='sms') + notify_db.session.add(rate) + response = client.get( + '/service/{}/monthly-usage?year=2016'.format(sample_service.id), + headers=[create_authorization_header()] + ) + assert response.status_code == 200 + assert json.loads(response.get_data(as_text=True)) == [] From baf84b53d312503e4a400d76440aeb68e5831bd3 Mon Sep 17 00:00:00 2001 From: Ken Tsang Date: Thu, 27 Apr 2017 18:06:23 +0100 Subject: [PATCH 8/8] Update test to check it doesn't get email usage --- tests/app/service/test_rest.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/app/service/test_rest.py b/tests/app/service/test_rest.py index f289960a9..f2150df1a 100644 --- a/tests/app/service/test_rest.py +++ b/tests/app/service/test_rest.py @@ -12,6 +12,7 @@ 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, Rate from tests import create_authorization_header +from tests.app.db import create_template from tests.app.conftest import ( sample_service as create_service, sample_service_permission as create_service_permission, @@ -1548,7 +1549,7 @@ def test_get_yearly_billing_usage_returns_400_if_missing_year(client, sample_ser } -def test_get_monthly_billing_usage(client, notify_db, notify_db_session): +def test_get_monthly_billing_usage(client, notify_db, notify_db_session, sample_service): 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), @@ -1560,6 +1561,12 @@ def test_get_monthly_billing_usage(client, notify_db, notify_db_session): create_sample_notification(notify_db, notify_db_session, created_at=datetime(2016, 7, 5), sent_at=datetime(2016, 7, 5), status='sending') + + template = create_template(sample_service, template_type='email') + create_sample_notification(notify_db, notify_db_session, created_at=datetime(2016, 6, 5), + sent_at=datetime(2016, 6, 5), + status='sending', + template=template) response = client.get( '/service/{}/monthly-usage?year=2016'.format(notification.service_id), headers=[create_authorization_header()]