Use the new subquery in fetch_sms_billing_for_organisation()

This is so we have granular data about billable units and costs
so that we can handle multiple sms rates within one financial
year.

We also cast chargeable_units_used_so_far in that subquery
to integer so we don't have type mismatch.

Co-authored-by: Leo Hemsted <leo.hemsted@digital.cabinet-office.gov.uk>
This commit is contained in:
Pea Tyczynska
2022-04-29 18:20:14 +01:00
parent 150eaf019b
commit 112c2ddf72
2 changed files with 102 additions and 23 deletions

View File

@@ -720,52 +720,42 @@ def fetch_email_usage_for_organisation(organisation_id, start_date, end_date):
return query.all()
def fetch_sms_billing_for_organisation(organisation_id, start_date, end_date):
def fetch_sms_billing_for_organisation(organisation_id, financial_year):
# ASSUMPTION: AnnualBilling has been populated for year.
allowance_left_at_start_date_query = fetch_sms_free_allowance_remainder_until_date(start_date).subquery()
ft_billing_subquery = query_organisation_sms_usage_for_year(organisation_id, financial_year).subquery()
sms_billable_units = func.coalesce(func.sum(FactBilling.billable_units * FactBilling.rate_multiplier), 0)
sms_billable_units = func.sum(func.coalesce(ft_billing_subquery.c.chargeable_units, 0))
# subtract sms_billable_units units accrued since report's start date to get up-to-date
# allowance remainder
sms_allowance_left = func.greatest(allowance_left_at_start_date_query.c.sms_remainder - sms_billable_units, 0)
sms_allowance_left = func.greatest(AnnualBilling.free_sms_fragment_limit - sms_billable_units, 0)
# billable units here are for period between start date and end date only, so to see
# how many are chargeable, we need to see how much free allowance was used up in the
# period up until report's start date and then do a subtraction
chargeable_sms = func.greatest(sms_billable_units - allowance_left_at_start_date_query.c.sms_remainder, 0)
sms_cost = chargeable_sms * FactBilling.rate
chargeable_sms = func.sum(ft_billing_subquery.c.charged_units)
sms_cost = func.sum(ft_billing_subquery.c.cost)
query = db.session.query(
Service.name.label("service_name"),
Service.id.label("service_id"),
func.coalesce(allowance_left_at_start_date_query.c.free_sms_fragment_limit, 0).label('free_sms_fragment_limit'),
func.coalesce(FactBilling.rate, 0).label('sms_rate'),
AnnualBilling.free_sms_fragment_limit,
func.coalesce(sms_allowance_left, 0).label("sms_remainder"),
func.coalesce(sms_billable_units, 0).label('sms_billable_units'),
func.coalesce(chargeable_sms, 0).label("chargeable_billable_sms"),
func.coalesce(sms_cost, 0).label('sms_cost'),
Service.active.label("active")
Service.active
).select_from(
Service
).outerjoin(
allowance_left_at_start_date_query, Service.id == allowance_left_at_start_date_query.c.service_id
AnnualBilling,
and_(Service.id == AnnualBilling.service_id, AnnualBilling.financial_year_start == financial_year)
).outerjoin(
FactBilling, and_(
Service.id == FactBilling.service_id,
FactBilling.bst_date >= start_date,
FactBilling.bst_date < end_date,
FactBilling.notification_type == SMS_TYPE,
)
ft_billing_subquery, Service.id == ft_billing_subquery.c.service_id
).filter(
Service.organisation_id == organisation_id,
Service.restricted.is_(False)
).group_by(
Service.id,
Service.name,
allowance_left_at_start_date_query.c.free_sms_fragment_limit,
allowance_left_at_start_date_query.c.sms_remainder,
FactBilling.rate,
AnnualBilling.free_sms_fragment_limit
).order_by(
Service.name
)
@@ -856,7 +846,7 @@ def fetch_usage_year_for_organisation(organisation_id, year):
'emails_sent': 0,
'active': service.active
}
sms_usages = fetch_sms_billing_for_organisation(organisation_id, year_start, year_end)
sms_usages = fetch_sms_billing_for_organisation(organisation_id, year)
letter_usages = fetch_letter_costs_for_organisation(organisation_id, year_start, year_end)
email_usages = fetch_email_usage_for_organisation(organisation_id, year_start, year_end)
for usage in sms_usages:

View File

@@ -906,6 +906,95 @@ def test_fetch_usage_year_for_organisation_populates_ft_billing_for_today(notify
assert FactBilling.query.count() == 1
@freeze_time('2022-05-01 13:30')
def test_fetch_usage_year_for_organisation_calculates_cost_from_multiple_rates(notify_db_session):
old_rate_date = date(2022, 4, 29)
new_rate_date = date(2022, 5, 1)
current_year = datetime.utcnow().year
org = create_organisation(name='Organisation 1')
service_1 = create_service(restricted=False, service_name="Service 1")
dao_add_service_to_organisation(service=service_1, organisation_id=org.id)
sms_template_1 = create_template(service=service_1)
create_ft_billing(
bst_date=old_rate_date, template=sms_template_1, rate=2,
billable_unit=4, notifications_sent=4
)
create_ft_billing(
bst_date=new_rate_date, template=sms_template_1, rate=3,
billable_unit=2, notifications_sent=2
)
create_annual_billing(service_id=service_1.id, free_sms_fragment_limit=3, financial_year_start=current_year)
results = fetch_usage_year_for_organisation(organisation_id=org.id, year=current_year)
assert len(results) == 1
assert results[str(service_1.id)]['free_sms_limit'] == 3
assert results[str(service_1.id)]['sms_remainder'] == 0
assert results[str(service_1.id)]['sms_billable_units'] == 6
assert results[str(service_1.id)]['chargeable_billable_sms'] == 3
assert results[str(service_1.id)]['sms_cost'] == 8.0
@freeze_time('2022-05-01 13:30')
def test_fetch_usage_year_for_organisation_when_no_usage(notify_db_session):
current_year = datetime.utcnow().year
org = create_organisation(name='Organisation 1')
service_1 = create_service(restricted=False, service_name="Service 1")
dao_add_service_to_organisation(service=service_1, organisation_id=org.id)
create_annual_billing(service_id=service_1.id, free_sms_fragment_limit=3, financial_year_start=current_year)
results = fetch_usage_year_for_organisation(organisation_id=org.id, year=current_year)
assert len(results) == 1
assert results[str(service_1.id)]['free_sms_limit'] == 3
assert results[str(service_1.id)]['sms_remainder'] == 3
assert results[str(service_1.id)]['sms_billable_units'] == 0
assert results[str(service_1.id)]['chargeable_billable_sms'] == 0
assert results[str(service_1.id)]['sms_cost'] == 0.0
@freeze_time('2022-05-01 13:30')
def test_fetch_usage_year_for_organisation_only_queries_present_year(notify_db_session):
current_year = datetime.utcnow().year
last_year = current_year - 1
date_two_years_ago = date(2021, 3, 31)
date_in_last_financial_year = date(2022, 3, 31)
date_in_this_year = date.today()
org = create_organisation(name='Organisation 1')
service_1 = create_service(restricted=False, service_name="Service 1")
dao_add_service_to_organisation(service=service_1, organisation_id=org.id)
sms_template_1 = create_template(service=service_1)
create_ft_billing(
bst_date=date_two_years_ago, template=sms_template_1, rate=1,
billable_unit=2, notifications_sent=2
)
create_ft_billing(
bst_date=date_in_last_financial_year, template=sms_template_1, rate=1,
billable_unit=4, notifications_sent=4
)
create_ft_billing(
bst_date=date_in_this_year, template=sms_template_1, rate=1,
billable_unit=8, notifications_sent=8
)
create_annual_billing(service_id=service_1.id, free_sms_fragment_limit=4, financial_year_start=last_year - 1)
create_annual_billing(service_id=service_1.id, free_sms_fragment_limit=0, financial_year_start=last_year)
create_annual_billing(service_id=service_1.id, free_sms_fragment_limit=8, financial_year_start=current_year)
results = fetch_usage_year_for_organisation(organisation_id=org.id, year=last_year)
assert len(results) == 1
assert results[str(service_1.id)]['sms_billable_units'] == 4
assert results[str(service_1.id)]['chargeable_billable_sms'] == 4
assert results[str(service_1.id)]['sms_cost'] == 4.0
@freeze_time('2020-02-27 13:30')
def test_fetch_usage_year_for_organisation_only_returns_data_for_live_services(notify_db_session):
org = create_organisation(name='Organisation without live services')