merge from main

This commit is contained in:
Kenneth Kehl
2023-06-19 07:56:10 -07:00
20 changed files with 138 additions and 107 deletions
+2 -2
View File
@@ -10,9 +10,9 @@ assignees: ''
# TITLE: ADR Title Here # TITLE: ADR Title Here
| CREATED DATE | LAST UPDATED | STATUS | AUTHOR | STAKEHOLDERS | | CREATED DATE | LAST UPDATED | STATUS | IMPLEMENTED | AUTHOR | STAKEHOLDERS |
| :---: | :---: | :---: | :---: | :---: | | :---: | :---: | :---: | :---: | :---: |
| Date when ADR was created - MM/DD/YYYY format | Date when ADR was last updated - MM/DD/YYYY format - or N/A | Current ADR status - one of Proposed / Accepted / Rejected / Deprecated / Superseded By ADR - link to ADR | GitHub username(s) of author(s) | GitHub username(s) or team name(s) of other folks involved | | Date when ADR was created - MM/DD/YYYY format | Date when ADR was last updated - MM/DD/YYYY format - or N/A | Current ADR status - one of Proposed / Accepted / Rejected / Deprecated / Superseded By ADR - link to ADR | Yes or No | GitHub username(s) of author(s) | GitHub username(s) or team name(s) of other folks involved |
## CONTEXT AND PROBLEM STATEMENT ## CONTEXT AND PROBLEM STATEMENT
@@ -36,6 +36,16 @@ body:
- Superseded By - Superseded By
validations: validations:
required: true required: true
- type: dropdown
id: implemented
attributes:
label: Implemented
description: Is this ADR implemented?
options:
- Yes
- No
validations:
required: true
- type: input - type: input
id: superseded_by id: superseded_by
attributes: attributes:
+2 -2
View File
@@ -11,7 +11,7 @@ from app.dao.annual_billing_dao import (
dao_update_annual_billing_for_future_years, dao_update_annual_billing_for_future_years,
set_default_free_allowance_for_service, set_default_free_allowance_for_service,
) )
from app.dao.date_util import get_current_financial_year_start_year from app.dao.date_util import get_current_calendar_year_start_year
from app.dao.fact_billing_dao import ( from app.dao.fact_billing_dao import (
fetch_billing_totals_for_year, fetch_billing_totals_for_year,
fetch_monthly_billing_for_year, fetch_monthly_billing_for_year,
@@ -87,7 +87,7 @@ def create_or_update_free_sms_fragment_limit(service_id):
def update_free_sms_fragment_limit_data(service_id, free_sms_fragment_limit, financial_year_start): def update_free_sms_fragment_limit_data(service_id, free_sms_fragment_limit, financial_year_start):
current_year = get_current_financial_year_start_year() current_year = get_current_calendar_year_start_year()
if not financial_year_start: if not financial_year_start:
financial_year_start = current_year financial_year_start = current_year
+3 -3
View File
@@ -2,7 +2,7 @@ from flask import current_app
from app import db from app import db
from app.dao.dao_utils import autocommit from app.dao.dao_utils import autocommit
from app.dao.date_util import get_current_financial_year_start_year from app.dao.date_util import get_current_calendar_year_start_year
from app.models import AnnualBilling from app.models import AnnualBilling
@@ -38,7 +38,7 @@ def dao_update_annual_billing_for_future_years(service_id, free_sms_fragment_lim
def dao_get_free_sms_fragment_limit_for_year(service_id, financial_year_start=None): def dao_get_free_sms_fragment_limit_for_year(service_id, financial_year_start=None):
if not financial_year_start: if not financial_year_start:
financial_year_start = get_current_financial_year_start_year() financial_year_start = get_current_calendar_year_start_year()
return AnnualBilling.query.filter_by( return AnnualBilling.query.filter_by(
service_id=service_id, service_id=service_id,
@@ -72,7 +72,7 @@ def set_default_free_allowance_for_service(service, year_start=None):
} }
} }
if not year_start: if not year_start:
year_start = get_current_financial_year_start_year() year_start = get_current_calendar_year_start_year()
# handle cases where the year is less than 2020 or greater than 2021 # handle cases where the year is less than 2020 or greater than 2021
if year_start < 2020: if year_start < 2020:
year_start = 2020 year_start = 2020
+13 -14
View File
@@ -15,12 +15,12 @@ def get_months_for_year(start, end, year):
return [datetime(year, month, 1) for month in range(start, end)] return [datetime(year, month, 1) for month in range(start, end)]
def get_financial_year(year): def get_calendar_year(year):
return get_april_fools(year), get_april_fools(year + 1) - timedelta(microseconds=1) return get_new_years(year), get_new_years(year + 1) - timedelta(microseconds=1)
def get_financial_year_dates(year): def get_calendar_year_dates(year):
year_start_datetime, year_end_datetime = get_financial_year(year) year_start_datetime, year_end_datetime = get_calendar_year(year)
return ( return (
year_start_datetime.date(), year_start_datetime.date(),
@@ -28,16 +28,15 @@ def get_financial_year_dates(year):
) )
def get_current_financial_year(): def get_current_calendar_year():
now = datetime.utcnow() now = datetime.utcnow()
current_month = int(now.strftime('%-m'))
current_year = int(now.strftime('%Y')) current_year = int(now.strftime('%Y'))
year = current_year if current_month > 3 else current_year - 1 year = current_year
return get_financial_year(year) return get_calendar_year(year)
def get_april_fools(year): def get_new_years(year):
return datetime(year, 4, 1, 0, 0, 0) return datetime(year, 1, 1, 0, 0, 0)
def get_month_start_and_end_date_in_utc(month_year): def get_month_start_and_end_date_in_utc(month_year):
@@ -53,21 +52,21 @@ def get_month_start_and_end_date_in_utc(month_year):
return first_day, last_day return first_day, last_day
def get_current_financial_year_start_year(): def get_current_calendar_year_start_year():
now = datetime.now() now = datetime.now()
financial_year_start = now.year financial_year_start = now.year
start_date, end_date = get_financial_year(now.year) start_date, end_date = get_calendar_year(now.year)
if now < start_date: if now < start_date:
financial_year_start = financial_year_start - 1 financial_year_start = financial_year_start - 1
return financial_year_start return financial_year_start
def get_financial_year_for_datetime(start_date): def get_calendar_year_for_datetime(start_date):
if type(start_date) == date: if type(start_date) == date:
start_date = datetime.combine(start_date, time.min) start_date = datetime.combine(start_date, time.min)
year = int(start_date.strftime('%Y')) year = int(start_date.strftime('%Y'))
if start_date < get_april_fools(year): if start_date < get_new_years(year):
return year - 1 return year - 1
else: else:
return year return year
+8 -8
View File
@@ -7,8 +7,8 @@ from sqlalchemy.sql.expression import case, literal
from app import db from app import db
from app.dao.date_util import ( from app.dao.date_util import (
get_financial_year_dates, get_calendar_year_dates,
get_financial_year_for_datetime, get_calendar_year_for_datetime,
) )
from app.dao.organisation_dao import dao_get_organisation_live_services from app.dao.organisation_dao import dao_get_organisation_live_services
from app.models import ( from app.models import (
@@ -31,7 +31,7 @@ from app.utils import get_midnight_in_utc
def fetch_sms_free_allowance_remainder_until_date(end_date): def fetch_sms_free_allowance_remainder_until_date(end_date):
# ASSUMPTION: AnnualBilling has been populated for year. # ASSUMPTION: AnnualBilling has been populated for year.
billing_year = get_financial_year_for_datetime(end_date) billing_year = get_calendar_year_for_datetime(end_date)
start_of_year = date(billing_year, 4, 1) start_of_year = date(billing_year, 4, 1)
billable_units = func.coalesce(func.sum(FactBilling.billable_units * FactBilling.rate_multiplier), 0) billable_units = func.coalesce(func.sum(FactBilling.billable_units * FactBilling.rate_multiplier), 0)
@@ -177,7 +177,7 @@ def fetch_monthly_billing_for_year(service_id, year):
Since the data in ft_billing is only refreshed once a day for all services, Since the data in ft_billing is only refreshed once a day for all services,
we also update the table on-the-fly if we need accurate data for this year. we also update the table on-the-fly if we need accurate data for this year.
""" """
_, year_end = get_financial_year_dates(year) _, year_end = get_calendar_year_dates(year)
today = datetime.utcnow().date() today = 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 is less than today, we are calculating for data in the past and have no need for deltas.
@@ -216,7 +216,7 @@ def fetch_monthly_billing_for_year(service_id, year):
def query_service_email_usage_for_year(service_id, year): def query_service_email_usage_for_year(service_id, year):
year_start, year_end = get_financial_year_dates(year) year_start, year_end = get_calendar_year_dates(year)
return db.session.query( return db.session.query(
FactBilling.local_date, FactBilling.local_date,
@@ -265,7 +265,7 @@ def query_service_sms_usage_for_year(service_id, year):
on a given local_date. This means we don't need to worry about how to assign on a given local_date. This means we don't need to worry about how to assign
free allowance if it happens to run out when a rate changes. free allowance if it happens to run out when a rate changes.
""" """
year_start, year_end = get_financial_year_dates(year) year_start, year_end = get_calendar_year_dates(year)
this_rows_chargeable_units = FactBilling.billable_units * FactBilling.rate_multiplier this_rows_chargeable_units = FactBilling.billable_units * FactBilling.rate_multiplier
# Subquery for the number of chargeable units in all rows preceding this one, # Subquery for the number of chargeable units in all rows preceding this one,
@@ -568,7 +568,7 @@ def query_organisation_sms_usage_for_year(organisation_id, year):
""" """
See docstring for query_service_sms_usage_for_year() See docstring for query_service_sms_usage_for_year()
""" """
year_start, year_end = get_financial_year_dates(year) year_start, year_end = get_calendar_year_dates(year)
this_rows_chargeable_units = FactBilling.billable_units * FactBilling.rate_multiplier this_rows_chargeable_units = FactBilling.billable_units * FactBilling.rate_multiplier
# Subquery for the number of chargeable units in all rows preceding this one, # Subquery for the number of chargeable units in all rows preceding this one,
@@ -622,7 +622,7 @@ def query_organisation_sms_usage_for_year(organisation_id, year):
def fetch_usage_year_for_organisation(organisation_id, year): def fetch_usage_year_for_organisation(organisation_id, year):
year_start, year_end = get_financial_year_dates(year) year_start, year_end = get_calendar_year_dates(year)
today = datetime.utcnow().date() today = datetime.utcnow().date()
services = dao_get_organisation_live_services(organisation_id) services = dao_get_organisation_live_services(organisation_id)
+2 -2
View File
@@ -8,7 +8,7 @@ from sqlalchemy.sql.expression import and_, asc, case, func
from app import db from app import db
from app.dao.dao_utils import VersionOptions, autocommit, version_class from app.dao.dao_utils import VersionOptions, autocommit, version_class
from app.dao.date_util import get_current_financial_year from app.dao.date_util import get_current_calendar_year
from app.dao.organisation_dao import dao_get_organisation_by_email_address from app.dao.organisation_dao import dao_get_organisation_by_email_address
from app.dao.service_sms_sender_dao import insert_service_sms_sender from app.dao.service_sms_sender_dao import insert_service_sms_sender
from app.dao.service_user_dao import dao_get_service_user from app.dao.service_user_dao import dao_get_service_user
@@ -79,7 +79,7 @@ def dao_count_live_services():
def dao_fetch_live_services_data(): def dao_fetch_live_services_data():
year_start_date, year_end_date = get_current_financial_year() year_start_date, year_end_date = get_current_calendar_year()
most_recent_annual_billing = db.session.query( most_recent_annual_billing = db.session.query(
AnnualBilling.service_id, AnnualBilling.service_id,
+3 -3
View File
@@ -2,7 +2,7 @@ from datetime import datetime
from flask import Blueprint, jsonify, request from flask import Blueprint, jsonify, request
from app.dao.date_util import get_financial_year_for_datetime from app.dao.date_util import get_calendar_year_for_datetime
from app.dao.fact_billing_dao import ( from app.dao.fact_billing_dao import (
fetch_billing_details_for_all_services, fetch_billing_details_for_all_services,
fetch_daily_sms_provider_volumes_for_platform, fetch_daily_sms_provider_volumes_for_platform,
@@ -54,8 +54,8 @@ def validate_date_range_is_within_a_financial_year(start_date, end_date):
if end_date < start_date: if end_date < start_date:
raise InvalidRequest(message="Start date must be before end date", status_code=400) raise InvalidRequest(message="Start date must be before end date", status_code=400)
start_fy = get_financial_year_for_datetime(get_midnight_in_utc(start_date)) start_fy = get_calendar_year_for_datetime(get_midnight_in_utc(start_date))
end_fy = get_financial_year_for_datetime(get_midnight_in_utc(end_date)) end_fy = get_calendar_year_for_datetime(get_midnight_in_utc(end_date))
if start_fy != end_fy: if start_fy != end_fy:
raise InvalidRequest(message="Date must be in a single financial year.", status_code=400) raise InvalidRequest(message="Date must be in a single financial year.", status_code=400)
+3 -3
View File
@@ -16,7 +16,7 @@ from app.dao.api_key_dao import (
save_model_api_key, save_model_api_key,
) )
from app.dao.dao_utils import dao_rollback, transaction from app.dao.dao_utils import dao_rollback, transaction
from app.dao.date_util import get_financial_year from app.dao.date_util import get_calendar_year
from app.dao.fact_notification_status_dao import ( from app.dao.fact_notification_status_dao import (
fetch_monthly_template_usage_for_service, fetch_monthly_template_usage_for_service,
fetch_notification_status_for_service_by_month, fetch_notification_status_for_service_by_month,
@@ -488,7 +488,7 @@ def get_monthly_notification_stats(service_id):
except ValueError: except ValueError:
raise InvalidRequest('Year must be a number', status_code=400) raise InvalidRequest('Year must be a number', status_code=400)
start_date, end_date = get_financial_year(year) start_date, end_date = get_calendar_year(year)
data = statistics.create_empty_monthly_notification_status_stats_dict(year) data = statistics.create_empty_monthly_notification_status_stats_dict(year)
@@ -629,7 +629,7 @@ def resume_service(service_id):
@service_blueprint.route('/<uuid:service_id>/notifications/templates_usage/monthly', methods=['GET']) @service_blueprint.route('/<uuid:service_id>/notifications/templates_usage/monthly', methods=['GET'])
def get_monthly_template_usage(service_id): def get_monthly_template_usage(service_id):
try: try:
start_date, end_date = get_financial_year(int(request.args.get('year', 'NaN'))) start_date, end_date = get_calendar_year(int(request.args.get('year', 'NaN')))
data = fetch_monthly_template_usage_for_service( data = fetch_monthly_template_usage_for_service(
start_date=start_date, start_date=start_date,
end_date=end_date, end_date=end_date,
+28 -6
View File
@@ -59,6 +59,25 @@ make additional updates, especially if an ADR becomes deprecated or superceded
by another one. by another one.
### Draft and Private ADRs
For ADRs that we are collaborating over in real-time or much more synchronously
as opposed to PR reviews and such, and/or storing private ADRs that we cannot
share publicly, we have an
:lock: [Architectural Decision Record Drive folder](https://drive.google.com/drive/folders/1APnbNZ81AuhZ8RFSyU5i9m_ZIetdHc-Q)
to store these documents in.
For Draft ADRs that can become **public**, once they're in a state that there
isn't as a great a need for synchronous collaboration they can be copied to a
Markdown file using the ADR template in GitHub and moved here, following the
process we have outlined in this document.
For ADRs that must remain **private**, there is a place to store them in the
aforementioned Drive folder once they're in a finalized state. We will still
reference them in the Architectural Decision Log below, but there either won't
be links or the link will go to a :lock: *private document* instead.
### Creating an ADR ### Creating an ADR
To create a new ADR in this repository, you can do one of two things: To create a new ADR in this repository, you can do one of two things:
@@ -116,7 +135,7 @@ retrospective:
* Norm Kerth, Project Retrospectives: A Handbook for Team Review* * Norm Kerth, Project Retrospectives: A Handbook for Team Review*
An approach we can take during the discussions is to use the principles of An approach we can take during the discussions is to use the principles of
[The Art of Alignment](https://drive.google.com/file/d/1pPIzJG1kcnudR1HjZiB5UZgwYJ1dyetS/view?usp=share_link). :lock: [The Art of Alignment](https://drive.google.com/file/d/1pPIzJG1kcnudR1HjZiB5UZgwYJ1dyetS/view?usp=share_link).
There are also other frameworks and tools for sharing proposals and achieving There are also other frameworks and tools for sharing proposals and achieving
consensus within a team. consensus within a team.
@@ -138,6 +157,9 @@ ADR statuses can be one of the following:
- Deprecated - Deprecated
- Superseded By (new ADR number and link) - Superseded By (new ADR number and link)
There is also a field for tracking if an ADR is implemented or not (`Yes` or
`No`).
Once the ADR itself is updated, this README also needs to be updated so that the Once the ADR itself is updated, this README also needs to be updated so that the
ADR is listed in the Architecture Decision Log just below. This lists all of ADR is listed in the Architecture Decision Log just below. This lists all of
our ADRs in reverse chronological order so we have a convenient index of them. our ADRs in reverse chronological order so we have a convenient index of them.
@@ -148,8 +170,8 @@ our ADRs in reverse chronological order so we have a convenient index of them.
This is the log of all of our ADRs in reverse chronological order (newest is up This is the log of all of our ADRs in reverse chronological order (newest is up
top!). top!).
| ADR | TITLE | CURRENT STATUS | LAST MODIFIED | | ADR | TITLE | CURRENT STATUS | IMPLEMENTED | LAST MODIFIED |
| :---: | :---: | :---: | :---: | | :---: | :---: | :---: | :---: | :---: |
| [ADR-0003](./0003-implementing-invite-expirations.md) | [Implementing User Invite Expirations](./0003-implementing-invite-expirations.md) | Proposed | 06/06/2023 | | [ADR-0003](./0003-implementing-invite-expirations.md) | [Implementing User Invite Expirations](./0003-implementing-invite-expirations.md) | Proposed | No | 06/06/2023 |
| [ADR-0002](./0002-how-to-handle-timezones.md) | [Determine How to Handle Timezones in US Notify](./0002-how-to-handle-timezones.md) | Accepted | 06/06/2023 | | [ADR-0002](./0002-how-to-handle-timezones.md) | [Determine How to Handle Timezones in US Notify](./0002-how-to-handle-timezones.md) | Accepted | Yes | 06/06/2023 |
| [ADR-0001](./0001-establishing-adrs-for-us-notify.md) | [Establishing ADRs for US Notify](./0001-establishing-adrs-for-us-notify.md) | Accepted | 06/05/2023 | | [ADR-0001](./0001-establishing-adrs-for-us-notify.md) | [Establishing ADRs for US Notify](./0001-establishing-adrs-for-us-notify.md) | Accepted | Yes | 06/05/2023 |
+2 -2
View File
@@ -1,9 +1,9 @@
# TITLE: ADR Title Here # TITLE: ADR Title Here
| CREATED DATE | LAST UPDATED | STATUS | AUTHOR | STAKEHOLDERS | | CREATED DATE | LAST UPDATED | STATUS | IMPLEMENTED | AUTHOR | STAKEHOLDERS |
| :---: | :---: | :---: | :---: | :---: | | :---: | :---: | :---: | :---: | :---: |
| Date when ADR was created - MM/DD/YYYY format | Date when ADR was last updated - MM/DD/YYYY format - or N/A | Current ADR status - one of Proposed / Accepted / Rejected / Deprecated / Superceded By ADR - link to ADR | GitHub username(s) of author(s) | GitHub username(s) or team name(s) of other folks involved | | Date when ADR was created - MM/DD/YYYY format | Date when ADR was last updated - MM/DD/YYYY format - or N/A | Current ADR status - one of Proposed / Accepted / Rejected / Deprecated / Superceded By ADR - link to ADR | Yes or No | GitHub username(s) of author(s) | GitHub username(s) or team name(s) of other folks involved |
## CONTEXT AND PROBLEM STATEMENT ## CONTEXT AND PROBLEM STATEMENT
@@ -8,7 +8,7 @@ Create Date: 2017-11-10 21:42:59.715203
from datetime import datetime from datetime import datetime
from alembic import op from alembic import op
import uuid import uuid
from app.dao.date_util import get_current_financial_year_start_year from app.dao.date_util import get_current_calendar_year_start_year
revision = '0139_migrate_sms_allowance_data' revision = '0139_migrate_sms_allowance_data'
@@ -16,7 +16,7 @@ down_revision = '0138_sms_sender_nullable'
def upgrade(): def upgrade():
current_year = get_current_financial_year_start_year() current_year = get_current_calendar_year_start_year()
default_limit = 250000 default_limit = 250000
# Step 1: update the column free_sms_fragment_limit in service table if it is empty # Step 1: update the column free_sms_fragment_limit in service table if it is empty
+8 -8
View File
@@ -5,7 +5,7 @@ from freezegun import freeze_time
from app.billing.rest import update_free_sms_fragment_limit_data from app.billing.rest import update_free_sms_fragment_limit_data
from app.dao.annual_billing_dao import dao_get_free_sms_fragment_limit_for_year from app.dao.annual_billing_dao import dao_get_free_sms_fragment_limit_for_year
from app.dao.date_util import get_current_financial_year_start_year from app.dao.date_util import get_current_calendar_year_start_year
from tests.app.db import ( from tests.app.db import (
create_annual_billing, create_annual_billing,
create_ft_billing, create_ft_billing,
@@ -32,7 +32,7 @@ def test_create_update_free_sms_fragment_limit_invalid_schema(admin_request, sam
def test_create_free_sms_fragment_limit_current_year_updates_future_years(admin_request, sample_service): def test_create_free_sms_fragment_limit_current_year_updates_future_years(admin_request, sample_service):
current_year = get_current_financial_year_start_year() current_year = get_current_calendar_year_start_year()
future_billing = create_annual_billing(sample_service.id, 1, current_year + 1) future_billing = create_annual_billing(sample_service.id, 1, current_year + 1)
admin_request.post( admin_request.post(
@@ -54,7 +54,7 @@ def test_create_or_update_free_sms_fragment_limit_past_year_doenst_update_other_
sample_service, sample_service,
update_existing update_existing
): ):
current_year = get_current_financial_year_start_year() current_year = get_current_calendar_year_start_year()
create_annual_billing(sample_service.id, 1, current_year) create_annual_billing(sample_service.id, 1, current_year)
if update_existing: if update_existing:
create_annual_billing(sample_service.id, 1, current_year - 1) create_annual_billing(sample_service.id, 1, current_year - 1)
@@ -71,7 +71,7 @@ def test_create_or_update_free_sms_fragment_limit_past_year_doenst_update_other_
def test_create_free_sms_fragment_limit_updates_existing_year(admin_request, sample_service): def test_create_free_sms_fragment_limit_updates_existing_year(admin_request, sample_service):
current_year = get_current_financial_year_start_year() current_year = get_current_calendar_year_start_year()
annual_billing = create_annual_billing(sample_service.id, 1, current_year) annual_billing = create_annual_billing(sample_service.id, 1, current_year)
admin_request.post( admin_request.post(
@@ -112,7 +112,7 @@ def test_get_free_sms_fragment_limit_current_year_creates_new_row_if_annual_bill
def test_update_free_sms_fragment_limit_data(client, sample_service): def test_update_free_sms_fragment_limit_data(client, sample_service):
current_year = get_current_financial_year_start_year() current_year = get_current_calendar_year_start_year()
create_annual_billing(sample_service.id, free_sms_fragment_limit=250000, financial_year_start=current_year - 1) create_annual_billing(sample_service.id, free_sms_fragment_limit=250000, financial_year_start=current_year - 1)
update_free_sms_fragment_limit_data(sample_service.id, 9999, current_year) update_free_sms_fragment_limit_data(sample_service.id, 9999, current_year)
@@ -128,7 +128,7 @@ def test_get_yearly_usage_by_monthly_from_ft_billing(admin_request, notify_db_se
sms_template = create_template(service=service, template_type="sms") sms_template = create_template(service=service, template_type="sms")
email_template = create_template(service=service, template_type="email") email_template = create_template(service=service, template_type="email")
for dt in (date(2016, 4, 28), date(2016, 11, 10), date(2017, 2, 26)): for dt in (date(2016, 1, 28), date(2016, 8, 10), date(2016, 12, 26)):
create_ft_billing(local_date=dt, template=sms_template, rate=0.0162) create_ft_billing(local_date=dt, template=sms_template, rate=0.0162)
create_ft_billing(local_date=dt, template=email_template, billable_unit=0, rate=0) create_ft_billing(local_date=dt, template=email_template, billable_unit=0, rate=0)
@@ -145,7 +145,7 @@ def test_get_yearly_usage_by_monthly_from_ft_billing(admin_request, notify_db_se
sms_row = next(x for x in json_response if x['notification_type'] == 'sms') sms_row = next(x for x in json_response if x['notification_type'] == 'sms')
assert sms_row["month"] == "April" assert sms_row["month"] == "January"
assert sms_row["notification_type"] == "sms" assert sms_row["notification_type"] == "sms"
assert sms_row["chargeable_units"] == 1 assert sms_row["chargeable_units"] == 1
assert sms_row["notifications_sent"] == 1 assert sms_row["notifications_sent"] == 1
@@ -185,7 +185,7 @@ def test_get_yearly_billing_usage_summary_from_ft_billing(admin_request, notify_
sms_template = create_template(service=service, template_type="sms") sms_template = create_template(service=service, template_type="sms")
email_template = create_template(service=service, template_type="email") email_template = create_template(service=service, template_type="email")
for dt in (date(2016, 4, 28), date(2016, 11, 10), date(2017, 2, 26)): for dt in (date(2016, 1, 28), date(2016, 8, 10), date(2016, 12, 26)):
create_ft_billing(local_date=dt, template=sms_template, rate=0.0162) create_ft_billing(local_date=dt, template=sms_template, rate=0.0162)
create_ft_billing(local_date=dt, template=email_template, billable_unit=0, rate=0) create_ft_billing(local_date=dt, template=email_template, billable_unit=0, rate=0)
+5 -5
View File
@@ -7,14 +7,14 @@ from app.dao.annual_billing_dao import (
dao_update_annual_billing_for_future_years, dao_update_annual_billing_for_future_years,
set_default_free_allowance_for_service, set_default_free_allowance_for_service,
) )
from app.dao.date_util import get_current_financial_year_start_year from app.dao.date_util import get_current_calendar_year_start_year
from app.models import AnnualBilling from app.models import AnnualBilling
from tests.app.db import create_annual_billing, create_service from tests.app.db import create_annual_billing, create_service
def test_dao_update_free_sms_fragment_limit(notify_db_session, sample_service): def test_dao_update_free_sms_fragment_limit(notify_db_session, sample_service):
new_limit = 9999 new_limit = 9999
year = get_current_financial_year_start_year() year = get_current_calendar_year_start_year()
dao_create_or_update_annual_billing_for_year(sample_service.id, new_limit, year) dao_create_or_update_annual_billing_for_year(sample_service.id, new_limit, year)
new_free_limit = dao_get_free_sms_fragment_limit_for_year(sample_service.id, year) new_free_limit = dao_get_free_sms_fragment_limit_for_year(sample_service.id, year)
@@ -31,7 +31,7 @@ def test_create_annual_billing(sample_service):
def test_dao_update_annual_billing_for_future_years(notify_db_session, sample_service): def test_dao_update_annual_billing_for_future_years(notify_db_session, sample_service):
current_year = get_current_financial_year_start_year() current_year = get_current_calendar_year_start_year()
limits = [1, 2, 3, 4] limits = [1, 2, 3, 4]
create_annual_billing(sample_service.id, limits[0], current_year - 1) create_annual_billing(sample_service.id, limits[0], current_year - 1)
create_annual_billing(sample_service.id, limits[2], current_year + 1) create_annual_billing(sample_service.id, limits[2], current_year + 1)
@@ -79,8 +79,8 @@ def test_set_default_free_allowance_for_service_using_correct_year(sample_servic
mock_dao.assert_called_once_with( mock_dao.assert_called_once_with(
sample_service.id, sample_service.id,
250000, 150000,
2020 2021
) )
+15 -15
View File
@@ -3,23 +3,23 @@ from datetime import date, datetime
import pytest import pytest
from app.dao.date_util import ( from app.dao.date_util import (
get_april_fools, get_calendar_year,
get_financial_year, get_calendar_year_for_datetime,
get_financial_year_for_datetime,
get_month_start_and_end_date_in_utc, get_month_start_and_end_date_in_utc,
get_new_years,
) )
def test_get_financial_year(): def test_get_calendar_year():
start, end = get_financial_year(2000) start, end = get_calendar_year(2000)
assert str(start) == '2000-04-01 00:00:00' assert str(start) == '2000-01-01 00:00:00'
assert str(end) == '2001-03-31 23:59:59.999999' assert str(end) == '2000-12-31 23:59:59.999999'
def test_get_april_fools(): def test_get_new_years():
april_fools = get_april_fools(2016) new_years = get_new_years(2016)
assert str(april_fools) == '2016-04-01 00:00:00' assert str(new_years) == '2016-01-01 00:00:00'
assert april_fools.tzinfo is None assert new_years.tzinfo is None
@pytest.mark.parametrize("month, year, expected_start, expected_end", [ @pytest.mark.parametrize("month, year, expected_start, expected_end", [
@@ -38,9 +38,9 @@ def test_get_month_start_and_end_date_in_utc(month, year, expected_start, expect
@pytest.mark.parametrize("dt, fy", [ @pytest.mark.parametrize("dt, fy", [
(datetime(2018, 4, 1, 1, 0, 0), 2018), (datetime(2018, 4, 1, 1, 0, 0), 2018),
(datetime(2019, 3, 31, 23, 59, 59), 2018), (datetime(2019, 3, 31, 23, 59, 59), 2019),
(date(2019, 3, 31), 2018), (date(2019, 3, 31), 2019),
(date(2019, 4, 2), 2019), (date(2019, 4, 2), 2019),
]) ])
def test_get_financial_year_for_datetime(dt, fy): def test_get_calendar_year_for_datetime(dt, fy):
assert get_financial_year_for_datetime(dt) == fy assert get_calendar_year_for_datetime(dt) == fy
+19 -16
View File
@@ -43,13 +43,13 @@ def set_up_yearly_data():
# use different rates for adjacent financial years to make sure the query # use different rates for adjacent financial years to make sure the query
# doesn't accidentally bleed over into them # doesn't accidentally bleed over into them
for dt in (date(2016, 3, 31), date(2017, 4, 1)): for dt in (date(2015, 12, 31), date(2017, 1, 1)):
create_ft_billing(local_date=dt, template=sms_template, rate=0.163) create_ft_billing(local_date=dt, template=sms_template, rate=0.163)
create_ft_billing(local_date=dt, template=email_template, rate=0, billable_unit=0) create_ft_billing(local_date=dt, template=email_template, rate=0, billable_unit=0)
# a selection of dates that represent the extreme ends of the financial year # a selection of dates that represent the extreme ends of the financial year
# and some arbitrary dates in between # and some arbitrary dates in between
for dt in (date(2016, 4, 1), date(2016, 4, 29), date(2017, 2, 6), date(2017, 3, 31)): for dt in (date(2016, 1, 1), date(2016, 1, 31), date(2016, 12, 6), date(2016, 12, 31)):
create_ft_billing(local_date=dt, template=sms_template, rate=0.162) create_ft_billing(local_date=dt, template=sms_template, rate=0.162)
create_ft_billing(local_date=dt, template=email_template, rate=0, billable_unit=0) create_ft_billing(local_date=dt, template=email_template, rate=0, billable_unit=0)
@@ -295,9 +295,10 @@ def test_fetch_monthly_billing_for_year(notify_db_session):
create_annual_billing(service_id=service.id, free_sms_fragment_limit=1, financial_year_start=2016) create_annual_billing(service_id=service.id, free_sms_fragment_limit=1, financial_year_start=2016)
results = fetch_monthly_billing_for_year(service.id, 2016) results = fetch_monthly_billing_for_year(service.id, 2016)
assert len(results) == 6 # 3 billed months for each type assert len(results) == 4 # 3 billed months for each type
print(f"RESULTS {results}")
assert str(results[0].month) == "2016-04-01" assert str(results[0].month) == "2016-01-01"
assert results[0].notification_type == 'email' assert results[0].notification_type == 'email'
assert results[0].notifications_sent == 2 assert results[0].notifications_sent == 2
assert results[0].chargeable_units == 0 assert results[0].chargeable_units == 0
@@ -306,7 +307,7 @@ def test_fetch_monthly_billing_for_year(notify_db_session):
assert results[0].free_allowance_used == 0 assert results[0].free_allowance_used == 0
assert results[0].charged_units == 0 assert results[0].charged_units == 0
assert str(results[1].month) == "2016-04-01" assert str(results[1].month) == "2016-01-01"
assert results[1].notification_type == 'sms' assert results[1].notification_type == 'sms'
assert results[1].notifications_sent == 2 assert results[1].notifications_sent == 2
assert results[1].chargeable_units == 2 assert results[1].chargeable_units == 2
@@ -316,8 +317,7 @@ def test_fetch_monthly_billing_for_year(notify_db_session):
assert results[1].free_allowance_used == 1 assert results[1].free_allowance_used == 1
assert results[1].charged_units == 1 assert results[1].charged_units == 1
assert str(results[2].month) == "2017-02-01" assert str(results[2].month) == "2016-12-01"
assert str(results[5].month) == "2017-03-01"
def test_fetch_monthly_billing_for_year_variable_rates(notify_db_session): def test_fetch_monthly_billing_for_year_variable_rates(notify_db_session):
@@ -394,8 +394,8 @@ def test_fetch_billing_totals_for_year(notify_db_session):
def test_fetch_billing_totals_for_year_uses_current_annual_billing(notify_db_session): def test_fetch_billing_totals_for_year_uses_current_annual_billing(notify_db_session):
service = set_up_yearly_data() service = set_up_yearly_data()
create_annual_billing(service_id=service.id, free_sms_fragment_limit=400, financial_year_start=2015) create_annual_billing(service_id=service.id, free_sms_fragment_limit=400, financial_year_start=2016)
create_annual_billing(service_id=service.id, free_sms_fragment_limit=0, financial_year_start=2016) create_annual_billing(service_id=service.id, free_sms_fragment_limit=0, financial_year_start=2017)
result = next( result = next(
result for result in result for result in
@@ -404,7 +404,10 @@ def test_fetch_billing_totals_for_year_uses_current_annual_billing(notify_db_ses
) )
assert result.chargeable_units == 4 assert result.chargeable_units == 4
assert result.cost > 0 # No charge for 2016 because we have free sms fragments.
# There would be a charge for 2017,
# but we are only billing for 2016 so cost is zero
assert result.cost == 0
def test_fetch_billing_totals_for_year_variable_rates(notify_db_session): def test_fetch_billing_totals_for_year_variable_rates(notify_db_session):
@@ -741,9 +744,9 @@ def test_fetch_usage_year_for_organisation_only_queries_present_year(notify_db_s
results = fetch_usage_year_for_organisation(organisation_id=org.id, year=last_year) results = fetch_usage_year_for_organisation(organisation_id=org.id, year=last_year)
assert len(results) == 1 assert len(results) == 1
assert results[str(service_1.id)]['sms_billable_units'] == 4 assert results[str(service_1.id)]['sms_billable_units'] == 2
assert results[str(service_1.id)]['chargeable_billable_sms'] == 4 assert results[str(service_1.id)]['chargeable_billable_sms'] == 2
assert results[str(service_1.id)]['sms_cost'] == 4.0 assert results[str(service_1.id)]['sms_cost'] == 2.0
@freeze_time('2020-02-27 13:30') @freeze_time('2020-02-27 13:30')
@@ -762,10 +765,10 @@ def test_fetch_usage_year_for_organisation_only_returns_data_for_live_services(n
notifications_sent=100) notifications_sent=100)
create_ft_billing(local_date=datetime.utcnow().date(), template=trial_sms_template, billable_unit=200, rate=0.0158, create_ft_billing(local_date=datetime.utcnow().date(), template=trial_sms_template, billable_unit=200, rate=0.0158,
notifications_sent=100) notifications_sent=100)
create_annual_billing(service_id=live_service.id, free_sms_fragment_limit=0, financial_year_start=2019) create_annual_billing(service_id=live_service.id, free_sms_fragment_limit=0, financial_year_start=2020)
create_annual_billing(service_id=trial_service.id, free_sms_fragment_limit=0, financial_year_start=2019) create_annual_billing(service_id=trial_service.id, free_sms_fragment_limit=0, financial_year_start=2020)
results = fetch_usage_year_for_organisation(organisation_id=org.id, year=2019) results = fetch_usage_year_for_organisation(organisation_id=org.id, year=2020)
assert len(results) == 1 assert len(results) == 1
assert results[str(live_service.id)]['sms_billable_units'] == 19 assert results[str(live_service.id)]['sms_billable_units'] == 19
+1 -1
View File
@@ -787,7 +787,7 @@ def test_get_organisation_users_returns_users_for_organisation(admin_request, sa
assert response['data'][0]['id'] == str(first.id) assert response['data'][0]['id'] == str(first.id)
@freeze_time('2020-02-24 13:30') @freeze_time('2019-12-24 13:30')
def test_get_organisation_services_usage(admin_request, notify_db_session): def test_get_organisation_services_usage(admin_request, notify_db_session):
org = create_organisation(name='Organisation without live services') org = create_organisation(name='Organisation without live services')
service = create_service() service = create_service()
+3 -5
View File
@@ -83,17 +83,15 @@ def test_get_platform_stats_with_real_query(admin_request, notify_db_session):
@pytest.mark.parametrize('start_date, end_date', @pytest.mark.parametrize('start_date, end_date',
[('2019-04-01', '2019-06-30'), [('2019-04-01', '2019-06-30'),
('2019-08-01', '2019-09-30'), ('2019-08-01', '2019-09-30'),
('2019-01-01', '2019-03-31'), ('2019-01-01', '2019-03-31')])
('2019-12-01', '2020-02-28')])
def test_validate_date_range_is_within_a_financial_year(start_date, end_date): def test_validate_date_range_is_within_a_financial_year(start_date, end_date):
validate_date_range_is_within_a_financial_year(start_date, end_date) validate_date_range_is_within_a_financial_year(start_date, end_date)
@pytest.mark.parametrize('start_date, end_date', @pytest.mark.parametrize('start_date, end_date',
[('2019-04-01', '2020-06-30'), [('2019-04-01', '2020-06-30'),
('2019-01-01', '2019-04-30'), ('2018-01-01', '2019-04-30'),
('2019-12-01', '2020-04-30'), ('2019-12-01', '2020-04-30')])
('2019-03-31', '2019-04-01')])
def test_validate_date_range_is_within_a_financial_year_raises(start_date, end_date): def test_validate_date_range_is_within_a_financial_year_raises(start_date, end_date):
with pytest.raises(expected_exception=InvalidRequest) as e: with pytest.raises(expected_exception=InvalidRequest) as e:
validate_date_range_is_within_a_financial_year(start_date, end_date) validate_date_range_is_within_a_financial_year(start_date, end_date)
+3 -4
View File
@@ -249,17 +249,16 @@ def test_get_monthly_notification_stats_ignores_test_keys(admin_request, sample_
def test_get_monthly_notification_stats_checks_dates(admin_request, sample_service): def test_get_monthly_notification_stats_checks_dates(admin_request, sample_service):
t = create_template(sample_service) t = create_template(sample_service)
create_ft_notification_status(datetime(2016, 3, 31), template=t, notification_status='created') # create_ft_notification_status(datetime(2016, 3, 31), template=t, notification_status='created')
create_ft_notification_status(datetime(2016, 4, 2), template=t, notification_status='sending') create_ft_notification_status(datetime(2016, 4, 2), template=t, notification_status='sending')
create_ft_notification_status(datetime(2017, 3, 31), template=t, notification_status='delivered') create_ft_notification_status(datetime(2017, 3, 31), template=t, notification_status='delivered')
create_ft_notification_status(datetime(2017, 4, 11), template=t, notification_status='permanent-failure') create_ft_notification_status(datetime(2017, 4, 11), template=t, notification_status='permanent-failure')
response = admin_request.get('service.get_monthly_notification_stats', service_id=sample_service.id, year=2016) response = admin_request.get('service.get_monthly_notification_stats', service_id=sample_service.id, year=2016)
assert '2016-04' in response['data']
assert '2016-03' not in response['data']
assert '2017-04' not in response['data'] assert '2017-04' not in response['data']
assert response['data']['2016-04']['sms'] == {'sending': 1} assert response['data']['2016-04']['sms'] == {'sending': 1}
assert response['data']['2017-03']['sms'] == {'delivered': 1} assert response['data']['2016-04']['sms'] == {'sending': 1}
def test_get_monthly_notification_stats_only_gets_for_one_service(admin_request, notify_db_session): def test_get_monthly_notification_stats_only_gets_for_one_service(admin_request, notify_db_session):
+6 -6
View File
@@ -1,16 +1,16 @@
from freezegun import freeze_time from freezegun import freeze_time
from app.dao.date_util import get_current_financial_year_start_year from app.dao.date_util import get_current_calendar_year_start_year
# see get_financial_year for conversion of financial years. # see get_financial_year for conversion of financial years.
@freeze_time("2017-03-31 23:59:59.999999") @freeze_time("2017-03-31 23:59:59.999999")
def test_get_current_financial_year_start_year_before_march(): def test_get_current_calendar_year_start_year_before_march():
current_fy = get_current_financial_year_start_year() current_fy = get_current_calendar_year_start_year()
assert current_fy == 2016 assert current_fy == 2017
@freeze_time("2017-04-01 04:00:00.000000") @freeze_time("2017-04-01 04:00:00.000000")
def test_get_current_financial_year_start_year_after_april(): def test_get_current_calendar_year_start_year_after_april():
current_fy = get_current_financial_year_start_year() current_fy = get_current_calendar_year_start_year()
assert current_fy == 2017 assert current_fy == 2017