2018-04-25 14:24:47 +01:00
|
|
|
from datetime import datetime, timedelta, time
|
|
|
|
|
|
2018-05-08 13:53:44 +01:00
|
|
|
from sqlalchemy import func, case, desc, Date
|
2018-04-06 11:55:49 +01:00
|
|
|
|
|
|
|
|
from app import db
|
2018-04-25 14:24:47 +01:00
|
|
|
from app.dao.date_util import get_financial_year
|
2018-04-24 17:37:04 +01:00
|
|
|
from app.models import (
|
2018-04-25 14:24:47 +01:00
|
|
|
FactBilling,
|
|
|
|
|
Notification,
|
|
|
|
|
Service,
|
|
|
|
|
NOTIFICATION_CREATED,
|
|
|
|
|
NOTIFICATION_TECHNICAL_FAILURE,
|
2018-04-24 17:37:04 +01:00
|
|
|
KEY_TYPE_TEST,
|
|
|
|
|
LETTER_TYPE,
|
|
|
|
|
SMS_TYPE,
|
|
|
|
|
Rate,
|
|
|
|
|
LetterRate
|
|
|
|
|
)
|
2018-04-25 14:24:47 +01:00
|
|
|
from app.utils import convert_utc_to_bst, convert_bst_to_utc
|
2018-04-09 11:38:00 +01:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
yearly_data = db.session.query(
|
|
|
|
|
func.sum(FactBilling.notifications_sent).label("notifications_sent"),
|
|
|
|
|
func.sum(FactBilling.billable_units * FactBilling.rate_multiplier).label("billable_units"),
|
|
|
|
|
FactBilling.service_id,
|
|
|
|
|
FactBilling.rate,
|
|
|
|
|
FactBilling.notification_type
|
|
|
|
|
).filter(
|
|
|
|
|
FactBilling.service_id == service_id,
|
|
|
|
|
FactBilling.bst_date >= year_start_date,
|
|
|
|
|
FactBilling.bst_date <= year_end_date
|
|
|
|
|
).group_by(
|
|
|
|
|
FactBilling.service_id,
|
|
|
|
|
FactBilling.rate,
|
|
|
|
|
FactBilling.notification_type
|
|
|
|
|
).order_by(
|
|
|
|
|
FactBilling.service_id,
|
|
|
|
|
FactBilling.notification_type
|
|
|
|
|
).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-04-09 11:38:00 +01:00
|
|
|
yearly_data = 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.service_id,
|
|
|
|
|
FactBilling.rate,
|
2018-04-27 15:15:55 +01:00
|
|
|
FactBilling.notification_type
|
2018-04-09 11:38:00 +01:00
|
|
|
).filter(
|
|
|
|
|
FactBilling.service_id == service_id,
|
|
|
|
|
FactBilling.bst_date >= year_start_date,
|
2018-04-24 17:37:04 +01:00
|
|
|
FactBilling.bst_date <= year_end_date
|
|
|
|
|
).group_by(
|
2018-04-30 16:34:40 +01:00
|
|
|
'month',
|
2018-04-24 17:37:04 +01:00
|
|
|
FactBilling.service_id,
|
|
|
|
|
FactBilling.rate,
|
2018-04-27 15:15:55 +01:00
|
|
|
FactBilling.notification_type
|
|
|
|
|
).order_by(
|
|
|
|
|
FactBilling.service_id,
|
2018-04-30 16:34:40 +01:00
|
|
|
'month',
|
2018-04-27 15:15:55 +01:00
|
|
|
FactBilling.notification_type
|
2018-04-09 11:38:00 +01:00
|
|
|
).all()
|
|
|
|
|
|
2018-04-24 17:37:04 +01:00
|
|
|
return yearly_data
|
|
|
|
|
|
|
|
|
|
|
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-04-24 17:37:04 +01:00
|
|
|
transit_data = db.session.query(
|
|
|
|
|
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:
|
2018-04-25 16:17:56 +01:00
|
|
|
transit_data = transit_data.filter(Notification.service_id == service_id)
|
2018-05-10 15:35:58 +01:00
|
|
|
|
2018-04-24 17:37:04 +01:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2018-04-25 14:24:47 +01:00
|
|
|
def update_fact_billing(data, process_day):
|
|
|
|
|
inserted_records = 0
|
|
|
|
|
updated_records = 0
|
2018-04-24 17:37:04 +01:00
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|