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-08-31 14:28:44 +01:00
|
|
|
from flask_script import Command, Option
|
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
|
|
|
|
|
)
|
2016-04-21 11:37:38 +01:00
|
|
|
from app.dao.provider_rates_dao import create_provider_rates
|
2016-05-06 11:07:11 +01:00
|
|
|
from app.dao.users_dao import (delete_model_user, delete_user_verify_codes)
|
2016-04-21 11:37:38 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class CreateProviderRateCommand(Command):
|
|
|
|
|
|
|
|
|
|
option_list = (
|
|
|
|
|
Option('-p', '--provider_name', dest="provider_name", help='Provider name'),
|
|
|
|
|
Option('-c', '--cost', dest="cost", help='Cost (pence) per message including decimals'),
|
|
|
|
|
Option('-d', '--valid_from', dest="valid_from", help="Date (%Y-%m-%dT%H:%M:%S) valid from")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def run(self, provider_name, cost, valid_from):
|
|
|
|
|
if provider_name not in PROVIDERS:
|
|
|
|
|
raise Exception("Invalid provider name, must be one of ({})".format(', '.join(PROVIDERS)))
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
cost = Decimal(cost)
|
|
|
|
|
except:
|
|
|
|
|
raise Exception("Invalid cost value.")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
valid_from = datetime.strptime('%Y-%m-%dT%H:%M:%S', valid_from)
|
|
|
|
|
except:
|
|
|
|
|
raise Exception("Invalid valid_from date. Use the format %Y-%m-%dT%H:%M:%S")
|
|
|
|
|
|
|
|
|
|
create_provider_rates(provider_name, valid_from, cost)
|
2016-05-06 11:07:11 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class PurgeFunctionalTestDataCommand(Command):
|
|
|
|
|
|
|
|
|
|
option_list = (
|
2016-05-11 15:52:49 +01:00
|
|
|
Option('-u', '-user-email-prefix', dest='user_email_prefix', help="Functional test user email prefix."),
|
2016-05-06 11:07:11 +01:00
|
|
|
)
|
|
|
|
|
|
2017-08-31 14:28:44 +01:00
|
|
|
def run(self, user_email_prefix=None):
|
2016-05-06 11:07:11 +01:00
|
|
|
if user_email_prefix:
|
|
|
|
|
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:
|
2016-05-11 15:52:49 +01:00
|
|
|
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-05-19 17:04:39 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class CustomDbScript(Command):
|
2017-06-29 15:44:40 +01:00
|
|
|
|
|
|
|
|
option_list = (
|
|
|
|
|
Option('-n', '-name-of-db-function', dest='name_of_db_function', help="Function name of the DB script to run"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def run(self, name_of_db_function):
|
|
|
|
|
db_function = getattr(self, name_of_db_function, None)
|
|
|
|
|
if callable(db_function):
|
|
|
|
|
db_function()
|
|
|
|
|
else:
|
|
|
|
|
print('The specified function does not exist.')
|
|
|
|
|
|
|
|
|
|
def backfill_notification_statuses(self):
|
|
|
|
|
"""
|
|
|
|
|
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()
|
2017-05-19 17:04:39 +01:00
|
|
|
|
|
|
|
|
def update_notification_international_flag(self):
|
|
|
|
|
# 250,000 rows takes 30 seconds to update.
|
2017-05-23 13:35:15 +01:00
|
|
|
subq = "select id from notifications where international is null limit 250000"
|
|
|
|
|
update = "update notifications set international = False where id in ({})".format(subq)
|
2017-05-19 17:04:39 +01:00
|
|
|
result = db.session.execute(subq).fetchall()
|
2017-06-29 15:44:40 +01:00
|
|
|
|
2017-05-19 17:04:39 +01:00
|
|
|
while len(result) > 0:
|
|
|
|
|
db.session.execute(update)
|
2017-05-23 13:35:15 +01:00
|
|
|
print('commit 250000 updates at {}'.format(datetime.utcnow()))
|
2017-05-19 17:04:39 +01:00
|
|
|
db.session.commit()
|
|
|
|
|
result = db.session.execute(subq).fetchall()
|
2017-05-23 13:35:15 +01:00
|
|
|
|
|
|
|
|
# Now update notification_history
|
|
|
|
|
subq_history = "select id from notification_history where international is null limit 250000"
|
2017-05-23 15:36:33 +01:00
|
|
|
update_history = "update notification_history set international = False where id in ({})".format(subq_history)
|
2017-05-23 13:35:15 +01:00
|
|
|
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-07-04 17:21:57 +01:00
|
|
|
|
|
|
|
|
def fix_notification_statuses_not_in_sync(self):
|
|
|
|
|
"""
|
|
|
|
|
This will be used to correct an issue where Notification._status_enum and NotificationHistory._status_fkey
|
|
|
|
|
became out of sync. See 979e90a.
|
|
|
|
|
|
|
|
|
|
Notification._status_enum is the source of truth so NotificationHistory._status_fkey will be updated with
|
|
|
|
|
these values.
|
|
|
|
|
"""
|
|
|
|
|
MAX = 10000
|
|
|
|
|
|
2017-07-05 13:34:54 +01:00
|
|
|
subq = "SELECT id FROM notifications WHERE cast (status as text) != notification_status LIMIT {}".format(MAX)
|
2017-07-04 17:21:57 +01:00
|
|
|
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)
|
2017-07-05 13:46:09 +01:00
|
|
|
print('Committed {} updates at {}'.format(len(result), datetime.utcnow()))
|
2017-07-04 17:21:57 +01:00
|
|
|
db.session.commit()
|
|
|
|
|
result = db.session.execute(subq).fetchall()
|
|
|
|
|
|
2017-07-05 13:34:54 +01:00
|
|
|
subq_hist = "SELECT id FROM notification_history WHERE cast (status as text) != notification_status LIMIT {}" \
|
|
|
|
|
.format(MAX)
|
2017-07-04 17:21:57 +01:00
|
|
|
update = "UPDATE notification_history SET notification_status = status WHERE id in ({})".format(subq_hist)
|
|
|
|
|
result = db.session.execute(subq_hist).fetchall()
|
|
|
|
|
|
|
|
|
|
while len(result) > 0:
|
|
|
|
|
db.session.execute(update)
|
2017-07-05 13:46:09 +01:00
|
|
|
print('Committed {} updates at {}'.format(len(result), datetime.utcnow()))
|
2017-07-04 17:21:57 +01:00
|
|
|
db.session.commit()
|
|
|
|
|
result = db.session.execute(subq_hist).fetchall()
|
2017-07-18 18:21:35 +01:00
|
|
|
|
2017-08-17 11:50:32 +01:00
|
|
|
def link_inbound_numbers_to_service(self):
|
|
|
|
|
update = """
|
|
|
|
|
UPDATE inbound_numbers SET
|
|
|
|
|
service_id = services.id,
|
|
|
|
|
updated_at = now()
|
|
|
|
|
FROM services
|
2017-08-21 13:31:35 +01:00
|
|
|
WHERE services.sms_sender = inbound_numbers.number AND
|
2017-08-17 18:13:17 +01:00
|
|
|
inbound_numbers.service_id is null
|
2017-08-17 11:50:32 +01:00
|
|
|
"""
|
|
|
|
|
result = db.session.execute(update)
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
|
print("Linked {} inbound numbers to service".format(result.rowcount))
|
|
|
|
|
|
2017-07-18 18:21:35 +01:00
|
|
|
|
|
|
|
|
class PopulateMonthlyBilling(Command):
|
2017-08-31 14:28:44 +01:00
|
|
|
option_list = (
|
|
|
|
|
Option('-y', '-year', dest="year", help="Use for integer value for year, e.g. 2017"),
|
|
|
|
|
)
|
2017-07-18 18:21:35 +01:00
|
|
|
|
2017-08-31 14:28:44 +01:00
|
|
|
def run(self, year):
|
|
|
|
|
service_ids = get_service_ids_that_need_billing_populated(
|
|
|
|
|
start_date=datetime(2016, 5, 1), end_date=datetime(2017, 8, 16)
|
|
|
|
|
)
|
|
|
|
|
start, end = 1, 13
|
|
|
|
|
if year == '2016':
|
|
|
|
|
start = 4
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
self.populate(service_id, year, i)
|
|
|
|
|
|
|
|
|
|
def populate(self, service_id, year, month):
|
|
|
|
|
create_or_update_monthly_billing(service_id, datetime(int(year), int(month), 1))
|
|
|
|
|
sms_res = get_monthly_billing_by_notification_type(
|
|
|
|
|
service_id, datetime(int(year), int(month), 1), SMS_TYPE
|
|
|
|
|
)
|
|
|
|
|
email_res = get_monthly_billing_by_notification_type(
|
|
|
|
|
service_id, datetime(int(year), int(month), 1), EMAIL_TYPE
|
|
|
|
|
)
|
|
|
|
|
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))
|