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 = '>'
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('>', '>')
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)