Refactor reporting task so that methods can be used for the annual usage page.

Still a work in progress, tests are coming.
This commit is contained in:
Rebecca Law
2018-04-24 17:37:04 +01:00
parent bd91aac763
commit 16ef133aa5
5 changed files with 257 additions and 243 deletions

View File

@@ -1,31 +1,16 @@
from datetime import datetime, timedelta, time from datetime import datetime, timedelta, time
from app.models import (Notification,
Rate,
NOTIFICATION_CREATED,
NOTIFICATION_TECHNICAL_FAILURE,
KEY_TYPE_TEST,
LetterRate,
FactBilling,
Service,
LETTER_TYPE, SMS_TYPE)
from app import db
from sqlalchemy import func, desc, case
from notifications_utils.statsd_decorators import statsd
from app import notify_celery
from flask import current_app from flask import current_app
from notifications_utils.statsd_decorators import statsd
from app import notify_celery
from app.dao.fact_billing_dao import (
fetch_billing_data,
update_fact_billing
)
from app.utils import convert_bst_to_utc from app.utils import convert_bst_to_utc
def get_rate(non_letter_rates, letter_rates, notification_type, date, crown=None, rate_multiplier=None):
if notification_type == LETTER_TYPE:
return next(r[3] for r in letter_rates if date > r[0] and crown == r[1] and rate_multiplier == r[2])
elif notification_type == SMS_TYPE:
return next(r[2] for r in non_letter_rates if notification_type == r[0] and date > r[1])
else:
return 0
@notify_celery.task(name="create-nightly-billing") @notify_celery.task(name="create-nightly-billing")
@statsd(namespace="tasks") @statsd(namespace="tasks")
def create_nightly_billing(day_start=None): def create_nightly_billing(day_start=None):
@@ -34,91 +19,21 @@ def create_nightly_billing(day_start=None):
if day_start is None: if day_start is None:
day_start = datetime.today() - timedelta(days=1) day_start = datetime.today() - timedelta(days=1)
non_letter_rates = [(r.notification_type, r.valid_from, r.rate) for r in
Rate.query.order_by(desc(Rate.valid_from)).all()]
letter_rates = [(r.start_date, r.crown, r.sheet_count, r.rate) for r in
LetterRate.query.order_by(desc(LetterRate.start_date)).all()]
for i in range(0, 3): for i in range(0, 3):
process_day = day_start - timedelta(days=i) process_day = day_start - timedelta(days=i)
ds = convert_bst_to_utc(datetime.combine(process_day, time.min)) ds = convert_bst_to_utc(datetime.combine(process_day, time.min))
de = convert_bst_to_utc(datetime.combine(process_day + timedelta(days=1), time.min)) de = convert_bst_to_utc(datetime.combine(process_day + timedelta(days=1), time.min))
transit_data = db.session.query( transit_data = fetch_billing_data(start_date=ds, end_date=de)
Notification.template_id,
Notification.service_id,
Notification.notification_type,
func.coalesce(Notification.sent_by,
case(
[
(Notification.notification_type == 'letter', 'dvla'),
(Notification.notification_type == 'sms', 'unknown'),
(Notification.notification_type == 'email', 'ses')
]),
).label('sent_by'),
func.coalesce(Notification.rate_multiplier, 1).label('rate_multiplier'),
func.coalesce(Notification.international, False).label('international'),
func.sum(Notification.billable_units).label('billable_units'),
func.count().label('notifications_sent'),
Service.crown,
).filter(
Notification.status != NOTIFICATION_CREATED, # at created status, provider information is not available
Notification.status != NOTIFICATION_TECHNICAL_FAILURE,
Notification.key_type != KEY_TYPE_TEST,
Notification.created_at >= ds,
Notification.created_at < de
).group_by(
Notification.template_id,
Notification.service_id,
Notification.notification_type,
'sent_by',
Notification.rate_multiplier,
Notification.international,
Service.crown
).join(
Service
).all()
updated_records = 0 updated_records = 0
inserted_records = 0 inserted_records = 0
for data in transit_data: for data in transit_data:
update_count = FactBilling.query.filter( inserted_records, updated_records = update_fact_billing(data,
FactBilling.bst_date == datetime.date(process_day), inserted_records,
FactBilling.template_id == data.template_id, process_day,
FactBilling.service_id == data.service_id, updated_records)
FactBilling.provider == data.sent_by, # This could be zero - this is a bug that needs to be fixed.
FactBilling.rate_multiplier == data.rate_multiplier,
FactBilling.notification_type == data.notification_type,
FactBilling.international == data.international
).update(
{"notifications_sent": data.notifications_sent,
"billable_units": data.billable_units},
synchronize_session=False)
if update_count == 0:
billing_record = FactBilling(
bst_date=process_day,
template_id=data.template_id,
service_id=data.service_id,
notification_type=data.notification_type,
provider=data.sent_by,
rate_multiplier=data.rate_multiplier,
international=data.international,
billable_units=data.billable_units,
notifications_sent=data.notifications_sent,
rate=get_rate(non_letter_rates,
letter_rates,
data.notification_type,
process_day,
data.crown,
data.rate_multiplier)
)
db.session.add(billing_record)
inserted_records += 1
updated_records += update_count
db.session.commit()
current_app.logger.info('ft_billing {} to {}: {} rows updated, {} rows inserted' current_app.logger.info('ft_billing {} to {}: {} rows updated, {} rows inserted'
.format(ds, de, updated_records, inserted_records)) .format(ds, de, updated_records, inserted_records))

View File

@@ -1,9 +1,16 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from sqlalchemy import func from sqlalchemy import func, case, desc, extract
from app import db from app import db
from app.dao.date_util import get_month_start_and_end_date_in_utc, get_financial_year from app.dao.date_util import get_month_start_and_end_date_in_utc, get_financial_year
from app.models import FactBilling from app.models import (
FactBilling, Notification, Service, NOTIFICATION_CREATED, NOTIFICATION_TECHNICAL_FAILURE,
KEY_TYPE_TEST,
LETTER_TYPE,
SMS_TYPE,
Rate,
LetterRate
)
from app.utils import convert_utc_to_bst from app.utils import convert_utc_to_bst
@@ -35,50 +42,143 @@ def fetch_annual_billing_by_month(service_id, billing_month, notification_type):
return monthly_data, start_date return monthly_data, start_date
def need_deltas(start_date, end_date, service_id, notification_type):
max_fact_billing_date = db.session.query(
func.max(FactBilling.bst_date)
).filter(
FactBilling.notification_type == notification_type,
FactBilling.service_id == service_id,
FactBilling.bst_date >= start_date,
FactBilling.bst_date <= end_date
).one()
print(max_fact_billing_date)
return max_fact_billing_date < end_date
def fetch_annual_billing_for_year(service_id, year): def fetch_annual_billing_for_year(service_id, year):
year_start_date, year_end_date = get_financial_year(year) year_start_date, year_end_date = get_financial_year(year)
utcnow = datetime.utcnow() utcnow = datetime.utcnow()
today = convert_utc_to_bst(utcnow) today = convert_utc_to_bst(utcnow)
last_2_days = utcnow - timedelta(days=2)
last_bst_date_for_ft = convert_utc_to_bst(last_2_days)
# 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.
if year_end_date >= today: if year_end_date >= today:
todays_data = get_deltas(service_id, last_2_days, today) last_2_days = utcnow - timedelta(days=2)
year_end_date = last_bst_date_for_ft data = fetch_billing_data(start_date=last_2_days, end_date=today, service_id=service_id)
inserted_records = 0
updated_records = 0
for d in data:
update_fact_billing(data=data,
inserted_records=inserted_records,
process_day=d.created_at,
updated_records=updated_records)
yearly_data = db.session.query( yearly_data = db.session.query(
FactBilling.notifications_sent, extract('month', FactBilling.bst_date).label("Month"),
FactBilling.billable_units, func.sum(FactBilling.notifications_sent).label("notifications_sent"),
func.sum(FactBilling.billable_units).label("billable_units"),
FactBilling.service_id, FactBilling.service_id,
FactBilling.notification_type,
FactBilling.rate, FactBilling.rate,
FactBilling.rate_multiplier, FactBilling.rate_multiplier,
FactBilling.international FactBilling.international
).filter( ).filter(
FactBilling.service_id == service_id, FactBilling.service_id == service_id,
FactBilling.bst_date >= year_start_date, FactBilling.bst_date >= year_start_date,
FactBilling.bst_date <= last_bst_date_for_ft FactBilling.bst_date <= year_end_date
).group_by(
extract('month', FactBilling.bst_date),
FactBilling.service_id,
FactBilling.rate,
FactBilling.rate_multiplier,
FactBilling.international
).all() ).all()
# today_data + yearly_data and aggregate by month return yearly_data
def get_deltas(service_id, start_date_end_date): def fetch_billing_data(start_date, end_date, service_id=None):
# query ft_billing data using queries from create_nightly_billing transit_data = db.session.query(
return [] Notification.template_id,
Notification.service_id,
Notification.notification_type,
func.coalesce(Notification.sent_by,
case(
[
(Notification.notification_type == 'letter', 'dvla'),
(Notification.notification_type == 'sms', 'unknown'),
(Notification.notification_type == 'email', 'ses')
]),
).label('sent_by'),
func.coalesce(Notification.rate_multiplier, 1).label('rate_multiplier'),
func.coalesce(Notification.international, False).label('international'),
func.sum(Notification.billable_units).label('billable_units'),
func.count().label('notifications_sent'),
Service.crown,
).filter(
Notification.status != NOTIFICATION_CREATED, # at created status, provider information is not available
Notification.status != NOTIFICATION_TECHNICAL_FAILURE,
Notification.key_type != KEY_TYPE_TEST,
Notification.created_at >= start_date,
Notification.created_at < end_date
).group_by(
Notification.template_id,
Notification.service_id,
Notification.notification_type,
'sent_by',
Notification.rate_multiplier,
Notification.international,
Service.crown
).join(
Service
)
if service_id:
transit_data.filter(Notification.service_id == service_id)
return transit_data.all()
def get_rates_for_billing():
non_letter_rates = [(r.notification_type, r.valid_from, r.rate) for r in
Rate.query.order_by(desc(Rate.valid_from)).all()]
letter_rates = [(r.start_date, r.crown, r.sheet_count, r.rate) for r in
LetterRate.query.order_by(desc(LetterRate.start_date)).all()]
return non_letter_rates, letter_rates
def get_rate(non_letter_rates, letter_rates, notification_type, date, crown=None, rate_multiplier=None):
if notification_type == LETTER_TYPE:
return next(r[3] for r in letter_rates if date > r[0] and crown == r[1] and rate_multiplier == r[2])
elif notification_type == SMS_TYPE:
return next(r[2] for r in non_letter_rates if notification_type == r[0] and date > r[1])
else:
return 0
def update_fact_billing(data, inserted_records, process_day, updated_records):
non_letter_rates, letter_rates = get_rates_for_billing()
update_count = FactBilling.query.filter(
FactBilling.bst_date == datetime.date(process_day),
FactBilling.template_id == data.template_id,
FactBilling.service_id == data.service_id,
FactBilling.provider == data.sent_by, # This could be zero - this is a bug that needs to be fixed.
FactBilling.rate_multiplier == data.rate_multiplier,
FactBilling.notification_type == data.notification_type,
FactBilling.international == data.international
).update(
{"notifications_sent": data.notifications_sent,
"billable_units": data.billable_units},
synchronize_session=False)
if update_count == 0:
rate = get_rate(non_letter_rates,
letter_rates,
data.notification_type,
process_day,
data.crown,
data.rate_multiplier)
billing_record = create_billing_record(data, rate, process_day)
db.session.add(billing_record)
inserted_records += 1
updated_records += update_count
db.session.commit()
return inserted_records, updated_records
def create_billing_record(data, rate, process_day):
billing_record = FactBilling(
bst_date=process_day,
template_id=data.template_id,
service_id=data.service_id,
notification_type=data.notification_type,
provider=data.sent_by,
rate_multiplier=data.rate_multiplier,
international=data.international,
billable_units=data.billable_units,
notifications_sent=data.notifications_sent,
rate=rate
)
return billing_record

View File

@@ -1,6 +1,7 @@
from datetime import datetime, timedelta, date from datetime import datetime, timedelta, date
from tests.app.conftest import sample_notification from tests.app.conftest import sample_notification
from app.celery.reporting_tasks import create_nightly_billing, get_rate from app.celery.reporting_tasks import create_nightly_billing
from app.dao.fact_billing_dao import get_rate
from app.models import (FactBilling, from app.models import (FactBilling,
Notification, Notification,
LETTER_TYPE, LETTER_TYPE,
@@ -44,7 +45,7 @@ def test_create_nightly_billing_sms_rate_multiplier(
yesterday = datetime.now() - timedelta(days=1) yesterday = datetime.now() - timedelta(days=1)
mocker.patch('app.celery.reporting_tasks.get_rate', side_effect=mocker_get_rate) mocker.patch('app.dao.fact_billing_dao.get_rate', side_effect=mocker_get_rate)
# These are sms notifications # These are sms notifications
sample_notification( sample_notification(
@@ -94,7 +95,7 @@ def test_create_nightly_billing_different_templates(
mocker): mocker):
yesterday = datetime.now() - timedelta(days=1) yesterday = datetime.now() - timedelta(days=1)
mocker.patch('app.celery.reporting_tasks.get_rate', side_effect=mocker_get_rate) mocker.patch('app.dao.fact_billing_dao.get_rate', side_effect=mocker_get_rate)
sample_notification( sample_notification(
notify_db, notify_db,
@@ -147,7 +148,7 @@ def test_create_nightly_billing_different_sent_by(
mocker): mocker):
yesterday = datetime.now() - timedelta(days=1) yesterday = datetime.now() - timedelta(days=1)
mocker.patch('app.celery.reporting_tasks.get_rate', side_effect=mocker_get_rate) mocker.patch('app.dao.fact_billing_dao.get_rate', side_effect=mocker_get_rate)
# These are sms notifications # These are sms notifications
sample_notification( sample_notification(
@@ -197,7 +198,7 @@ def test_create_nightly_billing_letter(
mocker): mocker):
yesterday = datetime.now() - timedelta(days=1) yesterday = datetime.now() - timedelta(days=1)
mocker.patch('app.celery.reporting_tasks.get_rate', side_effect=mocker_get_rate) mocker.patch('app.dao.fact_billing_dao.get_rate', side_effect=mocker_get_rate)
sample_notification( sample_notification(
notify_db, notify_db,
@@ -234,7 +235,7 @@ def test_create_nightly_billing_null_sent_by_sms(
mocker): mocker):
yesterday = datetime.now() - timedelta(days=1) yesterday = datetime.now() - timedelta(days=1)
mocker.patch('app.celery.reporting_tasks.get_rate', side_effect=mocker_get_rate) mocker.patch('app.dao.fact_billing_dao.get_rate', side_effect=mocker_get_rate)
sample_notification( sample_notification(
notify_db, notify_db,
@@ -272,7 +273,7 @@ def test_create_nightly_billing_consolidate_from_3_days_delta(
sample_template, sample_template,
mocker): mocker):
mocker.patch('app.celery.reporting_tasks.get_rate', side_effect=mocker_get_rate) mocker.patch('app.dao.fact_billing_dao.get_rate', side_effect=mocker_get_rate)
# create records from 11th to 15th # create records from 11th to 15th
for i in range(0, 5): for i in range(0, 5):
@@ -365,7 +366,7 @@ def test_create_nightly_billing_use_BST(
sample_template, sample_template,
mocker): mocker):
mocker.patch('app.celery.reporting_tasks.get_rate', side_effect=mocker_get_rate) mocker.patch('app.dao.fact_billing_dao.get_rate', side_effect=mocker_get_rate)
sample_notification( sample_notification(
notify_db, notify_db,
@@ -414,7 +415,7 @@ def test_create_nightly_billing_update_when_record_exists(
sample_template, sample_template,
mocker): mocker):
mocker.patch('app.celery.reporting_tasks.get_rate', side_effect=mocker_get_rate) mocker.patch('app.dao.fact_billing_dao.get_rate', side_effect=mocker_get_rate)
sample_notification( sample_notification(
notify_db, notify_db,

View File

@@ -1,11 +1,12 @@
from datetime import datetime from datetime import datetime
from decimal import Decimal from decimal import Decimal
from freezegun import freeze_time from app.dao.fact_billing_dao import fetch_annual_billing_by_month, fetch_annual_billing_for_year
from tests.app.db import (
from app.dao.date_util import get_month_start_and_end_date_in_utc create_ft_billing,
from app.dao.fact_billing_dao import fetch_annual_billing_by_month, need_deltas create_service,
from tests.app.db import create_ft_billing, create_service, create_template, create_notification create_template
)
def test_fetch_annual_billing_by_month(notify_db_session): def test_fetch_annual_billing_by_month(notify_db_session):
@@ -20,23 +21,20 @@ def test_fetch_annual_billing_by_month(notify_db_session):
results, month = fetch_annual_billing_by_month(service_id=record.service_id, results, month = fetch_annual_billing_by_month(service_id=record.service_id,
billing_month=datetime(2018, 1, 1), billing_month=datetime(2018, 1, 1),
notification_type='email') notification_type='email')
assert len(results) == 1 assert len(results) == 1
assert results[0] == (31, Decimal('31'), service.id, 'email', Decimal('0'), Decimal('1'), False) assert results[0] == (31, Decimal('31'), service.id, 'email', Decimal('0'), Decimal('1'), False)
assert month == datetime(2018, 1, 1) assert month == datetime(2018, 1, 1)
@freeze_time("2018-01-21 13:00:00") def test_fetch_annual_billing_for_year(notify_db_session):
def test_need_deltas(notify_db_session):
service = create_service() service = create_service()
template = create_template(service=service, template_type="email") template = create_template(service=service, template_type="email")
for i in range(1, 21): for i in range(1, 31):
record = create_ft_billing(bst_date='2018-01-{}'.format(i), create_ft_billing(bst_date='2018-06-{}'.format(i),
service=service, service=service,
template=template, template=template,
notification_type='email') notification_type='email')
start_date, end_date = get_month_start_and_end_date_in_utc(datetime.utcnow()) results = fetch_annual_billing_for_year(service_id=service.id,
year=2018)
result = need_deltas(start_date=start_date, end_date=end_date, assert results
service_id=service.id, notification_type='email')
assert result

View File

@@ -66,17 +66,17 @@ def create_user(mobile_number="+447700900986", email="notify@digital.cabinet-off
def create_service( def create_service(
user=None, user=None,
service_name="Sample service", service_name="Sample service",
service_id=None, service_id=None,
restricted=False, restricted=False,
service_permissions=[EMAIL_TYPE, SMS_TYPE], service_permissions=[EMAIL_TYPE, SMS_TYPE],
research_mode=False, research_mode=False,
active=True, active=True,
email_from=None, email_from=None,
prefix_sms=True, prefix_sms=True,
message_limit=1000, message_limit=1000,
organisation_type='central' organisation_type='central'
): ):
service = Service( service = Service(
name=service_name, name=service_name,
@@ -97,8 +97,8 @@ def create_service(
def create_service_with_inbound_number( def create_service_with_inbound_number(
inbound_number='1234567', inbound_number='1234567',
*args, **kwargs *args, **kwargs
): ):
service = create_service(*args, **kwargs) service = create_service(*args, **kwargs)
@@ -112,8 +112,8 @@ def create_service_with_inbound_number(
def create_service_with_defined_sms_sender( def create_service_with_defined_sms_sender(
sms_sender_value='1234567', sms_sender_value='1234567',
*args, **kwargs *args, **kwargs
): ):
service = create_service(*args, **kwargs) service = create_service(*args, **kwargs)
@@ -127,13 +127,13 @@ def create_service_with_defined_sms_sender(
def create_template( def create_template(
service, service,
template_type=SMS_TYPE, template_type=SMS_TYPE,
template_name=None, template_name=None,
subject='Template subject', subject='Template subject',
content='Dear Sir/Madam, Hello. Yours Truly, The Government.', content='Dear Sir/Madam, Hello. Yours Truly, The Government.',
reply_to=None, reply_to=None,
hidden=False hidden=False
): ):
data = { data = {
'name': template_name or '{} Template Name'.format(template_type), 'name': template_name or '{} Template Name'.format(template_type),
@@ -152,29 +152,29 @@ def create_template(
def create_notification( def create_notification(
template, template,
job=None, job=None,
job_row_number=None, job_row_number=None,
to_field=None, to_field=None,
status='created', status='created',
reference=None, reference=None,
created_at=None, created_at=None,
sent_at=None, sent_at=None,
updated_at=None, updated_at=None,
billable_units=1, billable_units=1,
personalisation=None, personalisation=None,
api_key=None, api_key=None,
key_type=KEY_TYPE_NORMAL, key_type=KEY_TYPE_NORMAL,
sent_by=None, sent_by=None,
client_reference=None, client_reference=None,
rate_multiplier=None, rate_multiplier=None,
international=False, international=False,
phone_prefix=None, phone_prefix=None,
scheduled_for=None, scheduled_for=None,
normalised_to=None, normalised_to=None,
one_off=False, one_off=False,
sms_sender_id=None, sms_sender_id=None,
reply_to_text=None reply_to_text=None
): ):
if created_at is None: if created_at is None:
created_at = datetime.utcnow() created_at = datetime.utcnow()
@@ -236,13 +236,13 @@ def create_notification(
def create_job( def create_job(
template, template,
notification_count=1, notification_count=1,
created_at=None, created_at=None,
job_status='pending', job_status='pending',
scheduled_for=None, scheduled_for=None,
processing_started=None, processing_started=None,
original_file_name='some.csv' original_file_name='some.csv'
): ):
data = { data = {
'id': uuid.uuid4(), 'id': uuid.uuid4(),
@@ -273,14 +273,14 @@ def create_service_permission(service_id, permission=EMAIL_TYPE):
def create_inbound_sms( def create_inbound_sms(
service, service,
notify_number=None, notify_number=None,
user_number='447700900111', user_number='447700900111',
provider_date=None, provider_date=None,
provider_reference=None, provider_reference=None,
content='Hello', content='Hello',
provider="mmg", provider="mmg",
created_at=None created_at=None
): ):
inbound = InboundSms( inbound = InboundSms(
service=service, service=service,
@@ -297,9 +297,9 @@ def create_inbound_sms(
def create_service_inbound_api( def create_service_inbound_api(
service, service,
url="https://something.com", url="https://something.com",
bearer_token="some_super_secret", bearer_token="some_super_secret",
): ):
service_inbound_api = ServiceInboundApi(service_id=service.id, service_inbound_api = ServiceInboundApi(service_id=service.id,
url=url, url=url,
@@ -311,9 +311,9 @@ def create_service_inbound_api(
def create_service_callback_api( def create_service_callback_api(
service, service,
url="https://something.com", url="https://something.com",
bearer_token="some_super_secret", bearer_token="some_super_secret",
): ):
service_callback_api = ServiceCallbackApi(service_id=service.id, service_callback_api = ServiceCallbackApi(service_id=service.id,
url=url, url=url,
@@ -377,11 +377,11 @@ def create_inbound_number(number, provider='mmg', active=True, service_id=None):
def create_monthly_billing_entry( def create_monthly_billing_entry(
service, service,
start_date, start_date,
end_date, end_date,
notification_type, notification_type,
monthly_totals=[] monthly_totals=[]
): ):
entry = MonthlyBilling( entry = MonthlyBilling(
service_id=service.id, service_id=service.id,
@@ -398,9 +398,9 @@ def create_monthly_billing_entry(
def create_reply_to_email( def create_reply_to_email(
service, service,
email_address, email_address,
is_default=True is_default=True
): ):
data = { data = {
'service': service, 'service': service,
@@ -416,10 +416,10 @@ def create_reply_to_email(
def create_service_sms_sender( def create_service_sms_sender(
service, service,
sms_sender, sms_sender,
is_default=True, is_default=True,
inbound_number_id=None inbound_number_id=None
): ):
data = { data = {
'service_id': service.id, 'service_id': service.id,
@@ -436,9 +436,9 @@ def create_service_sms_sender(
def create_letter_contact( def create_letter_contact(
service, service,
contact_block, contact_block,
is_default=True is_default=True
): ):
data = { data = {
'service': service, 'service': service,
@@ -454,7 +454,7 @@ def create_letter_contact(
def create_annual_billing( def create_annual_billing(
service_id, free_sms_fragment_limit, financial_year_start service_id, free_sms_fragment_limit, financial_year_start
): ):
annual_billing = AnnualBilling( annual_billing = AnnualBilling(
service_id=service_id, service_id=service_id,
@@ -468,12 +468,12 @@ def create_annual_billing(
def create_letter_rate( def create_letter_rate(
start_date=datetime(2017, 1, 1, 00, 00, 00), start_date=datetime(2017, 1, 1, 00, 00, 00),
end_date=None, end_date=None,
sheet_count=1, sheet_count=1,
rate=0.31, rate=0.31,
crown=True, crown=True,
post_class='second' post_class='second'
): ):
rate = LetterRate( rate = LetterRate(
start_date=start_date, start_date=start_date,
@@ -529,8 +529,8 @@ def create_daily_sorted_letter(billing_day=date(2018, 1, 18),
def create_ft_billing(bst_date, def create_ft_billing(bst_date,
notification_type, notification_type,
template = None, template=None,
service = None, service=None,
provider='test', provider='test',
rate_multiplier=1, rate_multiplier=1,
international=False, international=False,