File: //home/arjun/projects/buyercall_forms/buyercall/buyercall/blueprints/api2/doc/endpoints/agents.py
from datetime import datetime
import logging
from flask import request, jsonify
from flask_restx import Resource
from sqlalchemy import and_
from buyercall.lib.util_rest import rest_partnership, requires_auth, rest_is_partnership
from buyercall.blueprints.api2.doc import serializers
from buyercall.blueprints.api2.restplus import api
from buyercall.blueprints.billing.models.subscription import Subscription
from buyercall.blueprints.agents.models import Agent, AgentSchedule
from buyercall.blueprints.partnership.models import Partnership, PartnershipAccount
from buyercall.extensions import db
log = logging.getLogger(__name__)
ns = api.namespace('Agents', description='Operations related to agents.', path='/accounts')
def getDayNumber(scheduled_day):
switcher = {
'Sunday': 0,
'Monday': 1,
'Tuesday': 2,
'Wednesday': 3,
'Thursday': 4,
'Friday': 5,
'Saturday': 6
}
return switcher.get(scheduled_day)
def getboolean(param):
if param.lower() == 'true':
return True
else:
return False
def validate_time(time_text):
try:
retrieved = datetime.strptime(time_text, '%H:%M %p').strftime('%H:%M %p')
return True
except Exception as e:
return False
def format_time(time_text):
try:
retrieved = datetime.strptime(time_text, '%H:%M %p').strftime('%H:%M %p')
return retrieved
except Exception as e:
log.error('Error formatting time. Error: {}'.format(e))
return ''
def validate_time_compare(time_text_start, time_text_stop):
try:
start_hour = int(datetime.strptime(time_text_start, '%H:%M %p').strftime('%H'))
start_min = int(datetime.strptime(time_text_start, '%H:%M %p').strftime('%M'))
stop_hour = int(datetime.strptime(time_text_stop, '%H:%M %p').strftime('%H'))
stop_min = int(datetime.strptime(time_text_stop, '%H:%M %p').strftime('%M'))
if start_hour < stop_hour:
return True
elif start_hour == stop_hour:
if start_min < stop_min:
return True
else:
return False
else:
return False
except Exception as e:
return False
@ns.route('/<int:paid>/agents')
@api.doc(responses={200: 'OK',
400: 'Error performing operation.',
401: 'Unauthorized request.'},
params={'paid': 'The partner account Id'})
class ApiAgentCollection(Resource):
@requires_auth
def get(self, paid):
"""
Retrieves agents of a partnership account.
<p>The Agents GET endpoint should be used to return information an a specific partner account agent.
</p>
<br />
<p>
You require a partner authentication token and a partner account id to make a successful request.
A response will be returned, similar to the example below, based
on a successful request:
<br />
<br />
</p>
<pre class="code-background" style="color: white">
{
"agents": [
{
"all_hours": false,
"available_now": false,
"created_on": "2019-03-14 14:40:51",
"deactivated_on": "",
"department": "Sales",
"description": "",
"email": "harry@conway.co",
"firstname": "Harry",
"id": 9,
"is_deactivated": false,
"lastname": "Conway",
"mobile_number": "",
"phone_number": "7732909650",
"timezone": "US/Pacific",
"title": "Owner",
"updated_on": "2019-03-14 14:40:51"
},
{
"all_hours": true,
"available_now": true,
"created_on": "2019-01-16 21:53:08",
"deactivated_on": "",
"department": "none",
"description": "",
"email": "bullgull@buyercall.com",
"firstname": "Sue",
"id": 2,
"is_deactivated": false,
"lastname": "Matthews",
"mobile_number": "",
"phone_number": "7732909650",
"timezone": "",
"title": "",
"updated_on": "2019-03-18 21:22:49"
}
]
}
</pre>
"""
if rest_is_partnership and rest_partnership is not None:
agent_list = list()
partnership_account = PartnershipAccount\
.query\
.filter(and_(PartnershipAccount.id == paid, PartnershipAccount.partnership_id == rest_partnership.id))\
.first()
if partnership_account is not None:
agents = db.session.query(Agent) \
.filter(and_(Agent.partnership_account_id == paid, Agent.is_group == False)) \
.distinct() \
.all()
if agents is not None:
for agent in agents:
add_agent = {
'id': agent.id,
'created_on': agent.created_datetime,
'updated_on': agent.updated_datetime,
'title': agent.title,
'firstname': agent.firstname,
'lastname': agent.lastname,
'email': agent.email,
'phone_number': agent.phonenumber,
'mobile_number': agent.mobile,
'department': agent.department,
'description': agent.description,
'timezone': agent.timezone,
'all_hours': agent.all_hours,
'available_now': agent.available_now,
'is_deactivated': agent.is_deactivated,
'deactivated_on': agent.deactivated_on_datetime
}
agent_list.append(add_agent)
return jsonify(
agents=agent_list
)
else:
return api.abort(401)
else:
return api.abort(401)
"""
# DISABLE ENDPOINT
@api.response(200, 'Agent successfully added.')
@api.expect(serializers.agent_add, validate=True)
@requires_auth
def post(self, paid):
"""
"""
Adds a partnership account agent.
<p>
The Agents POST endpoint should be used to create new partner account agents. This means you will need a
partner account id before being able to create an agent. It's recommended that this endpoint be one of
the first endpoints utilized after the Accounts POST request. Many of the other BuyerCall Partner API
endpoints will depend on an agent existing for a partner account.
</p>
<br />
<p>
Please note when "ALL HOURS" is true, agents will be set to active 24/7. Otherwise, agents will be added by
default as active between 8AM and 5PM during week days. The schedule for the agent will be automatically be
created with this POST request. You can utilize the Agent Schedule PUT request endpoint to alter the schedule
for an agent.
</p>
<br />
<p>
You require a partner authentication token and a partner account id to make a successful request.
A response will be returned, similar to the example below, based on a successful request:
<br />
<br />
</p>
<pre class="code-background" style="color: white">
{
"agent_id": 12,
"partnership_account_id": 6
}
</pre>
"""
"""
if rest_is_partnership and rest_partnership is not None:
received = request.json
partnership = Partnership.query.filter(Partnership.id == rest_partnership.id).first()
partnership_account = PartnershipAccount\
.query\
.filter(and_(PartnershipAccount.id == paid, PartnershipAccount.partnership_id == rest_partnership.id))\
.first()
if received is not None and partnership_account is not None:
total_agents = Agent.query.filter(partnership_account.id == Agent.partnership_account_id).count()
# TODO: Review me - Should it check the partnership or partnership account?
subscription = (db.session.query(Subscription))\
.filter(Subscription.id == partnership.subscription_id)\
.first()
if total_agents is not None and subscription is not None:
if total_agents >= subscription.agent_limit:
api.abort(code=400, message="Error creating agent. Agent limit exceeded.")
else:
result_all_hours = True
result_firstname = ''
result_lastname = ''
result_title = ''
result_email = ''
result_mobile = ''
result_department = ''
result_description = ''
result_timezone = 'US/Central'
if 'all_hours' in received:
result_all_hours = received['all_hours']
if 'firstname' in received:
result_firstname = received['firstname']
if 'lastname' in received:
result_lastname = received['lastname']
if 'title' in received:
result_title = received['title']
if 'email' in received:
result_email = received['email']
if 'department' in received:
result_department = received['department']
if 'description' in received:
result_description = received['description']
if 'timezone' in received:
result_timezone = received['timezone']
if 'mobile_number' in received:
result_mobile = received['mobile_number']
params = {
'user_id': None,
'firstname': result_firstname,
'lastname': result_lastname,
'title': result_title,
'email': result_email,
'phonenumber': received['phone_number'],
'mobile': result_mobile,
'extension': None,
'department': result_department,
'description': result_description,
'partnership_account_id': partnership_account.id,
'all_hours': result_all_hours,
'timezone': result_timezone
}
agent_added = Agent.create(params)
if agent_added > 0:
day1param = {
'day': 0,
'available_from': '08:00 AM',
'available_to': '17:00 PM',
'is_active': False,
'partnership_account_id': partnership_account.id,
'agent_id': int(agent_added)
}
day2param = {
'day': 1,
'available_from': '08:00 AM',
'available_to': '17:00 PM',
'is_active': True,
'partnership_account_id': partnership_account.id,
'agent_id': int(agent_added)
}
day3param = {
'day': 2,
'available_from': '08:00 AM',
'available_to': '17:00 PM',
'is_active': True,
'partnership_account_id': partnership_account.id,
'agent_id': int(agent_added)
}
day4param = {
'day': 3,
'available_from': '08:00 AM',
'available_to': '17:00 PM',
'is_active': True,
'partnership_account_id': partnership_account.id,
'agent_id': int(agent_added)
}
day5param = {
'day': 4,
'available_from': '08:00 AM',
'available_to': '17:00 PM',
'is_active': True,
'partnership_account_id': partnership_account.id,
'agent_id': int(agent_added)
}
day6param = {
'day': 5,
'available_from': '08:00 AM',
'available_to': '17:00 PM',
'is_active': True,
'partnership_account_id': partnership_account.id,
'agent_id': int(agent_added)
}
day7param = {
'day': 6,
'available_from': '08:00 AM',
'available_to': '17:00 PM',
'is_active': False,
'partnership_account_id': partnership_account.id,
'agent_id': int(agent_added)
}
day_result = AgentSchedule.create(day1param,
day2param,
day3param,
day4param,
day5param,
day6param,
day7param)
if day_result:
return jsonify(
partnership_account_id=partnership_account.id,
agent_id=agent_added
)
else:
api.abort(code=400, message="Error creating agent schedule.")
else:
api.abort(code=400, message="Error creating agent.")
else:
api.abort(code=400, message="Error creating agent. No active subscription.")
else:
api.abort(code=404, message="Error creating agent. Partnership account not found.")
else:
api.abort(code=401)
"""
"""
# DISABLE ENDPOINT
@ns.route('/<int:paid>/agents/<int:aid>')
@api.doc(responses={200: 'OK',
400: 'Error performing operation.',
401: 'Unauthorized request.',
404: 'Agent not found.'},
params={'aid': 'The agent Id',
'paid': 'The partner account Id'})
"""
class ApiAgent(Resource):
@api.response(204, 'Agent successfully updated.')
@api.expect(serializers.agent_update, validate=True)
@requires_auth
def put(self, paid, aid):
"""
Update an agent.
<p>
The Accounts PUT endpoint should be used to update an existing partner account agent. For example,
you should use this endpoint to update the agent's phone number when provisioning
a mobile phone number for them using the Mobile phone number POST request endpoint.
</p>
<br />
<p>
You will require a partner authentication token, a partner account id as well as a partner account
agent id to make a successful request
</p>
"""
if rest_is_partnership and rest_partnership is not None:
received = request.json
partnership_account = PartnershipAccount\
.query\
.filter(and_(PartnershipAccount.id == paid,
PartnershipAccount.partnership_id == rest_partnership.id))\
.first()
if received is not None and partnership_account is not None:
result_all_hours = None
result_firstname = None
result_lastname = None
result_title = None
result_email = None
result_phonenumber = None
result_mobilenumber = None
result_department = None
result_description = None
result_timezone = None
if 'title' in received:
result_title = received['title']
if 'firstname' in received:
result_firstname = received['firstname']
if 'lastname' in received:
result_lastname = received['lastname']
if 'email' in received:
result_email = received['email']
if 'phone_number' in received:
result_phonenumber = received['phone_number']
if 'mobile_number' in received:
result_mobilenumber = received['mobile_number']
if 'department' in received:
result_department = received['department']
if 'description' in received:
result_description = received['description']
if 'timezone' in received:
result_timezone = received['timezone']
if 'all_hours' in received:
result_all_hours = received['all_hours']
final_result = Agent.api_update_v2(
partnership_account.id,
aid,
result_title,
result_firstname,
result_lastname,
result_phonenumber,
result_mobilenumber,
result_email,
result_department,
result_description,
result_timezone,
result_all_hours)
if final_result:
return final_result, 204
else:
api.abort(code=400, message="Error updating agent.")
else:
return api.abort(code=404)
else:
return api.abort(code=401)
@api.response(204, 'Agent successfully deactivated.')
@requires_auth
def delete(self, paid, aid):
"""
Deactivate a partnership account agent.
<p>
The Agents DELETE endpoint should be used to deactivate a partner account agent. Please note that the agent
does not get hard deleted, but rather deactivated, with their schedule being set to unavailable. A deactivated
agent can't perform any task and only exist for legacy purposes. Though deactivated, the agent will still be
returned in the Agents GET request with a is_deactivated set to True.
</p>
</br >
<p>
You will require a partner authentication token, a partner account id as well as a partner account agent
id to make a successful request.
</p>
"""
if rest_is_partnership and rest_partnership is not None:
partnership_account = PartnershipAccount\
.query\
.filter(and_(PartnershipAccount.id == paid,
PartnershipAccount.partnership_id == rest_partnership.id))\
.first()
if partnership_account is not None:
agent = Agent.query\
.filter(and_(Agent.id == aid, Agent.partnership_account_id == partnership_account.id))\
.first()
if agent is not None:
return agent.deactivate(aid, partnership_account.id), 204
else:
return api.abort(code=404)
else:
return api.abort(code=401)
else:
return api.abort(code=401)
"""
# DISABLE ENDPOINT
@ns.route('/<int:paid>/agents/<int:aid>/schedule')
@api.doc(responses={200: 'OK',
401: 'Unauthorized request.',
404: 'Agent not found.'},
params={'aid': 'The agent Id',
'paid': 'The partner account Id'})
"""
class ApiAgentSchedule(Resource):
@requires_auth
def get(self, paid, aid):
"""
Retrieves agent schedule of an agent.
<p>
The Agents Schedule GET endpoint should be used to return the schedule for a partner account agent. The schedule
dictates an agent's availability for a specific day of the week.
</p>
<br />
<p>
You require a partner authentication token and a partner account id to make a successful request.
A response will be returned, similar to the example below, based on a successful request:
<br />
<br />
</p>
<pre class="code-background" style="color: white">
{
"schedule": [
{
"available_from": "05:00 AM",
"available_to": "15:00 PM",
"day": "Sunday",
"id": 2,
"is_active": true
},
{
"available_from": "08:00 AM",
"available_to": "17:00 PM",
"day": "Monday",
"id": 3,
"is_active": false
},
{
"available_from": "08:00 AM",
"available_to": "17:00 PM",
"day": "Tuesday",
"id": 4,
"is_active": true
},
{
"available_from": "08:00 AM",
"available_to": "17:00 PM",
"day": "Wednesday",
"id": 5,
"is_active": true
},
{
"available_from": "08:00 AM",
"available_to": "17:00 PM",
"day": "Thursday",
"id": 6,
"is_active": true
},
{
"available_from": "08:00 AM",
"available_to": "17:00 PM",
"day": "Friday",
"id": 7,
"is_active": true
},
{
"available_from": "08:00 AM",
"available_to": "17:00 PM",
"day": "Saturday",
"id": 8,
"is_active": false
}
]
}
</pre>
"""
if rest_is_partnership and rest_partnership is not None:
partnership_account = PartnershipAccount\
.query\
.filter(and_(PartnershipAccount.id == paid,
PartnershipAccount.partnership_id == rest_partnership.id))\
.first()
if partnership_account is not None:
agent = (db.session.query(Agent))\
.filter(Agent.partnership_account_id == partnership_account.id)\
.filter(Agent.id == aid)\
.first()
schedules = list()
if agent is not None:
for sched in agent.schedules:
day_name = ''
if sched.day == 0:
day_name = 'Sunday'
elif sched.day == 1:
day_name = 'Monday'
elif sched.day == 2:
day_name = 'Tuesday'
elif sched.day == 3:
day_name = 'Wednesday'
elif sched.day == 4:
day_name = 'Thursday'
elif sched.day == 5:
day_name = 'Friday'
elif sched.day == 6:
day_name = 'Saturday'
day = {
'id': sched.id,
'day': day_name,
'available_from': sched.available_from,
'available_to': sched.available_to,
'is_active': sched.is_active
}
schedules.append(day)
return jsonify(
schedule=schedules
)
else:
api.abort(code=404, message="Agent not found.")
else:
return api.abort(code=404, message="Partnership account not found.")
else:
return api.abort(code=401)
@api.response(204, 'Agent schedule successfully updated.')
@api.expect(serializers.agent_schedule_update, validate=True)
@requires_auth
def put(self, paid, aid):
"""
Update schedule of an agent.
<p>
The Agents Schedule PUT endpoint should be used to update the schedule for a partner account agent. The schedule
dictates an agent's availability for a specific day of the week. You will be able to update multiple agent
schedule entries simultaneously using this endpoint.
</p>
<br />
<p>
Please note that an agent's schedule gets created when an agent gets created with the Agents POST
request endpoint. There is no Agents Schedule POST request endpoint.
</p>
<br />
<p>
You will require a partner authentication token, a partner account id as well as a partner account
agent id to make a successful request.
</p>
"""
if rest_is_partnership and rest_partnership:
received = request.json
if received:
partnership_account = PartnershipAccount\
.query\
.filter(and_(PartnershipAccount.id == paid,
PartnershipAccount.partnership_id == rest_partnership.id))\
.first()
if partnership_account:
agent = (db.session.query(Agent))\
.filter(Agent.partnership_account_id == partnership_account.id)\
.filter(Agent.id == aid)\
.first()
if agent:
result_schedule = received['schedule']
schedule_count = len(result_schedule)
if schedule_count and schedule_count > 0:
result_schedule_day_list = list()
for result_schedule_day in result_schedule:
result_is_active = None
result_from = None
result_to = None
result_day_name = result_schedule_day['day']
result_day = getDayNumber(result_day_name)
agent_schedule = AgentSchedule.query \
.filter(and_(AgentSchedule.day == result_day, AgentSchedule.agent_id == agent.id)) \
.first()
if agent_schedule:
if 'is_active' in result_schedule_day:
result_is_active = result_schedule_day['is_active']
if 'available_from' in result_schedule_day and \
'available_to' in result_schedule_day:
result_from = result_schedule_day['available_from']
result_to = result_schedule_day['available_to']
if not validate_time(result_from):
return api.abort(code=400,
message="Error updating agent schedule. "
"Incorrect FROM time format.")
if not validate_time(result_to):
return api.abort(code=result_to,
message="Error updating agent schedule. "
"Incorrect TO time format.")
date_passed = validate_time_compare(result_from, result_to)
if not date_passed:
return api.abort(code=400,
message="Error updating agent schedule. "
"Start time falls after stop time.")
result_from = format_time(result_from)
result_to = format_time(result_to)
if result_from == '' or result_to == '':
return api.abort(code=400,
message="Error updating agent schedule. "
"Incorrect time format.")
if 'available_from' in result_schedule_day \
and 'available_to' not in result_schedule_day:
result_from = result_schedule_day['available_from']
if not validate_time(result_from):
return api.abort(code=400,
message="Error updating agent schedule. "
"Incorrect FROM time format.")
elif not validate_time_compare(result_from, agent_schedule.available_to):
return api.abort(code=400,
message="Error updating agent schedule. "
"Start time falls after stop time.")
result_from = format_time(result_from)
if result_from == '':
return api.abort(code=400,
message="Error updating agent schedule. "
"Incorrect time format.")
if 'available_to' in result_schedule_day \
and 'available_from' not in result_schedule_day:
result_to = result_schedule_day['available_to']
if not validate_time(result_to):
return api.abort(code=400,
message="Error updating agent schedule. "
"Incorrect TO time format.")
elif not validate_time_compare(agent_schedule.available_from, result_to):
return api.abort(code=400,
message="Error updating agent schedule. "
"Stop time falls before start time.")
result_to = format_time(result_to)
if result_to == '':
return api.abort(code=400,
message="Error updating agent schedule. "
"Incorrect time format.")
final_result = AgentSchedule.api_update(agent_schedule.id, result_from, result_to,
result_is_active, partnership_account.id)
else:
result_schedule_day_list.append(result_day_name)
if len(result_schedule_day_list) > 0:
return api.abort(
code=404,
message="Agent schedule day not found. Days: {}".format(
jsonify(result_schedule_day_list))
)
return True, 204
return api.abort(code=404, message="Agent not found.")
return api.abort(code=404, message="Partnership account not found.")
return api.abort(code=401)
return api.abort(code=401)