HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux spn-python 5.15.0-89-generic #99-Ubuntu SMP Mon Oct 30 20:42:41 UTC 2023 x86_64
User: arjun (1000)
PHP: 8.1.2-1ubuntu2.20
Disabled: NONE
Upload Files
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