mirror of
https://github.com/GSA/notifications-api.git
synced 2026-08-26 01:03:41 -04:00
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:
@@ -720,52 +720,42 @@ def fetch_email_usage_for_organisation(organisation_id, start_date, end_date):
|
|||||||
return query.all()
|
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.
|
# 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
|
# subtract sms_billable_units units accrued since report's start date to get up-to-date
|
||||||
# allowance remainder
|
# 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
|
chargeable_sms = func.sum(ft_billing_subquery.c.charged_units)
|
||||||
# how many are chargeable, we need to see how much free allowance was used up in the
|
sms_cost = func.sum(ft_billing_subquery.c.cost)
|
||||||
# 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
|
|
||||||
|
|
||||||
query = db.session.query(
|
query = db.session.query(
|
||||||
Service.name.label("service_name"),
|
Service.name.label("service_name"),
|
||||||
Service.id.label("service_id"),
|
Service.id.label("service_id"),
|
||||||
func.coalesce(allowance_left_at_start_date_query.c.free_sms_fragment_limit, 0).label('free_sms_fragment_limit'),
|
AnnualBilling.free_sms_fragment_limit,
|
||||||
func.coalesce(FactBilling.rate, 0).label('sms_rate'),
|
|
||||||
func.coalesce(sms_allowance_left, 0).label("sms_remainder"),
|
func.coalesce(sms_allowance_left, 0).label("sms_remainder"),
|
||||||
func.coalesce(sms_billable_units, 0).label('sms_billable_units'),
|
func.coalesce(sms_billable_units, 0).label('sms_billable_units'),
|
||||||
func.coalesce(chargeable_sms, 0).label("chargeable_billable_sms"),
|
func.coalesce(chargeable_sms, 0).label("chargeable_billable_sms"),
|
||||||
func.coalesce(sms_cost, 0).label('sms_cost'),
|
func.coalesce(sms_cost, 0).label('sms_cost'),
|
||||||
Service.active.label("active")
|
Service.active
|
||||||
).select_from(
|
).select_from(
|
||||||
Service
|
Service
|
||||||
).outerjoin(
|
).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(
|
).outerjoin(
|
||||||
FactBilling, and_(
|
ft_billing_subquery, Service.id == ft_billing_subquery.c.service_id
|
||||||
Service.id == FactBilling.service_id,
|
|
||||||
FactBilling.bst_date >= start_date,
|
|
||||||
FactBilling.bst_date < end_date,
|
|
||||||
FactBilling.notification_type == SMS_TYPE,
|
|
||||||
)
|
|
||||||
).filter(
|
).filter(
|
||||||
Service.organisation_id == organisation_id,
|
Service.organisation_id == organisation_id,
|
||||||
Service.restricted.is_(False)
|
Service.restricted.is_(False)
|
||||||
).group_by(
|
).group_by(
|
||||||
Service.id,
|
Service.id,
|
||||||
Service.name,
|
Service.name,
|
||||||
allowance_left_at_start_date_query.c.free_sms_fragment_limit,
|
AnnualBilling.free_sms_fragment_limit
|
||||||
allowance_left_at_start_date_query.c.sms_remainder,
|
|
||||||
FactBilling.rate,
|
|
||||||
).order_by(
|
).order_by(
|
||||||
Service.name
|
Service.name
|
||||||
)
|
)
|
||||||
@@ -856,7 +846,7 @@ def fetch_usage_year_for_organisation(organisation_id, year):
|
|||||||
'emails_sent': 0,
|
'emails_sent': 0,
|
||||||
'active': service.active
|
'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)
|
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)
|
email_usages = fetch_email_usage_for_organisation(organisation_id, year_start, year_end)
|
||||||
for usage in sms_usages:
|
for usage in sms_usages:
|
||||||
|
|||||||
@@ -906,6 +906,95 @@ def test_fetch_usage_year_for_organisation_populates_ft_billing_for_today(notify
|
|||||||
assert FactBilling.query.count() == 1
|
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')
|
@freeze_time('2020-02-27 13:30')
|
||||||
def test_fetch_usage_year_for_organisation_only_returns_data_for_live_services(notify_db_session):
|
def test_fetch_usage_year_for_organisation_only_returns_data_for_live_services(notify_db_session):
|
||||||
org = create_organisation(name='Organisation without live services')
|
org = create_organisation(name='Organisation without live services')
|
||||||
|
|||||||
Reference in New Issue
Block a user