Merge pull request #699 from alphagov/monthly-billable-units

Add DAO and endpoint for getting billable units/financial year
This commit is contained in:
Chris Hill-Scott
2016-10-04 16:08:31 +01:00
committed by GitHub
4 changed files with 132 additions and 2 deletions

View File

@@ -1,13 +1,15 @@
import uuid
import pytz
from datetime import (
datetime,
timedelta,
date
)
from itertools import groupby
from flask import current_app
from werkzeug.datastructures import MultiDict
from sqlalchemy import (desc, func, or_, and_, asc)
from sqlalchemy import (desc, func, or_, and_, asc, cast, Text)
from sqlalchemy.orm import joinedload
from app import db
@@ -209,6 +211,30 @@ def get_notifications_for_job(service_id, job_id, filter_dict=None, page=1, page
)
@statsd(namespace="dao")
def get_notification_billable_unit_count_per_month(service_id, year):
start, end = get_financial_year(year)
notifications = db.session.query(
NotificationHistory.created_at,
NotificationHistory.billable_units
).order_by(
NotificationHistory.created_at
).filter(
NotificationHistory.billable_units != 0,
NotificationHistory.service_id == service_id,
NotificationHistory.created_at >= start,
NotificationHistory.created_at < end
)
return [
(month, sum(count for _, count in row))
for month, row in groupby(
notifications, lambda row: get_bst_month(row[0])
)
]
@statsd(namespace="dao")
def get_notification_with_personalisation(service_id, notification_id, key_type):
filter_dict = {'service_id': service_id, 'id': notification_id}
@@ -319,3 +345,20 @@ def dao_timeout_notifications(timeout_period_in_seconds):
update({'status': NOTIFICATION_TEMPORARY_FAILURE, 'updated_at': update_at}, synchronize_session=False)
db.session.commit()
return updated
def get_financial_year(year):
return get_april_fools(year), get_april_fools(year + 1)
def get_april_fools(year):
return datetime(
year, 4, 1, 0, 0, 0, 0,
pytz.timezone("Europe/London")
).astimezone(pytz.utc)
def get_bst_month(datetime):
return pytz.utc.localize(datetime).astimezone(
pytz.timezone("Europe/London")
).strftime('%B')

View File

@@ -310,3 +310,13 @@ def update_whitelist(service_id):
else:
dao_add_and_commit_whitelisted_contacts(whitelist_objs)
return '', 204
@service_blueprint.route('/<uuid:service_id>/billable-units')
def get_billable_unit_count(service_id):
try:
return jsonify(notifications_dao.get_notification_billable_unit_count_per_month(
service_id, int(request.args.get('year'))
))
except TypeError:
return jsonify(result='error', message='No valid year provided'), 400

View File

@@ -1,4 +1,5 @@
from datetime import datetime, timedelta, date
import pytz
import uuid
from functools import partial
@@ -31,13 +32,15 @@ from app.dao.notifications_dao import (
delete_notifications_created_more_than_a_week_ago,
get_notification_by_id,
get_notification_for_job,
get_notification_billable_unit_count_per_month,
get_notification_with_personalisation,
get_notifications_for_job,
get_notifications_for_service,
update_notification_status_by_id,
update_notification_status_by_reference,
dao_delete_notifications_and_history_by_id,
dao_timeout_notifications)
dao_timeout_notifications,
get_financial_year)
from notifications_utils.template import get_sms_fragment_count
@@ -685,6 +688,48 @@ def test_get_all_notifications_for_job_by_status(notify_db, notify_db_session, s
assert len(notifications(filter_dict={'status': NOTIFICATION_STATUS_TYPES[:3]}).items) == 3
def test_get_notification_billable_unit_count_per_month(notify_db, notify_db_session, sample_service):
for year, month, day in (
(2017, 1, 15), # ↓ 2016 financial year
(2016, 8, 1),
(2016, 7, 15),
(2016, 4, 15),
(2016, 4, 15),
(2016, 4, 1), # ↓ 2015 financial year
(2016, 3, 31),
(2016, 1, 15)
):
sample_notification(
notify_db, notify_db_session, service=sample_service,
created_at=datetime(
year, month, day, 0, 0, 0, 0
) - timedelta(hours=1, seconds=1) # one second before midnight
)
for financial_year, months in (
(
2017,
[]
),
(
2016,
[('April', 2), ('July', 2), ('January', 1)]
),
(
2015,
[('January', 1), ('March', 2)]
),
(
2014,
[]
)
):
assert get_notification_billable_unit_count_per_month(
sample_service.id, financial_year
) == months
def test_update_notification(sample_notification, sample_template):
assert sample_notification.status == 'created'
sample_notification.status = 'failed'
@@ -1158,3 +1203,11 @@ def test_should_exclude_test_key_notifications_by_default(
all_notifications = get_notifications_for_service(sample_service.id, limit_days=1, key_type=KEY_TYPE_TEST).items
assert len(all_notifications) == 1
def test_get_financial_year():
start, end = get_financial_year(2000)
assert start.tzinfo == pytz.utc
assert start.isoformat() == '2000-04-01T00:01:00+00:00'
assert end.tzinfo == pytz.utc
assert end.isoformat() == '2001-04-01T00:01:00+00:00'

View File

@@ -1300,3 +1300,27 @@ def test_get_detailed_services_only_includes_todays_notifications(notify_db, not
'email': {'delivered': 0, 'failed': 0, 'requested': 0},
'sms': {'delivered': 0, 'failed': 0, 'requested': 2}
}
@freeze_time('2012-12-12T12:00:01')
def test_get_notification_billable_unit_count(client, notify_db, notify_db_session):
notification = create_sample_notification(notify_db, notify_db_session)
response = client.get(
'/service/{}/billable-units?year=2012'.format(notification.service_id),
headers=[create_authorization_header(service_id=notification.service_id)]
)
assert response.status_code == 200
assert json.loads(response.get_data(as_text=True)) == {
'December': 1
}
def test_get_notification_billable_unit_count_missing_year(client, sample_service):
response = client.get(
'/service/{}/billable-units'.format(sample_service.id),
headers=[create_authorization_header(service_id=sample_service.id)]
)
assert response.status_code == 400
assert json.loads(response.get_data(as_text=True)) == {
'message': 'No valid year provided', 'result': 'error'
}