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.
This commit is contained in:
Ben Thorner
2022-04-20 16:51:32 +01:00
parent 4cca01a8cb
commit 646de16ace

View File

@@ -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