File: //home/arjun/projects/buyercall_forms/buyercall/buyercall/blueprints/contacts/contact_tasks.py
import logging as log
import traceback
import json
from flask import render_template, current_app as app
from buyercall.lib.util_crypto import AESCipher
from buyercall.extensions import db
from buyercall.app import create_celery_app
from sqlalchemy import or_, and_, text
import uuid
from flask_weasyprint import HTML, render_pdf
from ..contacts.models import CreditReports
from ..form_leads.models import FormLead, FormLeadField
import re
from celery.exceptions import SoftTimeLimitExceeded
import pendulum
from datetime import datetime
from buyercall.app import create_app
from buyercall.lib.util_ses_email import send_ses_email
celery = create_celery_app(app)
@celery.task
def generate_pdf(contact_id, account, contact, date, business_type, **kwargs):
# Make a PDF straight from HTML in a string.
html = render_template('contacts/contact_lead_pdf.html', lead_id=contact_id, account=account, contact=contact,
date=date, business_type=business_type)
return render_pdf(HTML(string=html))
@celery.task
def unsuccessful_credit_check(contact_id, paid, description, bureau, product_type, trans_id, service_provider):
# Set the credit report table
credit_table = CreditReports()
# Add a entry to credit reports table with default values and description if credit check was unsuccessful
credit_table.service_provider = service_provider
credit_table.transaction_id = trans_id
credit_table.contact_id = contact_id
credit_table.credit_bureau = bureau
credit_table.product_type = product_type
credit_table.partnership_account_id = paid
credit_table.description = description
credit_table.save()
db.session.commit()
return credit_table.id
@celery.task
def successful_credit_check_seven_hundred(contact_id, paid, trans_id, credit_score, description,
bureau, product_type, service_provider, url, score_card_name):
# Set the credit report table
credit_table = CreditReports()
# Add a entry to credit reports table with default values and description if credit check was unsuccessful
credit_table.service_provider = service_provider
credit_table.iframe_url = url
credit_table.transaction_id = trans_id
credit_table.credit_score = credit_score
credit_table.score_card_name = score_card_name
credit_table.contact_id = contact_id
credit_table.credit_bureau = bureau
credit_table.product_type = product_type
credit_table.partnership_account_id = paid
credit_table.description = description
credit_table.is_successful = True
credit_table.save()
db.session.commit()
return credit_table.id
@celery.task
def successful_credit_check_finserv(contact_id, paid, trans_id, credit_score,
description, bureau, product_type, service_provider, approved, trades):
# Set the credit report table
credit_table = CreditReports()
# Add a entry to credit reports table with default values and description if credit check was unsuccessful
credit_table.service_provider = service_provider
credit_table.transaction_id = trans_id
credit_table.credit_score = credit_score
credit_table.contact_id = contact_id
credit_table.credit_bureau = bureau
credit_table.product_type = product_type
credit_table.partnership_account_id = paid
credit_table.description = description
credit_table.is_successful = True
credit_table.is_approved = approved
credit_table.trades = trades
credit_table.save()
db.session.commit()
return credit_table.id
@celery.task
def pull_prequalify_credit(bureau, product_type, contact_id, paid, firstname, lastname, address,
city, state, zip_code, service_provider, birthday):
# Import all functions required for credit checks
from buyercall.lib.util_credit_reports import seven_hundred_credit_client, \
validate_name, validate_address_street, format_street, validate_address_city, \
validate_address_state, format_city, format_state, validate_address_zip, \
format_full_name, validate_city_state_zip_combo, format_zip, finserv_credit_client, format_dob_year, \
validate_ssn
# Set default of credit report ID to NONE
credit_entry = None
# Set the encryption and cipher key
encrypt_key = app.config['CRYPTO_SECRET_KEY']
cipher = AESCipher(encrypt_key)
validate_name = validate_name(firstname, lastname)
if not validate_name:
description = 'This lead does not have a valid first name and/or last name.'
trans_id = cipher.encrypt('Unavailable')
return unsuccessful_credit_check(contact_id, paid, description, bureau, product_type, trans_id,
service_provider)
validate_street = validate_address_street(address)
if not validate_street:
description = 'This lead does not have a valid street address.'
trans_id = cipher.encrypt('Unavailable')
return unsuccessful_credit_check(contact_id, paid, description, bureau, product_type, trans_id,
service_provider)
validate_city = validate_address_city(city)
if not validate_city:
description = 'This lead does not have a valid city.'
trans_id = cipher.encrypt('Unavailable')
return unsuccessful_credit_check(contact_id, paid, description, bureau, product_type, trans_id,
service_provider)
validate_state = validate_address_state(state)
if not validate_state:
description = 'This lead does not have a valid state.'
trans_id = cipher.encrypt('Unavailable')
return unsuccessful_credit_check(contact_id, paid, description, bureau, product_type, trans_id,
service_provider)
validate_zip = validate_address_zip(zip_code)
if not validate_zip:
description = 'This lead does not have a valid zip code.'
trans_id = cipher.encrypt('Unavailable')
return unsuccessful_credit_check(contact_id, paid, description, bureau, product_type, trans_id,
service_provider)
validate_combo = validate_city_state_zip_combo(zip_code, city, state)
if not validate_combo:
description = 'The lead zip code, city and state does not seem to match as a valid zip, ' \
'city and state combination.'
trans_id = cipher.encrypt('Unavailable')
return unsuccessful_credit_check(contact_id, paid, description, bureau, product_type, trans_id,
service_provider)
existing_report = CreditReports.query \
.filter(and_(CreditReports.contact_id == contact_id,
CreditReports.partnership_account_id == paid,
CreditReports.credit_bureau == bureau,
CreditReports.product_type == product_type,
CreditReports.is_successful.is_(True))).first()
if existing_report is not None:
log.info('An existing report already exist. An automatic soft pull is '
'not performed for contact: {}'.format(contact_id))
return existing_report.id
else:
if service_provider == '700Credit':
client = seven_hundred_credit_client(paid, product_type)
# Set to SSN to empty string to avoid errors
ssn = ''
# Get form lead associated with the contact id
form_lead = FormLead.query.filter(FormLead.contact_id == contact_id).first()
if form_lead:
ssn_field = FormLeadField.query.filter(FormLeadField.lead_id == form_lead.id) \
.filter(
or_(FormLeadField.field_id == 'cumemberfield',
FormLeadField.field_id == 'ssnfield',
FormLeadField.field_id == 'ssn')).first()
if ssn_field.field_value:
# Check to see if SSN field is not a string. If not make it a string
decrypted_ssn = cipher.decrypt(ssn_field.field_value)
# decrypted_ssn = '000344544'
if type(decrypted_ssn) == str:
ssn = decrypted_ssn
else:
ssn = str(decrypted_ssn)
if ssn:
# Check to see if its a valid SSN number
valid_ssn = validate_ssn(ssn)
if not valid_ssn:
description = 'This lead does not have a valid Social Security Number (SSN).'
trans_id = cipher.encrypt('Unavailable')
return unsuccessful_credit_check(contact_id, paid, description, bureau, product_type,
trans_id, service_provider)
try:
# Set the credit report table
if bureau == 'experian':
bureau_abv = 'XPN'
elif bureau == 'transunion':
bureau_abv = 'TU'
elif bureau == 'equifax':
bureau_abv = 'EFX'
else:
bureau_abv = ''
user_fullname = firstname + ' ' + lastname
name = format_full_name(user_fullname)
address = format_street(address)
city = format_city(city)
state = format_state(state)
c_zip = format_zip(zip_code)
a_username = 'buyercall'
a_user_id = paid
# Perform the API post request to get credit report
credit_pull = client.credit_report.get_prequalify_credit_report(bureau_abv, name, address, city, state,
c_zip, ssn, a_username, a_user_id)
log.info('The response return by the credit POST: {}'.format(credit_pull))
trans_id = cipher.encrypt(credit_pull['Results']['XML_Report']['Transid'])
if credit_pull['Results']['XML_Report']['Prescreen_Report']['Score']:
credit_score = cipher.encrypt(credit_pull['Results']['XML_Report']['Prescreen_Report']['Score'])
score_card_name = credit_pull['Results']['XML_Report']['Prescreen_Report']['ScorecardName']
iframe = credit_pull['Results']['custom_report']
extract_url = re.findall(
'HTTPS?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\), ]|(?:%[0-9a-fA-F][0-9a-fA-F]))+',
iframe)
url = json.dumps(extract_url[0])
encrypted_url = cipher.encrypt(url)
description = 'Successful soft credit pull.'
credit_entry = successful_credit_check_seven_hundred(contact_id, paid, trans_id, credit_score,
description, bureau, product_type,
service_provider, encrypted_url,
score_card_name)
log.info('The credit report id is: {}'.format(credit_entry))
else:
description = credit_pull['Results']['XML_Report']['Prescreen_Report']['ResultDescription']
unsuccessful_credit_check(contact_id, paid, description, bureau, product_type, trans_id,
service_provider)
except:
log.error(traceback.format_exc())
elif service_provider == 'Finserv':
# Set the client to connect to the Finserv API
client = finserv_credit_client(paid, 'prequalify')
# Set a random trans_id for BuyerCall purposes because Finserv does not provide one
trans_id = cipher.encrypt(uuid.uuid4().hex)
# Get the birth year from for the lead
try:
birth_year = format_dob_year(birthday)
except:
description = 'This lead does not have a valid birth year.'
unsuccessful_credit_check(contact_id, paid, description, bureau, product_type, trans_id,
service_provider)
try:
r = client.credit_prequalify.get_token()
r_content = json.loads(r)
token = 'Bearer ' + str(r_content['access_token'])
kwargs = {}
kwargs['FirstName'] = firstname
kwargs['LastName'] = lastname
kwargs['YearOfBirth'] = birth_year
kwargs['Address'] = format_street(address)
kwargs['Zip'] = format_zip(zip_code)
kwargs['City'] = format_city(city)
kwargs['State'] = format_state(state)
finserv_response = client.credit_prequalify.soft_pull(token, **kwargs)
credit_score = cipher.encrypt(str(finserv_response['score']))
approved = finserv_response['approved']
trades = cipher.encrypt(json.dumps({'trades': finserv_response['trades']}))
description = 'Successful soft credit pull.'
credit_entry = successful_credit_check_finserv(contact_id, paid, trans_id, credit_score,
description, bureau, product_type,
service_provider, approved, trades)
log.info('The credit report id is: {}'.format(credit_entry))
except Exception as e:
description = str(e)
log.error('The finserv exception is: {} for contact {}'.format(e, contact_id))
unsuccessful_credit_check(contact_id, paid, description, bureau, product_type, trans_id,
service_provider)
return credit_entry
@celery.task(soft_time_limit=300)
def contacts_retention_policy(**kwargs):
"""
Apply retention policy on contact data. Different partnerships have different
policy time periods.
:param kwargs: None
:return: True
"""
try:
# Create a context for the database connection.
app_c = app or create_app()
db.app = app_c
with app_c.app_context():
# Get today's date
today_date = datetime.today().strftime('%Y-%m-%d')
today_date_time = datetime.today().strftime('%Y-%m-%d %H:%M:%S')
# Create db connection
conn = db.engine.connect()
# Get a count of all contact data before deletion
count_all_contacts = conn.execute(text("SELECT count(co.id) as begin_all_count FROM contacts co"))
all_data_begin = 0
for c in count_all_contacts:
all_data_begin = c.begin_all_count
# Get a count of ddh contact data before deletion
count_ddh_contacts = conn.execute(text("SELECT count(co.id) as ddh_begin_count FROM contacts co "
"INNER JOIN partnership_accounts pa "
"ON co.partnership_account_id = pa.id "
"WHERE pa.partnership_id in (3, 12)"))
ddh_data_begin = 0
for tvc in count_ddh_contacts:
ddh_data_begin = tvc.ddh_begin_count
# Delete contact data for all partnerships
conn.execute(text("DELETE from contacts co "
"WHERE co.updated_on < current_timestamp - interval '18 months'"))
# Delete contact data for ddh that is older than 120 days
conn.execute(text("DELETE from contacts co "
"WHERE co.partnership_account_id in "
"(SELECT pa.id FROM partnership_accounts pa WHERE pa.partnership_id in (3, 12)) "
"AND co.updated_on < current_timestamp - interval '120 days'"))
# Get a count of all contact data after deletion
count_all_contacts = conn.execute(text("SELECT count(co.id) as end_all_count FROM contacts co"))
all_data_end = 0
for c in count_all_contacts:
all_data_end = c.end_all_count
# Get a count of ddh contact data after deletion
count_ddh_contacts = conn.execute(text("SELECT count(co.id) as ddh_end_count FROM contacts co "
"INNER JOIN partnership_accounts pa "
"ON co.partnership_account_id = pa.id "
"WHERE pa.partnership_id in (3, 12)"))
ddh_data_end = 0
for tvc in count_ddh_contacts:
ddh_data_end = tvc.ddh_end_count
# Close db connection
db.engine.dispose()
# Count of all data deleted
total_data_deleted = all_data_begin - all_data_end
# Count of ddh data deleted
ddh_data_deleted = ddh_data_begin - ddh_data_end
email_message = "The total leads before delete is: " + str(all_data_begin) \
+ " \nThe ddh leads before delete is: " + str(ddh_data_begin) \
+ " \n\nThe total leads after deletion is: " + str(all_data_end) \
+ " \nThe ddh leads after deletion is:" + str(ddh_data_end) \
+ "\n\nThe total contact deleted on : " + str(today_date_time) + " is: " \
+ str(total_data_deleted) + " and ddh total data deleted is: " + str(ddh_data_deleted)
support_emails = app.config.get('SUPPORT_EMAILS', [])
send_ses_email(recipients=support_emails,
p_id=1,
subject='Retention Policy - Deletion Totals - ' + str(today_date),
text=email_message)
return True
except SoftTimeLimitExceeded:
# Create a context for the database connection.
db.app = app
with app.app_context():
email_message = 'The automated retention policy timed-out for date: ' + str(pendulum.today())
support_emails = app.config.get('SUPPORT_EMAILS', [])
send_ses_email(recipients=support_emails,
p_id=1,
subject='Retention Policy Job Timed-out ' + str(pendulum.today()),
text=email_message)
return False