mirror of
https://github.com/GSA/notifications-api.git
synced 2026-09-10 10:03:38 -04:00
Merge pull request #699 from alphagov/monthly-billable-units
Add DAO and endpoint for getting billable units/financial year
This commit is contained in:
@@ -1,13 +1,15 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
import pytz
|
||||||
from datetime import (
|
from datetime import (
|
||||||
datetime,
|
datetime,
|
||||||
timedelta,
|
timedelta,
|
||||||
date
|
date
|
||||||
)
|
)
|
||||||
|
from itertools import groupby
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from werkzeug.datastructures import MultiDict
|
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 sqlalchemy.orm import joinedload
|
||||||
|
|
||||||
from app import db
|
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")
|
@statsd(namespace="dao")
|
||||||
def get_notification_with_personalisation(service_id, notification_id, key_type):
|
def get_notification_with_personalisation(service_id, notification_id, key_type):
|
||||||
filter_dict = {'service_id': service_id, 'id': notification_id}
|
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)
|
update({'status': NOTIFICATION_TEMPORARY_FAILURE, 'updated_at': update_at}, synchronize_session=False)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return updated
|
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')
|
||||||
|
|||||||
@@ -310,3 +310,13 @@ def update_whitelist(service_id):
|
|||||||
else:
|
else:
|
||||||
dao_add_and_commit_whitelisted_contacts(whitelist_objs)
|
dao_add_and_commit_whitelisted_contacts(whitelist_objs)
|
||||||
return '', 204
|
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
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from datetime import datetime, timedelta, date
|
from datetime import datetime, timedelta, date
|
||||||
|
import pytz
|
||||||
import uuid
|
import uuid
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
@@ -31,13 +32,15 @@ from app.dao.notifications_dao import (
|
|||||||
delete_notifications_created_more_than_a_week_ago,
|
delete_notifications_created_more_than_a_week_ago,
|
||||||
get_notification_by_id,
|
get_notification_by_id,
|
||||||
get_notification_for_job,
|
get_notification_for_job,
|
||||||
|
get_notification_billable_unit_count_per_month,
|
||||||
get_notification_with_personalisation,
|
get_notification_with_personalisation,
|
||||||
get_notifications_for_job,
|
get_notifications_for_job,
|
||||||
get_notifications_for_service,
|
get_notifications_for_service,
|
||||||
update_notification_status_by_id,
|
update_notification_status_by_id,
|
||||||
update_notification_status_by_reference,
|
update_notification_status_by_reference,
|
||||||
dao_delete_notifications_and_history_by_id,
|
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
|
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
|
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):
|
def test_update_notification(sample_notification, sample_template):
|
||||||
assert sample_notification.status == 'created'
|
assert sample_notification.status == 'created'
|
||||||
sample_notification.status = 'failed'
|
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
|
all_notifications = get_notifications_for_service(sample_service.id, limit_days=1, key_type=KEY_TYPE_TEST).items
|
||||||
assert len(all_notifications) == 1
|
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'
|
||||||
|
|||||||
@@ -1300,3 +1300,27 @@ def test_get_detailed_services_only_includes_todays_notifications(notify_db, not
|
|||||||
'email': {'delivered': 0, 'failed': 0, 'requested': 0},
|
'email': {'delivered': 0, 'failed': 0, 'requested': 0},
|
||||||
'sms': {'delivered': 0, 'failed': 0, 'requested': 2}
|
'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'
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user