File: //home/arjun/projects/buyercall_forms/buyercall/buyercall/blueprints/contacts/views.py
import logging as log
import csv
import json
import uuid
import re
import sys
import traceback
from pytz import timezone
from io import StringIO
from html import unescape
from contextlib import closing
from datetime import date, timedelta
from datetime import datetime
from dateutil import rrule
from collections import OrderedDict
from buyercall.blueprints.filters import format_phone_number
from buyercall.lib.util_crypto import AESCipher
from flask import (
Blueprint,
request,
flash,
url_for,
send_file,
jsonify,
redirect,
make_response,
render_template,
current_app as app)
from sqlalchemy import extract, or_, and_, desc, func, text, distinct
from sqlalchemy.orm import contains_eager, load_only
from flask_login import login_required, current_user
from flask_babel import gettext as _
from buyercall.extensions import csrf
from buyercall.extensions import db
from .forms import ContactForm, ContactVehicleForm, ContactNoteForm
from .models import Contact, ContactNotes, Lead, Message, CreditReports, Campaigns, BdcStatuses, Status, \
MarketingSources, ContactVehicle
from buyercall.blueprints.form_leads.models import FormLead, ExternalForm, FormLeadField
from buyercall.blueprints.agents.models import Agent
from buyercall.blueprints.billing.decorators import subscription_required
from buyercall.lib.util_wtforms import choices_from_dict
from buyercall.blueprints.widgets.bw_outbound import agent_manual_outbound_call
from buyercall.blueprints.activity.models import ActivityType, ActivityName, ActivityLogs
contacts = Blueprint('contacts', __name__, template_folder='templates')
log = log.getLogger(__name__)
email_regex = re.compile(r'\S+@\S+\.\S+')
phone_regex = re.compile(r'^\+?[0-9]{10,12}$')
ALLOWED_EXTENSIONS = {'csv'}
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
# Searches the statuses dictionary for the correct key
def get_call_status_key(status_value):
result = ''
status_value = status_value.replace('_', ' ')
for k, v in Contact.STATUSES.items():
if k == status_value:
result = k
return result
# Searches the sources dictionary for the correct key
def get_marketing_source_key(source_value):
result = ''
source_value = source_value.replace('_', ' ')
for k, v in Contact.MARKETING_SOURCES.items():
if k == source_value:
result = k
return result
def days_between(d1, d2):
d1 = datetime.strptime(d1, "%m%d%Y")
d2 = datetime.strptime(d2, "%m%d%Y")
return abs((d2 - d1).days)
def getboolean(param):
if param == 'true' or param == 'True':
return True
else:
return False
def valid_csv_value(value):
result = True
if value:
if '<--invalid' in value.lower():
result = False
return result
def is_do_not_call_csv_value(value):
result = False
if value:
value = value.lower()
if 'dnc list - don''t contact' in value:
result = True
elif 'don''t contact' in value:
result = True
elif 'do not contact' in value:
result = True
elif 'don''t call' in value:
result = True
elif 'do not call' in value:
result = True
elif 'true' in value:
result = True
return result
def validate_contact(row, firstname, lastname, phonenumber, email, phone_list):
lead_details = []
error_message = ""
missing = []
if firstname is None or firstname == '':
missing.append('firstname')
lead_details.append('')
else:
lead_details.append(firstname)
if lastname is None or lastname == '':
missing.append('lastname')
lead_details.append('')
else:
lead_details.append(lastname)
if email is None or email == '':
# missing.append('email')
lead_details.append('')
else:
lead_details.append(email)
if phonenumber is None or phonenumber == '' or phonenumber == "+1":
missing.append('phonenumber')
lead_details.append('')
else:
lead_details.append(phonenumber)
test_number = phonenumber\
.replace(" ", "")\
.replace("(", "")\
.replace(")", "")\
.replace("-", "")\
.replace("+", "")
if not phone_regex.match(test_number):
error_message += "Phonenumber " + phonenumber + " is invalid. "
elif test_number in phone_list or phonenumber in phone_list:
error_message += "Phonenumber " + phonenumber + " already exists. "
if len(missing) > 0:
error_message = error_message + "Required field(s) are missing: "
count = 0
for miss in missing:
count += 1
error_message = error_message + miss
if count < len(missing):
error_message += "; "
else:
error_message += "."
if error_message and error_message is not "":
lead_details.append(error_message)
lead_details.insert(0, str(row))
else:
lead_details = None
return lead_details
@contacts.route('/api/contacts/message/<int:lead_id>', methods=['GET'])
@login_required
@csrf.exempt
def get_lead_message_info(lead_id):
""" Return this lead's message information in JSON format, with the following
fields:
- agentNumber: The phone number of the agent used to call the lead.
- routingNumber: The routing number used to call the lead.
- allAgentNumbers: An array of all agent numbers for this user.
- allRoutingNumbers: An array of all routing numbers for this user.
The numbers should be returned in E.123 format.
"""
from buyercall.blueprints.phonenumbers.models import Phone
routing_number = ''
display_agents = False
agent_list = []
partnership_account_id = current_user.partnership_account_id
partnership_account_group_viewing = current_user.is_viewing_partnership
# Check if being viewed by super partner
if partnership_account_group_viewing:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
contact = Contact.query.filter(
Contact.partnership_account_id == partnership_account_id, Contact.id == lead_id
).first()
if not contact:
return ''
# retrieve specific message details
message = Phone.query.join(Message)\
.filter(and_(Message.contact_id == contact.id, Message.partnership_account_id == partnership_account_id))\
.order_by(Message.updated_on.desc()).first()
if message is not None:
routing_number = message.phonenumber
routings = Phone.query.filter(and_(
Phone.partnership_account_id == partnership_account_id,
Phone.active,
Phone.is_deactivated == False
)).all()
processsed_routing_list = []
for route in routings:
if route.routing_config:
if route.routing_config.get('configSMSSetup', ''):
processsed_routing_list.append(route)
routing_nos = [
{
"friendlyName": '{} ({})'.format(x.friendly_name, x.phonenumber),
"number": x.id
} for x in processsed_routing_list
]
if partnership_account_group_viewing:
if current_user.partnership_account_id != partnership_account_id:
display_agents = True
agent_list = [(g.id, g.full_name) for g in Agent.query.filter(
Agent.partnership_account_id == partnership_account_id,
Agent.is_deactivated.is_(False)
).distinct().order_by('firstname')]
return jsonify({
"routingNumber": routing_number,
"allRoutingNumbers": routing_nos,
"agentView": display_agents,
"agents": agent_list,
"phonenumber": contact.phonenumber_1,
"name": contact.name
})
@contacts.route('/contacts/campaigns', methods=['GET'])
@login_required
@csrf.exempt
def get_partnership_account_campaigns():
default_campaign = False
partnership_account_id = current_user.partnership_account_id
partnership_account_group_viewing = current_user.is_viewing_partnership
# Check if being viewed by super partner
if partnership_account_group_viewing:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
campaign_list, default_campaign = Campaigns.get_assigned_campaign_list_as_dict(partnership_account_id)
return jsonify({
"default_campaign": default_campaign,
"campaigns": campaign_list
})
@contacts.route('/contacts/campaigns', methods=['POST'])
@login_required
@csrf.exempt
def set_partnership_account_campaigns():
result = False
partnership_account_id = current_user.partnership_account_id
partnership_account_group_viewing = current_user.is_viewing_partnership
# Check if being viewed by super partner
if partnership_account_group_viewing:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
campaign_id = request.args.get('id', '')
campaign_text = request.args.get('text', '')
if (int(campaign_id) < 0):
result = Campaigns.add_campaign(campaign_text, partnership_account_id)
else:
result = Campaigns.update_campaign(campaign_id, campaign_text, partnership_account_id)
return jsonify({
"result": result
})
@contacts.route('/contacts/contact/<int:id>', methods=['GET', 'POST'])
@login_required
def contact_lead_page(id):
partnership_account_id = current_user.partnership_account_id
is_admin_in_group = current_user.is_admin_user_with_groups
viewing_partnership_account = current_user.is_viewing_partnership
# Check if being viewed by super partner
if is_admin_in_group and viewing_partnership_account:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
elif not viewing_partnership_account and is_admin_in_group:
return redirect(url_for('partnership.company_accounts'))
contact = Contact.query.filter(
Contact.id == id, Contact.partnership_account_id == partnership_account_id
).first()
if not contact:
flash('Contact with ID {} not found.'.format(id))
return redirect(url_for('contacts.contact_list'))
ActivityLogs.add_log(current_user.id, ActivityType.PAGE, ActivityName.VIEW, request, id)
from buyercall.blueprints.phonenumbers.models import Phone
phonenumber = Phone.query.filter(Phone.partnership_account_id == partnership_account_id).first()
return render_template('contacts/contact_lead_page.jinja2',
contact=contact,
phonenumber=phonenumber)
@contacts.route('/contacts/edit/<int:id>', methods=['GET', 'POST'])
@login_required
def contact_edit(id):
partnership_account_id = current_user.partnership_account_id
is_admin_in_group = current_user.is_admin_user_with_groups
viewing_partnership_account = current_user.is_viewing_partnership
contact_vehicle = None
is_adf = False
# Check if being viewed by super partner
if is_admin_in_group and viewing_partnership_account:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
elif not viewing_partnership_account and is_admin_in_group:
return redirect(url_for('partnership.company_accounts'))
# Import the partnership account model
from ..partnership.models import PartnershipAccount
partnership_account = PartnershipAccount\
.query\
.filter(PartnershipAccount.id == partnership_account_id)\
.first()
contact = Contact.query.filter(
Contact.id == id, Contact.partnership_account_id == partnership_account_id
).first()
if contact:
contact_vehicle = ContactVehicle.query.filter(ContactVehicle.contact_id == contact.id).first()
if contact.external_source_type == 'adf_import':
is_adf = True
if not contact:
flash('Contact with ID {} not found.'.format(id))
return redirect(url_for('contacts.contact_list'))
if request.method == 'GET':
args = request.args
if not args:
ActivityLogs.add_log(current_user.id, ActivityType.PAGE, ActivityName.VIEW, request, id)
if not contact.marketing_source:
contact.marketing_source = 'no source'
if not contact.bdc_status:
contact.bdc_status = 'no status'
form = ContactForm(obj=contact)
form_vehicle = ContactVehicleForm(obj=contact_vehicle)
form_note = ContactNoteForm(obj=contact)
# Get list of agents
form.agent_assigned.choices = [(g.full_name, g.full_name) for g in Agent.query.filter(
Agent.partnership_account_id == partnership_account_id,
Agent.is_deactivated.is_(False)
).order_by('firstname')]
if contact.agent_assigned:
form.agent_assigned.choices.insert(0, (contact.agent_assigned, contact.agent_assigned))
form.agent_assigned.choices.insert(0, ('', ''))
# Get list of bdc statuses
bdc_status_list = BdcStatuses.get_assigned_status_list(partnership_account_id)
form.bdc_status.choices = [(s.status, s.display_name) for s in bdc_status_list]
# Get list of statuses
status_list = Status.get_assigned_status_list(partnership_account_id)
form.status.choices = [(s.status, s.display_name) for s in status_list]
# Get list of marketing sources
source_list = MarketingSources.get_assigned_source_list(partnership_account_id)
form.marketing_source.choices = [(ms.source, ms.display_name) for ms in source_list]
# Get campaign id
form.campaign_id = contact.campaign_id
# Get voi status
form_vehicle.interest_status.choices = ['', 'new', 'used']
# Get vehicle conditions
form_vehicle.interest_condition.choices = ['', 'excellent', 'good', 'fair', 'poor', 'unknown']
form_vehicle.current_condition.choices = ['', 'excellent', 'good', 'fair', 'poor', 'unknown']
if form.validate_on_submit():
form.populate_obj(contact)
if contact.campaign_id == str(-1) or contact.campaign_id == "":
contact.campaign_id = None
# Lookup Agent id to save to the contacts.agent_id field
old_agent_id = contact.agent_id
agent_id = Agent.reverse_agent_id_lookup(partnership_account_id, contact.agent_assigned)
contact.agent_id = agent_id
# Save the fields to the contact table
ActivityLogs.add_log(current_user.id, ActivityType.PAGE, ActivityName.EDIT, request, id)
contact.save()
# Current Vehicle and Vehicle of Interest fields
edit_current_year = form_vehicle.data['current_year']
if edit_current_year and edit_current_year == 0:
edit_current_year = None
edit_interest_year = form_vehicle.data['interest_year']
if edit_interest_year and edit_interest_year == 0:
edit_interest_year = None
if partnership_account.business_type == 'automotive':
if contact_vehicle:
ContactVehicle.update(contact_id=contact.id,
current_vin=form_vehicle.data['current_vin'],
current_make=form_vehicle.data['current_make'],
current_model=form_vehicle.data['current_model'],
current_year=edit_current_year,
current_mileage=form_vehicle.data['current_mileage'],
current_condition=form_vehicle.data['current_condition'],
current_value=form_vehicle.data['current_value'],
interest_vin=form_vehicle.data['interest_vin'],
interest_make=form_vehicle.data['interest_make'],
interest_model=form_vehicle.data['interest_model'],
interest_year=edit_interest_year,
interest_trim=form_vehicle.data['interest_trim'],
interest_stock=form_vehicle.data['interest_stock'],
interest_price=form_vehicle.data['interest_price'],
interest_status=form_vehicle.data['interest_status'],
interest_mileage=form_vehicle.data['interest_mileage'],
interest_condition=form_vehicle.data['interest_condition'],
interest_listing_url=form_vehicle.data['interest_listing_url']
)
else:
ContactVehicle.create(contact_id=contact.id,
current_vin=form_vehicle.data['current_vin'],
current_make=form_vehicle.data['current_make'],
current_model=form_vehicle.data['current_model'],
current_year=edit_current_year,
current_mileage=form_vehicle.data['current_mileage'],
current_condition=form_vehicle.data['current_condition'],
current_value=form_vehicle.data['current_value'],
interest_vin=form_vehicle.data['interest_vin'],
interest_make=form_vehicle.data['interest_make'],
interest_model=form_vehicle.data['interest_model'],
interest_year=edit_interest_year,
interest_trim=form_vehicle.data['interest_trim'],
interest_stock=form_vehicle.data['interest_stock'],
interest_price=form_vehicle.data['interest_price'],
interest_status=form_vehicle.data['interest_status'],
interest_mileage=form_vehicle.data['interest_mileage'],
interest_condition=form_vehicle.data['interest_condition'],
interest_listing_url=form_vehicle.data['interest_listing_url']
)
# Update status across contact and lead
if current_user.is_bdc_user:
Contact.update_status(contact.id, None, form.data['status'], partnership_account_id, False)
if contact.agent_id != old_agent_id:
from buyercall.blueprints.mobile.utils import send_agent_push_notification
send_agent_push_notification(contact)
try:
form_leads = FormLead.query.filter(FormLead.contact_id == contact.id).all()
for form_lead in form_leads:
lead_fields = FormLeadField.query.filter(
and_(FormLeadField.lead_id == form_lead.id, FormLeadField.field_id == 'repfield')
).first()
lead_fields.field_value = contact.agent_assigned
db.session.commit()
except Exception as e:
log.error('No form leads available for the contact. Error: ' + str(sys.exc_info()[0]))
if form_note.validate_on_submit():
form_note.populate_obj(contact)
contact_form_note = contact.notes
contact.notes = ""
contact.save()
notequery = ''
if form.edited_notes_str.data is not None:
notequery = str(unescape(form.edited_notes_str.data))
db.session.commit()
if len(str(contact_form_note)) > 0:
note = {
'text': contact_form_note,
'contact_id': contact.id,
'created_on': datetime.now(),
'updated_on': datetime.now(),
'user_id': int(current_user.id),
'is_enabled': True,
'partnership_account_id': int(partnership_account_id)
}
result = ContactNotes.create(note)
else:
result = True
if len(notequery) > 0:
noteobject = json.loads(notequery)
for lpnote in noteobject:
_lpresult = ContactNotes.update(
int(lpnote["id"]),
str(lpnote["newtext"]),
datetime.now(),
int(current_user.id),
getboolean(str(lpnote["isenabled"]))
)
if result:
flash(_('The contact has been updated successfully.'), 'success')
return redirect(url_for('contacts.contact_edit', id=contact.id, redirect='true'))
# Return all calls for this contact
previous_calls = Lead.query.filter(
Lead.partnership_account_id == partnership_account_id,
Lead.contact_id == contact.id
).options(
load_only("firstname", "lastname", "starttime", "call_type", "status")
).order_by(text('starttime desc')).limit(10).all()
# Return the number of calls for this contact
previous_calls_count = Lead.query.filter(
Lead.partnership_account_id == partnership_account_id,
Lead.contact_id == contact.id
).count()
# Return all messages for this contact
previous_messages_raw = Message.query.filter(
Message.partnership_account_id == partnership_account_id,
Message.contact_id == contact.id
).order_by(text('messages.created_on desc')).limit(100).all()
previous_messages = []
for msg in previous_messages_raw:
if msg.media_url and msg.media_url[:1] == '[':
bracket_msg_list = str(msg.media_url).replace("[", "").replace("]", "").replace(" ", "")
bracket_msg_list_split = bracket_msg_list.split(",")
msg.media_url = bracket_msg_list_split
previous_messages.append(msg)
elif msg.media_url and msg.media_url[:1] == '{':
replace_msg_str = str(msg.media_url).replace("{", "").replace("}", "").replace(" ", "")
media_str_links = replace_msg_str.split(",")
msg.media_url = media_str_links
previous_messages.append(msg)
elif msg.media_url and msg.media_url[:1] not in ['[', '{']:
single_link = str(msg.media_url).split(",")
single_link_list = []
for s in single_link:
single_link_list.append(s)
msg.media_url = single_link_list
previous_messages.append(msg)
else:
previous_messages.append(msg)
# Return the number of messages for this contact
previous_message_count = Message.query.filter(
Message.partnership_account_id == partnership_account_id,
Message.contact_id == contact.id
).count()
# Return all form lead for this contact
previous_forms = FormLead.query.filter(
FormLead.partnership_account_id == partnership_account_id,
FormLead.contact_id == contact.id
).order_by(text('form_leads.created_on desc')).limit(10).all()
# Return the number of form lead for this contact
previous_form_count = FormLead.query.filter(
FormLead.partnership_account_id == partnership_account_id,
FormLead.contact_id == contact.id
).count()
# Return the contact notes of this contact
all_contact_notes = ContactNotes.query.join(Contact).filter(
contact.id == ContactNotes.contact_id,
contact.partnership_account_id == partnership_account_id,
ContactNotes.is_enabled == 't'
).order_by(desc(ContactNotes.created_on)).all()
# Import the partnership account model for credit tie
from ..partnership.models import PartnershipAccountCreditTie, PartnershipCreditTie
# Return the credentials for an account based on the credit product type
credit_credentials = PartnershipAccountCreditTie.partner_account_seven_hundred_credit_info(
current_user.partnership_account_id,
'credit'
)
account_prequalify_provider = PartnershipAccountCreditTie.query\
.filter(and_(PartnershipAccountCreditTie.partnership_account_id == current_user.partnership_account_id,
PartnershipAccountCreditTie.active.is_(True),
PartnershipAccountCreditTie.product_type == 'prequalify')).first()
if account_prequalify_provider is not None and account_prequalify_provider.service_provider != 'offerlogix':
# Return the credentials for an account based on the prequalify product type
prequalify_credentials = PartnershipAccountCreditTie.partner_account_seven_hundred_credit_info(
current_user.partnership_account_id,
'prequalify'
)
if not prequalify_credentials:
prequalify_credentials = PartnershipCreditTie.\
partner_finserv_credit_info(current_user.partnership_id, 'prequalify')
else:
prequalify_credentials = account_prequalify_provider
# Return the credit reports for this contact
credit_reports = CreditReports.contact_credit_score_object(contact.id, partnership_account_id)
xpn_credit = 0
xpn_prequalify = 0
tu_credit = 0
tu_prequalify = 0
eq_credit = 0
eq_prequalify = 0
for report in credit_reports:
if report.credit_bureau == 'experian' and report.product_type == 'credit' and report.is_successful:
xpn_credit = xpn_credit + 1
elif report.credit_bureau == 'experian' and report.product_type == 'prequalify' and report.is_successful:
xpn_prequalify = xpn_prequalify + 1
if report.credit_bureau == 'transunion' and report.product_type == 'credit' and report.is_successful:
tu_credit = tu_credit + 1
elif report.credit_bureau == 'transunion' and report.product_type == 'prequalify' and report.is_successful:
tu_prequalify = tu_prequalify + 1
if report.credit_bureau == 'equifax' and report.product_type == 'credit' and report.is_successful:
eq_credit = eq_credit + 1
elif report.credit_bureau == 'equifax' and report.product_type == 'prequalify' and report.is_successful:
eq_prequalify = eq_prequalify + 1
# Return the credit report count for this contact
credit_report_count = CreditReports.contact_credit_report_count(contact.id, partnership_account_id)
# Get the current user
user_id = current_user.id
# Check to see if enough data is available to run a credit prequalification
if not contact.firstname or not contact.lastname or not contact.address_1 or not contact.city \
or not contact.state or not contact.zip:
credit_pull_able = False
else:
credit_pull_able = True
# Get available forms forms
forms_result = {}
forms_list = ExternalForm\
.query\
.filter(ExternalForm.partnership_account_id == partnership_account_id,
ExternalForm.is_deactivated == False)\
.all()
for item in forms_list:
form_id = item.id
forms_result[form_id] = item.display_name
supervisor_user_business_type = None
from buyercall.lib.supervisor_manager import current_supervisor_user
if current_supervisor_user and current_supervisor_user.is_authenticated:
partner_id = current_supervisor_user.partnership_id
if partner_id:
from ..partnership.models import Partnership
supervisor_user_partnership = Partnership.query.filter(Partnership.id == partner_id).first()
if supervisor_user_partnership and supervisor_user_partnership.business_type:
supervisor_user_business_type = supervisor_user_partnership.business_type
elif current_supervisor_user.role == 'limitsysadmin':
supervisor_user_business_type = 'automotive'
return render_template('contacts/edit.jinja2',
bdc_status_user=current_user.is_bdc_user,
business_type=partnership_account.business_type,
contact=contact,
contact_notes=all_contact_notes,
credit_credentials=credit_credentials,
credit_pull_able=credit_pull_able,
credit_report_log=credit_reports,
credit_report_count=credit_report_count,
eq_credit=eq_credit,
eq_prequalify=eq_prequalify,
form=form,
form_log=previous_forms,
form_log_count=previous_form_count,
form_vehicle=form_vehicle,
forms_choice=choices_from_dict(forms_result, prepend_blank=False),
is_adf=is_adf,
message_log=previous_messages,
message_log_count=previous_message_count,
phone_log=previous_calls,
phone_log_count=previous_calls_count,
prequalify_credentials=prequalify_credentials,
supervisor_user_business_type=supervisor_user_business_type,
total_calls=previous_calls_count,
total_messages=previous_message_count,
total_forms=previous_form_count,
tu_credit=tu_credit,
tu_prequalify=tu_prequalify,
user_id=user_id,
xpn_credit=xpn_credit,
xpn_prequalify=xpn_prequalify
)
@contacts.route('/contacts/pdf/<int:id>', methods=['GET', 'POST'])
@login_required
@subscription_required
def contact_pdf(id):
partnership_account_id = current_user.partnership_account_id
partnership_account_group_viewing = current_user.is_viewing_partnership
# Check if being viewed by super partner
if partnership_account_group_viewing:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
contact = Contact.query.filter(
Contact.id == id, Contact.partnership_account_id == partnership_account_id
).first()
if not contact:
flash('Contact with ID {} not found.'.format(id))
return redirect(url_for('contacts.contact_list'))
# Return the partnership account name
from buyercall.blueprints.partnership.models import PartnershipAccount
account = PartnershipAccount.query.filter(PartnershipAccount.id == contact.partnership_account_id).first()
# Return current day's date
import datetime
today = datetime.date.today()
today_str = today.strftime('%b, %d %Y')
if contact.form_leads:
max_field = None
for field in contact.form_leads:
if max_field is None and field.updated_datetime is not None:
max_field = field
elif field.updated_datetime > max_field.updated_datetime:
max_field = field
form_lead_id = max_field.id
log.info("The contact leads form lead id is {}:".format(field.id))
return redirect(url_for('form_leads.leads_pdf', lead_id=form_lead_id))
else:
from .contact_tasks import generate_pdf
ActivityLogs.add_log(current_user.id, ActivityType.DATA, ActivityName.PDF, request, id)
return generate_pdf(contact.id, account.name, contact, today_str, account.business_type)
@contacts.route('/contacts')
@login_required
def contact_list():
from buyercall.blueprints.phonenumbers.models import Phone
partnership_account_id = current_user.partnership_account_id
is_admin_in_group = current_user.is_admin_user_with_groups
viewing_partnership_account = current_user.is_viewing_partnership
# Check if being viewed by super partner
if is_admin_in_group and viewing_partnership_account:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
elif not viewing_partnership_account and is_admin_in_group:
return redirect(url_for('partnership.company_accounts'))
phonenumber = Phone.query.filter(Phone.partnership_account_id == partnership_account_id).first()
total_contacts = Contact.query.filter(Contact.partnership_account_id == partnership_account_id).count()
total_notes = ContactNotes.query.filter(ContactNotes.partnership_account_id == partnership_account_id).count()
# Import the partnership account model
from ..partnership.models import PartnershipAccount
partnership_account = PartnershipAccount\
.query\
.filter(PartnershipAccount.id == partnership_account_id)\
.first()
# Get available forms forms
forms_result = {}
forms_list = ExternalForm\
.query\
.filter(ExternalForm.partnership_account_id == partnership_account_id,
ExternalForm.is_deactivated == False)\
.all()
for item in forms_list:
form_id = item.id
forms_result[form_id] = item.display_name
# Populate bdc status result
bdc_status_result = {}
bdc_status_list = BdcStatuses.get_assigned_status_list(partnership_account_id)
for item in bdc_status_list:
status_id = item.status
bdc_status_result[status_id] = item.display_name
# Populate status result
status_result = {}
status_list = Status.get_assigned_status_list(partnership_account_id)
for item in status_list:
status_id = item.status
status_result[status_id] = item.display_name
# Populate marketing result
marketing_result = {}
marketing_list = MarketingSources.get_assigned_source_list(partnership_account_id)
for item in marketing_list:
marketing_id = item.source
marketing_result[marketing_id] = item.display_name
supervisor_user_business_type = None
from buyercall.lib.supervisor_manager import current_supervisor_user
if current_supervisor_user and current_supervisor_user.is_authenticated:
partner_id = current_supervisor_user.partnership_id
if partner_id:
from ..partnership.models import Partnership
supervisor_user_partnership = Partnership.query.filter(Partnership.id == partner_id).first()
if supervisor_user_partnership and supervisor_user_partnership.business_type:
supervisor_user_business_type = supervisor_user_partnership.business_type
elif current_supervisor_user.role == 'limitsysadmin':
supervisor_user_business_type = 'automotive'
if not partnership_account:
flash(_('You do not have permission to access this page. Please try again.'), 'danger')
return redirect(url_for('user.settings'))
return render_template('contacts/contacts.jinja2',
forms_choice=choices_from_dict(forms_result, prepend_blank=False),
supervisor_user_business_type=supervisor_user_business_type,
business_type=partnership_account.business_type,
status_choice=choices_from_dict(status_result, prepend_blank=False),
bdc_status_user=current_user.is_bdc_user,
bdc_status_choice=choices_from_dict(bdc_status_result, prepend_blank=False),
marketing_source=choices_from_dict(marketing_result, prepend_blank=False),
phonenumber=phonenumber,
total_contacts=total_contacts,
import_leads=partnership_account.import_leads,
total_notes=total_notes)
@contacts.route('/contacts/data')
@csrf.exempt
@login_required
def data():
"""Return server side data."""
phone_list = []
lead_list = []
form_list = []
message_list = []
filtered_contact_list = []
filtered_phone_list = []
filtered_message_list = []
filtered_form_list = []
partnership_account_id = current_user.partnership_account_id
partnership_account_group_viewing = current_user.is_viewing_partnership
# Check if being viewed by super partner
if partnership_account_group_viewing:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
search = request.args.get('search[value]', '')
order = int(request.args.get('order[0][column]', '-1'))
direction = request.args.get('order[0][dir]', 'asc')
offset = int(request.args.get('start', 0))
limit = int(request.args.get('length', 99))
date_from = str(request.args.get('df'))
date_to = str(request.args.get('dt'))
assigned_agent = (request.args.get('aa'))
form_check = str(request.args.get('stf'))
phone_check = str(request.args.get('stp'))
sms_check = str(request.args.get('sts'))
import_check = str(request.args.get('sis'))
api_check = str(request.args.get('sas'))
adf_import_check = str(request.args.get('sais'))
phone_source = str(request.args.get('ps'))
sms_source = str(request.args.get('ms'))
marketing_source = str(request.args.get('mks'))
form_source = str(request.args.get('fs'))
call_status = str(request.args.get('cs'))
call_bdc_status = str(request.args.get('cbs'))
date_difference = days_between(date_from, date_to)
# filter initiation section - check box filter
filter_by_phone_check = text('')
filter_by_sms_check = text('')
filter_by_form_check = text('')
all_check = False
# filter initiation section - external source type filters
filter_by_api_check = text('')
filter_by_adf_import_check = text('')
filter_by_import_check = text('')
# filter initiation section - drop down filters
filter_by_assigned_agent = text('')
filter_by_form = text('')
filter_by_call_status = text('')
filter_by_call_bdc_status = text('')
filter_by_marketing_source = text('')
filter_by_assigned_agent_id = text('')
assigned_agent_id = -1
# filter initiation section - source
filter_by_phone_source = text('')
filter_by_sms_source = text('')
filter_by_sms_inbound_source = text('')
filter_by_form_source = text('')
filter_by_phone_complete_source = text('')
filter_by_sms_complete_source = text('')
filter_by_form_complete_source = text('')
# set the date filters
converted_dateFrom = datetime.strptime(date_from, "%m%d%Y").replace(tzinfo=timezone('UTC'))
converted_dateTo = datetime.strptime(date_to, "%m%d%Y").replace(tzinfo=timezone('UTC'))
filter_by_date = and_(
func.date(Contact.updated_on) >= converted_dateFrom,
func.date(Contact.updated_on) <= converted_dateTo
)
lead_filter_by_date = and_(
func.date(Lead.created_on) >= converted_dateFrom,
func.date(Lead.created_on) <= converted_dateTo
)
message_filter_by_date = and_(
func.date(Message.created_on) >= converted_dateFrom,
func.date(Message.created_on) <= converted_dateTo
)
form_filter_by_date = and_(
func.date(FormLead.created_on) >= converted_dateFrom,
func.date(FormLead.created_on) <= converted_dateTo
)
# set the data permission restrictions
filter_by = (Contact.partnership_account_id == partnership_account_id)
# Lead phone number filter section
if phone_source and phone_source != 'null' and phone_source != 'None':
if phone_source.count('-') == 4:
filter_by_phone_source = text("widgets.guid = '" + phone_source + "'")
else:
filter_by_phone_source = text("phonenumbers.id = " + phone_source)
phone_list = Lead.query.outerjoin(Lead.widget).outerjoin(Lead.inbound).options(
contains_eager(Lead.widget).load_only('id', 'name'),
contains_eager(Lead.inbound).load_only('id', 'friendly_name'),
).join(Contact).filter(filter_by)\
.filter(filter_by_phone_source)\
.with_entities('leads.contact_id')\
.distinct().all()
if phone_list is not None:
filter_by_phone_complete_source = and_(Contact.id.in_(phone_list))
# Message filter section
if sms_source and sms_source != 'null' and sms_source != 'None':
filter_by_sms_source = text("phonenumbers.id = {}".format(sms_source))
filter_by_sms_inbound_source = (Message.inbound_id == sms_source)
message_list = Message.query.outerjoin(
Message.inbound
).options(
contains_eager(Message.inbound).load_only('id'),
).join(
Contact
).filter(filter_by).filter(
filter_by_sms_source
).with_entities('messages.contact_id').distinct().all()
if message_list is not None:
filter_by_sms_complete_source = and_(Contact.id.in_(message_list))
# Form lead filter section
if form_source and form_source != 'null' and form_source != 'None':
filter_by_form = text("form_leads.id = {}".format(form_source))
filter_by_form_source = text("external_forms.id = {}".format(form_source))
form_list = FormLead.query.outerjoin(
FormLead.form
).options(
contains_eager(FormLead.form).load_only('id'),
).join(Contact)\
.filter(filter_by)\
.filter(filter_by_form_source)\
.with_entities('form_leads.contact_id')\
.distinct().all()
if form_list is not None:
filter_by_form_complete_source = and_(Contact.id.in_(form_list))
# Assigned agent filter section
if assigned_agent != 'null' and assigned_agent:
formatted_assigned_agent = assigned_agent.replace("'", "''")
filter_by_assigned_agent = and_(text("contacts.agent_assigned = '" + formatted_assigned_agent + "'"))
assigned_agent_id = Agent.query\
.filter(and_(Agent.partnership_account_id == partnership_account_id, Agent.full_name.like(assigned_agent)))\
.with_entities('id')\
.first()
if assigned_agent_id is not None:
filter_by_assigned_agent_id = and_(text("leads.agent_assigned_id = " + str(assigned_agent_id[0])))
# Form check filter section
if form_check == '1' or form_check == 1:
filter_by_form_check = and_(Contact.form_count > 0)
# Message check filter section
if sms_check == '1' or sms_check == 1:
filter_by_sms_check = and_(Contact.sms_count > 0)
# Phone check filter section
if phone_check == '1' or phone_check == 1:
filter_by_phone_check = and_(Contact.phone_count > 0)
if (
(form_check == '1' or form_check == 1) and (sms_check == '1' or sms_check == 1)
and (phone_check == '1' or phone_check == 1)
) or (
(form_check == '0' or form_check == 0) and (sms_check == '0' or sms_check == 0)
and (phone_check == '0' or phone_check == 0)
):
all_check = True
# Call status filter section
if call_status != 'null' and call_status:
filter_by_call_status = and_(text("contacts.status = '{}'".format(call_status)))
# Call status filter section
if current_user.is_bdc_user and call_bdc_status != 'null' and call_bdc_status:
filter_by_call_bdc_status = and_(text("contacts.bdc_status = '{}'".format(call_bdc_status)))
# Marketing source filter section
if marketing_source != 'null' and marketing_source:
if marketing_source == 'no_source':
formatted_marketing_source = marketing_source.replace("'", "''")
filter_by_marketing_source = and_(
or_(text("contacts.marketing_source = '" + formatted_marketing_source + "'"),
text("contacts.marketing_source = '""'")))
else:
filter_by_marketing_source = and_((text("contacts.marketing_source = '" + marketing_source + "'")))
# Source type - API
if api_check == '1' or api_check == 1:
filter_by_api_check = and_(Contact.external_source_type == 'api')
# Source type - ADF Import
if adf_import_check == '1' or adf_import_check == 1:
filter_by_adf_import_check = and_(Contact.external_source_type == 'adf_import')
# Source type - Import
if import_check == '1' or import_check == 1:
filter_by_import_check = and_(Contact.external_source_type == 'import')
# define data columns for the searching data view window
columns_search = [
Contact.id,
Contact.firstname,
Contact.lastname,
Contact.phonenumber_1,
Contact.email,
Status.status,
Contact.agent_assigned
]
# define data columns for the shifting data view window
columns_window = [
Contact.id,
Contact.firstname,
Contact.phonenumber_1,
Contact.email,
Contact.source,
Contact.created_on,
Contact.updated_on,
Status.status,
Contact.agent_assigned,
Contact.credit_score,
#Contact.phone_source,
Contact.lastname
#Contact.caller_id
]
# define data columns for the counts
columns_total = [
Contact.id
]
# define/execute main query
total = Contact.query\
.join(Status, Contact.status == Status.status) \
.filter(filter_by) \
.filter(filter_by_date) \
.filter(filter_by_sms_check) \
.filter(filter_by_phone_check)\
.filter(filter_by_form_check)\
.filter(filter_by_call_status) \
.filter(filter_by_call_bdc_status) \
.filter(filter_by_marketing_source) \
.filter(filter_by_assigned_agent) \
.filter(filter_by_phone_complete_source)\
.filter(filter_by_sms_complete_source)\
.filter(filter_by_form_complete_source)\
.filter(filter_by_api_check) \
.filter(filter_by_adf_import_check) \
.filter(filter_by_import_check)
filtered = total
# Remove this searching due to issues with db encrypted field.
#if search:
# pattern = '%{}%'.format(search)
# filtered = total.filter(or_(
# Contact.name.ilike(pattern),
# Contact.phonenumber_1.ilike(pattern),
# Contact.email.ilike(pattern),
# Contact.status.ilike(pattern),
# Contact.agent_assigned.ilike(pattern),
# Contact.source.ilike(pattern)
# ))
# New searching functionality that works with encrypted fields
if search:
total_list = total.with_entities(*columns_search).all()
searched_result_list = [x for x in total_list
if x.firstname.lower() + ' ' + x.lastname.lower() == search.lower()
or x.email.lower() == search.lower()
or x.firstname.lower() == search.lower()
or x.lastname.lower() == search.lower()
or x.phonenumber_1 == search
or x.status.lower() == search.lower()
or x.agent_assigned == search]
searched_result_ids = [x.id for x in searched_result_list]
filtered = Contact.query.join(Status, Contact.status == Status.status)\
.filter(Contact.id.in_(searched_result_ids))
sorted_ = filtered.with_entities(*columns_window)
if order in range(len(columns_window)):
order_pred = columns_window[order]
if direction == 'desc':
order_pred = desc(order_pred)
sorted_ = sorted_.order_by(order_pred)
sorted_ = sorted_.offset(offset).limit(limit)
data = [
{i: row[i] for i in range(len(row))
} for row in sorted_.all()
]
for lp_data in data:
if lp_data[9]:
encrypt_key = app.config['CRYPTO_SECRET_KEY']
cipher = AESCipher(encrypt_key)
lp_data[9] = cipher.decrypt(lp_data[9])
else:
lp_data[9] = 'Unknown'
# set specific columns for the total count
filtered_total = filtered.with_entities(*columns_total)
for c in filtered_total.all():
filtered_contact_list.append(c[0])
# total lead count
total_leads = len(filtered_contact_list)
# total call count
if phone_check == '1' or phone_check == 1 or all_check:
phone_query = Lead.query.outerjoin(
Lead.widget
).outerjoin(
Lead.inbound
).options(
contains_eager(Lead.widget).load_only('id', 'name'),
contains_eager(Lead.inbound).load_only('id', 'friendly_name')
).filter(Lead.partnership_account_id == partnership_account_id)\
.filter(filter_by_phone_source).with_entities(Lead.phonenumber)\
.filter(lead_filter_by_date)\
.filter(filter_by_assigned_agent_id)\
total_phone = phone_query.count()
total_unique_phone = phone_query.distinct().count()
for phone in phone_query.with_entities(Lead.id).all():
filtered_phone_list.append(phone[0])
else:
total_phone = 0
total_unique_phone = 0
# total message count
if sms_check == '1' or sms_check == 1 or all_check:
messages_query = Message.query\
.filter(Message.partnership_account_id == current_user.partnership_account_id)\
.filter(filter_by_sms_inbound_source)\
.with_entities(Message.from_)\
.filter(message_filter_by_date)
if messages_query is not None:
total_messages = messages_query.count()
total_unique_messages = messages_query.distinct().count()
for message in messages_query.with_entities(Message.id).all():
filtered_message_list.append(message[0])
else:
total_messages = 0
total_unique_messages = 0
else:
total_messages = 0
total_unique_messages = 0
# total form lead count
if form_check == '1' or form_check == 1 or all_check:
form_query = FormLead.query\
.filter(FormLead.partnership_account_id == partnership_account_id)\
.filter(form_filter_by_date)\
.filter(filter_by_form) \
.filter(FormLead.contact_id > 0)\
.with_entities(FormLead.form_id)
total_formleads = form_query.count()
# total_unique_formleads = form_query.distinct().count()
for form in form_query.with_entities(FormLead.id).all():
filtered_form_list.append(form[0])
total_unique_formleads_query = FormLeadField.query\
.filter(or_(FormLeadField.field_id == 'phonefield', FormLeadField.field_id == 'phonenumber'))\
.filter(FormLeadField.lead_id.in_(filtered_form_list)) \
.with_entities(FormLeadField.field_value).distinct().all()
total_unique_formleads_list = []
for i in total_unique_formleads_query:
if i is not None:
formatted_phonenumber = format_phone_number(i[0])
if formatted_phonenumber not in total_unique_formleads_list:
total_unique_formleads_list.append(formatted_phonenumber)
total_unique_formleads = len(total_unique_formleads_list)
else:
total_formleads = 0
total_unique_formleads = 0
graph_labels = []
if date_difference <= 31:
for i in range((converted_dateTo - converted_dateFrom).days + 1):
graph_labels.append((converted_dateFrom + timedelta(days=i)).strftime('%d/%m'))
elif 32 <= date_difference <= 366:
daysLater = converted_dateFrom + timedelta(days=date_difference)
for dt in rrule.rrule(rrule.MONTHLY, dtstart=converted_dateFrom, until=daysLater):
graph_labels.append(dt.strftime('%m/%Y'))
elif date_difference > 366:
daysLater = converted_dateFrom + timedelta(days=date_difference)
for dt in rrule.rrule(rrule.YEARLY, dtstart=converted_dateFrom, until=daysLater):
graph_labels.append(dt.strftime('%Y'))
graph_leads_points = contact_chart_data(date_difference, converted_dateFrom, converted_dateTo,
filtered_contact_list, graph_labels)
graph_phone_points = phone_chart_data(date_difference, converted_dateFrom, converted_dateTo,
filtered_phone_list, graph_labels)
graph_message_points = message_chart_data(date_difference, converted_dateFrom, converted_dateTo,
filtered_message_list, graph_labels)
graph_form_points = form_chart_data(date_difference, converted_dateFrom, converted_dateTo,
filtered_form_list, graph_labels)
return jsonify(
draw=request.args['draw'],
recordsFiltered=total_leads,
recordsTotal=total_leads,
data=data,
graphLeadPoints=graph_leads_points,
graphFormPoints=graph_form_points,
graphMessagePoints=graph_message_points,
graphPhonePoints=graph_phone_points,
graphLabels=graph_labels,
totalLeads=total_leads,
totalPhone=total_phone,
totalUniquePhone=total_unique_phone,
totalMessages=total_messages,
totalUniqueMessages=total_unique_messages,
totalFormLeads=total_formleads,
totalUniqueFormLeads=total_unique_formleads
)
@contacts.route('/contacts/agents', methods=['GET'])
@csrf.exempt
@login_required
def contact_agents():
partnership_account_id = current_user.partnership_account_id
viewing_partnership_account = current_user.is_viewing_partnership
# Check if being viewed by super partner
if viewing_partnership_account:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
agents = [(g.id, g.full_name) for g in Agent
.query
.filter((Agent.partnership_account_id == partnership_account_id),
Agent.is_deactivated.is_(False))
.order_by('firstname')]
return jsonify({
"agents": agents
})
@contacts.route('/contacts/details/add', methods=['POST'])
@csrf.exempt
@login_required
def details_add():
partnership_account_id = current_user.partnership_account_id
viewing_partnership_account = current_user.is_viewing_partnership
missing_message = []
invalid_message = []
error_message = []
success = False
# Check if being viewed by super partner
if viewing_partnership_account:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
data = request.get_json()
if data:
request_firstname = data.get('firstname')
request_lastname = data.get('lastname')
request_status = data.get('status')
request_marketing_source = data.get('marketing_source')
request_agent_assigned_id = data.get('agent_assigned_id')
request_agent_assigned_text = data.get('agent_assigned_text')
request_phonenumber = data.get('phonenumber')
request_email = data.get('email')
request_contact_note = data.get('note')
request_address_1 = data.get('address1')
request_address_2 = data.get('address2')
request_city = data.get('city')
request_state = data.get('state')
request_country = data.get('country')
request_zip = data.get('zip')
request_birthday = data.get('birthday')
request_do_not_call = data.get('donotcall')
request_unsubscribed = data.get('unsubscribed')
agent_assigned = None
agent_assigned_id = None
if not request_firstname:
missing_message.append('firstname')
if not request_lastname:
missing_message.append('lastname')
if request_email:
request_email = request_email.lower()
if not email_regex.match(request_email):
invalid_message.append('Email is invalid.')
if not request_phonenumber or request_phonenumber == "+1":
missing_message.append('phonenumber')
else:
request_phonenumber = request_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(request_phonenumber),
Contact.phonenumber_1 == request_phonenumber))\
.first()
if phonenumber_exists:
error_message.append('Phonenumber already exists.')
elif not phone_regex.match(request_phonenumber):
invalid_message.append('Phonenumber is invalid.')
if request_status != "Select status..." and request_status != "":
request_status = request_status
else:
request_status = 'no status'
if request_marketing_source == "Select marketing source..." or request_marketing_source == "":
request_marketing_source = 'no source'
if request_agent_assigned_text != "Select agent..." and request_agent_assigned_text != "":
agent_assigned = request_agent_assigned_text
agent_assigned_id = request_agent_assigned_id
if request_birthday and request_birthday != "":
try:
datetime.strptime(request_birthday, '%m/%d/%Y')
except ValueError:
invalid_message.append('Birthday is invalid, it should be in mm/dd/yyyy format.')
if not error_message and not missing_message and not invalid_message:
new_contact = Contact()
new_contact.email = request_email
new_contact.status = request_status
new_contact.marketing_source = request_marketing_source
new_contact.firstname = request_firstname
new_contact.lastname = request_lastname
new_contact.phonenumber_1 = format_phone_number(request_phonenumber)
new_contact.agent_assigned = agent_assigned
new_contact.agent_id = agent_assigned_id
new_contact.partnership_account_id = partnership_account_id
new_contact.address_1 = request_address_1
new_contact.address_2 = request_address_2
new_contact.city = request_city
new_contact.state = request_state
new_contact.country = request_country
new_contact.zip = request_zip
new_contact.birthday = request_birthday
new_contact.is_do_not_call = request_do_not_call
new_contact.is_unsubscribe = request_unsubscribed
new_contact.external_source_type = 'import'
db.session.add(new_contact)
db.session.commit()
from buyercall.blueprints.mobile.utils import send_agent_push_notification
send_agent_push_notification(new_contact)
if request_contact_note:
ContactNotes.create({
'text': request_contact_note,
'contact_id': new_contact.id,
'created_on': datetime.now(),
'updated_on': datetime.now(),
'user_id': int(current_user.id),
'is_enabled': True,
'partnership_account_id': int(partnership_account_id)
})
success = True
else:
log.error('No request parameters received to add lead.')
return jsonify({
'error_message': error_message,
'missing_message': missing_message,
'invalid_message': invalid_message,
'success': success
})
@contacts.route('/contacts/details/<int:contact_id>', methods=['GET'])
@csrf.exempt
@login_required
def details_view(contact_id):
partnership_account_id = current_user.partnership_account_id
viewing_partnership_account = current_user.is_viewing_partnership
# Check if being viewed by super partner
if viewing_partnership_account:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
contact = Contact.query.filter(
Contact.partnership_account_id == partnership_account_id, Contact.id == contact_id
).first()
# Get the latest note for the contact
latest_contact_note = ContactNotes.query.filter(
ContactNotes.contact_id == contact_id
).order_by(desc(ContactNotes.created_on)).first()
if latest_contact_note:
note_date = latest_contact_note.date_edited
note_text = latest_contact_note.text
note_user = "{} {}".format(latest_contact_note.user.firstname, latest_contact_note.user.lastname)
else:
note_date = ''
note_text = ''
note_user = ''
# Get list of agents
agents = [(g.full_name, g.full_name) for g in Agent.query.filter(
(Agent.partnership_account_id == partnership_account_id),
Agent.is_deactivated.is_(False)).order_by('firstname')]
if agents and contact:
agents.insert(0, (contact.agent_assigned, contact.agent_assigned))
if not contact:
return ''
else:
ActivityLogs.add_log(current_user.id, ActivityType.MODAL, ActivityName.VIEW, request, contact_id)
return jsonify({
"agents": agents,
"firstname": contact.firstname,
"lastname": contact.lastname,
"status": contact.status,
"bdc_status": contact.bdc_status,
"source": contact.marketing_source,
"agentassigned": contact.agent_assigned,
"phonenumber": contact.phonenumber_1,
"email": contact.email,
"campaign_id": contact.campaign_id,
"notedate": note_date,
"notetext": note_text,
"noteuser": note_user,
})
@contacts.route('/contacts/details/<int:contact_id>', methods=['POST'])
@csrf.exempt
@login_required
def details_save(contact_id):
partnership_account_id = current_user.partnership_account_id
viewing_partnership_account = current_user.is_viewing_partnership
# Check if being viewed by super partner
if viewing_partnership_account:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
try:
data = request.get_json()
if data is not None and contact_id is not None and contact_id > 0:
contact = Contact.query.filter(
Contact.partnership_account_id == partnership_account_id, Contact.id == contact_id
).first()
if not contact:
return 'NOK'
else:
firstname = data.get('firstname')
lastname = data.get('lastname')
status = data.get('status')
bdc_status = data.get('bdc_status')
marketing_source = data.get('source')
agentassigned = data.get('agentassigned')
email = data.get('email')
campaign_id = data.get('campaign_id')
contact_note = data.get('note')
contact.firstname = firstname
contact.lastname = lastname
if status != "Select status..." and status != "":
Contact.update_status(contact.id, None, status, partnership_account_id, None)
if marketing_source != "Select marketing source..." and marketing_source != "":
contact.marketing_source = marketing_source
if current_user.is_bdc_user and bdc_status != "Select BDC status..." and bdc_status != "":
contact.bdc_status = bdc_status
old_agent_id = contact.agent_id
if agentassigned != "Select agent..." and agentassigned != "":
contact.agent_assigned = agentassigned
assigned_agent_id = Agent.reverse_agent_id_lookup(partnership_account_id, agentassigned)
contact.agent_id = assigned_agent_id
if campaign_id == "" or str(campaign_id) == "-1":
contact.campaign_id = None
else:
contact.campaign_id = campaign_id
contact.email = email
try:
form_leads = FormLead.query.filter(FormLead.contact_id == contact.id).all()
for form_lead in form_leads:
lead_fields = FormLeadField.query.filter(
and_(FormLeadField.lead_id == form_lead.id, FormLeadField.field_id == 'repfield')).first()
lead_fields.field_value = contact.agent_assigned
except Exception as e:
log.error('No form leads available for the contact. Error: ' + str(sys.exc_info()[0]))
ActivityLogs.add_log(current_user.id, ActivityType.MODAL, ActivityName.EDIT, request, contact_id)
db.session.commit()
if old_agent_id != contact.agent_id:
from buyercall.blueprints.mobile.utils import send_agent_push_notification
send_agent_push_notification(contact)
if contact_note and contact_note != "" and len(contact_note) > 3:
note = {
'text': contact_note,
'contact_id': contact_id,
'created_on': datetime.now(),
'updated_on': datetime.now(),
'user_id': int(current_user.id),
'partnership_account_id': int(partnership_account_id),
'is_enabled': True
}
result = ContactNotes.create(note)
return 'OK'
else:
log.error('No request parameters received to edit lead.')
return 'NOK'
except Exception as e:
log.error('Error editing lead. Error: ' + str(sys.exc_info()[0]))
return 'NOK'
@contacts.route('/api/contacts/message/<int:inbound_id>', methods=['POST'])
@login_required
@csrf.exempt
def send_text_message(inbound_id):
from buyercall.blueprints.sms.views import send_text_message
agent_id = None
try:
result = request.json
if result is not None and inbound_id is not None and inbound_id > 0:
param_to = result['to']
param_text = result['text']
param_media = result['media']
param_agent_id = result['agent_id']
param_agents_visible = result['agents_visible']
if param_agents_visible and param_agents_visible == True and param_agent_id:
try:
agent_id = int(param_agent_id)
if agent_id <= 0:
agent_id = None
except Exception:
log.error("Error retrieving agent ID for send SMS. Error: " + traceback.format_exc())
send_text_message(inbound_id, param_to, param_text, param_media, agent_id)
return 'OK'
else:
log.error('No request parameters received for SMS API.')
return 'NOK'
except Exception as e:
log.error('Error sending text message via API. Error: ' + str(sys.exc_info()[0]))
return 'NOK'
def contact_chart_data(date_difference, converted_dateFrom, converted_dateTo, contact_list, graph_labels):
filter_by_date = and_(
func.date(Contact.updated_on) >= converted_dateFrom,
func.date(Contact.updated_on) <= converted_dateTo
)
partnership_account_id = current_user.partnership_account_id
partnership_account_group_viewing = current_user.is_viewing_partnership
# Check if being viewed by super partner
if partnership_account_group_viewing:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
# Get selectors
parent_type_filter = extract('day', func.date(Contact.updated_on))
child_type_filter = extract('month', func.date(Contact.updated_on))
year_selector = False
order_by_year = 'parent'
# Check filter type days/month/year
if date_difference <= 31:
parent_type_filter = extract('day', func.date(Contact.updated_on))
child_type_filter = extract('month', func.date(Contact.updated_on))
elif 32 <= date_difference <= 366:
parent_type_filter = extract('month', func.date(Contact.updated_on))
child_type_filter = extract('year', func.date(Contact.updated_on))
if converted_dateFrom.year < converted_dateTo.year:
order_by_year = 'child'
elif date_difference > 366:
parent_type_filter = extract('year', func.date(Contact.updated_on))
child_type_filter = extract('year', func.date(Contact.updated_on))
year_selector = True
labels = db.session.query(
parent_type_filter.label('parent'),
child_type_filter.label('child'),
func.count(Contact.id).label('total')) \
.filter(filter_by_date) \
.filter(Contact.partnership_account_id == partnership_account_id) \
.filter(Contact.id.in_(contact_list)) \
.group_by('parent', 'child') \
.order_by(order_by_year)
filter_data_points = []
filter_labels = []
filter_date = []
for label in labels:
split_filter_date = str(label.parent).split('.')
split_filter_child = str(label.child).split('.')
if label.parent not in filter_date:
filter_date.append(label.parent)
if year_selector:
filter_labels.append(split_filter_date[0])
else:
if len(split_filter_date[0]) == 1:
split_filter_date[0] = '0{}'.format(split_filter_date[0])
if len(split_filter_child[0]) == 1:
split_filter_child[0] = '0{}'.format(split_filter_child[0])
filter_labels.append('{}/{}'.format(split_filter_date[0], split_filter_child[0]))
filter_data_points.append(label.total)
totals = labels
date = []
value = []
for row in totals:
date.append(row.parent)
value.append(row.total)
data_points = []
point = 0
for day in filter_date:
if day in date:
if point < (len(value)):
data_points.append(value[point])
point = point + 1
else:
data_points.append(0)
graph_points = []
for i in data_points:
graph_points.append(i)
result = []
for x in graph_labels:
if x in filter_labels:
index_pos = filter_labels.index(x)
if index_pos is not None and index_pos >= 0:
if index_pos >= len(graph_points):
log.error('Error drawing lead charts (contact). Point out of range.')
log.error('Index: {}'.format(index_pos))
log.error('Filter labels: {}'.format(filter_labels))
log.error('Graph points: {}'.format(graph_points))
result.append(0)
else:
result.append(graph_points[index_pos])
else:
result.append(0)
else:
result.append(0)
return result
def phone_chart_data(date_difference, converted_dateFrom, converted_dateTo, phone_list, graph_labels):
filter_by_date = and_(
func.date(Lead.created_on) >= converted_dateFrom,
func.date(Lead.created_on) <= converted_dateTo
)
partnership_account_id = current_user.partnership_account_id
partnership_account_group_viewing = current_user.is_viewing_partnership
# Check if being viewed by super partner
if partnership_account_group_viewing:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
# Get selectors
parent_type_filter = extract('day', func.date(Lead.created_on))
child_type_filter = extract('month', func.date(Lead.created_on))
year_selector = False
order_by_year = 'parent'
# Check filter type days/month/year
if date_difference <= 31:
parent_type_filter = extract('day', func.date(Lead.created_on))
child_type_filter = extract('month', func.date(Lead.created_on))
elif 32 <= date_difference <= 366:
parent_type_filter = extract('month', func.date(Lead.created_on))
child_type_filter = extract('year', func.date(Lead.created_on))
if converted_dateFrom.year < converted_dateTo.year:
order_by_year = 'child'
elif date_difference > 366:
parent_type_filter = extract('year', func.date(Lead.created_on))
child_type_filter = extract('year', func.date(Lead.created_on))
year_selector = True
labels = db.session.query(
parent_type_filter.label('parent'),
child_type_filter.label('child'),
func.count(Lead.id).label('total')) \
.filter(filter_by_date) \
.filter(Lead.partnership_account_id == partnership_account_id) \
.filter(Lead.id.in_(phone_list)) \
.group_by('parent', 'child') \
.order_by(order_by_year)
filter_data_points = []
filter_labels = []
filter_date = []
for label in labels:
split_filter_date = str(label.parent).split('.')
split_filter_child = str(label.child).split('.')
if label.parent not in filter_date:
filter_date.append(label.parent)
if year_selector:
filter_labels.append(split_filter_date[0])
else:
if len(split_filter_date[0]) == 1:
split_filter_date[0] = '0' + split_filter_date[0]
if len(split_filter_child[0]) == 1:
split_filter_child[0] = '0' + split_filter_child[0]
filter_labels.append(split_filter_date[0] + '/' + split_filter_child[0])
filter_data_points.append(label.total)
date_list = []
value = []
for row in labels:
date_list.append(row.parent)
value.append(row.total)
data_points = []
point = 0
for day in filter_date:
if day in date_list:
if point < (len(value)):
data_points.append(value[point])
point = point + 1
else:
data_points.append(0)
graph_points = []
for i in data_points:
graph_points.append(i)
result = []
for x in graph_labels:
if x in filter_labels:
index_pos = filter_labels.index(x)
if index_pos is not None and index_pos >= 0:
if index_pos >= len(graph_points):
log.error('Error drawing lead charts (phone). Point out of range.')
log.error('Index: ' + str(index_pos))
log.error('Filter labels: ' + str(filter_labels))
log.error('Graph points: ' + str(graph_points))
result.append(0)
else:
result.append(graph_points[index_pos])
else:
result.append(0)
else:
result.append(0)
return result
def message_chart_data(date_difference, converted_dateFrom, converted_dateTo, message_list, graph_labels):
filter_by_date = and_(
func.date(Message.created_on) >= converted_dateFrom,
func.date(Message.created_on) <= converted_dateTo
)
partnership_account_id = current_user.partnership_account_id
partnership_account_group_viewing = current_user.is_viewing_partnership
# Check if being viewed by super partner
if partnership_account_group_viewing:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
# Get selectors
parent_type_filter = extract('day', func.date(Message.created_on))
child_type_filter = extract('month', func.date(Message.created_on))
year_selector = False
order_by_year = 'parent'
# Check filter type days/month/year
if date_difference <= 31:
parent_type_filter = extract('day', func.date(Message.created_on))
child_type_filter = extract('month', func.date(Message.created_on))
elif 32 <= date_difference <= 366:
parent_type_filter = extract('month', func.date(Message.created_on))
child_type_filter = extract('year', func.date(Message.created_on))
if converted_dateFrom.year < converted_dateTo.year:
order_by_year = 'child'
elif date_difference > 366:
parent_type_filter = extract('year', func.date(Message.created_on))
child_type_filter = extract('year', func.date(Message.created_on))
year_selector = True
labels = db.session.query(
parent_type_filter.label('parent'),
child_type_filter.label('child'),
func.count(Message.id).label('total')) \
.filter(filter_by_date) \
.filter(Message.partnership_account_id == partnership_account_id) \
.filter(Message.id.in_(message_list)) \
.group_by('parent', 'child') \
.order_by(order_by_year)
filter_data_points = []
filter_labels = []
filter_date = []
for label in labels:
split_filter_date = str(label.parent).split('.')
split_filter_child = str(label.child).split('.')
if label.parent not in filter_date:
filter_date.append(label.parent)
if year_selector:
filter_labels.append(split_filter_date[0])
else:
if len(split_filter_date[0]) == 1:
split_filter_date[0] = '0{}'.format(split_filter_date[0])
if len(split_filter_child[0]) == 1:
split_filter_child[0] = '0{}'.format(split_filter_child[0])
filter_labels.append(split_filter_date[0] + '/' + split_filter_child[0])
filter_data_points.append(label.total)
date_list = []
value = []
for row in labels:
date_list.append(row.parent)
value.append(row.total)
data_points = []
point = 0
for day in filter_date:
if day in date_list:
if point < (len(value)):
data_points.append(value[point])
point = point + 1
else:
data_points.append(0)
graph_points = []
for i in data_points:
graph_points.append(i)
result = []
for x in graph_labels:
if x in filter_labels:
index_pos = filter_labels.index(x)
if index_pos is not None and index_pos >= 0:
if index_pos >= len(graph_points):
log.error('Error drawing lead charts (message). Point out of range.')
log.error('Index: ' + str(index_pos))
log.error('Filter labels: ' + str(filter_labels))
log.error('Graph points: ' + str(graph_points))
result.append(0)
else:
result.append(graph_points[index_pos])
else:
result.append(0)
else:
result.append(0)
return result
def form_chart_data(date_difference, converted_dateFrom, converted_dateTo, form_list, graph_labels):
filter_by_date = and_(
func.date(FormLead.created_on) >= converted_dateFrom,
func.date(FormLead.created_on) <= converted_dateTo
)
partnership_account_id = current_user.partnership_account_id
partnership_account_group_viewing = current_user.is_viewing_partnership
# Check if being viewed by super partner
if partnership_account_group_viewing:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
# Get selectors
parent_type_filter = extract('day', func.date(FormLead.created_on))
child_type_filter = extract('month', func.date(FormLead.created_on))
year_selector = False
order_by_year = 'parent'
# Check filter type days/month/year
if date_difference <= 31:
parent_type_filter = extract('day', func.date(FormLead.created_on))
child_type_filter = extract('month', func.date(FormLead.created_on))
elif 32 <= date_difference <= 366:
parent_type_filter = extract('month', func.date(FormLead.created_on))
child_type_filter = extract('year', func.date(FormLead.created_on))
if converted_dateFrom.year < converted_dateTo.year:
order_by_year = 'child'
elif date_difference > 366:
parent_type_filter = extract('year', func.date(FormLead.created_on))
child_type_filter = extract('year', func.date(FormLead.created_on))
year_selector = True
labels = db.session.query(
parent_type_filter.label('parent'),
child_type_filter.label('child'),
func.count(FormLead.id).label('total')) \
.filter(filter_by_date) \
.filter(FormLead.partnership_account_id == partnership_account_id) \
.filter(FormLead.id.in_(form_list)) \
.group_by('parent', 'child') \
.order_by(order_by_year)
filter_data_points = []
filter_labels = []
filter_date = []
for label in labels:
split_filter_date = str(label.parent).split('.')
split_filter_child = str(label.child).split('.')
if label.parent not in filter_date:
filter_date.append(label.parent)
if year_selector:
filter_labels.append(split_filter_date[0])
else:
if len(split_filter_date[0]) == 1:
split_filter_date[0] = '0{}'.format(split_filter_date[0])
if len(split_filter_child[0]) == 1:
split_filter_child[0] = '0{}'.format(split_filter_child[0])
filter_labels.append('{}/{}'.format(split_filter_date[0], split_filter_child[0]))
filter_data_points.append(label.total)
date_list = []
value = []
for row in labels:
date_list.append(row.parent)
value.append(row.total)
data_points = []
point = 0
for day in filter_date:
if day in date_list:
if point < (len(value)):
data_points.append(value[point])
point = point + 1
else:
data_points.append(0)
graph_points = []
for i in data_points:
graph_points.append(i)
result = []
for x in graph_labels:
if x in filter_labels:
index_pos = filter_labels.index(x)
if index_pos is not None and index_pos >= 0:
if index_pos >= len(graph_points):
log.error('Error drawing lead charts (form). Point out of range.')
log.error('Index: ' + str(index_pos))
log.error('Filter labels: ' + str(filter_labels))
log.error('Graph points: ' + str(graph_points))
result.append(0)
else:
result.append(graph_points[index_pos])
else:
result.append(0)
else:
result.append(0)
return result
# return data for the filter options
@contacts.route('/contacts/filteroptions', methods=['GET'])
@csrf.exempt
@login_required
def filteroptions():
"""Return server side filter data."""
phone_source_result = {}
message_source_result = {}
form_source_result = {}
status_result = {}
bdc_status_result = {}
marketing_result = {}
assigned_agent_result = {}
partnership_account_id = current_user.partnership_account_id
partnership_account_group_viewing = current_user.is_viewing_partnership
# Check if being viewed by super partner
if partnership_account_group_viewing:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
filter_by_lead = and_(
Lead.partnership_account_id == partnership_account_id,
Lead.contact_id == Contact.id,
Contact.partnership_account_id == partnership_account_id
)
filter_by_sms = and_(
Message.partnership_account_id == partnership_account_id,
Message.contact_id == Contact.id,
Contact.partnership_account_id == partnership_account_id
)
filter_by_form = and_(
FormLead.partnership_account_id == partnership_account_id,
FormLead.contact_id == Contact.id,
Contact.partnership_account_id == partnership_account_id
)
filter_by_partnership_account = and_(
Contact.partnership_account_id == partnership_account_id,
Contact.agent_assigned != ""
)
# populate agent result
agent_data = Contact.query.with_entities('contacts.agent_assigned').filter(filter_by_partnership_account).distinct()
for i in agent_data:
if i:
if i[0]:
id = i[0]
name = i[0]
assigned_agent_result[id] = name
# populate phone result
inbound_data = Lead.query.join(Lead.contact)\
.outerjoin(Lead.inbound).options(
contains_eager(Lead.inbound)
).with_entities(
'phonenumbers.id', 'phonenumbers.friendly_name', 'phonenumbers.phonenumber'
).filter(filter_by_lead).distinct()
outbound_data = Lead.query.join(Lead.contact)\
.outerjoin(Lead.widget).options(
contains_eager(Lead.widget)
).with_entities('widgets.guid', 'widgets.name').filter(filter_by_lead).distinct()
for i in inbound_data:
if i:
if i[0] and i[2]:
id = str(i[0])
name = str(i[2])
friendly_name = str(i[1])
phone_source_result[id] = '{} ({})'.format(name, friendly_name)
for i in outbound_data:
if i:
if i[0] and i[1]:
id = str(i[0])
name = str(i[1])
phone_source_result[id] = name
# populate message result
message_data = Message.query.join(Message.contact)\
.outerjoin(Message.inbound).options(
contains_eager(Message.inbound)
).with_entities(
'phonenumbers.id', 'phonenumbers.friendly_name', 'phonenumbers.phonenumber'
).filter(filter_by_sms).distinct()
for i in message_data:
if i:
if i[0] and i[2]:
id = str(i[0])
name = str(i[2])
friendly_name = str(i[1])
message_source_result[id] = '{} ({})'.format(name, friendly_name)
# populate form lead result
form_data = FormLead.query.join(FormLead.contact)\
.outerjoin(FormLead.form).options(
contains_eager(FormLead.form)
).with_entities('external_forms.id', 'external_forms.display_name').filter(filter_by_form).distinct()
for i in form_data:
if i:
if i[0] and i[1]:
id = str(i[0])
name = str(i[1])
form_source_result[id] = name
# populate bdc status result
bdc_status_list = BdcStatuses.get_assigned_status_list(partnership_account_id)
for item in bdc_status_list:
id = item.status
bdc_status_result[id] = item.display_name
# populate status result
status_list = Status.get_assigned_status_list(partnership_account_id)
for item in status_list:
id = item.status
status_result[id] = item.display_name
# populate marketing result
marketing_list = MarketingSources.get_assigned_source_list(partnership_account_id)
for item in marketing_list:
id = item.source
marketing_result[id] = item.display_name
return jsonify(
phone_source_data=phone_source_result,
message_source_data=message_source_result,
form_source_data=form_source_result,
assigned_agent_data=assigned_agent_result,
bdc_status_data=bdc_status_result,
status_data=status_result,
marketing_data=marketing_result
)
@contacts.route('/contacts/csv', methods=['GET'])
@login_required
def data_csv():
phone_list = []
lead_list = []
form_list = []
message_list = []
filtered_contact_list = []
filtered_phone_list = []
filtered_message_list = []
filtered_form_list = []
partnership_account_id = current_user.partnership_account_id
partnership_account_group_viewing = current_user.is_viewing_partnership
# Check if being viewed by super partner
if partnership_account_group_viewing:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
from ..partnership.models import PartnershipAccount
partnership_account = PartnershipAccount \
.query \
.filter(PartnershipAccount.id == partnership_account_id) \
.first()
search = request.args.get('search[value]', '')
order = int(request.args.get('order[0][column]', '-1'))
direction = request.args.get('order[0][dir]', 'asc')
offset = int(request.args.get('start', 0))
limit = int(request.args.get('length', 99))
date_from = str(request.args.get('df'))
date_to = str(request.args.get('dt'))
assigned_agent = str(request.args.get('aa'))
form_check = str(request.args.get('stf'))
phone_check = str(request.args.get('stp'))
sms_check = str(request.args.get('sts'))
import_check = str(request.args.get('sis'))
api_check = str(request.args.get('sas'))
adf_import_check = str(request.args.get('sais'))
phone_source = str(request.args.get('ps'))
sms_source = str(request.args.get('ms'))
form_source = str(request.args.get('fs'))
call_status = str(request.args.get('cs'))
call_bdc_status = str(request.args.get('cbs'))
marketing_source = str(request.args.get('mks'))
date_difference = days_between(date_from, date_to)
# set the date filters
converted_dateFrom = datetime.strptime(date_from, "%m%d%Y").date()
converted_dateTo = datetime.strptime(date_to, "%m%d%Y").date()
filter_by_date = and_(
func.date(Contact.updated_on) >= converted_dateFrom,
func.date(Contact.updated_on) <= converted_dateTo
)
# filter initiation section - check box filter
filter_by_phone_check = text('')
filter_by_sms_check = text('')
filter_by_form_check = text('')
# filter initiation section - external source type filters
filter_by_api_check = text('')
filter_by_adf_import_check = text('')
filter_by_import_check = text('')
# filter initiation section - drop down filters
filter_by_assigned_agent = text('')
filter_by_form = text('')
filter_by_call_status = text('')
filter_by_call_bdc_status = text('')
filter_by_marketing_source = text('')
filter_by_assigned_agent_id = text('')
assigned_agent_id = -1
# filter initiation section - source
filter_by_phone_source = text('')
filter_by_sms_source = text('')
filter_by_form_source = text('')
filter_by_phone_complete_source = text('')
filter_by_sms_complete_source = text('')
filter_by_form_complete_source = text('')
# set the data permission restrictions
filter_by = (Contact.partnership_account_id == partnership_account_id)
# Lead phone number filter section
if phone_source and phone_source != 'null' and phone_source != 'None':
if phone_source.count('-') == 4:
filter_by_phone_source = "widgets.guid = '{}'".format(phone_source)
else:
filter_by_phone_source = text("phonenumbers.id = {}".format(phone_source))
phone_list = Lead.query.outerjoin(
Lead.widget
).outerjoin(Lead.inbound).options(
contains_eager(Lead.widget).load_only('id', 'name'),
contains_eager(Lead.inbound).load_only('id', 'friendly_name'),
).join(Contact)\
.filter(filter_by)\
.filter(filter_by_phone_source)\
.with_entities('leads.contact_id')\
.distinct().all()
if phone_list is not None:
filter_by_phone_complete_source = and_(Contact.id.in_(phone_list))
# Message filter section
if sms_source and sms_source != 'null' and sms_source != 'None':
filter_by_sms_source = "phonenumbers.id = {}".format(sms_source)
message_list = Message.query.outerjoin(
Message.inbound
).options(
contains_eager(Message.inbound).load_only('id'),
).join(Contact)\
.filter(filter_by)\
.filter(filter_by_sms_source)\
.with_entities('messages.contact_id')\
.distinct().all()
if message_list is not None:
filter_by_sms_complete_source = and_(Contact.id.in_(message_list))
# Form lead filter section
if form_source and form_source != 'null' and form_source != 'None':
filter_by_form_source = text("external_forms.id = {}".format(form_source))
form_list = FormLead.query.outerjoin(
FormLead.form
).options(
contains_eager(FormLead.form).load_only('id'),
).join(Contact)\
.filter(filter_by)\
.filter(filter_by_form_source)\
.with_entities('form_leads.contact_id')\
.distinct().all()
if form_list is not None:
filter_by_form_complete_source = and_(Contact.id.in_(form_list))
# Assigned agent filter section
if assigned_agent != 'null' and assigned_agent:
filter_by_assigned_agent = and_(text("contacts.agent_assigned = '" + str(assigned_agent) + "'"))
# Form check filter section
if form_check == '1' or form_check == 1:
filter_by_form_check = and_(Contact.form_count > 0)
# Message check filter section
if sms_check == '1' or sms_check == 1:
filter_by_sms_check = and_(Contact.sms_count > 0)
# Phone check filter section
if phone_check == '1' or phone_check == 1:
filter_by_phone_check = and_(Contact.phone_count > 0)
# Source type - API
if api_check == '1' or api_check == 1:
filter_by_api_check = and_(Contact.external_source_type == 'api')
# Source type - ADF Import
if adf_import_check == '1' or adf_import_check == 1:
filter_by_adf_import_check = and_(Contact.external_source_type == 'adf_import')
# Source type - Import
if import_check == '1' or import_check == 1:
filter_by_import_check = and_(Contact.external_source_type == 'import')
# call status filter section
if call_status != 'null' and call_status:
filter_by_call_status = and_(text("contacts.status = '" + call_status + "'"))
# call bdc status filter section
if current_user.is_bdc_user and call_bdc_status != 'null' and call_bdc_status:
filter_by_call_bdc_status = and_(text("contacts.bdc_status = '" + call_bdc_status + "'"))
# Marketing source filter section
if marketing_source != 'null' and marketing_source:
if marketing_source == 'no_source':
filter_by_marketing_source = and_(
or_(text("contacts.marketing_source = '" + marketing_source + "'"),
text("contacts.marketing_source = '""'")))
else:
filter_by_marketing_source = and_((text("contacts.marketing_source = '" + marketing_source + "'")))
search = request.args.get('search[value]', '')
header = [
'No',
'Created On',
'Updated On',
'First Name',
'Last Name',
'Credit Score',
'Caller Id',
'Phonenumber_1',
'Phonenumber_2',
'Email',
'Address_1',
'Address_2',
'City',
'State',
'Zip',
'Country',
'Do Not Call',
'SMS Unsubscribe',
'Agent Assigned',
'Status',
'BDC Status',
'Marketing Source',
# 'Total Phone Calls', # Removed to optimize download and speed it up
# 'Phone Source', # Removed to optimize download and speed it up
# 'Total Messages', # Removed to optimize download and speed it up
# 'Message Source', # Removed to optimize download and speed it up
# 'Total Form Leads', # Removed to optimize download and speed it up
# 'Form Lead Source' # Removed to optimize download and speed it up
]
if partnership_account.business_type != 'automotive':
del header[5]
total = Contact.query \
.join(Status, Contact.status == Status.status) \
.filter(filter_by) \
.filter(filter_by_date) \
.filter(filter_by_form_check)\
.filter(filter_by_sms_check) \
.filter(filter_by_phone_check) \
.filter(filter_by_assigned_agent) \
.filter(filter_by_call_status) \
.filter(filter_by_call_bdc_status) \
.filter(filter_by_marketing_source) \
.filter(filter_by_phone_complete_source)\
.filter(filter_by_sms_complete_source)\
.filter(filter_by_form_complete_source)\
.filter(filter_by_api_check) \
.filter(filter_by_adf_import_check) \
.filter(filter_by_import_check)
filtered = total
# define data columns for the searching data view window
columns_search = [
Contact.id,
Contact.name,
Contact.firstname,
Contact.lastname,
Contact.phonenumber_1,
Contact.email,
Status.status,
Contact.agent_assigned
]
#if search:
# pattern = '%{}%'.format(search)
# filtered = total.filter(or_(
# Contact.name.ilike(pattern),
# Contact.phonenumber_1.ilike(pattern),
# Contact.email.ilike(pattern),
# Contact.status.ilike(pattern),
# Contact.agent_assigned.ilike(pattern),
# Contact.source.ilike(pattern)
# ))
if search:
total_list = total.with_entities(*columns_search).all()
searched_result_list = [x for x in total_list if x.name == search
or x.email == search
or x.firstname == search
or x.lastname == search
or x.phonenumber_1 == search
or x.status == search
or x.agent_assigned == search]
searched_result_ids = [x.id for x in searched_result_list]
filtered = Contact.query.join(Status, Contact.status == Status.status)\
.filter(Contact.id.in_(searched_result_ids))
query = filtered.order_by(Contact.updated_on)
# Get status list
bdc_status_list = BdcStatuses.get_complete_status_list_as_dict()
# Get status list
status_list = Status.get_complete_status_list_as_dict()
# Get source list
marketing_source_list = MarketingSources.get_complete_source_list_as_dict()
# Build the CSV
row_no = 0
with closing(StringIO()) as out:
writer = csv.writer(out)
writer.writerow(header)
for contact in query.all():
# lead_source_name = '' # Removed to optimize download and speed it up
# sms_source_name = '' # Removed to optimize download and speed it up
# form_lead_source_name = '' # Removed to optimize download and speed it up
row_no += 1
decrypted_credit_score = ''
# lead_source = Lead.query.filter(and_(
# Lead.contact_id == contact.id,
# Lead.partnership_account_id == partnership_account_id
# )).first() # Removed to optimize download and speed it up
# sms_source = Message.query.filter(and_(
# Message.contact_id == contact.id,
# Message.partnership_account_id == partnership_account_id
# )).first() # Removed to optimize download and speed it up
# form_lead_source = FormLead.query.filter(and_(
# FormLead.contact_id == contact.id,
# FormLead.partnership_account_id == partnership_account_id
# )).first() # Removed to optimize download and speed it up
# if lead_source is not None:
# lead_source_name = lead_source.source # Removed to optimize download and speed it up
# if sms_source is not None:
# sms_source_name = sms_source.source # Removed to optimize download and speed it up
# if form_lead_source is not None:
# form_lead_source_name = form_lead_source.source # Removed to optimize download and speed it up
sms_unsubscribe = "Yes" if contact.is_unsubscribe else "No"
do_not_call = "Yes" if contact.is_do_not_call else "No"
if partnership_account.business_type == 'automotive':
# Decrypt credit score
encrypt_key = app.config['CRYPTO_SECRET_KEY']
cipher = AESCipher(encrypt_key)
if contact.credit_score:
decrypted_credit_score = cipher.decrypt(contact.credit_score)
csv_row = [
row_no,
contact.created_datetime,
contact.updated_datetime,
contact.firstname,
contact.lastname,
decrypted_credit_score,
contact.caller_id,
contact.phonenumber_1,
contact.phonenumber_2,
contact.email,
contact.address_1,
contact.address_2,
contact.city,
contact.state,
contact.zip,
contact.country,
do_not_call,
sms_unsubscribe,
contact.agent_assigned,
Status.get_formatted_status(status_list, contact.status),
BdcStatuses.get_formatted_status(bdc_status_list, contact.bdc_status),
MarketingSources.get_formatted_source(marketing_source_list, contact.marketing_source),
# contact.phone_count, # Removed to optimize download and speed it up
# lead_source_name, # Removed to optimize download and speed it up
# contact.sms_count, # Removed to optimize download and speed it up
# sms_source_name, # Removed to optimize download and speed it up
# contact.form_count, # Removed to optimize download and speed it up
# form_lead_source_name # Removed to optimize download and speed it up
]
if partnership_account.business_type != 'automotive':
del csv_row[5]
writer.writerow([x for x in csv_row])
filename = 'Buyercall Leads - {}.csv'.format(
date.today().strftime('%Y-%m-%d')
)
resp = make_response(out.getvalue())
resp.headers['Content-Type'] = 'text/csv'
resp.headers['Content-Disposition'] = \
'attachment; filename="{}"'.format(filename)
ActivityLogs.add_log(current_user.id, ActivityType.DATA, ActivityName.DOWNLOAD, request, None, 'csv')
return resp
@contacts.route('/contacts/call/<int:contact_id>', methods=['GET'])
@login_required
@csrf.exempt
def get_contact_call_info(contact_id):
""" Return this contact lead's call information in JSON format, with the following
fields:
- agentNumber: The phone number of the agent used to call the lead.
- routingNumber: The routing number used to call the lead.
- allAgentNumbers: An array of all agent numbers for this user.
- allRoutingNumbers: An array of all routing numbers for this user.
The numbers should be returned in E.123 format.
"""
from buyercall.blueprints.phonenumbers.models import Phone
partnership_account_id = current_user.partnership_account_id
partnership_account_group_viewing = current_user.is_viewing_partnership
# Check if being viewed by super partner
if partnership_account_group_viewing:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
result_agent_number = ''
result_my_phone = ''
lead = Lead.query.filter(
Lead.partnership_account_id == partnership_account_id, Lead.contact_id == contact_id
).first()
agent_nos = []
if current_user.role == 'agent':
agents = Agent.query.filter(and_(
Agent.user_id == current_user.id,
Agent.is_deactivated.is_(False))
).all()
else:
agents = Agent.query.filter(and_(
Agent.partnership_account_id == partnership_account_id,
Agent.is_deactivated.is_(False))
).all()
for agent in agents:
if agent.phonenumber:
agent_nos.append({
"agent": "{}, Phone ({})".format(
agent.full_name, agent.phonenumber),
"number": agent.phonenumber
})
if agent.mobile and (
agent.mobile != agent.phonenumber or not agent.phonenumber
):
agent_nos.append({
"agent": "{}, Mobile ({})".format(
agent.full_name, agent.mobile),
"number": agent.mobile
})
if agent.app_number and (
agent.app_number != agent.mobile or agent.app_number != agent.phonenumber or not agent.phonenumber
):
agent_nos.append({
"agent": "{}, App ({})".format(
agent.full_name, agent.app_number),
"number": agent.app_number
})
routings = Phone.query.filter(and_(
Phone.partnership_account_id == partnership_account_id,
Phone.active == True, Phone.is_deactivated == False
)).all()
routing_nos = [
{
"friendlyName": '{} ({})'.format(x.friendly_name, x.phonenumber),
"number": x.phonenumber
} for x in routings
]
if lead:
result_agent_number = lead.agent and lead.agent.phonenumber
result_my_phone = lead.my_phone
else:
contact_details = Contact.query.filter(
Contact.partnership_account_id == current_user.partnership_account_id, Contact.id == contact_id
).first()
if contact_details:
route_number = Phone.query \
.filter(and_(Phone.partnership_account_id == current_user.partnership_account_id, Phone.active,
Phone.is_deactivated == False)).first()
assigned_agent = Agent.query.filter(
and_(Agent.full_name == contact_details.agent_assigned,
Agent.partnership_account_id == current_user.partnership_account_id)).first()
if route_number:
result_my_phone = route_number.phonenumber
if assigned_agent:
result_agent_number = assigned_agent and assigned_agent.phonenumber
return jsonify({
"agentNumber": result_agent_number,
"allAgentNumbers": agent_nos,
"routingNumber": result_my_phone,
"allRoutingNumbers": routing_nos
})
@contacts.route('/contacts/call/<int:contact_id>', methods=['POST'])
@csrf.exempt
def call_contact(contact_id):
from buyercall.blueprints.phonenumbers.models import Phone
from buyercall.blueprints.agents.models import Agent
from buyercall.blueprints.phonenumbers.tasks import connect_lead_to_agent
partnership_account_id = current_user.partnership_account_id
partnership_id = current_user.partnership_id
partnership_account_group_viewing = current_user.is_viewing_partnership
# Check if being viewed by super partner
if partnership_account_group_viewing:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
data = request.get_json()
agent_number, routing_number = (
data.get('agentNumber'), data.get('routingNumber')
)
routing = Phone.query.filter(
Phone.partnership_account_id == partnership_account_id,
Phone.phonenumber == routing_number
).options(load_only('id')).first()
if not routing:
return 'Cannot find routing phone number', 400
agent = Agent.query.filter(
Agent.partnership_account_id == partnership_account_id,
or_(Agent.phonenumber == agent_number,
Agent.mobile == agent_number,
Agent.app_number == agent_number), # TODO: Include extension!
).options(load_only('id')).first()
if not agent:
return 'Cannot find agent', 400
old_lead = Lead.query.filter(
Lead.partnership_account_id == partnership_account_id, Lead.contact_id == contact_id
).first()
if old_lead:
progress_status = old_lead.progress_status
else:
progress_status = 'new lead'
# Return contact details
contact_lead = Contact.query.filter(Contact.id == contact_id).first()
contact_lead_id = contact_lead.id
if not contact_lead_id:
return 'Contact lead with ID {} not found'.format(contact_id), 400
lead = Lead(
partnership_account_id=partnership_account_id,
firstname=contact_lead.firstname,
lastname=contact_lead.lastname,
phonenumber=contact_lead.phonenumber_1,
email=contact_lead.email,
starttime=datetime.utcnow(),
call_type='outbound',
my_phone=routing_number,
inbound_id=routing.id,
agent_id=agent.id,
progress_status=progress_status,
status='ringing',
contact_id=contact_lead_id,
originating_number=routing_number,
call_source='Agent Outbound Call',
)
lead.save()
# A hack to specify how to contact the agent
if agent.mobile == agent_number:
contact_using = 'mobile'
elif agent.app_number == agent_number:
contact_using = 'app'
else:
contact_using = 'phone'
call_settings = {
'agents': [{'id': agent.id, 'contactUsing': contact_using}]
}
if routing.type == 'tracking':
agent_manual_outbound_call(lead, call_settings)
else:
connect_lead_to_agent.delay(lead.id, agent.id, call_settings)
return ''
def format_date(date):
""" Format a local date for consumption by Excel
"""
if not date:
return ''
return date.strftime('%Y-%m-%d %H:%M:%S')
@contacts.route('/contacts/credit_report/<bureau>/<product_type>/<int:contact_id>/<service_provider>', methods=['POST', 'GET'])
@login_required
@csrf.exempt
def contact_credit_report(bureau, product_type, contact_id, service_provider):
from .contact_tasks import successful_credit_check_seven_hundred, unsuccessful_credit_check, \
successful_credit_check_finserv
from buyercall.lib.util_credit_reports import seven_hundred_credit_client, validate_ssn, \
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, format_dob_year, finserv_credit_client
from buyercall.blueprints.sysadmin.utilities.request_log_task_call import LogRequestTaskTrigger
from flask import request
LogRequestTaskTrigger().log_request_task_trigger(
request, "seven-hundred-credit")
# Set the encryption and cipher key
encrypt_key = app.config['CRYPTO_SECRET_KEY']
cipher = AESCipher(encrypt_key)
lead = Contact.query.filter(Contact.id == contact_id).first()
# Set the Jinja2 page veriables to avoid errors
url = ''
finserv_credit_score = ''
finserv_credit_approve = ''
finserv_credit_trade_lines = ''
contact = Contact.query.filter(Contact.id == contact_id).first()
existing_report = CreditReports.query\
.filter(and_(CreditReports.contact_id == contact_id,
CreditReports.partnership_account_id == lead.partnership_account_id,
CreditReports.credit_bureau == bureau,
CreditReports.product_type == product_type,
CreditReports.is_successful.is_(True))).first()
if existing_report:
if existing_report.iframe_url:
if contact.is_eq_lead:
decrypt_url_field = cipher.decrypt(existing_report.iframe_url).split('::')
print(decrypt_url_field)
url_bucket = decrypt_url_field[0]
url_key = decrypt_url_field[1]
# build presigned url
from buyercall.lib.util_boto3_s3 import generate_presigned_aws_url
url = generate_presigned_aws_url(url_key, url_bucket)
else:
url = cipher.decrypt(existing_report.iframe_url)
else:
url = ''
if existing_report.trades:
finserv_credit_score = cipher.decrypt(existing_report.credit_score)
finserv_credit_approve = existing_report.is_approved
finserv_credit_trade_lines = json.loads(cipher.decrypt(existing_report.trades))
else:
finserv_credit_score = ''
finserv_credit_approve = ''
finserv_credit_trade_lines = ''
else:
validate_name = validate_name(lead.firstname, lead.lastname)
if not validate_name:
description = 'This lead does not have a valid first name and/or last name.'
trans_id = cipher.encrypt('Unavailable')
unsuccessful_credit_check(contact_id, lead.partnership_account_id, description, bureau, product_type,
trans_id, service_provider)
flash(_('{} Please update the lead with a valid name and try again.').format(description), 'danger')
return redirect(url_for('contacts.contact_edit', id=contact_id) + '#credit')
validate_street = validate_address_street(lead.address_1)
if not validate_street:
description = 'This lead does not have a valid street address.'
trans_id = cipher.encrypt('Unavailable')
unsuccessful_credit_check(contact_id, lead.partnership_account_id, description, bureau, product_type,
trans_id, service_provider)
flash(_('{} Please update the lead with a valid address and try again.').format(description), 'danger')
return redirect(url_for('contacts.contact_edit', id=contact_id) + '#credit')
validate_city = validate_address_city(lead.city)
if not validate_city:
description = 'This lead does not have a valid city.'
trans_id = cipher.encrypt('Unavailable')
unsuccessful_credit_check(contact_id, lead.partnership_account_id, description, bureau, product_type,
trans_id, service_provider)
flash(_('{} Please update the lead with a valid city and try again.').format(description), 'danger')
return redirect(url_for('contacts.contact_edit', id=contact_id) + '#credit')
validate_state = validate_address_state(lead.state)
if not validate_state:
description = 'This lead does not have a valid state.'
trans_id = cipher.encrypt('Unavailable')
unsuccessful_credit_check(contact_id, lead.partnership_account_id, description, bureau, product_type,
trans_id, service_provider)
flash(_('{} Please update the lead with a valid state and try again.').format(description), 'danger')
return redirect(url_for('contacts.contact_edit', id=contact_id) + '#credit')
validate_zip = validate_address_zip(lead.zip)
if not validate_zip:
description = 'This lead does not have a valid zip code.'
trans_id = cipher.encrypt('Unavailable')
unsuccessful_credit_check(contact_id, lead.partnership_account_id, description, bureau, product_type,
trans_id, service_provider)
flash(_('{} Please update the lead with a valid zip code and try again.').format(description), 'danger')
return redirect(url_for('contacts.contact_edit', id=contact_id) + '#credit')
validate_combo = validate_city_state_zip_combo(lead.zip, lead.city, lead.state)
if not validate_combo:
description = 'This lead zip code, city and state does not seem to match as a valid zip, ' \
'city and state combination.'
trans_id = cipher.encrypt('Unavailable')
unsuccessful_credit_check(contact_id, lead.partnership_account_id, description, bureau, product_type,
trans_id, service_provider)
flash(_('{} Please review the zip code, city, state for the lead and try again.').format(description),
'danger')
return redirect(url_for('contacts.contact_edit', id=contact_id) + '#credit')
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 = ''
name = format_full_name(lead.user_fullname)
address = format_street(lead.address_1)
city = format_city(lead.city)
state = format_state(lead.state)
zip_code = format_zip(lead.zip)
a_username = 'buyercall'
a_user_id = lead.partnership_account_id
score_card_name = ''
credit_score = ''
ssn = ''
if service_provider == '700Credit':
client = seven_hundred_credit_client(lead.partnership_account_id, product_type)
# 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')
unsuccessful_credit_check(contact_id, lead.partnership_account_id, description, bureau,
product_type, trans_id, service_provider)
flash(_('{} Please update the lead with a valid SSN and try again.').format(description),
'danger')
return redirect(url_for('contacts.contact_edit', id=contact_id) + '#credit')
# Perform the API post request to get credit report
if product_type == 'credit':
if not ssn:
description = 'This lead does not have a Social Security Number (SSN).'
trans_id = cipher.encrypt('Unavailable')
unsuccessful_credit_check(contact_id, lead.partnership_account_id, description, bureau,
product_type, trans_id, service_provider)
flash(_(' {} Please update the lead with a valid SSN and try generating a'
' Credit Report again.').format(description), 'danger')
return redirect(url_for('contacts.contact_edit', id=contact_id) + '#credit')
credit_pull = client.credit_report.get_full_credit_report(bureau_abv, name, ssn, address,
city, state, zip_code, a_username,
a_user_id)
else:
credit_pull = client.credit_report.get_prequalify_credit_report(bureau_abv, name, address, city,
state, zip_code, ssn, a_username,
a_user_id)
log.info('The response return by the credit POST: {}'.format(credit_pull))
if product_type == 'prequalify':
trans_id = cipher.encrypt(credit_pull['Results']['XML_Report']['Transid'])
credit_score_raw = credit_pull['Results']['XML_Report']['Prescreen_Report']['Score']
if credit_score_raw is None:
description = credit_pull['Results']['XML_Report']['Prescreen_Report']['ResultDescription']
unsuccessful_credit_check(contact_id, lead.partnership_account_id, description, bureau,
product_type, trans_id, service_provider)
flash(_('Error Description: {}. Please contact Support for assistance.'.format(description)),
'danger')
return redirect(url_for('contacts.contact_edit', id=contact_id) + '#credit')
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.'
else:
trans_id = cipher.encrypt(credit_pull['Results']['Transaction_Information']['Transid'])
if bureau == 'experian':
try:
credit_score = cipher.encrypt(credit_pull['Results']['bureau_xml_data']['XPN_Report']['subject_segments']['risk_models']['risk_model'][0]['score']['#text'])
score_card_name = credit_pull['Results']['bureau_xml_data']['XPN_Report']['subject_segments']['risk_models']['risk_model'][0]['ModelIndicator']['#text']
except:
description = 'Consumer file not found.'
unsuccessful_credit_check(contact_id, lead.partnership_account_id, description,
bureau, product_type, trans_id, service_provider)
flash(_('{} Please contact Support for assistance.').format(description), 'danger')
return redirect(url_for('contacts.contact_edit', id=contact_id) + '#credit')
elif bureau == 'transunion' or bureau == 'equifax':
try:
credit_score = cipher.encrypt(credit_pull['Results']['bureau_xml_data']['TU_Report'][
'subject_segments']['scoring_segments'][
'scoring'][0]['score'])
score_card_name = credit_pull['Results']['bureau_xml_data']['TU_Report'][
'subject_segments']['scoring_segments']['scoring'][0]['ModelIndicator']['#text']
except Exception as e:
log.info('The exception is: {}'.format(e))
log.info('The trans id is {}'.format(trans_id))
try:
description = credit_pull['Results']['bureau_xml_data']['TU_Report']['Error']['#text']
except:
description = 'Consumer file not found.'
unsuccessful_credit_check(contact_id, lead.partnership_account_id, description,
bureau, product_type, trans_id, service_provider)
flash(_('{} Please contact Support for assistance.').format(description), 'danger')
return redirect(url_for('contacts.contact_edit', id=contact_id) + '#credit')
url = credit_pull['Results']['custom_report_url']['iframe']['@src']
encrypted_url = cipher.encrypt(url)
description = 'Successful hard credit pull.'
credit_entry = successful_credit_check_seven_hundred(contact_id, lead.partnership_account_id, 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))
elif service_provider == 'Finserv':
# Set the client to connect to the Finserv API
client = finserv_credit_client(lead.partnership_account_id, 'prequalify')
contact_birthday = ''
if lead.birthday is None:
form_lead = FormLead \
.query \
.filter(and_(FormLead.contact_id == contact_id,
FormLead.partnership_account_id == lead.partnership_account_id)) \
.first()
if form_lead is not None:
form_lead_field = FormLeadField \
.query \
.filter(and_(FormLeadField.lead_id == form_lead.id,
or_(FormLeadField.field_id == 'birthday',
FormLeadField.field_id == 'datepicker'))) \
.first()
if form_lead_field is None or form_lead_field.field_value is None:
contact_birthday = cipher.decrypt(form_lead_field.field_value)
else:
contact_birthday = lead.birthday
# Set a random trans_id for BuyerCall purposes because Finserv does not provide one
trans_id = cipher.encrypt(uuid.uuid4().hex)
try:
# format the birthday for the lead
contact_birthday = format_dob_year(contact_birthday)
if not contact_birthday:
description = 'This lead does not have a valid birth year.'
unsuccessful_credit_check(contact_id, lead.partnership_account_id, description, bureau,
product_type, trans_id, service_provider)
flash(_('{} Please update the lead with a valid Year of'
' Birth and try again.').format(description), 'danger')
return redirect(url_for('contacts.contact_edit', id=contact_id) + '#credit')
r = client.credit_prequalify.get_token()
r_content = json.loads(r)
token = 'Bearer ' + str(r_content['access_token'])
kwargs = {}
kwargs['FirstName'] = lead.firstname
kwargs['LastName'] = lead.lastname
kwargs['YearOfBirth'] = contact_birthday
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, lead.partnership_account_id, trans_id,
credit_score, description, bureau, product_type,
service_provider, approved, trades)
log.info('The credit report id is: {}'.format(credit_entry))
finserv_credit_report = CreditReports.query.filter(CreditReports.id == credit_entry).first()
finserv_credit_score = cipher.decrypt(finserv_credit_report.credit_score)
finserv_credit_approve = finserv_credit_report.is_approved
finserv_credit_trade_lines = json.loads(cipher.decrypt(finserv_credit_report.trades))
except Exception as e:
log.error(traceback.format_exc())
description = str(e)
log.error('The finserv exception is: {} for contact {}'.format(e, contact_id))
unsuccessful_credit_check(contact_id, lead.partnership_account_id, description, bureau,
product_type, trans_id, service_provider)
flash(_('We were unable to run the soft pull for this lead. Please make sure all the lead fields '
'are complete and correct.'), 'danger')
return redirect(url_for('contacts.contact_edit', id=contact_id) + '#credit')
except Exception as e:
log.error(traceback.format_exc())
flash(_('An unexpected error occurred: {}. Please contact Support for assistance.'.format(e)), 'danger')
return redirect(url_for('contacts.contact_edit', id=contact_id) + '#credit')
ActivityLogs.add_log(current_user.id, ActivityType.CREDIT, ActivityName.VIEW, request, contact_id)
return render_template('contacts/credit_report.jinja2', lead=lead, url=url, contact_id=contact_id,
finserv_credit_score=finserv_credit_score, finserv_credit_approve=finserv_credit_approve,
finserv_credit_trade_lines=finserv_credit_trade_lines)
@contacts.route('/contacts/import/leads', methods=['GET', 'POST'])
@login_required
@csrf.exempt
def import_lead_file():
error_result = []
contacts = []
csvfile = None
csv_error_list = ''
csv_errors = False
error_status = False
error_message = ''
contact_phone_list = []
contact_count = 0
contact_total_count = 0
partnership_account_id = current_user.partnership_account_id
viewing_partnership_account = current_user.is_viewing_partnership
header = [
'No', 'First Name', 'Last Name', 'Email', 'Phone Number', 'Error'
]
if request.method == 'POST':
# Check if being viewed by super partner
if viewing_partnership_account:
partnership_account_id = current_user.get_user_viewing_partnership_account_id
current_datetime = datetime.now()
if 'file' not in request.files:
error_message = 'No file found.'
error_status = True
else:
try:
csvfile = request.files['file']
contact_list = db.session\
.query(Contact.phonenumber_1)\
.filter(Contact.partnership_account_id == partnership_account_id)\
.distinct()
for contact in contact_list:
if contact.phonenumber_1 and contact.phonenumber_1 not in contact_phone_list:
formated_phonenumber = contact\
.phonenumber_1\
.replace(" ", "")\
.replace("(", "")\
.replace(")", "")\
.replace("-", "") \
.replace("+", "")
contact_phone_list.append(formated_phonenumber)
except Exception as ex:
error_message = 'An error occurred while processing the file.'
error_status = True
log.error(traceback.format_exc())
if not error_status and allowed_file(csvfile.filename):
stream = StringIO(csvfile.stream.read().decode("UTF8"), newline=None)
reader = csv.DictReader(stream)
reader.fieldnames = [fn.lower()
.replace(" ", "")
.replace("_", "")
.replace("-", "") for fn in reader.fieldnames]
data = [row for row in reader]
status_list = Status.get_assigned_status_list_as_dict(current_user.partnership_account_id)
new_status_dict = {}
marketing_source_list = MarketingSources.get_assigned_source_list_as_dict(current_user.partnership_account_id)
new_marketing_source_dict = {}
for row in data:
contact_total_count += 1
contact_firstname = ''
if 'firstname' in row and valid_csv_value(row['firstname']):
contact_firstname = row['firstname']
contact_lastname = ''
if 'lastname' in row and valid_csv_value(row['lastname']):
contact_lastname = row['lastname']
contact_city = ''
if 'city' in row and valid_csv_value(row['city']):
contact_city = row['city']
contact_state = ''
if 'state' in row and valid_csv_value(row['state']):
contact_state = row['state']
contact_zip = ''
if 'zip' in row and valid_csv_value(row['zip']):
contact_zip = row['zip']
elif 'postalcode' in row and valid_csv_value(row['postalcode']):
contact_zip = row['postalcode']
elif 'zipcode' in row and valid_csv_value(row['zipcode']):
contact_zip = row['zipcode']
contact_address_1 = ''
if 'address1' in row and valid_csv_value(row['address1']):
contact_address_1 = row['address1']
contact_address_2 = ''
if 'address2' in row and valid_csv_value(row['address2']):
contact_address_2 = row['address2']
contact_phonenumber_1 = None
phone_found_on_main = False
contact_phonenumber_2 = None
if 'home/mainnumber' in row and valid_csv_value(row['home/mainnumber']):
contact_phonenumber_1 = row['home/mainnumber']
phone_found_on_main = True
elif 'phonenumber' in row and valid_csv_value(row['phonenumber']):
contact_phonenumber_1 = row['phonenumber']
phone_found_on_main = True
elif 'phonenumber1' in row and valid_csv_value(row['phonenumber1']):
contact_phonenumber_1 = row['phonenumber1']
phone_found_on_main = True
elif 'mainnumber' in row and valid_csv_value(row['mainnumber']):
contact_phonenumber_1 = row['mainnumber']
phone_found_on_main = True
elif 'phone' in row and valid_csv_value(row['phone']):
contact_phonenumber_1 = row['phone']
phone_found_on_main = True
if contact_phonenumber_1 is None or contact_phonenumber_1 == '':
if 'mobilenumber' in row and valid_csv_value(row['mobilenumber']):
contact_phonenumber_1 = row['mobilenumber']
elif 'mobilephonenumber' in row and valid_csv_value(row['mobilephonenumber']):
contact_phonenumber_1 = row['mobilephonenumber']
elif 'cellphone' in row and valid_csv_value(row['cellphone']):
contact_phonenumber_1 = row['cellphone']
elif 'cellphonenumber' in row and valid_csv_value(row['cellphonenumber']):
contact_phonenumber_1 = row['cellphonenumber']
elif 'cell' in row and valid_csv_value(row['cell']):
contact_phonenumber_1 = row['cell']
if contact_phonenumber_1 is None or contact_phonenumber_1 == '':
if 'worknumber' in row and valid_csv_value(row['worknumber']):
contact_phonenumber_1 = row['worknumber']
elif 'workphonenumber' in row and valid_csv_value(row['workphonenumber']):
contact_phonenumber_1 = row['workphonenumber']
elif 'work' in row and valid_csv_value(row['work']):
contact_phonenumber_1 = row['work']
if contact_phonenumber_1:
if phone_found_on_main:
if 'mobilenumber' in row and valid_csv_value(row['mobilenumber']):
contact_phonenumber_2 = row['mobilenumber']
elif 'mobilephonenumber' in row and valid_csv_value(row['mobilephonenumber']):
contact_phonenumber_2 = row['mobilephonenumber']
elif 'cellphone' in row and valid_csv_value(row['cellphone']):
contact_phonenumber_2 = row['cellphone']
elif 'cellphonenumber' in row and valid_csv_value(row['cellphonenumber']):
contact_phonenumber_2 = row['cellphonenumber']
elif 'cell' in row and valid_csv_value(row['cell']):
contact_phonenumber_2 = row['cell']
elif 'phonenumber2' in row and valid_csv_value(row['phonenumber2']):
contact_phonenumber_2 = row['phonenumber2']
if contact_phonenumber_2 is None or contact_phonenumber_2 == '':
if 'worknumber' in row and valid_csv_value(row['worknumber']):
contact_phonenumber_2 = row['worknumber']
elif 'phonenumber2' in row and valid_csv_value(row['phonenumber2']):
contact_phonenumber_2 = row['phonenumber2']
elif 'workphonenumber' in row and valid_csv_value(row['workphonenumber']):
contact_phonenumber_2 = row['workphonenumber']
elif 'work' in row and valid_csv_value(row['work']):
contact_phonenumber_2 = row['work']
if contact_phonenumber_1:
if not is_do_not_call_csv_value(contact_phonenumber_1):
contact_phonenumber_1 = contact_phonenumber_1
if contact_phonenumber_2:
if not is_do_not_call_csv_value(contact_phonenumber_2):
contact_phonenumber_2 = contact_phonenumber_2
contact_is_do_not_call = False
if 'nevercontacttype' in row:
contact_is_do_not_call = is_do_not_call_csv_value(row['nevercontacttype'])
elif 'isdonotcall' in row:
contact_is_do_not_call = is_do_not_call_csv_value(row['isdonotcall'])
elif 'donotcall' in row:
contact_is_do_not_call = is_do_not_call_csv_value(row['donotcall'])
contact_email = ''
if 'email' in row and valid_csv_value(row['email']):
contact_email = row['email']
elif 'emailaddress' in row and valid_csv_value(row['emailaddress']):
contact_email = row['emailaddress']
contact_birthday = ''
if 'birthday' in row and valid_csv_value(row['birthday']):
contact_birthday = row['birthday']
elif 'birthdate' in row and valid_csv_value(row['birthdate']):
contact_birthday = row['birthdate']
elif 'dateofbirth' in row and valid_csv_value(row['dateofbirth']):
contact_birthday = row['dateofbirth']
if len(contact_birthday) > 10:
contact_birthday = contact_birthday[0: 10]
try:
datetime.strptime(contact_birthday, '%m/%d/%Y')
except ValueError:
contact_birthday = ''
contact_status = ''
if 'status' in row and valid_csv_value(row['status']):
contact_status = row['status']
elif 'individualtype' in row and valid_csv_value(row['individualtype']):
contact_status = row['individualtype']
if contact_status:
contact_status_lower_case = contact_status.lower()
if contact_status_lower_case not in status_list:
new_status_dict[contact_status_lower_case] = contact_status
contact_status = contact_status_lower_case
else:
contact_status = 'no status'
contact_marketing_source = ''
if 'marketingsource' in row and valid_csv_value(row['marketingsource']):
contact_marketing_source = row['marketingsource']
if contact_marketing_source:
contact_marketing_source_lower_case = contact_marketing_source.lower()
if contact_marketing_source_lower_case not in marketing_source_list:
new_marketing_source_dict[contact_marketing_source_lower_case] = contact_marketing_source
contact_marketing_source = contact_marketing_source_lower_case
contact_note = ''
if 'note' in row and valid_csv_value(row['note']):
contact_note = row['note']
elif 'notes' in row and valid_csv_value(row['notes']):
contact_note = row['notes']
elif 'specialnote' in row and valid_csv_value(row['specialnote']):
contact_note = row['specialnote']
elif 'specialnotes' in row and valid_csv_value(row['specialnotes']):
contact_note = row['specialnotes']
valid_message = validate_contact(contact_total_count,
contact_firstname,
contact_lastname,
contact_phonenumber_1,
contact_email,
contact_phone_list)
if valid_message is None:
new_contact = Contact()
new_contact.firstname = contact_firstname
new_contact.lastname = contact_lastname
new_contact.phonenumber_1 = format_phone_number(contact_phonenumber_1)
new_contact.phonenumber_2 = format_phone_number(contact_phonenumber_2)
new_contact.email = contact_email
new_contact.is_do_not_call = contact_is_do_not_call
new_contact.address_1 = contact_address_1
new_contact.address_2 = contact_address_2
new_contact.state = contact_state
new_contact.zip = contact_zip
new_contact.city = contact_city
new_contact.birthday = contact_birthday
new_contact.status = contact_status
new_contact.marketing_source = contact_marketing_source
new_contact.partnership_account_id = partnership_account_id
new_contact.external_source_type = 'import'
if contact_note and contact_note != "":
new_contact_note = ContactNotes()
new_contact_note.created_on = current_datetime
new_contact_note.updated_on = current_datetime
new_contact_note.partnership_account_id = partnership_account_id
new_contact_note.user_id = current_user.id
new_contact_note.text = contact_note
new_contact_note.is_enabled = True
new_contact.note_list.append(new_contact_note)
contacts.append(new_contact)
contact_count += 1
else:
error_result.append(valid_message)
try:
if len(contacts) > 0:
# process status
if new_status_dict:
Status.add_status_dict(new_status_dict, partnership_account_id)
# process marketing sources
if new_marketing_source_dict:
MarketingSources.add_source_dict(new_marketing_source_dict, partnership_account_id)
db.session.add_all(contacts)
db.session.commit()
except Exception as ex:
error_message = 'An error occurred while processing the file.'
error_status = True
log.error(traceback.format_exc())
else:
error_message = 'Only CSV files allowed.'
error_status = True
if error_result and len(error_result) > 0:
csv_errors = True
error_result.insert(0, header)
joiner = ","
for row in error_result:
if row:
csv_error_list += joiner.join(row) + "\n"
if error_status:
return jsonify(success=False, message=error_message), 201
elif not error_status:
if contact_count == 0 and contact_total_count > 0:
success_message = str(contact_count) + " leads out of " + str(contact_total_count) + " were imported."
success_status = False
elif contact_count == contact_total_count:
success_message = "All " + str(contact_count) + " lead(s) successfully imported."
success_status = True
else:
success_message = "Only " + str(contact_count) + " lead(s) out of " + str(contact_total_count) + " were successfully imported."
success_status = True
return jsonify(success=success_status, message=success_message, csv_download=csv_errors, csv_error_list=csv_error_list, count=contact_count, total_count=contact_total_count), 200
@contacts.route('/contacts/import/leads/example', methods=['GET', 'POST'])
@login_required
def download():
return send_file('blueprints/contacts/templates/contacts/example.csv', as_attachment=True)