2018-04-25 14:24:47 +01:00
|
|
|
from datetime import datetime, timedelta, time
|
|
|
|
|
|
2018-05-16 12:21:59 +01:00
|
|
|
from flask import current_app
|
2018-11-26 12:53:39 +00:00
|
|
|
from notifications_utils.timezones import convert_bst_to_utc, convert_utc_to_bst
|
2018-05-15 11:21:10 +01:00
|
|
|
from sqlalchemy.dialects.postgresql import insert
|
2018-05-21 10:56:16 +01:00
|
|
|
from sqlalchemy import func, case, desc, Date, Integer
|
2018-04-06 11:55:49 +01:00
|
|
|
|
|
|
|
|
from app import db
|
2019-08-06 13:29:59 +01:00
|
|
|
from app.dao.date_util import (
|
|
|
|
|
get_april_fools as financial_year_start,
|
|
|
|
|
get_financial_year,
|
2019-08-28 14:27:08 +01:00
|
|
|
get_financial_year_for_datetime
|
2019-08-06 13:29:59 +01:00
|
|
|
)
|
|
|
|
|
|
2018-04-24 17:37:04 +01:00
|
|
|
from app.models import (
|
2018-04-25 14:24:47 +01:00
|
|
|
FactBilling,
|
|
|
|
|
Notification,
|
|
|
|
|
Service,
|
2018-04-24 17:37:04 +01:00
|
|
|
KEY_TYPE_TEST,
|
|
|
|
|
LETTER_TYPE,
|
|
|
|
|
SMS_TYPE,
|
|
|
|
|
Rate,
|
2018-05-14 16:21:16 +01:00
|
|
|
LetterRate,
|
2018-05-21 11:06:31 +01:00
|
|
|
NOTIFICATION_STATUS_TYPES_BILLABLE,
|
2018-05-21 10:56:16 +01:00
|
|
|
NotificationHistory,
|
2018-10-24 17:07:30 +01:00
|
|
|
EMAIL_TYPE,
|
2019-08-06 13:29:59 +01:00
|
|
|
NOTIFICATION_STATUS_TYPES_BILLABLE_FOR_LETTERS,
|
|
|
|
|
AnnualBilling,
|
|
|
|
|
Organisation,
|
2018-04-24 17:37:04 +01:00
|
|
|
)
|
2019-04-03 14:52:41 +01:00
|
|
|
from app.utils import get_london_midnight_in_utc
|
2018-04-09 11:38:00 +01:00
|
|
|
|
|
|
|
|
|
2019-08-06 13:29:59 +01:00
|
|
|
def fetch_sms_free_allowance_remainder(start_date):
|
|
|
|
|
# ASSUMPTION: AnnualBilling has been populated for year.
|
2019-08-28 14:27:08 +01:00
|
|
|
billing_year = get_financial_year_for_datetime(start_date)
|
2019-08-23 17:02:44 +01:00
|
|
|
print(billing_year)
|
2019-08-06 13:29:59 +01:00
|
|
|
start_of_year = convert_utc_to_bst(financial_year_start(billing_year))
|
|
|
|
|
query = db.session.query(
|
|
|
|
|
FactBilling.service_id.label("service_id"),
|
|
|
|
|
AnnualBilling.free_sms_fragment_limit,
|
|
|
|
|
func.sum(FactBilling.billable_units * FactBilling.rate_multiplier).label('billable_units'),
|
|
|
|
|
func.greatest((AnnualBilling.free_sms_fragment_limit -
|
|
|
|
|
func.sum(FactBilling.billable_units * FactBilling.rate_multiplier)
|
|
|
|
|
).cast(Integer), 0).label('sms_remainder')
|
|
|
|
|
).filter(
|
|
|
|
|
FactBilling.service_id == Service.id,
|
|
|
|
|
FactBilling.service_id == AnnualBilling.service_id,
|
|
|
|
|
FactBilling.bst_date >= start_of_year,
|
|
|
|
|
FactBilling.bst_date < start_date,
|
|
|
|
|
FactBilling.notification_type == SMS_TYPE,
|
|
|
|
|
AnnualBilling.financial_year_start == billing_year,
|
|
|
|
|
).group_by(
|
|
|
|
|
FactBilling.service_id,
|
|
|
|
|
AnnualBilling.free_sms_fragment_limit,
|
|
|
|
|
)
|
|
|
|
|
return query
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_sms_billing_for_all_services(start_date, end_date):
|
|
|
|
|
|
|
|
|
|
# ASSUMPTION: AnnualBilling has been populated for year.
|
2019-08-28 14:27:08 +01:00
|
|
|
billing_year = get_financial_year_for_datetime(start_date)
|
2019-08-06 13:29:59 +01:00
|
|
|
free_allowance_remainder = fetch_sms_free_allowance_remainder(start_date).subquery()
|
|
|
|
|
sms_billable_units = func.sum(FactBilling.billable_units * FactBilling.rate_multiplier)
|
2019-08-23 17:02:44 +01:00
|
|
|
sms_remainder = func.coalesce(free_allowance_remainder.c.sms_remainder, AnnualBilling.free_sms_fragment_limit)
|
2019-08-06 13:29:59 +01:00
|
|
|
chargeable_sms = case([(sms_remainder == 0, sms_billable_units),
|
|
|
|
|
(sms_billable_units - sms_remainder <= 0, 0),
|
|
|
|
|
(sms_billable_units - sms_remainder > 0,
|
|
|
|
|
sms_billable_units - sms_remainder)], else_=0)
|
|
|
|
|
sms_cost = case([(sms_remainder == 0, sms_billable_units * FactBilling.rate),
|
|
|
|
|
(sms_billable_units - sms_remainder <= 0, 0),
|
|
|
|
|
(sms_billable_units - sms_remainder > 0, (sms_billable_units - sms_remainder) * FactBilling.rate)
|
|
|
|
|
], else_=0)
|
|
|
|
|
|
|
|
|
|
query = db.session.query(
|
|
|
|
|
Organisation.name.label('organisation_name'),
|
|
|
|
|
Organisation.id.label('organisation_id'),
|
|
|
|
|
Service.name.label("service_name"),
|
2019-08-19 14:44:08 +01:00
|
|
|
FactBilling.service_id.label("service_id"),
|
2019-08-06 13:29:59 +01:00
|
|
|
AnnualBilling.free_sms_fragment_limit,
|
|
|
|
|
FactBilling.rate.label('sms_rate'),
|
|
|
|
|
sms_remainder.label("sms_remainder"),
|
|
|
|
|
sms_billable_units.label('sms_billable_units'),
|
|
|
|
|
chargeable_sms.label("chargeable_billable_sms"),
|
|
|
|
|
sms_cost.label('sms_cost'),
|
|
|
|
|
).join(
|
|
|
|
|
Service.annual_billing,
|
|
|
|
|
).outerjoin(
|
|
|
|
|
free_allowance_remainder, Service.id == free_allowance_remainder.c.service_id
|
|
|
|
|
).outerjoin(
|
|
|
|
|
Organisation, Service.organisation_id == Organisation.id
|
|
|
|
|
).filter(
|
|
|
|
|
FactBilling.service_id == Service.id,
|
|
|
|
|
FactBilling.bst_date >= start_date,
|
|
|
|
|
FactBilling.bst_date <= end_date,
|
|
|
|
|
FactBilling.notification_type == SMS_TYPE,
|
|
|
|
|
AnnualBilling.financial_year_start == billing_year,
|
|
|
|
|
).group_by(
|
|
|
|
|
Organisation.name,
|
|
|
|
|
Organisation.id,
|
|
|
|
|
FactBilling.service_id,
|
|
|
|
|
Service.name,
|
|
|
|
|
AnnualBilling.free_sms_fragment_limit,
|
|
|
|
|
free_allowance_remainder.c.sms_remainder,
|
|
|
|
|
FactBilling.rate,
|
|
|
|
|
).order_by(
|
|
|
|
|
Organisation.name,
|
|
|
|
|
Service.name
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return query.all()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_letter_costs_for_all_services(start_date, end_date):
|
|
|
|
|
query = db.session.query(
|
|
|
|
|
Organisation.name.label("organisation_name"),
|
|
|
|
|
Organisation.id.label("organisation_id"),
|
|
|
|
|
Service.name.label("service_name"),
|
2019-08-19 14:23:09 +01:00
|
|
|
FactBilling.service_id.label("service_id"),
|
2019-08-06 13:29:59 +01:00
|
|
|
func.sum(FactBilling.notifications_sent * FactBilling.rate).label("letter_cost")
|
|
|
|
|
).outerjoin(
|
|
|
|
|
Organisation, Service.organisation_id == Organisation.id
|
|
|
|
|
).filter(
|
|
|
|
|
FactBilling.service_id == Service.id,
|
|
|
|
|
FactBilling.bst_date >= start_date,
|
|
|
|
|
FactBilling.bst_date <= end_date,
|
|
|
|
|
FactBilling.notification_type == LETTER_TYPE,
|
|
|
|
|
).group_by(
|
|
|
|
|
Organisation.name,
|
|
|
|
|
Organisation.id,
|
|
|
|
|
FactBilling.service_id,
|
|
|
|
|
Service.name,
|
|
|
|
|
).order_by(
|
|
|
|
|
Organisation.name,
|
|
|
|
|
Service.name
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return query.all()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_letter_line_items_for_all_services(start_date, end_date):
|
|
|
|
|
query = db.session.query(
|
|
|
|
|
Organisation.name.label("organisation_name"),
|
|
|
|
|
Organisation.id.label("organisation_id"),
|
|
|
|
|
Service.name.label("service_name"),
|
2019-08-19 14:23:09 +01:00
|
|
|
FactBilling.service_id.label("service_id"),
|
|
|
|
|
FactBilling.billable_units.label("sheet_count"),
|
2019-08-06 13:29:59 +01:00
|
|
|
FactBilling.rate.label("letter_rate"),
|
2019-08-19 14:23:09 +01:00
|
|
|
FactBilling.postage.label("postage"),
|
2019-08-06 13:29:59 +01:00
|
|
|
func.sum(FactBilling.notifications_sent).label("letters_sent"),
|
|
|
|
|
).outerjoin(
|
|
|
|
|
Organisation, Service.organisation_id == Organisation.id
|
|
|
|
|
).filter(
|
|
|
|
|
FactBilling.service_id == Service.id,
|
|
|
|
|
FactBilling.bst_date >= start_date,
|
|
|
|
|
FactBilling.bst_date <= end_date,
|
|
|
|
|
FactBilling.notification_type == LETTER_TYPE,
|
2019-08-19 14:44:08 +01:00
|
|
|
).group_by(
|
2019-08-06 13:29:59 +01:00
|
|
|
Organisation.name,
|
|
|
|
|
Organisation.id,
|
|
|
|
|
FactBilling.service_id,
|
|
|
|
|
Service.name,
|
|
|
|
|
FactBilling.billable_units,
|
|
|
|
|
FactBilling.rate,
|
|
|
|
|
FactBilling.postage
|
|
|
|
|
).order_by(
|
|
|
|
|
Organisation.name,
|
|
|
|
|
Service.name,
|
2019-08-19 14:23:09 +01:00
|
|
|
FactBilling.postage.desc(),
|
|
|
|
|
FactBilling.rate,
|
2019-08-06 13:29:59 +01:00
|
|
|
)
|
|
|
|
|
return query.all()
|
|
|
|
|
|
|
|
|
|
|
2018-05-11 16:25:16 +01:00
|
|
|
def fetch_billing_totals_for_year(service_id, year):
|
|
|
|
|
year_start_date, year_end_date = get_financial_year(year)
|
2018-05-16 12:21:59 +01:00
|
|
|
"""
|
|
|
|
|
Billing for email: only record the total number of emails.
|
2018-05-16 13:18:36 +01:00
|
|
|
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.
|
2018-05-16 12:21:59 +01:00
|
|
|
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_date,
|
|
|
|
|
FactBilling.bst_date <= year_end_date,
|
|
|
|
|
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(
|
2018-05-11 16:25:16 +01:00
|
|
|
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_date,
|
2018-05-16 12:21:59 +01:00
|
|
|
FactBilling.bst_date <= year_end_date,
|
|
|
|
|
FactBilling.notification_type == SMS_TYPE
|
2018-05-11 16:25:16 +01:00
|
|
|
).group_by(
|
|
|
|
|
FactBilling.rate,
|
|
|
|
|
FactBilling.notification_type
|
2018-05-16 12:21:59 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
yearly_data = email_and_letters.union_all(sms).order_by(
|
|
|
|
|
'notification_type',
|
|
|
|
|
'rate'
|
2018-05-11 16:25:16 +01:00
|
|
|
).all()
|
|
|
|
|
|
|
|
|
|
return yearly_data
|
|
|
|
|
|
|
|
|
|
|
2018-04-27 15:15:55 +01:00
|
|
|
def fetch_monthly_billing_for_year(service_id, year):
|
2018-04-09 11:38:00 +01:00
|
|
|
year_start_date, year_end_date = get_financial_year(year)
|
|
|
|
|
utcnow = datetime.utcnow()
|
2018-05-04 13:09:14 +01:00
|
|
|
today = convert_utc_to_bst(utcnow)
|
2018-04-09 11:38:00 +01:00
|
|
|
# if year end date is less than today, we are calculating for data in the past and have no need for deltas.
|
2018-05-04 13:09:14 +01:00
|
|
|
if year_end_date >= today:
|
2018-04-25 14:24:47 +01:00
|
|
|
yesterday = today - timedelta(days=1)
|
|
|
|
|
for day in [yesterday, today]:
|
|
|
|
|
data = fetch_billing_data_for_day(process_day=day, service_id=service_id)
|
|
|
|
|
for d in data:
|
|
|
|
|
update_fact_billing(data=d, process_day=day)
|
|
|
|
|
|
2018-05-16 12:21:59 +01:00
|
|
|
email_and_letters = db.session.query(
|
|
|
|
|
func.date_trunc('month', FactBilling.bst_date).cast(Date).label("month"),
|
|
|
|
|
func.sum(FactBilling.notifications_sent).label("notifications_sent"),
|
|
|
|
|
func.sum(FactBilling.notifications_sent).label("billable_units"),
|
|
|
|
|
FactBilling.rate.label('rate'),
|
2018-09-27 13:58:01 +01:00
|
|
|
FactBilling.notification_type.label('notification_type'),
|
|
|
|
|
FactBilling.postage
|
2018-05-16 12:21:59 +01:00
|
|
|
).filter(
|
|
|
|
|
FactBilling.service_id == service_id,
|
|
|
|
|
FactBilling.bst_date >= year_start_date,
|
|
|
|
|
FactBilling.bst_date <= year_end_date,
|
|
|
|
|
FactBilling.notification_type.in_([EMAIL_TYPE, LETTER_TYPE])
|
|
|
|
|
).group_by(
|
|
|
|
|
'month',
|
|
|
|
|
FactBilling.rate,
|
2018-09-27 13:58:01 +01:00
|
|
|
FactBilling.notification_type,
|
|
|
|
|
FactBilling.postage
|
2018-05-16 12:21:59 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
sms = db.session.query(
|
2018-05-08 13:53:44 +01:00
|
|
|
func.date_trunc('month', FactBilling.bst_date).cast(Date).label("month"),
|
2018-04-24 17:37:04 +01:00
|
|
|
func.sum(FactBilling.notifications_sent).label("notifications_sent"),
|
2018-05-04 13:09:14 +01:00
|
|
|
func.sum(FactBilling.billable_units * FactBilling.rate_multiplier).label("billable_units"),
|
2018-04-09 11:38:00 +01:00
|
|
|
FactBilling.rate,
|
2018-09-27 13:58:01 +01:00
|
|
|
FactBilling.notification_type,
|
|
|
|
|
FactBilling.postage
|
2018-04-09 11:38:00 +01:00
|
|
|
).filter(
|
|
|
|
|
FactBilling.service_id == service_id,
|
|
|
|
|
FactBilling.bst_date >= year_start_date,
|
2018-05-16 12:21:59 +01:00
|
|
|
FactBilling.bst_date <= year_end_date,
|
|
|
|
|
FactBilling.notification_type == SMS_TYPE
|
2018-04-24 17:37:04 +01:00
|
|
|
).group_by(
|
2018-04-30 16:34:40 +01:00
|
|
|
'month',
|
2018-04-24 17:37:04 +01:00
|
|
|
FactBilling.rate,
|
2018-09-27 13:58:01 +01:00
|
|
|
FactBilling.notification_type,
|
|
|
|
|
FactBilling.postage
|
2018-05-16 12:21:59 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
yearly_data = email_and_letters.union_all(sms).order_by(
|
2018-04-30 16:34:40 +01:00
|
|
|
'month',
|
2018-05-16 12:21:59 +01:00
|
|
|
'notification_type',
|
|
|
|
|
'rate'
|
2018-04-09 11:38:00 +01:00
|
|
|
).all()
|
|
|
|
|
|
2018-04-24 17:37:04 +01:00
|
|
|
return yearly_data
|
2018-07-26 18:41:06 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
Returns how many rows were deleted
|
|
|
|
|
"""
|
|
|
|
|
return FactBilling.query.filter(
|
|
|
|
|
FactBilling.bst_date == process_day,
|
|
|
|
|
FactBilling.service_id == service_id
|
|
|
|
|
).delete()
|
2018-04-24 17:37:04 +01:00
|
|
|
|
|
|
|
|
|
2018-04-25 14:24:47 +01:00
|
|
|
def fetch_billing_data_for_day(process_day, service_id=None):
|
|
|
|
|
start_date = convert_bst_to_utc(datetime.combine(process_day, time.min))
|
|
|
|
|
end_date = convert_bst_to_utc(datetime.combine(process_day + timedelta(days=1), time.min))
|
2018-05-16 12:21:59 +01:00
|
|
|
current_app.logger.info("Populate ft_billing for {} to {}".format(start_date, end_date))
|
2018-10-24 17:07:30 +01:00
|
|
|
transit_data = []
|
2019-07-18 15:29:54 +01:00
|
|
|
if not service_id:
|
|
|
|
|
service_ids = [x.id for x in Service.query.all()]
|
|
|
|
|
else:
|
|
|
|
|
service_ids = [service_id]
|
|
|
|
|
for id_of_service in service_ids:
|
|
|
|
|
for notification_type in (SMS_TYPE, EMAIL_TYPE, LETTER_TYPE):
|
|
|
|
|
results = _query_for_billing_data(
|
|
|
|
|
table=Notification,
|
|
|
|
|
notification_type=notification_type,
|
|
|
|
|
start_date=start_date,
|
|
|
|
|
end_date=end_date,
|
2019-07-18 16:24:06 +01:00
|
|
|
service_id=id_of_service
|
2019-07-18 15:29:54 +01:00
|
|
|
)
|
|
|
|
|
# If data has been purged from Notification then use NotificationHistory
|
|
|
|
|
if len(results) == 0:
|
|
|
|
|
results = _query_for_billing_data(
|
|
|
|
|
table=NotificationHistory,
|
|
|
|
|
notification_type=notification_type,
|
|
|
|
|
start_date=start_date,
|
|
|
|
|
end_date=end_date,
|
|
|
|
|
service_id=id_of_service
|
|
|
|
|
)
|
2018-10-24 17:07:30 +01:00
|
|
|
|
2019-07-18 15:29:54 +01:00
|
|
|
transit_data = transit_data + results
|
2018-10-24 17:07:30 +01:00
|
|
|
|
|
|
|
|
return transit_data
|
2018-04-24 17:37:04 +01:00
|
|
|
|
|
|
|
|
|
2019-07-18 15:29:54 +01:00
|
|
|
def _query_for_billing_data(table, notification_type, start_date, end_date, service_id):
|
|
|
|
|
billable_type_list = {
|
|
|
|
|
SMS_TYPE: NOTIFICATION_STATUS_TYPES_BILLABLE,
|
|
|
|
|
EMAIL_TYPE: NOTIFICATION_STATUS_TYPES_BILLABLE,
|
|
|
|
|
LETTER_TYPE: NOTIFICATION_STATUS_TYPES_BILLABLE_FOR_LETTERS
|
|
|
|
|
}
|
|
|
|
|
query = db.session.query(
|
|
|
|
|
table.template_id,
|
|
|
|
|
table.service_id,
|
|
|
|
|
table.notification_type,
|
|
|
|
|
func.coalesce(table.sent_by,
|
|
|
|
|
case(
|
|
|
|
|
[
|
|
|
|
|
(table.notification_type == 'letter', 'dvla'),
|
|
|
|
|
(table.notification_type == 'sms', 'unknown'),
|
|
|
|
|
(table.notification_type == 'email', 'ses')
|
|
|
|
|
]),
|
|
|
|
|
).label('sent_by'),
|
|
|
|
|
func.coalesce(table.rate_multiplier, 1).cast(Integer).label('rate_multiplier'),
|
|
|
|
|
func.coalesce(table.international, False).label('international'),
|
|
|
|
|
case(
|
|
|
|
|
[
|
|
|
|
|
(table.notification_type == 'letter', table.billable_units),
|
|
|
|
|
]
|
|
|
|
|
).label('letter_page_count'),
|
|
|
|
|
func.sum(table.billable_units).label('billable_units'),
|
|
|
|
|
func.count().label('notifications_sent'),
|
|
|
|
|
Service.crown,
|
|
|
|
|
func.coalesce(table.postage, 'none').label('postage')
|
|
|
|
|
).filter(
|
|
|
|
|
table.status.in_(billable_type_list[notification_type]),
|
|
|
|
|
table.key_type != KEY_TYPE_TEST,
|
|
|
|
|
table.created_at >= start_date,
|
|
|
|
|
table.created_at < end_date,
|
|
|
|
|
table.notification_type == notification_type,
|
|
|
|
|
table.service_id == service_id
|
|
|
|
|
).group_by(
|
|
|
|
|
table.template_id,
|
|
|
|
|
table.service_id,
|
|
|
|
|
table.notification_type,
|
|
|
|
|
'sent_by',
|
|
|
|
|
'letter_page_count',
|
|
|
|
|
table.rate_multiplier,
|
|
|
|
|
table.international,
|
|
|
|
|
Service.crown,
|
|
|
|
|
table.postage,
|
|
|
|
|
).join(
|
|
|
|
|
Service
|
|
|
|
|
)
|
|
|
|
|
return query.all()
|
|
|
|
|
|
|
|
|
|
|
2018-04-24 17:37:04 +01:00
|
|
|
def get_rates_for_billing():
|
2019-04-03 13:07:49 +01:00
|
|
|
non_letter_rates = Rate.query.order_by(desc(Rate.valid_from)).all()
|
|
|
|
|
letter_rates = LetterRate.query.order_by(desc(LetterRate.start_date)).all()
|
2018-04-24 17:37:04 +01:00
|
|
|
return non_letter_rates, letter_rates
|
|
|
|
|
|
|
|
|
|
|
2018-07-23 15:14:37 +01:00
|
|
|
def get_service_ids_that_need_billing_populated(start_date, end_date):
|
|
|
|
|
return db.session.query(
|
|
|
|
|
NotificationHistory.service_id
|
|
|
|
|
).filter(
|
|
|
|
|
NotificationHistory.created_at >= start_date,
|
|
|
|
|
NotificationHistory.created_at <= end_date,
|
|
|
|
|
NotificationHistory.notification_type.in_([SMS_TYPE, EMAIL_TYPE, LETTER_TYPE]),
|
|
|
|
|
NotificationHistory.billable_units != 0
|
|
|
|
|
).distinct().all()
|
|
|
|
|
|
|
|
|
|
|
2018-09-14 17:52:16 +01:00
|
|
|
def get_rate(
|
|
|
|
|
non_letter_rates, letter_rates, notification_type, date, crown=None, letter_page_count=None, post_class='second'
|
|
|
|
|
):
|
2019-04-03 14:52:41 +01:00
|
|
|
start_of_day = get_london_midnight_in_utc(date)
|
|
|
|
|
|
2018-04-24 17:37:04 +01:00
|
|
|
if notification_type == LETTER_TYPE:
|
2018-07-31 11:04:48 +01:00
|
|
|
if letter_page_count == 0:
|
|
|
|
|
return 0
|
2018-09-14 17:52:16 +01:00
|
|
|
return next(
|
2019-04-03 13:07:49 +01:00
|
|
|
r.rate
|
|
|
|
|
for r in letter_rates if (
|
2019-04-03 14:52:41 +01:00
|
|
|
start_of_day >= r.start_date and
|
2019-04-03 13:07:49 +01:00
|
|
|
crown == r.crown and
|
|
|
|
|
letter_page_count == r.sheet_count and
|
|
|
|
|
post_class == r.post_class
|
|
|
|
|
)
|
2018-09-14 17:52:16 +01:00
|
|
|
)
|
2018-04-24 17:37:04 +01:00
|
|
|
elif notification_type == SMS_TYPE:
|
2019-04-03 13:07:49 +01:00
|
|
|
return next(
|
|
|
|
|
r.rate
|
|
|
|
|
for r in non_letter_rates if (
|
|
|
|
|
notification_type == r.notification_type and
|
2019-04-03 14:52:41 +01:00
|
|
|
start_of_day >= r.valid_from
|
2019-04-03 13:07:49 +01:00
|
|
|
)
|
|
|
|
|
)
|
2018-04-24 17:37:04 +01:00
|
|
|
else:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
2018-04-25 14:24:47 +01:00
|
|
|
def update_fact_billing(data, process_day):
|
2018-04-24 17:37:04 +01:00
|
|
|
non_letter_rates, letter_rates = get_rates_for_billing()
|
2018-05-15 11:21:10 +01:00
|
|
|
rate = get_rate(non_letter_rates,
|
|
|
|
|
letter_rates,
|
|
|
|
|
data.notification_type,
|
|
|
|
|
process_day,
|
|
|
|
|
data.crown,
|
2018-09-14 17:52:16 +01:00
|
|
|
data.letter_page_count,
|
2018-09-28 16:32:18 +01:00
|
|
|
data.postage)
|
2018-05-15 11:21:10 +01:00
|
|
|
billing_record = create_billing_record(data, rate, process_day)
|
2019-07-18 15:29:54 +01:00
|
|
|
|
2018-05-15 11:21:10 +01:00
|
|
|
table = FactBilling.__table__
|
|
|
|
|
'''
|
|
|
|
|
This uses the Postgres upsert to avoid race conditions when two threads try to insert
|
|
|
|
|
at the same row. The excluded object refers to values that we tried to insert but were
|
|
|
|
|
rejected.
|
|
|
|
|
http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#insert-on-conflict-upsert
|
|
|
|
|
'''
|
|
|
|
|
stmt = insert(table).values(
|
|
|
|
|
bst_date=billing_record.bst_date,
|
|
|
|
|
template_id=billing_record.template_id,
|
|
|
|
|
service_id=billing_record.service_id,
|
|
|
|
|
provider=billing_record.provider,
|
|
|
|
|
rate_multiplier=billing_record.rate_multiplier,
|
|
|
|
|
notification_type=billing_record.notification_type,
|
|
|
|
|
international=billing_record.international,
|
|
|
|
|
billable_units=billing_record.billable_units,
|
|
|
|
|
notifications_sent=billing_record.notifications_sent,
|
2018-09-26 11:28:59 +01:00
|
|
|
rate=billing_record.rate,
|
|
|
|
|
postage=billing_record.postage,
|
2018-05-15 11:21:10 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
stmt = stmt.on_conflict_do_update(
|
2018-05-21 14:38:25 +01:00
|
|
|
constraint="ft_billing_pkey",
|
2018-05-15 11:21:10 +01:00
|
|
|
set_={"notifications_sent": stmt.excluded.notifications_sent,
|
2018-05-22 14:49:48 +01:00
|
|
|
"billable_units": stmt.excluded.billable_units,
|
|
|
|
|
"updated_at": datetime.utcnow()
|
2018-05-15 11:21:10 +01:00
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
db.session.connection().execute(stmt)
|
2018-04-24 17:37:04 +01:00
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_billing_record(data, rate, process_day):
|
|
|
|
|
billing_record = FactBilling(
|
2019-04-02 15:15:07 +01:00
|
|
|
bst_date=process_day,
|
2018-04-24 17:37:04 +01:00
|
|
|
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,
|
2018-09-26 11:28:59 +01:00
|
|
|
rate=rate,
|
|
|
|
|
postage=data.postage,
|
2018-04-24 17:37:04 +01:00
|
|
|
)
|
|
|
|
|
return billing_record
|