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/api2/doc/endpoints/adf.py
import logging
import traceback
import xml.etree.ElementTree as et
import sys
import re

from datetime import datetime
from flask import request
from sqlalchemy import and_, or_
from buyercall.extensions import db
from flask_restx import Resource
from buyercall.blueprints.api2.restplus import api
from buyercall.blueprints.contacts.models import Contact, ContactNotes, ContactVehicle, Campaigns
from buyercall.blueprints.filters import format_phone_number
from buyercall.blueprints.partnership.models import PartnershipAccount

log = logging.getLogger(__name__)
ns = api.namespace('ADF', description='Operations related to ADF.', path='/adf')


class AdfUtils:
    ALLOWED_EXTENSIONS = (['xml', 'txt'])

    @classmethod
    def allowed_file(cls, filename):
        return '.' in filename and \
               filename.rsplit('.', 1)[1].lower() in cls.ALLOWED_EXTENSIONS

    @classmethod
    def clean_mail_string(cls, email_text):
        new_email_text = ''
        greater_than = '&gt'
        greater_than_semi = '>'

        if greater_than_semi in email_text:
            email_text = email_text.replace('>', '>')
        elif greater_than in email_text:
            email_text = email_text.replace('&gt', '>')

        split_lines = iter(email_text.splitlines())
        old_line = ''

        for i in split_lines:
            pos = i.find('<')
            if pos > 0:
                prev_char = old_line[len(old_line)-1: len(old_line)]
                if prev_char is '<':
                    new_line = i[0:len(i)]
                else:
                    new_line = i[pos:len(i)]
            else:
                new_line = i[0:len(i)]

            old_line = new_line
            new_email_text = new_email_text + new_line + '\n'

        start = new_email_text.find('<?xml version="1.0"?>')

        if start < 1:
            start = new_email_text.find('<adf>')
        elif start < 1:
            start = new_email_text.find('<?adf version="1.0"?>')

        stop = new_email_text.find('</adf>')

        if start is None or start is '' or stop is None or stop is '':
            return ''
        else:
            new_email_text = new_email_text[start:stop + 6]

        # log.error('Email after some house cleaning')
        # log.error(new_email_text)
        # log.error('****************************************************************************************************')

        return new_email_text

    @classmethod
    def clean_xml_string(cls, email_text):
        new_text = ''
        split_lines = iter(email_text.splitlines())
        old_line = ''
        found_next_line = False
        customer_done = False

        for i in split_lines:
            pos_c = i.find('</customer')

            if pos_c >= 0:
                customer_done = True

            pos_s = i.find('<phone')
            pos_e = i.find('</phone>')
            pos_s_e = i.find('>')

            # finished with the customer phone data
            if customer_done:
                if pos_s >= 0 and pos_e is -1 and pos_s_e is -1:
                    new_text += i + '>' + '\n'
                else:
                    new_text += i + '\n'

            # no phone data found in line
            elif pos_s is -1 and pos_e is -1:
                new_text += i + '\n'

            # phone data found with both tags on the same line
            elif pos_s >= 0 and pos_e > 0:
                pos_e = i.find('>')
                tag_text = i[pos_s: pos_e]
                phone_text = i[pos_e: len(i)]
                pos_ee = phone_text.find('<')
                phone_text = phone_text[0:pos_ee].replace(" ", "")
                new_text += tag_text + phone_text + '</phone>' + '\n'
                found_next_line = False

            # phone data found with only the first tag on the line
            elif pos_s >= 0 and pos_e is -1:
                found_next_line = True
                pos_s = i.find('<')
                pos_e = i.find('>')
                phone_text = i[pos_e: len(i)]
                pos_ee = phone_text.find('<')
                phone_text = phone_text[0:pos_ee].replace(" ", "")
                new_phone_tag = i[pos_s:pos_e]
                new_text += new_phone_tag + phone_text + '</phone>' + '\n'
                old_line = i

            # phone data second tag found on the following line
            elif found_next_line is True and old_line is not '' and pos_e >= 0:
                found_next_line = False
                old_line = ''

        return new_text

    @classmethod
    def get_lead_details(cls, prospect):
        # Retrieve customer details
        customer = prospect.find('customer')
        customer_contact = customer.find('contact')
        customer_email = None
        customer_phone = ''
        customer_address_1 = ''
        customer_address_2 = ''
        customer_city = ''
        customer_state = ''
        customer_zip = ''
        customer_country = ''
        customer_comments = ''

        if customer.find('comments') is not None and customer.find('comments').text:
            customer_comments = customer.find('comments').text

        # Customer contact details retrieval
        for contact_item in customer_contact:
            if str(contact_item.tag).lower() == "name":
                if str(contact_item.get('part')).lower() == "first":
                    customer_firstname = contact_item.text
                elif str(contact_item.get('part')).lower() == "last":
                    customer_lastname = contact_item.text
                elif str(contact_item.get('part')).lower() == "full":
                    name_split = contact_item.text.split(" ")
                    count = 0

                    for shortname in name_split:
                        count += 1

                        if count == 1:
                            customer_firstname = shortname
                        elif count == 2:
                            customer_lastname = shortname
                        elif count > 2:
                            customer_lastname += " " + shortname
            elif str(contact_item.tag).lower() == "email":
                if contact_item.get('preferredcontact') is not None and\
                        str(contact_item.get('preferredcontact')).lower() == "1":
                    customer_email = contact_item.text
                if customer_email is None:
                    customer_email = contact_item.text
            elif str(contact_item.tag).lower() == "phone":
                if contact_item.get('preferredcontact') is None:
                    if str(contact_item.get('type')) is None:
                        customer_phone = contact_item.text
                    elif str(contact_item.get('type')).lower() != "fax" and str(
                            contact_item.get('type')).lower() != "voice":
                        customer_phone = contact_item.text
                elif str(contact_item.get('preferredcontact')).lower() == "1":
                    customer_phone = contact_item.text
                elif str(contact_item.get('preferredcontact')).lower() == "0" and \
                        (customer_phone is None or customer_phone is ''):
                    customer_phone = contact_item.text

        # Customer address details
        if customer_contact.find('address') is not None:
            customer_address = customer_contact.find('address')

            if customer_address.find('street') is not None:
                customer_street_address = customer_address.findall('street')

                if len(customer_street_address) == 1:
                    customer_address_1 = customer_address.find('street').text
                else:
                    for street in customer_street_address:
                        if str(street.get('line')).lower() == "1":
                            customer_address_1 = street.text
                        else:
                            customer_address_2 = street.text

            if customer_address.find('apartment') is not None:
                customer_apartment = customer_address.find('apartment')
                customer_address_2_spacing = ", "

                if customer_address_2 and customer_address_2 != '':
                    customer_address_2 += customer_address_2_spacing + customer_apartment.text
                else:
                    customer_address_2 += customer_apartment.text

            if customer_address.find('city') is not None and customer_address.find('city').text:
                customer_city = customer_address.find('city').text

            if customer_address.find('regioncode') is not None and customer_address.find('regioncode').text:
                customer_state = customer_address.find('regioncode').text

            if customer_address.find('postalcode') is not None and customer_address.find('postalcode').text:
                customer_zip = customer_address.find('postalcode').text

            if customer_address.find('country') is not None and customer_address.find('country').text:
                customer_country = customer_address.find('country').text

        customer_phone = customer_phone.replace('-', '')
        customer_zip = customer_zip.replace('-', '')

        log.info('ADF Lead Details')
        log.info('{} - {} - {} - {} - {} - {} - {} - {} - {} - {} - {}'.format(
            customer_firstname, customer_lastname, customer_email, customer_phone, customer_comments,
            customer_address_1, customer_address_2, customer_city, customer_state, customer_zip, customer_country
        ))

        result = [customer_firstname, customer_lastname, customer_email, customer_phone, customer_comments,
                  customer_address_1, customer_address_2, customer_city, customer_state, customer_zip, customer_country]

        return result

    @classmethod
    def get_vehicle_details(cls, prospect):
        # Set initial values
        vehicle_oi_year = None
        vehicle_oi_make = ''
        vehicle_oi_model = ''
        vehicle_oi_vin = ''
        vehicle_oi_stock = ''
        vehicle_oi_trim = ''
        vehicle_oi_price = None
        vehicle_oi_price_type = ''
        vehicle_oi_price_currency = 'USD'
        vehicle_oi_status = ''
        vehicle_oi_odometer = None
        vehicle_oi_odometer_status = 'unknown'
        vehicle_oi_odometer_units = 'miles'
        vehicle_oi_condition = ''
        vehicle_io_found = False
        vehicle_cu_year = None
        vehicle_cu_make = ''
        vehicle_cu_model = ''
        vehicle_cu_vin = ''
        vehicle_cu_stock = ''
        vehicle_cu_trim = ''
        vehicle_cu_price = None
        vehicle_cu_price_type = ''
        vehicle_cu_price_currency = 'USD'
        vehicle_cu_status = ''
        vehicle_cu_found = False
        vehicle_cu_odometer = None
        vehicle_cu_odometer_status = 'unknown'
        vehicle_cu_odometer_units = 'miles'
        vehicle_cu_condition = ''

        # Retrieve vehicle details
        vehicles = prospect.findall('vehicle')

        for vehicle in vehicles:
            vehicle_interest = str(vehicle.get('interest')).lower()
            if vehicle_interest == 'buy' or vehicle_interest == 'lease':
                vehicle_io_found = True
                if vehicle.get('status'):
                    vehicle_oi_status = vehicle.get('status')
                for vehicle_item in vehicle:
                    if str(vehicle_item.tag).lower() == "year":
                        vehicle_oi_year = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "make":
                        vehicle_oi_make = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "model":
                        vehicle_oi_model = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "vin":
                        vehicle_oi_vin = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "stock":
                        vehicle_oi_stock = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "trim":
                        vehicle_oi_trim = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "price":
                        if vehicle_item.get('type'):
                            vehicle_oi_price_type = vehicle_item.get('type')
                        if vehicle_item.get('currency'):
                            vehicle_oi_price_currency = vehicle_item.get('currency')
                        vehicle_oi_price = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "odometer":
                        if vehicle_item.get('status'):
                            vehicle_oi_odometer_status = vehicle_item.get('status')
                        if vehicle_item.get('units'):
                            vehicle_oi_odometer_units = vehicle_item.get('units')
                        vehicle_oi_odometer = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "condition":
                        vehicle_oi_condition = vehicle_item.text
            elif vehicle_interest == 'sell' or vehicle_interest == 'trade-in':
                vehicle_cu_found = True
                if vehicle.get('status'):
                    vehicle_cu_status = vehicle.get('status')
                for vehicle_item in vehicle:
                    if str(vehicle_item.tag).lower() == "year":
                        vehicle_cu_year = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "make":
                        vehicle_cu_make = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "model":
                        vehicle_cu_model = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "vin":
                        vehicle_cu_vin = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "stock":
                        vehicle_cu_stock = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "trim":
                        vehicle_cu_trim = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "price":
                        if vehicle_item.get('type'):
                            vehicle_cu_price_type = vehicle_item.get('type')
                        if vehicle_item.get('currency'):
                            vehicle_cu_price_currency = vehicle_item.get('currency')
                        vehicle_cu_price = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "odometer":
                        if vehicle_item.get('status'):
                            vehicle_cu_odometer_status = vehicle_item.get('status')
                        if vehicle_item.get('units'):
                            vehicle_cu_odometer_units = vehicle_item.get('units')
                        vehicle_cu_odometer = vehicle_item.text
                    elif str(vehicle_item.tag).lower() == "condition":
                        vehicle_cu_condition = vehicle_item.text
        if vehicle_io_found:
            log.info('ADF Vehicle of Interest Details')
            log.info('{} - {} - {} - {} - {} - {} - {} - {} - {} - {} - {} - {} - {} - {}'.format(
                vehicle_oi_status, vehicle_oi_year, vehicle_oi_make, vehicle_oi_model, vehicle_oi_vin, vehicle_oi_stock,
                vehicle_oi_trim, vehicle_oi_price, vehicle_oi_price_type, vehicle_oi_price_currency,
                vehicle_oi_odometer_status, vehicle_oi_odometer_units, vehicle_oi_odometer, vehicle_oi_condition
            ))
        else:
            log.info('ADF NO Vehicle of Interest Details found.')

        if vehicle_cu_found:
            log.info('ADF Current Vehicle Details')
            log.info('{} - {} - {} - {} - {} - {} - {} - {} - {} - {} - {} - {} - {} - {}'.format(
                vehicle_cu_status, vehicle_cu_year, vehicle_cu_make, vehicle_cu_model, vehicle_cu_vin, vehicle_cu_stock,
                vehicle_cu_trim, vehicle_cu_price, vehicle_cu_price_type, vehicle_cu_price_currency,
                vehicle_cu_odometer_status, vehicle_cu_odometer_units, vehicle_cu_odometer, vehicle_cu_condition
            ))
        else:
            log.info('ADF NO Current Vehicle Details found.')

        result = [vehicle_io_found, vehicle_oi_status, vehicle_oi_year, vehicle_oi_make, vehicle_oi_model,
                  vehicle_oi_vin, vehicle_oi_stock, vehicle_oi_trim, vehicle_oi_price, vehicle_oi_price_type,
                  vehicle_oi_price_currency, vehicle_oi_odometer, vehicle_oi_odometer_status, vehicle_oi_odometer_units,
                  vehicle_oi_condition,
                  vehicle_cu_found, vehicle_cu_status, vehicle_cu_year, vehicle_cu_make, vehicle_cu_model,
                  vehicle_cu_vin, vehicle_cu_stock, vehicle_cu_trim, vehicle_cu_price, vehicle_cu_price_type,
                  vehicle_cu_price_currency, vehicle_cu_odometer, vehicle_cu_odometer_status, vehicle_cu_odometer_units,
                  vehicle_cu_condition]

        return result

    @classmethod
    def get_campaign(cls, prospect):
        # Retrieve campaign details
        campaign_text = ''
        campaign = prospect.find('campaign')
        if campaign is not None and campaign.text:
            campaign_text = campaign.text
        return campaign_text

    @classmethod
    def valid_item(cls, item):
        if item and len(item) > 1:
            return True
        return False


@ns.route('/')
#@api.doc(responses={200: 'OK',
#                    400: 'Error performing operation.'})
@api.doc(False)
class ApiAdfContactImport(Resource):
    def post(self):
        if request:
            adf_utils = AdfUtils()
            adf_request = request
            valid_state = False
            valid_contents = False
            valid_import_contact = False
            valid_error = ""
            partnership_account_id = None

            # retrieve the email type
            if 'Content-Type' in adf_request.headers:
                email_type = str(adf_request.headers['Content-Type'])
                email_text = ""
            else:
                valid_error = "Invalid content type."

            # test the email type
            if "application/x-www-form-urlencoded" in email_type:
                email_beta_text = adf_request.form['stripped-text']
                email_text = adf_utils.clean_mail_string(email_beta_text)
                email_text = adf_utils.clean_xml_string(email_text)

                if email_text is None or email_text is '':
                    valid_state = False
                else:
                    valid_state = True
            elif "multipart/form-data" in email_type:
                for email_attachment in adf_request.files.values():
                    if adf_utils.allowed_file(email_attachment.filename):
                        email_text = email_attachment.stream.read()
                        valid_state = True
            else:
                valid_error = "Invalid content type. Content type: {}.".format(email_type)

            if valid_state:
                try:
                    # Define the regex pattern for an email address
                    email_pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
                    # Email address where the ADF was sent. This is a BuyerCall inbound email address
                    to_email = adf_request.form['To'].lower()
                    # Split the text at the first comma
                    part_before_comma = to_email.split(',')[0]
                    # Search for the email address in the part before the comma
                    match = re.search(email_pattern, part_before_comma)
                    # If a match is found, return the email address
                    if match:
                        adf_email = match.group(0)
                    else:
                        adf_email = ''

                    xml_entity = et.fromstring(email_text)
                    prospect = xml_entity.find('prospect')
                    partnership_account = PartnershipAccount \
                        .query \
                        .filter(PartnershipAccount.adf_assigned_email == adf_email) \
                        .first()

                    if partnership_account:
                        partnership_account_id = partnership_account.id
                        valid_import_contact = True
                        log.info(
                            'Partnership account {}, found for {}.'.format(str(partnership_account_id), to_email))
                    else:
                        log.error('No partnership account found for {}.'.format(to_email))

                    if valid_import_contact is True:
                        if prospect:
                            # Get lead details
                            retrieved_lead_details_obj = adf_utils.get_lead_details(prospect)
                            firstname = retrieved_lead_details_obj[0]
                            lastname = retrieved_lead_details_obj[1]
                            email = retrieved_lead_details_obj[2]
                            phonenumber = retrieved_lead_details_obj[3]
                            comments = retrieved_lead_details_obj[4]
                            address_1 = retrieved_lead_details_obj[5]
                            address_2 = retrieved_lead_details_obj[6]
                            city = retrieved_lead_details_obj[7]
                            state = retrieved_lead_details_obj[8]
                            zip = retrieved_lead_details_obj[9]
                            country = retrieved_lead_details_obj[10]

                            # Get vehicle details
                            retrieved_vehicle_details_obj = adf_utils.get_vehicle_details(prospect)

                            # Get campagin
                            retrieved_campaign = adf_utils.get_campaign(prospect)

                            if adf_utils.valid_item(firstname) \
                                    and adf_utils.valid_item(lastname) \
                                    and adf_utils.valid_item(phonenumber):
                                valid_contents = True
                            else:
                                valid_error = "One of the required fields is invalid."

                            if valid_contents:
                                phonenumber = phonenumber.replace(" ", "") \
                                    .replace("(", "") \
                                    .replace(")", "") \
                                    .replace("-", "")

                                phonenumber_exists = Contact \
                                    .query \
                                    .filter(Contact.partnership_account_id == partnership_account_id) \
                                    .filter(or_(
                                        Contact.phonenumber_1 == format_phone_number(phonenumber),
                                        Contact.phonenumber_1 == phonenumber)) \
                                    .first()

                                if phonenumber_exists:
                                    return "Data not modified because it already exists.", 304
                                else:
                                    new_contact = Contact()
                                    new_contact.email = email
                                    new_contact.status = None
                                    new_contact.marketing_source = None
                                    new_contact.firstname = firstname
                                    new_contact.lastname = lastname
                                    new_contact.phonenumber_1 = format_phone_number(phonenumber)
                                    new_contact.agent_assigned = None
                                    new_contact.agent_id = None
                                    new_contact.partnership_account_id = partnership_account_id
                                    new_contact.external_source_type = 'adf_import'

                                    if retrieved_campaign:
                                        # Add campaign
                                        new_campaign_id = Campaigns.add_campaign(retrieved_campaign, partnership_account_id)
                                        if new_campaign_id:
                                            new_contact.campaign_id = new_campaign_id

                                    comments = retrieved_lead_details_obj[4]
                                    address_1 = retrieved_lead_details_obj[5]
                                    address_2 = retrieved_lead_details_obj[6]
                                    city = retrieved_lead_details_obj[7]
                                    state = retrieved_lead_details_obj[8]
                                    zip = retrieved_lead_details_obj[9]
                                    country = retrieved_lead_details_obj[10]

                                    if address_1:
                                        new_contact.address_1 = address_1

                                    if address_2:
                                        new_contact.address_2 = address_2

                                    if city:
                                        new_contact.city = city

                                    if state:
                                        new_contact.state = state

                                    if zip:
                                        new_contact.zip = zip

                                    if country:
                                        new_contact.country = country

                                    #if comments is not None:
                                    #    new_contact_note = ContactNotes()
                                    #    new_contact_note.created_on = datetime.now()
                                    #    new_contact_note.updated_on = datetime.now()
                                    #    new_contact_note.partnership_account_id = partnership_account_id
                                    #    #new_contact_note.user_id = current_user.id
                                    #    new_contact_note.text = comments
                                    #    new_contact_note.is_enabled = True
                                    #    new_contact.note_list.append(new_contact_note)

                                    # Add new contact
                                    db.session.add(new_contact)
                                    db.session.flush()
                                    db.session.commit()

                                    # Add new contact vehicle of interest
                                    if retrieved_vehicle_details_obj:
                                        interest_year = None
                                        if retrieved_vehicle_details_obj[2]:
                                            interest_year = retrieved_vehicle_details_obj[2]
                                        interest_price = None
                                        if retrieved_vehicle_details_obj[8]:
                                            interest_price = retrieved_vehicle_details_obj[8]

                                        current_interest_year = None
                                        if retrieved_vehicle_details_obj[17]:
                                            current_interest_year = retrieved_vehicle_details_obj[17]

                                        interest_odometer = None
                                        if retrieved_vehicle_details_obj[11]:
                                            interest_odometer = retrieved_vehicle_details_obj[11]

                                        current_odometer = None
                                        if retrieved_vehicle_details_obj[26]:
                                            current_odometer = retrieved_vehicle_details_obj[26]

                                        current_price = None
                                        if retrieved_vehicle_details_obj[23]:
                                            current_price = retrieved_vehicle_details_obj[23]

                                        ContactVehicle.create(contact_id=new_contact.id,
                                                              current_make=retrieved_vehicle_details_obj[18],
                                                              current_model=retrieved_vehicle_details_obj[19],
                                                              current_vin=retrieved_vehicle_details_obj[20],
                                                              current_year=current_interest_year,
                                                              current_mileage=current_odometer,
                                                              current_condition=retrieved_vehicle_details_obj[29],
                                                              current_value=current_price,
                                                              interest_status=retrieved_vehicle_details_obj[1],
                                                              interest_year=interest_year,
                                                              interest_make=retrieved_vehicle_details_obj[3],
                                                              interest_model=retrieved_vehicle_details_obj[4],
                                                              interest_vin=retrieved_vehicle_details_obj[5],
                                                              interest_stock=retrieved_vehicle_details_obj[6],
                                                              interest_trim=retrieved_vehicle_details_obj[7],
                                                              interest_price=interest_price,
                                                              interest_mileage=interest_odometer,
                                                              interest_condition=retrieved_vehicle_details_obj[14],
                                                              interest_listing_url=''
                                                              )
                        else:
                            valid_contents = False
                            valid_error = "No prospect found."
                    else:
                        valid_contents = False
                        valid_error = 'No partnership account found for {}.'.format(to_email)

                except Exception as e:
                    valid_contents = False
                    exception_message = str(sys.exc_info()[0])
                    log.error(traceback.format_exc())
                    valid_error = "An exception occurred processing adf request. Error: {}. Email address: {}.".format(
                        exception_message, to_email)

            if valid_contents is False or valid_state is False or valid_import_contact is False:
                log.error('ADF Error')
                log.error('********************************************************')
                # log.error('Email sent to: {}'.format(to_email))
                # log.error('Email content: {}'.format(email_text + "\n\n"))
                log.error('Error: {}'.format(valid_error))

                return "Error performing operation.", 400
            else:
                return "OK", 200
        else:
            api.abort(code=404)