From 646de16ace0acb3e31d8c4365d9691319accc92c Mon Sep 17 00:00:00 2001 From: Ben Thorner Date: Wed, 20 Apr 2022 16:51:32 +0100 Subject: [PATCH] Refactor yearly usage API into functions per type This makes it easier to extend each function with costs and free allowances - especially for SMS. In each function I've started using the "chargeable" terminology, which we should eventually change in the API. I've chosen to duplicate the "WHERE" clause in each subquery vs. the top-level query. This will make more sense in later commits where we start adding free allowance calculations, which need to be done on a yearly basis - knowledge the subqueries should have. --- app/dao/fact_billing_dao.py | 115 ++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 45 deletions(-) diff --git a/app/dao/fact_billing_dao.py b/app/dao/fact_billing_dao.py index 0fd3282dd..24deee66f 100644 --- a/app/dao/fact_billing_dao.py +++ b/app/dao/fact_billing_dao.py @@ -2,7 +2,7 @@ from datetime import date, datetime, timedelta from flask import current_app from notifications_utils.timezones import convert_utc_to_bst -from sqlalchemy import Date, Integer, and_, desc, func +from sqlalchemy import Date, Integer, and_, desc, func, union from sqlalchemy.dialects.postgresql import insert from sqlalchemy.sql.expression import case, literal @@ -197,52 +197,28 @@ def fetch_letter_line_items_for_all_services(start_date, end_date): def fetch_billing_totals_for_year(service_id, year): - year_start, year_end = get_financial_year_dates(year) - """ - Billing for email: only record the total number of emails. - Billing for letters: The billing units is used to fetch the correct rate for the sheet count of the letter. - Total cost is notifications_sent * rate. - Rate multiplier does not apply to email or letters. - """ - email_and_letters = db.session.query( - func.sum(FactBilling.notifications_sent).label("notifications_sent"), - func.sum(FactBilling.notifications_sent).label("billable_units"), - FactBilling.rate.label('rate'), - FactBilling.notification_type.label('notification_type') - ).filter( - FactBilling.service_id == service_id, - FactBilling.bst_date >= year_start, - FactBilling.bst_date <= year_end, - FactBilling.notification_type.in_([EMAIL_TYPE, LETTER_TYPE]) - ).group_by( - FactBilling.rate, - FactBilling.notification_type - ) - """ - Billing for SMS using the billing_units * rate_multiplier. Billing unit of SMS is the fragment count of a message - """ - sms = db.session.query( - func.sum(FactBilling.notifications_sent).label("notifications_sent"), - func.sum(FactBilling.billable_units * FactBilling.rate_multiplier).label("billable_units"), - FactBilling.rate, - FactBilling.notification_type - ).filter( - FactBilling.service_id == service_id, - FactBilling.bst_date >= year_start, - FactBilling.bst_date <= year_end, - FactBilling.notification_type == SMS_TYPE - ).group_by( - FactBilling.rate, - FactBilling.notification_type - ) - - yearly_data = email_and_letters.union_all(sms).order_by( - 'notification_type', - 'rate' + return db.session.query( + union(*[ + db.session.query( + func.sum(query.c.notifications_sent).label("notifications_sent"), + func.sum(query.c.chargeable_units).label("billable_units"), + query.c.rate.label("rate"), + query.c.notification_type.label("notification_type"), + ).group_by( + query.c.rate, + query.c.notification_type + ) + for query in [ + query_service_sms_usage_for_year(service_id, year).subquery(), + query_service_email_usage_for_year(service_id, year).subquery(), + query_service_letter_usage_for_year(service_id, year).subquery(), + ] + ]).subquery() + ).order_by( + "notification_type", + "rate", ).all() - return yearly_data - def fetch_monthly_billing_for_year(service_id, year): year_start, year_end = get_financial_year_dates(year) @@ -301,6 +277,55 @@ def fetch_monthly_billing_for_year(service_id, year): return yearly_data +def query_service_email_usage_for_year(service_id, year): + year_start, year_end = get_financial_year_dates(year) + + return db.session.query( + FactBilling.notifications_sent, + FactBilling.notifications_sent.label("chargeable_units"), + FactBilling.rate, + FactBilling.notification_type, + ).filter( + FactBilling.service_id == service_id, + FactBilling.bst_date >= year_start, + FactBilling.bst_date <= year_end, + FactBilling.notification_type == EMAIL_TYPE + ) + + +def query_service_letter_usage_for_year(service_id, year): + year_start, year_end = get_financial_year_dates(year) + + return db.session.query( + FactBilling.notifications_sent, + FactBilling.notifications_sent.label("chargeable_units"), + FactBilling.rate, + FactBilling.notification_type, + ).filter( + FactBilling.service_id == service_id, + FactBilling.bst_date >= year_start, + FactBilling.bst_date <= year_end, + FactBilling.notification_type == LETTER_TYPE + ) + + +def query_service_sms_usage_for_year(service_id, year): + year_start, year_end = get_financial_year_dates(year) + chargeable_units = FactBilling.billable_units * FactBilling.rate_multiplier + + return db.session.query( + FactBilling.notifications_sent, + chargeable_units.label("chargeable_units"), + FactBilling.rate, + FactBilling.notification_type, + ).filter( + FactBilling.service_id == service_id, + FactBilling.bst_date >= year_start, + FactBilling.bst_date <= year_end, + FactBilling.notification_type == SMS_TYPE + ) + + def delete_billing_data_for_service_for_day(process_day, service_id): """ Delete all ft_billing data for a given service on a given bst_date