Merge pull request #2723 from alphagov/organisation-usage

Organisation usage
This commit is contained in:
Rebecca Law
2020-02-27 10:34:23 +00:00
committed by GitHub
8 changed files with 313 additions and 16 deletions

View File

@@ -12,6 +12,7 @@ from app.dao.date_util import (
get_financial_year,
get_financial_year_for_datetime
)
from app.dao.organisation_dao import dao_get_organisation_live_services
from app.models import (
FactBilling,
@@ -243,11 +244,9 @@ def fetch_monthly_billing_for_year(service_id, year):
today = convert_utc_to_bst(datetime.utcnow()).date()
# if year end date is less than today, we are calculating for data in the past and have no need for deltas.
if year_end_date >= today:
yesterday = today - timedelta(days=1)
for day in [yesterday, today]:
data = fetch_billing_data_for_day(process_day=day, service_id=service_id, check_permissions=True)
for d in data:
update_fact_billing(data=d, process_day=day)
data = fetch_billing_data_for_day(process_day=today, service_id=service_id, check_permissions=True)
for d in data:
update_fact_billing(data=d, process_day=today)
email_and_letters = db.session.query(
func.date_trunc('month', FactBilling.bst_date).cast(Date).label("month"),
@@ -539,3 +538,150 @@ def create_billing_record(data, rate, process_day):
postage=data.postage,
)
return billing_record
@statsd(namespace="dao")
def fetch_letter_costs_for_organisation(organisation_id, start_date, end_date):
query = db.session.query(
Service.name.label("service_name"),
Service.id.label("service_id"),
func.sum(FactBilling.notifications_sent * FactBilling.rate).label("letter_cost")
).select_from(
Service
).join(
FactBilling, FactBilling.service_id == Service.id,
).filter(
FactBilling.bst_date >= start_date,
FactBilling.bst_date <= end_date,
FactBilling.notification_type == LETTER_TYPE,
Service.organisation_id == organisation_id
).group_by(
Service.id,
Service.name,
).order_by(
Service.name
)
return query.all()
@statsd(namespace="dao")
def fetch_email_usage_for_organisation(organisation_id, start_date, end_date):
query = db.session.query(
Service.name.label("service_name"),
Service.id.label("service_id"),
func.sum(FactBilling.notifications_sent).label("emails_sent")
).select_from(
Service
).join(
FactBilling, FactBilling.service_id == Service.id,
).filter(
FactBilling.bst_date >= start_date,
FactBilling.bst_date <= end_date,
FactBilling.notification_type == EMAIL_TYPE,
Service.organisation_id == organisation_id
).group_by(
Service.id,
Service.name,
).order_by(
Service.name
)
return query.all()
@statsd(namespace="dao")
def fetch_sms_billing_for_organisation(organisation_id, start_date, end_date):
# ASSUMPTION: AnnualBilling has been populated for year.
free_allowance_remainder = fetch_sms_free_allowance_remainder(start_date).subquery()
sms_billable_units = func.sum(FactBilling.billable_units * FactBilling.rate_multiplier)
sms_remainder = func.coalesce(
free_allowance_remainder.c.sms_remainder,
free_allowance_remainder.c.free_sms_fragment_limit
)
chargeable_sms = func.greatest(sms_billable_units - sms_remainder, 0)
sms_cost = chargeable_sms * FactBilling.rate
query = db.session.query(
Service.name.label("service_name"),
Service.id.label("service_id"),
free_allowance_remainder.c.free_sms_fragment_limit,
FactBilling.rate.label('sms_rate'),
sms_remainder.label("sms_remainder"),
sms_billable_units.label('sms_billable_units'),
chargeable_sms.label("chargeable_billable_sms"),
sms_cost.label('sms_cost'),
).select_from(
Service
).outerjoin(
free_allowance_remainder, Service.id == free_allowance_remainder.c.service_id
).join(
FactBilling, FactBilling.service_id == Service.id,
).filter(
FactBilling.bst_date >= start_date,
FactBilling.bst_date <= end_date,
FactBilling.notification_type == SMS_TYPE,
Service.organisation_id == organisation_id
).group_by(
Service.id,
Service.name,
free_allowance_remainder.c.free_sms_fragment_limit,
free_allowance_remainder.c.sms_remainder,
FactBilling.rate,
).order_by(
Service.name
)
return query.all()
@statsd(namespace="dao")
def fetch_usage_year_for_organisation(organisation_id, year):
year_start_datetime, year_end_datetime = get_financial_year(year)
year_start_date = convert_utc_to_bst(year_start_datetime).date()
year_end_date = convert_utc_to_bst(year_end_datetime).date()
today = convert_utc_to_bst(datetime.utcnow()).date()
services = dao_get_organisation_live_services(organisation_id)
# if year end date is less than today, we are calculating for data in the past and have no need for deltas.
if year_end_date >= today:
for service in services:
data = fetch_billing_data_for_day(process_day=today, service_id=service.id)
for d in data:
update_fact_billing(data=d, process_day=today)
service_with_usage = {}
# initialise results
for service in services:
service_with_usage[str(service.id)] = {
'service_id': service.id,
'service_name': service.name,
'free_sms_limit': 0,
'sms_remainder': 0,
'sms_billable_units': 0,
'chargeable_billable_sms': 0,
'sms_cost': 0.0,
'letter_cost': 0.0,
'emails_sent': 0
}
sms_usages = fetch_sms_billing_for_organisation(organisation_id, year_start_date, year_end_date)
letter_usages = fetch_letter_costs_for_organisation(organisation_id, year_start_date, year_end_date)
email_usages = fetch_email_usage_for_organisation(organisation_id, year_start_date, year_end_date)
for usage in sms_usages:
service_with_usage[str(usage.service_id)] = {
'service_id': usage.service_id,
'service_name': usage.service_name,
'free_sms_limit': usage.free_sms_fragment_limit,
'sms_remainder': usage.sms_remainder,
'sms_billable_units': usage.sms_billable_units,
'chargeable_billable_sms': usage.chargeable_billable_sms,
'sms_cost': float(usage.sms_cost),
'letter_cost': 0.0,
'emails_sent': 0
}
for letter_usage in letter_usages:
service_with_usage[str(letter_usage.service_id)]['letter_cost'] = float(letter_usage.letter_cost)
for email_usage in email_usages:
service_with_usage[str(email_usage.service_id)]['emails_sent'] = email_usage.emails_sent
return service_with_usage

View File

@@ -17,7 +17,7 @@ def dao_get_organisations():
).all()
def dao_count_organsations_with_live_services():
def dao_count_organisations_with_live_services():
return db.session.query(Organisation.id).join(Organisation.services).filter(
Service.active.is_(True),
Service.restricted.is_(False),
@@ -31,6 +31,13 @@ def dao_get_organisation_services(organisation_id):
).one().services
def dao_get_organisation_live_services(organisation_id):
return Service.query.filter_by(
organisation_id=organisation_id,
restricted=False
).all()
def dao_get_organisation_by_id(organisation_id):
return Organisation.query.filter_by(id=organisation_id).one()

View File

@@ -1,7 +1,9 @@
from flask import abort, Blueprint, jsonify, request, current_app
from sqlalchemy.exc import IntegrityError
from app.config import QueueNames
from app.dao.fact_billing_dao import fetch_usage_year_for_organisation
from app.dao.organisation_dao import (
dao_create_organisation,
dao_get_organisations,
@@ -125,6 +127,18 @@ def get_organisation_services(organisation_id):
return jsonify([s.serialize_for_org_dashboard() for s in sorted_services])
@organisation_blueprint.route('/<uuid:organisation_id>/services-with-usage', methods=['GET'])
def get_organisation_services_usage(organisation_id):
try:
year = int(request.args.get('year', 'none'))
except ValueError:
return jsonify(result='error', message='No valid year provided'), 400
services = fetch_usage_year_for_organisation(organisation_id, year)
list_services = services.values()
sorted_services = sorted(list_services, key=lambda s: s['service_name'].lower())
return jsonify(services=sorted_services)
@organisation_blueprint.route('/<uuid:organisation_id>/users/<uuid:user_id>', methods=['POST'])
def add_user_to_organisation(organisation_id, user_id):
new_org_user = dao_add_user_to_organisation(organisation_id, user_id)

View File

@@ -6,7 +6,7 @@ from flask import (
from app import db, version
from app.dao.services_dao import dao_count_live_services
from app.dao.organisation_dao import dao_count_organsations_with_live_services
from app.dao.organisation_dao import dao_count_organisations_with_live_services
status = Blueprint('status', __name__)
@@ -28,7 +28,7 @@ def show_status():
@status.route('/_status/live-service-and-organisation-counts')
def live_service_and_organisation_counts():
return jsonify(
organisations=dao_count_organsations_with_live_services(),
organisations=dao_count_organisations_with_live_services(),
services=dao_count_live_services(),
), 200