2016-05-06 11:07:11 +01:00
|
|
|
import uuid
|
2017-08-31 14:28:44 +01:00
|
|
|
from datetime import datetime, timedelta
|
2016-04-21 11:37:38 +01:00
|
|
|
from decimal import Decimal
|
2017-11-24 12:01:28 +00:00
|
|
|
import functools
|
2017-11-06 12:34:29 +00:00
|
|
|
|
2017-11-22 16:29:51 +00:00
|
|
|
import flask
|
2017-11-06 12:34:29 +00:00
|
|
|
from flask import current_app
|
|
|
|
|
import click
|
2017-11-28 11:10:19 +00:00
|
|
|
from click_datetime import Datetime as click_dt
|
2017-05-19 17:04:39 +01:00
|
|
|
|
|
|
|
|
from app import db
|
2017-08-16 15:15:34 +01:00
|
|
|
from app.dao.monthly_billing_dao import (
|
|
|
|
|
create_or_update_monthly_billing,
|
|
|
|
|
get_monthly_billing_by_notification_type,
|
|
|
|
|
get_service_ids_that_need_billing_populated
|
|
|
|
|
)
|
2017-08-10 17:12:03 +01:00
|
|
|
from app.models import PROVIDERS, User, SMS_TYPE, EMAIL_TYPE
|
2016-05-11 15:52:49 +01:00
|
|
|
from app.dao.services_dao import (
|
|
|
|
|
delete_service_and_all_associated_db_objects,
|
|
|
|
|
dao_fetch_all_services_by_user
|
|
|
|
|
)
|
2017-11-06 12:34:29 +00:00
|
|
|
from app.dao.provider_rates_dao import create_provider_rates as dao_create_provider_rates
|
2016-05-06 11:07:11 +01:00
|
|
|
from app.dao.users_dao import (delete_model_user, delete_user_verify_codes)
|
2017-08-31 14:29:13 +01:00
|
|
|
from app.utils import get_midnight_for_day_before, get_london_midnight_in_utc
|
2018-03-05 16:44:20 +00:00
|
|
|
from app.performance_platform.processing_time import (send_processing_time_for_start_and_end)
|
|
|
|
|
from app.celery.scheduled_tasks import send_total_sent_notifications_to_performance_platform
|
2016-04-21 11:37:38 +01:00
|
|
|
|
|
|
|
|
|
2017-11-22 16:29:51 +00:00
|
|
|
@click.group(name='command', help='Additional commands')
|
2017-11-24 12:01:28 +00:00
|
|
|
def command_group():
|
2017-11-22 16:29:51 +00:00
|
|
|
pass
|
2016-04-21 11:37:38 +01:00
|
|
|
|
|
|
|
|
|
2017-11-24 12:01:28 +00:00
|
|
|
class notify_command:
|
|
|
|
|
def __init__(self, name=None):
|
|
|
|
|
self.name = name
|
|
|
|
|
|
|
|
|
|
def __call__(self, func):
|
|
|
|
|
# we need to call the flask with_appcontext decorator to ensure the config is loaded, db connected etc etc.
|
|
|
|
|
# we also need to use functools.wraps to carry through the names and docstrings etc of the functions.
|
|
|
|
|
# Then we need to turn it into a click.Command - that's what command_group.add_command expects.
|
|
|
|
|
@click.command(name=self.name)
|
|
|
|
|
@functools.wraps(func)
|
|
|
|
|
@flask.cli.with_appcontext
|
|
|
|
|
def wrapper(*args, **kwargs):
|
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
command_group.add_command(wrapper)
|
|
|
|
|
|
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@notify_command()
|
2017-11-28 11:10:19 +00:00
|
|
|
@click.option('-p', '--provider_name', required=True, type=click.Choice(PROVIDERS))
|
2017-11-28 10:35:46 +00:00
|
|
|
@click.option('-c', '--cost', required=True, help='Cost (pence) per message including decimals', type=float)
|
2017-11-28 11:10:19 +00:00
|
|
|
@click.option('-d', '--valid_from', required=True, type=click_dt(format='%Y-%m-%dT%H:%M:%S'))
|
2017-11-06 12:34:29 +00:00
|
|
|
def create_provider_rates(provider_name, cost, valid_from):
|
2017-11-22 15:01:32 +00:00
|
|
|
"""
|
|
|
|
|
Backfill rates for a given provider
|
|
|
|
|
"""
|
2017-11-28 10:35:46 +00:00
|
|
|
cost = Decimal(cost)
|
2017-11-06 12:34:29 +00:00
|
|
|
dao_create_provider_rates(provider_name, valid_from, cost)
|
2016-05-06 11:07:11 +01:00
|
|
|
|
|
|
|
|
|
2017-11-24 12:01:28 +00:00
|
|
|
@notify_command()
|
2017-11-22 15:01:32 +00:00
|
|
|
@click.option('-u', '--user_email_prefix', required=True, help="""
|
|
|
|
|
Functional test user email prefix. eg "notify-test-preview"
|
|
|
|
|
""") # noqa
|
2017-11-06 12:34:29 +00:00
|
|
|
def purge_functional_test_data(user_email_prefix):
|
2017-11-22 15:01:32 +00:00
|
|
|
"""
|
|
|
|
|
Remove non-seeded functional test data
|
|
|
|
|
|
|
|
|
|
users, services, etc. Give an email prefix. Probably "notify-test-preview".
|
|
|
|
|
"""
|
2017-11-06 12:34:29 +00:00
|
|
|
users = User.query.filter(User.email_address.like("{}%".format(user_email_prefix))).all()
|
|
|
|
|
for usr in users:
|
|
|
|
|
# Make sure the full email includes a uuid in it
|
|
|
|
|
# Just in case someone decides to use a similar email address.
|
|
|
|
|
try:
|
|
|
|
|
uuid.UUID(usr.email_address.split("@")[0].split('+')[1])
|
|
|
|
|
except ValueError:
|
|
|
|
|
print("Skipping {} as the user email doesn't contain a UUID.".format(usr.email_address))
|
|
|
|
|
else:
|
|
|
|
|
services = dao_fetch_all_services_by_user(usr.id)
|
|
|
|
|
if services:
|
|
|
|
|
for service in services:
|
|
|
|
|
delete_service_and_all_associated_db_objects(service)
|
|
|
|
|
else:
|
|
|
|
|
delete_user_verify_codes(usr)
|
|
|
|
|
delete_model_user(usr)
|
|
|
|
|
|
|
|
|
|
|
2017-11-24 12:01:28 +00:00
|
|
|
@notify_command()
|
2017-11-06 12:34:29 +00:00
|
|
|
def backfill_notification_statuses():
|
|
|
|
|
"""
|
2017-11-22 15:01:32 +00:00
|
|
|
DEPRECATED. Populates notification_status.
|
|
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
This will be used to populate the new `Notification._status_fkey` with the old
|
|
|
|
|
`Notification._status_enum`
|
|
|
|
|
"""
|
|
|
|
|
LIMIT = 250000
|
|
|
|
|
subq = "SELECT id FROM notification_history WHERE notification_status is NULL LIMIT {}".format(LIMIT)
|
|
|
|
|
update = "UPDATE notification_history SET notification_status = status WHERE id in ({})".format(subq)
|
|
|
|
|
result = db.session.execute(subq).fetchall()
|
|
|
|
|
|
|
|
|
|
while len(result) > 0:
|
|
|
|
|
db.session.execute(update)
|
|
|
|
|
print('commit {} updates at {}'.format(LIMIT, datetime.utcnow()))
|
|
|
|
|
db.session.commit()
|
|
|
|
|
result = db.session.execute(subq).fetchall()
|
2016-05-06 11:07:11 +01:00
|
|
|
|
|
|
|
|
|
2017-11-24 12:01:28 +00:00
|
|
|
@notify_command()
|
2017-11-06 12:34:29 +00:00
|
|
|
def update_notification_international_flag():
|
2017-11-22 15:01:32 +00:00
|
|
|
"""
|
|
|
|
|
DEPRECATED. Set notifications.international=false.
|
|
|
|
|
"""
|
2017-11-06 12:34:29 +00:00
|
|
|
# 250,000 rows takes 30 seconds to update.
|
|
|
|
|
subq = "select id from notifications where international is null limit 250000"
|
|
|
|
|
update = "update notifications set international = False where id in ({})".format(subq)
|
|
|
|
|
result = db.session.execute(subq).fetchall()
|
2017-06-29 15:44:40 +01:00
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
while len(result) > 0:
|
|
|
|
|
db.session.execute(update)
|
|
|
|
|
print('commit 250000 updates at {}'.format(datetime.utcnow()))
|
|
|
|
|
db.session.commit()
|
2017-06-29 15:44:40 +01:00
|
|
|
result = db.session.execute(subq).fetchall()
|
|
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
# Now update notification_history
|
|
|
|
|
subq_history = "select id from notification_history where international is null limit 250000"
|
|
|
|
|
update_history = "update notification_history set international = False where id in ({})".format(subq_history)
|
|
|
|
|
result_history = db.session.execute(subq_history).fetchall()
|
|
|
|
|
while len(result_history) > 0:
|
|
|
|
|
db.session.execute(update_history)
|
|
|
|
|
print('commit 250000 updates at {}'.format(datetime.utcnow()))
|
|
|
|
|
db.session.commit()
|
|
|
|
|
result_history = db.session.execute(subq_history).fetchall()
|
|
|
|
|
|
2017-05-19 17:04:39 +01:00
|
|
|
|
2017-11-24 12:01:28 +00:00
|
|
|
@notify_command()
|
2017-11-06 12:34:29 +00:00
|
|
|
def fix_notification_statuses_not_in_sync():
|
|
|
|
|
"""
|
2017-11-22 15:01:32 +00:00
|
|
|
DEPRECATED.
|
2017-11-06 12:34:29 +00:00
|
|
|
This will be used to correct an issue where Notification._status_enum and NotificationHistory._status_fkey
|
|
|
|
|
became out of sync. See 979e90a.
|
2017-06-29 15:44:40 +01:00
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
Notification._status_enum is the source of truth so NotificationHistory._status_fkey will be updated with
|
|
|
|
|
these values.
|
|
|
|
|
"""
|
|
|
|
|
MAX = 10000
|
2017-05-23 13:35:15 +01:00
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
subq = "SELECT id FROM notifications WHERE cast (status as text) != notification_status LIMIT {}".format(MAX)
|
|
|
|
|
update = "UPDATE notifications SET notification_status = status WHERE id in ({})".format(subq)
|
|
|
|
|
result = db.session.execute(subq).fetchall()
|
|
|
|
|
|
|
|
|
|
while len(result) > 0:
|
|
|
|
|
db.session.execute(update)
|
|
|
|
|
print('Committed {} updates at {}'.format(len(result), datetime.utcnow()))
|
|
|
|
|
db.session.commit()
|
2017-07-04 17:21:57 +01:00
|
|
|
result = db.session.execute(subq).fetchall()
|
|
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
subq_hist = "SELECT id FROM notification_history WHERE cast (status as text) != notification_status LIMIT {}" \
|
|
|
|
|
.format(MAX)
|
|
|
|
|
update = "UPDATE notification_history SET notification_status = status WHERE id in ({})".format(subq_hist)
|
|
|
|
|
result = db.session.execute(subq_hist).fetchall()
|
2017-07-04 17:21:57 +01:00
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
while len(result) > 0:
|
|
|
|
|
db.session.execute(update)
|
|
|
|
|
print('Committed {} updates at {}'.format(len(result), datetime.utcnow()))
|
|
|
|
|
db.session.commit()
|
2017-07-04 17:21:57 +01:00
|
|
|
result = db.session.execute(subq_hist).fetchall()
|
|
|
|
|
|
2017-08-17 11:50:32 +01:00
|
|
|
|
2017-11-24 12:01:28 +00:00
|
|
|
@notify_command()
|
2017-11-28 11:10:19 +00:00
|
|
|
@click.option('-y', '--year', required=True, help="e.g. 2017", type=int)
|
2017-11-06 12:34:29 +00:00
|
|
|
def populate_monthly_billing(year):
|
2017-11-22 15:01:32 +00:00
|
|
|
"""
|
|
|
|
|
Populate monthly billing table for all services for a given year.
|
|
|
|
|
"""
|
2017-11-06 12:34:29 +00:00
|
|
|
def populate(service_id, year, month):
|
2017-11-28 10:35:46 +00:00
|
|
|
create_or_update_monthly_billing(service_id, datetime(year, month, 1))
|
2017-08-31 14:28:44 +01:00
|
|
|
sms_res = get_monthly_billing_by_notification_type(
|
2017-11-28 10:35:46 +00:00
|
|
|
service_id, datetime(year, month, 1), SMS_TYPE
|
2017-08-31 14:28:44 +01:00
|
|
|
)
|
|
|
|
|
email_res = get_monthly_billing_by_notification_type(
|
2017-11-28 10:35:46 +00:00
|
|
|
service_id, datetime(year, month, 1), EMAIL_TYPE
|
2017-08-31 14:28:44 +01:00
|
|
|
)
|
2017-12-15 17:29:32 +00:00
|
|
|
letter_res = get_monthly_billing_by_notification_type(
|
|
|
|
|
service_id, datetime(year, month, 1), 'letter'
|
|
|
|
|
)
|
2017-12-14 17:17:05 +00:00
|
|
|
|
2017-08-31 14:28:44 +01:00
|
|
|
print("Finished populating data for {} for service id {}".format(month, str(service_id)))
|
|
|
|
|
print('SMS: {}'.format(sms_res.monthly_totals))
|
|
|
|
|
print('Email: {}'.format(email_res.monthly_totals))
|
2017-12-15 17:29:32 +00:00
|
|
|
print('Letter: {}'.format(letter_res.monthly_totals))
|
|
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
service_ids = get_service_ids_that_need_billing_populated(
|
|
|
|
|
start_date=datetime(2016, 5, 1), end_date=datetime(2017, 8, 16)
|
2017-08-31 14:29:13 +01:00
|
|
|
)
|
2017-11-06 12:34:29 +00:00
|
|
|
start, end = 1, 13
|
2017-08-31 14:29:13 +01:00
|
|
|
|
2017-11-28 10:35:46 +00:00
|
|
|
if year == 2016:
|
2017-11-06 12:34:29 +00:00
|
|
|
start = 4
|
2017-08-31 14:29:13 +01:00
|
|
|
|
2017-12-18 10:40:13 +00:00
|
|
|
for service_id in service_ids:
|
|
|
|
|
print('Starting to populate data for service {}'.format(str(service_id)))
|
|
|
|
|
print('Starting populating monthly billing for {}'.format(year))
|
|
|
|
|
for i in range(start, end):
|
|
|
|
|
print('Population for {}-{}'.format(i, year))
|
|
|
|
|
populate(service_id, year, i)
|
2017-08-31 14:29:13 +01:00
|
|
|
|
|
|
|
|
|
2018-03-05 16:44:20 +00:00
|
|
|
@notify_command()
|
|
|
|
|
@click.option('-s', '--start_date', required=True, help="start date inclusive", type=click_dt(format='%Y-%m-%d'))
|
|
|
|
|
@click.option('-e', '--end_date', required=True, help="end date inclusive", type=click_dt(format='%Y-%m-%d'))
|
|
|
|
|
def backfill_performance_platform_totals(start_date, end_date):
|
|
|
|
|
"""
|
|
|
|
|
Send historical total messages sent to Performance Platform.
|
2018-03-06 14:42:50 +00:00
|
|
|
|
|
|
|
|
WARNING: This does not overwrite existing data. You need to delete
|
|
|
|
|
the existing data or Performance Platform will double-count.
|
2018-03-05 16:44:20 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
delta = end_date - start_date
|
|
|
|
|
|
|
|
|
|
print('Sending total messages sent for all days between {} and {}'.format(start_date, end_date))
|
|
|
|
|
|
|
|
|
|
for i in range(delta.days + 1):
|
|
|
|
|
|
|
|
|
|
process_date = start_date + timedelta(days=i)
|
|
|
|
|
|
|
|
|
|
print('Sending total messages sent for {}'.format(
|
|
|
|
|
process_date.isoformat()
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
send_total_sent_notifications_to_performance_platform(process_date)
|
|
|
|
|
|
|
|
|
|
|
2017-11-24 12:01:28 +00:00
|
|
|
@notify_command()
|
2017-11-28 11:10:19 +00:00
|
|
|
@click.option('-s', '--start_date', required=True, help="start date inclusive", type=click_dt(format='%Y-%m-%d'))
|
|
|
|
|
@click.option('-e', '--end_date', required=True, help="end date inclusive", type=click_dt(format='%Y-%m-%d'))
|
2017-11-06 12:34:29 +00:00
|
|
|
def backfill_processing_time(start_date, end_date):
|
2017-11-22 15:01:32 +00:00
|
|
|
"""
|
2018-03-05 16:44:20 +00:00
|
|
|
Send historical processing time to Performance Platform.
|
2017-11-22 15:01:32 +00:00
|
|
|
"""
|
2017-08-31 14:29:13 +01:00
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
delta = end_date - start_date
|
2017-08-31 14:29:13 +01:00
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
print('Sending notification processing-time data for all days between {} and {}'.format(start_date, end_date))
|
2017-09-08 13:26:14 +01:00
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
for i in range(delta.days + 1):
|
|
|
|
|
# because the tz conversion funcs talk about midnight, and the midnight before last,
|
|
|
|
|
# we want to pretend we're running this from the next morning, so add one.
|
|
|
|
|
process_date = start_date + timedelta(days=i + 1)
|
2017-09-08 13:26:14 +01:00
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
process_start_date = get_midnight_for_day_before(process_date)
|
|
|
|
|
process_end_date = get_london_midnight_in_utc(process_date)
|
2017-09-08 13:26:14 +01:00
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
print('Sending notification processing-time for {} - {}'.format(
|
|
|
|
|
process_start_date.isoformat(),
|
|
|
|
|
process_end_date.isoformat()
|
|
|
|
|
))
|
|
|
|
|
send_processing_time_for_start_and_end(process_start_date, process_end_date)
|
2017-09-08 13:26:14 +01:00
|
|
|
|
|
|
|
|
|
2017-11-24 12:01:28 +00:00
|
|
|
@notify_command()
|
2017-11-06 12:34:29 +00:00
|
|
|
def populate_annual_billing():
|
2017-11-22 15:01:32 +00:00
|
|
|
"""
|
|
|
|
|
add annual_billing for 2016, 2017 and 2018.
|
|
|
|
|
"""
|
2017-11-06 12:34:29 +00:00
|
|
|
financial_year = [2016, 2017, 2018]
|
2017-10-18 15:09:05 +01:00
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
for fy in financial_year:
|
|
|
|
|
populate_data = """
|
|
|
|
|
INSERT INTO annual_billing(id, service_id, free_sms_fragment_limit, financial_year_start,
|
|
|
|
|
created_at, updated_at)
|
|
|
|
|
SELECT uuid_in(md5(random()::text || now()::text)::cstring), id, 250000, {}, '{}', '{}'
|
|
|
|
|
FROM services
|
|
|
|
|
WHERE id NOT IN(
|
|
|
|
|
SELECT service_id
|
|
|
|
|
FROM annual_billing
|
|
|
|
|
WHERE financial_year_start={})
|
|
|
|
|
""".format(fy, datetime.utcnow(), datetime.utcnow(), fy)
|
2017-10-18 15:09:05 +01:00
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
services_result1 = db.session.execute(populate_data)
|
|
|
|
|
db.session.commit()
|
2017-10-18 15:09:05 +01:00
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
print("Populated annual billing {} for {} services".format(fy, services_result1.rowcount))
|
2017-10-18 15:09:05 +01:00
|
|
|
|
|
|
|
|
|
2017-11-24 12:01:28 +00:00
|
|
|
@notify_command(name='list-routes')
|
2017-11-06 12:34:29 +00:00
|
|
|
def list_routes():
|
|
|
|
|
"""List URLs of all application routes."""
|
|
|
|
|
for rule in sorted(current_app.url_map.iter_rules(), key=lambda r: r.rule):
|
|
|
|
|
print("{:10} {}".format(", ".join(rule.methods - set(['OPTIONS', 'HEAD'])), rule.rule))
|
|
|
|
|
|
2017-10-31 15:20:14 +00:00
|
|
|
|
2018-02-21 10:00:17 +00:00
|
|
|
@notify_command(name='insert-inbound-numbers')
|
|
|
|
|
@click.option('-f', '--file_name', required=True,
|
2018-02-21 11:52:52 +00:00
|
|
|
help="""Full path of the file to upload, file is a contains inbound numbers,
|
|
|
|
|
one number per line. The number must have the format of 07... not 447....""")
|
2018-02-21 10:00:17 +00:00
|
|
|
def insert_inbound_numbers_from_file(file_name):
|
|
|
|
|
print("Inserting inbound numbers from {}".format(file_name))
|
|
|
|
|
file = open(file_name)
|
|
|
|
|
sql = "insert into inbound_numbers values('{}', '{}', 'mmg', null, True, now(), null);"
|
|
|
|
|
|
|
|
|
|
for line in file:
|
|
|
|
|
print(line)
|
|
|
|
|
db.session.execute(sql.format(uuid.uuid4(), line.strip()))
|
|
|
|
|
db.session.commit()
|
|
|
|
|
file.close()
|
|
|
|
|
|
|
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
def setup_commands(application):
|
2017-11-24 12:01:28 +00:00
|
|
|
application.cli.add_command(command_group)
|