File: //home/arjun/projects/buyercall_forms/buyercall/buyercall/blueprints/phonenumbers/bw_inbound.py
"""
Governs the logic of the incoming lead calls and lead/agent interations.
At any given moment the call is in one of the following states:
* 'NEW': New inbound call
* 'CALLBACK': Callback call, agent called first
* 'LEAD_ONHOLD': Lead selected a routing, placed on hold until agent answers
* 'AGENT_ONHOLD': In a scheduled callback, the agent is called first and placed
on hold.
* 'CALLBACK_PROMPT': Lead has been waiting for a while and is asked if he wants
to be called back.
* 'ONGOING': Agent answered/call ongoing.
* 'MISSED': Call ended/lead missed.
* 'CAPTURED': Call ended/lead captured.
"""
# +13127618003
from __future__ import print_function
import traceback
import logging
from datetime import datetime
from flask import (
Blueprint,
request,
current_app as app,
url_for,
jsonify
)
from twilio.twiml.voice_response import VoiceResponse
import redis
from sqlalchemy.orm import (
load_only,
)
import pytz
from buyercall.extensions import csrf, db
from buyercall.lib.flask_mailplus import _try_renderer_template
from buyercall.lib.util_ses_email import send_ses_email
import buyercall.lib.bandwidth as bandwidth
from buyercall.lib.util_twilio import (
CallStorage,
InboundCallState as State,
bw_client,
select_voice,
)
from buyercall.lib.util_webhooks import WebhookUtil
from .models import (
Phone,
)
from buyercall.blueprints.leads.models import Lead
from buyercall.blueprints.agents.models import Agent
from buyercall.blueprints.user.models import User
from buyercall.blueprints.block_numbers.models import Block
from buyercall.blueprints.partnership.models import Partnership, PartnershipAccount
from .routing import (
get_routing_agents, schedule_callback,
get_agent_number, get_agent_text,
hold_music_url,
)
from .twilio_inbound import send_notifications
log = logging.getLogger(__name__)
webhooker = WebhookUtil()
bw_inbound = Blueprint(
'bw_inbound', __name__, template_folder='templates'
)
HOURS = 3600
""" The length of an hour in seconds """
DAYS = 86400
""" The length of a day in seconds """
TWIMLET_CLASSICAL = 'http://twimlets.com/holdmusic?Bucket=com.twilio.music.classical' # noqa
""" The URL for the Twimlet with some nice hold music """
CALLBACK_PROMPT_DELAY = 60
""" Seconds to wait until prompting the user for callback """
DEFAULT_VOICE = 'alice'
""" The voice to use when speaking a message for the lead """
MSG_TOO_SLOW = "We're sorry, another agent has accepted this call."
MSG_DIGIT_PROMPT = 'Press any key to accept this call, or this call will \
be canceled.'
MSG_SERVICE_UNAVAILABLE = "We're sorry, our service is currently unavailable. \
An agent will call you back as soon as possible."
MSG_CANNOT_CONNECT = """We're sorry, we cannot connect you to an agent
presently. Please try again later."""
MSG_NOT_AVAILABLE = """Sorry, we are not available at the moment. We will
remember your call, and will get back to you as soon as possible."""
MSG_INVALID_DIGIT = 'You have pressed an invalid digit. Please try again.'
@bw_inbound.route('/api/bw/random')
@csrf.exempt
def random_url():
print(request.args)
print(request.form)
return ''
@bw_inbound.route('/api/bw/inbound/<int:inbound_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_inbound_call(inbound_id):
""" Entry point for the incoming lead call.
"""
# Declare redis config url
redis_db = redis.StrictRedis(
host=app.config['REDIS_CONFIG_URL'],
port=app.config['REDIS_CONFIG_PORT'],
decode_responses=True
)
args = request.json
print(args)
is_number_blocked = Block.blocked(inbound_id, args.get('from'))
if is_number_blocked:
from .bw_tasks import bw_hangup_calls
bw_hangup_calls.delay(args.get('callId', ''))
log.info('This call was not processed because phone number: {} was blocked'.format(args.get('from')))
return ''
event_type = args.get('eventType')
call_state = args.get('callState')
call_id = args.get('callId', '')
if event_type == 'incomingcall':
# Do nothing, since the call is auto-accepted
return ''
if event_type == 'answer':
phone_number = args.get('from', '')
from .bw_tasks import bw_lead_create
# FYI: Webhook added to method below.
bw_lead_create.delay(
inbound_id, call_id, phone_number,
start_time=args.get('time'),
)
return ''
if call_state == 'completed':
key = 'CALL:{}'.format(call_id)
lead_id = redis_db.get(key)
lead_mark_finished(lead_id)
return ''
@bw_inbound.route(
'/api/bw/inbound/continue/<int:lead_id>',
methods=['GET', 'POST']
)
@csrf.exempt
def lead_inbound_call_continue(lead_id):
""" Continuation of the lead's call. Why two methods for the same call?
To make sure that we call the agent after the lead's call connected.
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
args = request.json
print(args)
event_type = args.get('eventType')
call_id = args.get('callId')
# state = args.get('state')
status = args.get('status')
tag = args.get('tag')
storage = CallStorage(redis_db, lead_id)
log.debug('{}: {}'.format(lead_id, storage.state))
routing_config = storage.routing_config
language = routing_config.get('language', 'en')
gender, locale, voice = select_voice(language)
r = bandwidth.Response()
if (status == 'done' and tag == 'whisper_default'):
from .bw_tasks import bw_play_hold_music
from .bw_tasks import bw_call_agents
bw_play_hold_music.delay(lead_id, call_id, routing_config)
bw_call_agents.delay(lead_id, storage.routing)
elif event_type == 'timeout':
r = voicemail_redirect(lead_id, r)
elif status == 'done' and tag == 'voicemail_prompt':
from .bw_tasks import bw_play_voicemail_beep
bw_play_voicemail_beep.delay(lead_id)
elif status == 'done' and tag == 'voicemail_beep':
from .bw_tasks import bw_start_recording
bw_start_recording.delay(lead_id, call_id)
elif event_type == 'hangup' and call_id == storage.call_sid:
# Calculate the total call duration and response time
start_time = datetime.strptime(
storage.start_time, '%Y-%m-%dT%H:%M:%SZ'
)
duration, response_time_seconds = None, None
if storage.connect_time:
connect_time = datetime.strptime(
storage.connect_time, '%Y-%m-%dT%H:%M:%SZ'
)
response_time_seconds = (connect_time - start_time).total_seconds()
hangup_time = datetime.strptime(
args.get('time'), '%Y-%m-%dT%H:%M:%SZ'
)
duration = (hangup_time - start_time).total_seconds()
lead_mark_finished(
lead_id,
response_time_seconds=response_time_seconds,
duration=duration,
)
from .bw_tasks import bw_upload_recording
bw_upload_recording.delay(call_id)
elif event_type == 'hangup' and call_id != storage.call_sid:
# Agent hangs up or didn't answer
pass
return ''
@bw_inbound.route(
'/api/bw/inbound/conference/<int:lead_id>',
methods=['GET', 'POST']
)
@csrf.exempt
def lead_inbound_conference(lead_id):
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
args = request.json
print(args)
storage = CallStorage(redis_db, lead_id)
log.debug('{}: {}'.format(lead_id, storage.state))
if (args.get('eventType') == 'conference-member' and
args.get('callId') == storage.call_sid):
storage.bw_conf_member_id = args.get('memberId')
return ''
def lead_mark_finished(lead_id, response_time_seconds=None, duration=None):
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
storage = CallStorage(redis_db, lead_id)
routing_config = storage.routing_config
new_state = None
with storage.lock():
if storage.state in ['NEW', 'LEAD_ONHOLD']:
new_state = State.MISSED
if storage.state in ['ONGOING']:
new_state = State.CAPTURED
if new_state:
storage.state = new_state
lead = Lead.query.options(load_only("id", "status")).filter(
Lead.id == lead_id
).first()
if new_state:
from .bw_tasks import bw_hangup_calls
call_ids = [call_id for _, call_id in storage.current_agent_calls]
bw_hangup_calls.delay(call_ids)
if new_state == State.MISSED:
lead.status = 'missed'
if routing_config.get('configSMSSetup') and routing_config.get('MissedCallAutoReply'):
from buyercall.blueprints.sms.views import send_text_message
text = routing_config.get('MissedCallAutoReplyText') + ' Reply STOP to unsubscribe.'
media_url = ''
send_text_message(lead.inbound_id, lead.phonenumber, text, media_url)
else:
log.info('The SMS Configuration is turned off for this phone number')
elif new_state == State.CAPTURED:
lead.status = 'completed'
lead.response_time_seconds = response_time_seconds
lead.duration = duration
db.session.commit()
webhooker.trigger_generic_webhook('operational_end_call', lead.id)
send_notifications(lead)
def call_agents(lead_id, r, routing):
""" Add the BXML instructions to call the agents to the response.
:param lead_id: The database id of the lead calling us.
:param r: The BXML Response being built.
:param routing: The selected inbound routing dict (default or numbered).
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
storage = CallStorage(redis_db, lead_id)
agents = get_routing_agents(routing)
if not agents:
r = voicemail_redirect(storage, r)
return to_response(r)
inbound = Lead.query.options(
load_only('my_phone')
).filter(Lead.id == lead_id).scalar()
storage.routing = routing
storage.state = State.LEAD_ONHOLD
call_order = routing.get('callOrder', 'sequence')
if call_order == 'shuffle':
import random
log.debug('Shuffling agent list...')
random.shuffle(agents)
call_order = 'sequence'
storage.call_order = call_order
if call_order == 'sequence':
for a in agents:
number, extension = get_agent_number(a, routing)
r.transfer(
transfer_to=number,
request_url=url_for(
'bw_inbound.lead_call_transfer',
lead_id=lead_id,
agent_id=a.id,
_external=True,
_scheme='https'
))
if call_order == 'simultaneous':
with r.transfer(
from_=inbound,
to=get_agent_number(a, routing),
request_url=url_for(
'bw_inbound.lead_call_transfer',
lead_id=lead_id,
agent_id=a.id,
_external=True,
_scheme='https'
)
) as t:
for a in agents:
number, extension = get_agent_number(a, routing)
t.phone_number(number)
return to_response(r)
@bw_inbound.route(
'/api/bw/inbound/lead_digits_choice/<int:lead_id>',
methods=['GET']
)
@csrf.exempt
def lead_digits_choice(lead_id):
""" Gather the digits from the lead.
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
storage = CallStorage(redis_db, lead_id)
log.debug('{}: {}'.format(lead_id, storage.state))
routing_config = storage.routing_config
r = bxml_digits_choice(lead_id, routing_config)
return to_response(r)
@bw_inbound.route(
'/api/bw/inbound/lead_digits_choice_repeat/<int:lead_id>',
methods=['GET']
)
@csrf.exempt
def lead_digits_choice_repeat(lead_id):
""" Gather the digits from the lead.
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
storage = CallStorage(redis_db, lead_id)
log.debug('{}: {}'.format(lead_id, storage.state))
routing_config = storage.routing_config
no_digit_whisper_message = routing_config.get('noDigitWhisperMessage', '')
r = bxml_digits_choice(lead_id, routing_config, no_digit_whisper_message)
return to_response(r)
@bw_inbound.route(
'/api/bw/inbound/lead_digit_result/<int:lead_id>',
methods=['GET']
)
@csrf.exempt
def lead_digit_result(lead_id):
""" Route the lead according to the digit pressed.
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
args = request.args
print(args)
event_type = args.get('eventType')
storage = CallStorage(redis_db, lead_id)
log.debug('{}: {}'.format(lead_id, storage.state))
routing_config = storage.routing_config
language = routing_config.get('language', 'en')
gender, locale, voice = select_voice(language)
r = bandwidth.Response()
if event_type == 'gather':
digit = args['digits']
routings = [
x for x in routing_config['digitRoutings']
if x['dialDigit'] == digit
]
if not routings:
r.say(MSG_INVALID_DIGIT, gender, locale, voice)
r.redirect(url_for(
'bw_inbound.lead_digits_choice',
lead_id=lead_id,
_external=True,
_scheme='https'
))
return to_response(r)
routing = routings[0]
agents = get_routing_agents(routing)
if not agents:
r = voicemail_redirect(storage, r)
return to_response(r)
storage.routing = routing
storage.state = State.LEAD_ONHOLD
# Play hold music while calling agent
r.play_audio(hold_music_url(routing_config))
from .bw_tasks import bw_call_agents
bw_call_agents.delay(lead_id, routing)
elif event_type == 'hangup':
lead_mark_finished(lead_id)
r.hangup()
return to_response(r)
@bw_inbound.route(
'/api/bw/inbound/lead_call_transfer/<int:lead_id>/<int:agent_id>',
methods=['GET']
)
@csrf.exempt
def lead_call_transfer(lead_id, agent_id):
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
args = request.args
print(args)
event_type = args.get('eventType', '')
storage = CallStorage(redis_db, lead_id)
language = storage.routing_config.get('language', 'en')
gender, locale, voice = select_voice(language)
r = bandwidth.Response()
if event_type == 'transferError':
r.say(MSG_CANNOT_CONNECT, gender, locale, voice)
r.hangup()
return to_response(r)
@bw_inbound.route(
'/api/bw/inbound/lead_voicemail/<int:lead_id>',
methods=['GET']
)
@csrf.exempt
def lead_voicemail(lead_id):
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
args = request.args
print(args)
storage = CallStorage(redis_db, lead_id)
log.debug('{}: {}'.format(lead_id, storage.state))
routing_config = storage.routing_config
language = routing_config.get('language', 'en')
gender, locale, voice = select_voice(language)
r = bandwidth.Response()
if routing_config.get('voicemail', False):
r.say(routing_config['voicemailMessage'], gender, locale, voice)
r.record()
r.hangup()
return to_response(r)
@bw_inbound.route(
'/api/bw/inbound/lead_hangup/<int:lead_id>',
methods=['GET']
)
@csrf.exempt
def lead_hangup(lead_id):
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
r = bandwidth.Response()
storage = CallStorage(redis_db, lead_id)
language = storage.routing_config.get('language', 'en')
gender, locale, voice = select_voice(language)
r.say(MSG_NOT_AVAILABLE, gender, locale, voice)
return to_response(r)
@bw_inbound.route(
'/api/bw/inbound/lead_voicemail_callback/<int:lead_id>',
methods=['POST']
)
@csrf.exempt
def lead_voicemail_callback(lead_id):
lead = Lead.query.filter(Lead.id == lead_id).first()
lead.recording_url = request.form['RecordingUrl']
db.session.commit()
r = VoiceResponse()
return to_response(r)
@bw_inbound.route(
'/api/bw/inbound/call_result',
methods=['POST']
)
@csrf.exempt
def call_result_callback():
""" Called after the lead hangs up. """
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
call_sid = request.form.get('CallSid', '')
key = 'LEAD_{}'.format(call_sid)
lead_id = redis_db.get(key)
storage = CallStorage(redis_db, lead_id)
log.debug('{}: {}'.format(lead_id, storage.state))
log.debug('Call sid is {}, lead id is {}'.format(call_sid, lead_id))
if not lead_id:
log.warning('Cannot determine lead ID')
return ''
with storage.lock():
if storage.state in [State.ONGOING, State.CAPTURED]:
storage.state = State.CAPTURED
elif storage.state != State.CALL_ME_BACK:
storage.state = State.MISSED
from .bw_tasks import bw_cancel_agent_calls
bw_cancel_agent_calls.delay(lead_id)
lead = Lead.query.filter(Lead.id == lead_id).first()
lead.endtime = datetime.utcnow()
lead.duration = (lead.endtime - lead.starttime).total_seconds()
if storage.state == State.CAPTURED:
lead.status = 'completed'
elif storage.state == State.MISSED:
lead.status = 'missed'
schedule_callback(storage)
db.session.commit()
send_notifications(lead)
return ''
@bw_inbound.route( # noqa
'/api/bw/inbound/agent/<int:agent_id>/lead/<int:lead_id>',
methods=['POST', 'GET']
)
@csrf.exempt
def agent_status_callback(agent_id, lead_id):
""" Callback method for Bandwidth agent call status events.
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
args = request.json
print(args)
lead = Lead.query.filter(Lead.id == lead_id).first()
partner_account = PartnershipAccount.query \
.filter(PartnershipAccount.id == lead.partnership_account_id).first()
partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()
storage = CallStorage(redis_db, lead_id)
log.debug('{}: {}'.format(lead_id, storage.state))
client = bw_client(partner.id)
call_id = args.get('callId')
call = client.calls[call_id]
tag = args.get('tag')
event_type = args.get('eventType')
state = args.get('state')
routing_config = storage.routing_config
routing = storage.routing
language = routing_config.get('language', 'en')
gender, locale, voice = select_voice(language)
do_gather = False
if event_type == 'answer':
storage.connect_time = args.get('time')
agent_text = get_agent_text(
routing_config.get('hiddenInformation', '')
)
if not agent_text:
do_gather = True
else:
call.audio(
sentence=agent_text, tag='hidden_info',
gender=gender, locale=locale, voice=voice
)
if tag == 'end' and state == 'PLAYBACK_STOP':
call.hangup()
do_bridge = False
if do_gather or (tag == 'hidden_info' and state == 'PLAYBACK_STOP'):
# TODO: Digit prompt
# If digit prompt is disabled for the phone number, just continue
if not routing_config.get('digitPrompt', False):
do_bridge = True
else:
call.gather({
"max_digits": 1,
"prompt": {
"sentence": MSG_DIGIT_PROMPT,
"gender": gender,
"locale": locale
},
"tag": 'gather',
})
if do_bridge or (
tag == 'gather' and args.get('reason') == 'max-digits'
):
with storage.lock():
if storage.agent_id is None:
storage.agent_id = agent_id
storage.agent_call_id = call_id
if int(storage.agent_id) != agent_id:
call.audio(
sentence=MSG_TOO_SLOW, tag='end',
gender=gender, locale=locale, voice=voice
)
return ''
from .bw_tasks import (
bw_cancel_agent_calls,
bw_agent_bridge_lead,
bw_stop_hold_music,
)
bw_cancel_agent_calls.delay(lead_id, other_than=agent_id)
bw_agent_bridge_lead.delay(lead_id)
bw_stop_hold_music.delay(lead_id)
storage.state = State.ONGOING
lead.agent_id = agent_id
lead.status = 'in-progress'
db.session.commit()
if tag == 'gather' and args.get('reason') == 'inter-digit-timeout':
call.hangup()
if event_type == 'hangup':
storage.remove_agent_call(agent_id, call_id)
if (
args.get('cause') == 'NORMAL_CLEARING' and
storage.state == State.ONGOING and
storage.agent_id == str(agent_id)
):
client.calls[storage.call_sid].hangup()
lead = Lead.query.filter(Lead.id == lead_id).first()
lead.status = 'completed'
db.session.commit()
# Update minutes consumed
call_details = call.details()
call_duration = call_details.get('chargeableDuration')
if call_duration:
# TODO: Optimize joined load
lead = Lead.query.options(
load_only('partnership_account_id')
).filter(Lead.id == lead_id).first()
subscription = lead.partnership_account.subscription
subscription.update_usage(lead.partnership_account_id, seconds=call_duration)
else: # Agent busy or call rejected
from .bw_tasks import (
bw_call_agents,
bw_try_call_agent_sequence,
bw_redirect_to_voicemail,
)
storage.remove_agent_call(agent_id, call_id)
# Are there still agents left on the call?
agents_left = len(storage.current_agent_calls)
log.info('{} agents still left on the call.'.format(agents_left))
lead_on_call = storage.state not in [State.MISSED, State.CAPTURED]
if (
agents_left == 0 and
routing.get('callOrder', '') == 'sequence' and
len(storage.agents_to_call) > 0 and
lead_on_call
):
log.info('Calling next agent...')
routing = storage.routing
bw_try_call_agent_sequence.delay(routing, lead_id)
return ''
# Do we need to retry the call?
cnt = storage.inc_retry_cnt()
if cnt <= int(routing['retryRouting']) and lead_on_call:
bw_call_agents.delay(lead_id, routing)
return ''
if lead_on_call:
bw_redirect_to_voicemail.delay(lead_id)
return ''
def bxml_digits_choice(lead_id, routing_config, message=None):
""" :returns VoiceResponse
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
whisper_message = message or routing_config.get('whisperMessage')
storage = CallStorage(redis_db, lead_id)
language = storage.routing_config.get('language', 'en')
gender, locale, voice = select_voice(language)
r = bandwidth.Response()
with r.gather(
request_url=url_for(
'bw_inbound.lead_digit_result',
lead_id=lead_id,
_external=True,
_scheme='https'
),
max_digits=1
) as g:
if whisper_message:
g.say(whisper_message, gender, locale, voice)
# If nothing pressed, prompt again.
r.redirect(url_for(
'bw_inbound.lead_digits_choice_repeat',
lead_id=lead_id,
_external=True,
_scheme='https'
))
return r
def voicemail_redirect(lead_id, r):
""" Add the BXML instructions to redirect the calling user to voicemail.
:param lead_id: The database id of the lead calling us.
:param r: The BXML Response being built.
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
storage = CallStorage(redis_db, lead_id)
routing_config = storage.routing_config
try:
# If voicemail enabled, redirect there...
voicemail = routing_config.get('voicemail', False)
if voicemail:
r.redirect(url_for(
'bw_inbound.lead_voicemail',
lead_id=storage.lead_id,
_external=True,
_scheme='https'
))
else:
# ...otherwise, hang up
r.redirect(url_for(
'bw_inbound.lead_hangup',
lead_id=storage.lead_id,
_external=True,
_scheme='https'
))
except Exception as e:
log.error(traceback.format_exc())
return r
def send_notifications(lead): # noqa
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
from buyercall.blueprints.widgets.models import split_emails
storage = CallStorage(redis_db, lead.id)
if storage.manual_call:
log.debug('Notification email suppressed.')
return
inbound = Phone.query.filter(
Phone.id == lead.inbound_id,
Phone.partnership_account_id == lead.partnership_account_id
).first()
if not inbound:
return
notifications = inbound.notifications
routings = inbound.routing_config
if notifications.get('notifyLeads', 'none') == 'none':
return
# Send eventual notifications
emails = []
adf_emails = []
user_email = None
admin = User.query.filter(
User.role == 'admin',
User.partnership_account_id == lead.partnership_account_id,
User.is_deactivated.is_(False)
).first()
if admin:
user_email = admin.email
agent_ids = [
a['id'] for a in inbound.routing_config['defaultRouting']['agents']
]
agent_emails = [
a.email
for a in Agent.query.options(load_only('email')).filter(
Agent.partnership_account_id == lead.partnership_account_id,
Agent.id.in_(agent_ids)
).all()
]
if (notifications.get('notifyLeads', 'none') == 'missed'
and lead.status == 'missed'):
emails.extend(split_emails(notifications.get('notifyMissedCustom', '')))
if notifications.get('notifyMissedAgents'):
emails.extend(agent_emails)
if notifications.get('notifyMissedMe') and user_email:
emails.append(user_email)
if notifications.get('notifyLeads', 'none') == 'all':
emails.extend(split_emails(notifications.get('notifyAllCustom', '')))
if notifications.get('notifyAllAgents'):
emails.extend(agent_emails)
if notifications.get('notifyAllMe') and user_email:
emails.append(user_email)
if not emails:
log.warning('Nowhere to send {} lead notification!'.format(
lead.status
))
return
partner_account = PartnershipAccount.query.filter(PartnershipAccount.id == lead.partnership_account_id).first()
partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()
ctx = vars(lead)
eastern = pytz.timezone('US/Eastern')
ctx['created_on'] = lead.created_on.astimezone(eastern).strftime('%c')
ctx['updated_on'] = lead.updated_on.astimezone(eastern).strftime('%c')
if lead.firstname:
ctx['caller_name_label'] = 'Caller Name:'
ctx['caller_name'] = ''.join((lead.firstname, ' ', lead.lastname))
if lead.caller_id:
ctx['caller_id_label'] = 'Caller Id:'
ctx['caller_id'] = lead.caller_id
if lead.recording_url:
ctx['vm_div_style'] = 'block'
else:
ctx['vm_div_style'] = 'none'
if lead.source:
ctx['call_source'] = lead.source
if lead.inbound:
ctx['reference'] = lead.inbound.routing_config.get(
'hiddenInformation', ''
)
ctx['partner_logo'] = partner.logo
ctx['company'] = partner.name
try:
app.logger.debug('The email list are {}'.format(emails))
if routings['notifyAdf'] is not None and routings['notifyAdf'] is 'True' or routings['notifyAdf']:
if routings['notifyAdfCustom'] is not None and routings['notifyAdfCustom'] is not '':
multi_adf_emails = split_emails(routings['notifyAdfCustom'])
if multi_adf_emails is not None:
adf_emails.extend(multi_adf_emails)
else:
adf_emails.append(routings['notifyAdfCustom'])
ctx['lead_comments'] = 'Call status: {}'.format(lead.status)
ctx['receiver'] = ''
agent_name = lead.agent_name
friendly_name = lead.source
phonenumber = lead.inbound.phonenumber
dealership = partner_account.name
if dealership:
ctx['vendor_name'] = dealership
else:
ctx['vendor_name'] = ''
call_lead_id = lead.id
if call_lead_id:
ctx['call_lead_id'] = call_lead_id
else:
ctx['call_lead_id'] = ''
if agent_name and len(agent_name) > 2:
ctx['lead_comments'] = '{}, Agent name: {}'.format(ctx['lead_comments'], agent_name)
if friendly_name:
ctx['lead_comments'] = '{}, Call source: {}'.format(ctx['lead_comments'], friendly_name)
ctx['receiver'] = friendly_name
if phonenumber:
ctx['lead_comments'] = '{}, Phonenumber: '.format(ctx['lead_comments'], phonenumber)
ctx['campaign_name'] = ''
ctx['campaign_exists'] = False
from buyercall.blueprints.contacts.models import Contact, Campaigns
contact = Contact.query.filter(
Contact.id == lead.contact_id,
Contact.partnership_account_id == lead.partnership_account_id)\
.first()
if contact and contact.campaign_id:
campaign = Campaigns.query.filter(
Campaigns.id == contact.campaign_id)\
.first()
if campaign:
ctx['campaign_exists'] = True
ctx['campaign_name'] = campaign.display_name
if adf_emails and len(adf_emails) > 0:
# Render text template for adf email
adf_lead_email_template = _try_renderer_template('mail/adf_lead', ext='txt', **ctx)
send_ses_email(recipients=adf_emails,
p_id=partner.id,
subject='{} - ADF lead notification'.format(partner.name),
text=adf_lead_email_template
)
if lead.status == 'missed' and storage.state == State.MISSED:
# Render html template for email
missed_lead_email_template = _try_renderer_template('mail/missed_lead', ext='html', **ctx)
send_ses_email(recipients=emails,
p_id=partner.id,
subject='{} - Missed lead notification'.format(partner.name),
html=missed_lead_email_template
)
elif lead.status == 'completed' and storage.state == State.CAPTURED:
# Render html template for email
completed_lead_email_template = _try_renderer_template('mail/captured_lead', ext='html', **ctx)
send_ses_email(recipients=emails,
p_id=partner.id,
subject='{} - Answered lead notification'.format(partner.name),
html=completed_lead_email_template
)
else:
# Render html template for email
captured_lead_email_template = _try_renderer_template('mail/captured_lead', ext='html', **ctx)
send_ses_email(recipients=emails,
p_id=partner.id,
subject='{} - New lead notification'.format(partner.name),
html=captured_lead_email_template
)
except Exception as e:
log.error(traceback.format_exc())
# DEBUG
def to_response(r):
""" Transform a Twiml Response to a Flask response object. """
import xml.dom.minidom
xml = xml.dom.minidom.parseString(str(r))
result = xml.toprettyxml()
print(result)
return result