HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux spn-python 5.15.0-89-generic #99-Ubuntu SMP Mon Oct 30 20:42:41 UTC 2023 x86_64
User: arjun (1000)
PHP: 8.1.2-1ubuntu2.20
Disabled: NONE
Upload Files
File: //home/arjun/projects/buyercall/buyercall/blueprints/api2/doc/endpoints/appointments.py
import logging
import time
import uuid
from flask import (
    Blueprint,
    make_response,
    request,
    jsonify)
from datetime import datetime
from flask import Blueprint, jsonify, make_response
from buyercall.lib.util_rest import rest_partnership_account, rest_partnership, requires_auth, rest_is_partnership_account, rest_is_partnership
from flask_restx import Resource, reqparse
from buyercall.blueprints.api2.doc import serializers
from buyercall.blueprints.api2.restx import api
from buyercall.blueprints.partnership.models import Partnership, PartnershipAccount
from buyercall.blueprints.appointments.models import Appointment, AppointmentSlot
from buyercall.lib.util_datetime import week_day
from buyercall.blueprints.user.models import User
from sqlalchemy import func, or_, and_, extract

log = logging.getLogger(__name__)
ns = api.namespace('Appointments', description='Operations related to appointments.', path='/accounts')

get_existing_appointment_parser = reqparse.RequestParser()
get_existing_appointment_parser.add_argument('user_id', type=int, location='args', help='The appointment user id')
get_existing_appointment_parser.add_argument('contact_id', type=int, location='args', help='The appointment contact id')
get_existing_appointment_parser.add_argument('appointment_type', type=str, location='args', help='The appointment type')
get_existing_appointment_parser.add_argument('appointment_date', type=str, location='args', help='The appointment date')
get_existing_appointment_parser.add_argument('appointment_start_time', type=str, location='args',
                                             help='The appointment start time')

get_available_appointment_parser = reqparse.RequestParser()
get_available_appointment_parser.add_argument('user_id', type=int, location='args', help='The appointment user id')
get_available_appointment_parser.add_argument('appointment_type', type=str, location='args', help='The appointment type',
                                              default='general')
get_available_appointment_parser.add_argument('date', type=str, location='args', help='The preferred appointment date')


@ns.route('/<int:paid>/appointments')
@api.doc(responses={200: 'OK',
                    400: 'Error performing operation.',
                    401: 'Unauthorized request.'},
         params={'paid': 'The partnership account Id'})
class ApiAppointmentsCollection(Resource):
    @api.expect(serializers.appointment_add, validate=True)
    @api.response(200, 'Appointment successfully created.')
    @requires_auth
    def post(self, paid):
        """
        Creates an appointment for a partnership account or partnership account user.

        <p>
        The Appointment API POST endpoint should be used to create an appointment for either a partnership account
        using the partnership account's operating hours or for a partnership account user using the user's schedule.
        </p>
        <br />
        <p>
        You will require a partner authentication token and a partner account to make a successful request.
        Providing a user id is not mandatory and an appointment can be created without a user id. If you would like
        create an appointment for a specific user please make sure to provide a user id in the request body.
        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">
        {
          "appointment_date": "2021-05-31",
          "appointment_end_time": "11:30",
          "appointment_sid": "8c807b85-4340-47f0-b686-3c693b0b8e99",
          "appointment_start_time": "11:00",
          "appointment_timezone": "US/Eastern",
          "partnership_account_id": 1
        }
        </pre>
        """
        if rest_is_partnership and rest_partnership is not None:
            # Find and get the partnership account
            if rest_partnership.partner_type == 'system':
                partnership_account = PartnershipAccount \
                    .query.filter(PartnershipAccount.id == paid).first()
            else:
                partnership_account = PartnershipAccount.query \
                    .filter(and_(PartnershipAccount.id == paid,
                                 PartnershipAccount.partnership_id == rest_partnership.id)).first()
            # Set the parameters sent in he request
            received = request.json
            # Check for partnership account and that request body data was provided
            if received is not None and partnership_account is not None:
                contact_id = received['contact_id']
                date = received['appointment_date']
                start_time = received['appointment_start_time']
                if 'user_id' in received:
                    user = received['user_id']
                else:
                    user = None
                if 'appointment_type' in received:
                    appointment_type = received['appointment_type']
                else:
                    appointment_type = 'general'

                result = Appointment.create(partnership_account.id,
                                            contact_id, date, start_time,
                                            appointment_type, user)

                if type(result) == uuid.UUID:
                    new_appointment = Appointment.query.filter(Appointment.sid == result).first()
                    appointment_slot = AppointmentSlot.query\
                        .filter(AppointmentSlot.appointment_id == new_appointment.id).first()
                    date_formatted = datetime.strftime(new_appointment.appointment_date, '%Y-%m-%d')
                    response = jsonify(
                        appointment_sid=result,
                        partnership_account_id=partnership_account.id,
                        appointment_date=date_formatted,
                        appointment_start_time=appointment_slot.start_time,
                        appointment_end_time=appointment_slot.end_time,
                        appointment_timezone=new_appointment.appointment_timezone

                    )
                    response.status_code = 200
                    return response
                elif result == -2:
                    return api.abort(code=400, message="Error creating appointment. The user does not have availability"
                                                       " at the requested date and time.")
                elif result == -3:
                    return api.abort(code=404, message="Error creating appointment. The user does not seem to exist.")
                elif result == -4:
                    return api.abort(code=400, message="Error creating appointment. The partnership account does not "
                                                       "have availability at the requested date and time.")
                else:
                    return api.abort(code=404, message="Error creating appointment.")
            else:
                api.abort(code=404, message="Error creating appointment. Partnership account not found.")
        else:
            api.abort(code=401)

    @api.expect(get_existing_appointment_parser)
    @requires_auth
    def get(self, paid):
        """
        Retrieve all appointments set for a partnership account or partnership account user.
        <p>
        The Appointments API GET endpoint should be used to retrieve all the appointments for a specific partnership
        account or partnership account user. Add the user id as parameter to return user specific appointments otherwise
        all appointments will be returned for the specific partnership account. You can also choose to return all
        appointments for a specific consumer by providing an optional contact id parameter, appointment date and
        appointment start time.
        </p>
        <br />
        <p>
        You require a partner authentication token and a partner account id to make a successful request.
         Optionally you can also add a user id, appointment type, appointment date, appointment time and/or contact id
         as parameters to refine the results.
         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">
        {
          "appointments": [
            {
              "appointment_date": "2021-05-31",
              "appointment_end_time": "10:30",
              "appointment_sid": "fc76039c-e86d-24ca-db11-131ba35898a4",
              "appointment_start_time": "10:00",
              "appointment_timezone": "US/Eastern",
              "appointment_type": "general",
              "contact_id": 26,
              "created_on": "2021-04-07 21:51:02",
              "deactivated_on": null,
              "is_deactivated": false,
              "updated_on": "2021-04-07 21:51:02",
              "user_id": null
            },
            {
              "appointment_date": "2021-05-31",
              "appointment_end_time": "10:30",
              "appointment_sid": "6fe5051b-f1cd-987a-2243-b2d89d897843",
              "appointment_start_time": "10:00",
              "appointment_timezone": "US/Eastern",
              "appointment_type": "general",
              "contact_id": 40,
              "created_on": "2021-04-07 21:51:02",
              "deactivated_on": null,
              "is_deactivated": false,
              "updated_on": "2021-04-07 21:51:02",
              "user_id": null
            }
          ]
        }
     </pre>
        """
        if rest_is_partnership and rest_partnership is not None:
            # Find and get the partnership account
            if rest_partnership.partner_type == 'system':
                partnership_account = PartnershipAccount \
                    .query.filter(PartnershipAccount.id == paid).first()
            else:
                partnership_account = PartnershipAccount.query \
                    .filter(and_(PartnershipAccount.id == paid,
                                 PartnershipAccount.partnership_id == rest_partnership.id)).first()

            if partnership_account is not None:
                # Get any parameters sent with the GET request
                args = get_existing_appointment_parser.parse_args()
                # Set the filtering structure to be used to get all appointments
                queries = [Appointment.partnership_account_id == partnership_account.id]
                if 'user_id' in args and args['user_id'] is not None:
                    user_id = args['user_id']
                    queries.append(Appointment.user_id == user_id)
                if 'contact_id' in args and args['contact_id'] is not None:
                    contact_id = args['contact_id']
                    queries.append(Appointment.contact_id == contact_id)
                if 'appointment_type' in args and args['appointment_type'] is not None:
                    appointment_type = args['appointment_type']
                    queries.append(Appointment.appointment_type == appointment_type)
                if 'appointment_date' in args and args['appointment_date'] is not None:
                    try:
                        datetime.strptime(args['appointment_date'], '%Y-%m-%d')
                        appointment_date = args['appointment_date']
                        queries.append(Appointment.appointment_date == appointment_date)
                    except ValueError:
                        api.abort(code=400, message="An incorrect appointment date format was provided. "
                                                    "It should be YYYY-MM-DD")
                # Set start time variable as empty to avoid errors when not provided
                appointment_start_time = ''
                if 'appointment_start_time' in args and args['appointment_start_time'] is not None:
                    if len(args['appointment_start_time']) == 5:
                        appointment_start_time = args['appointment_start_time']
                    else:
                        api.abort(code=400, message="An incorrect appointment start time format was provided. "
                                                    "It should be HH:MM")

                if appointment_start_time:
                    appointments = Appointment.query.filter(*queries).join(AppointmentSlot)\
                        .filter(AppointmentSlot.start_time == appointment_start_time).all()
                else:
                    # Get all the appointments based on the filtering established above
                    appointments = Appointment.query.filter(*queries).all()

                appointment_list = []
                # run through the appointments returned and add them to a appointment list above
                if appointments is not None:
                    for appointment in appointments:
                        for appointment_slot in appointment.appointment_slot:
                            format_date = datetime.strftime(appointment.appointment_date, '%Y-%m-%d')
                            add_appointment = {
                                'appointment_sid': appointment.sid,
                                'created_on': appointment.created_datetime,
                                'updated_on': appointment.updated_datetime,
                                'appointment_date': format_date,
                                'appointment_start_time': appointment_slot.start_time,
                                'appointment_end_time': appointment_slot.end_time,
                                'appointment_timezone': appointment.appointment_timezone,
                                'appointment_type': appointment.appointment_type,
                                'contact_id': appointment.contact_id,
                                'user_id': appointment.user_id,
                                'is_deactivated': appointment.is_deactivated,
                                'deactivated_on': appointment.deactivated_on
                            }
                            appointment_list.append(add_appointment)

                return jsonify(
                    appointments=appointment_list
                )
            else:
                api.abort(code=404, message="Error retrieving appointment. Partnership account not found.")
        else:
            api.abort(code=401)


@ns.route('/<int:paid>/appointments/availability')
@api.doc(responses={200: 'OK',
                    400: 'Error performing operation.',
                    401: 'Unauthorized request.'},
         params={'paid': 'The partnership account Id'})
class ApiAppointmentsAvailableCollection(Resource):
    @api.response(200, 'Appointment successfully created.')
    @api.expect(get_available_appointment_parser)
    @requires_auth
    def get(self, paid):
        """
        Retrieve all available appointments set for a partnership account or partnership account user.
        <p>
        The Appointments Availability API GET endpoint should be used to retrieve all available appointment slots for
        a specific partnership account or partnership account user. Available slots up until 2 months in the future
        will be returned. You will be able to filter the request based on the user or appointment type by providing
        a user id and/or appointment type parameters. You are also able to specify a date in the future to find
        availability based on a future date instead of using today's date to availability. When specifying a date
        please use the format yyyy-mm-dd.
        </p>
        <br />
        <p>
        You require a partner authentication token and a partner account id to make a successful request.
         Optionally you can also add a user id, appointment type or date. 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">
        {
          "available_appointment_slots": [
            {
              "date": "2021-04-21",
              "day_of_week": "wednesday",
              "end_time": "9:00",
              "start_time": "8:30"
            },
            {
              "date": "2021-04-21",
              "day_of_week": "wednesday",
              "end_time": "9:30",
              "start_time": "9:00"
            }
          ]
        }
     </pre>
        """
        if rest_is_partnership and rest_partnership is not None:
            # Find and get the partnership account
            if rest_partnership.partner_type == 'system':
                partnership_account = PartnershipAccount \
                    .query.filter(PartnershipAccount.id == paid).first()
            else:
                partnership_account = PartnershipAccount.query \
                    .filter(and_(PartnershipAccount.id == paid,
                                 PartnershipAccount.partnership_id == rest_partnership.id)).first()

            if partnership_account is not None:
                # Get any parameters sent with the GET request
                args = get_available_appointment_parser.parse_args()
                appointment_type = None
                user_id = None
                appointment_date = None
                if 'user_id' in args:
                    user_id = args['user_id']
                if 'appointment_type' in args:
                    appointment_type = args['appointment_type']
                if 'date' in args and args['date'] is not None:
                    try:
                        datetime.strptime(args['date'], '%Y-%m-%d')
                        appointment_date = args['date']
                    except ValueError:
                        api.abort(code=400, message="An incorrect appointment date format was provided. "
                                                    "It should be YYYY-MM-DD")
                else:
                    appointment_date = None
                if user_id:
                    available_appointments = User.user_available_appointment_slots(user_id, appointment_date)
                else:
                    available_appointments = PartnershipAccount.partnership_account_available_appointment_slots(
                        partnership_account.id, appointment_type, appointment_date)

                # run through the appointments returned and add them to a appointment list above
                available_appointment_slots = []
                for slot in available_appointments:
                    format_date = slot['date'][:-9]
                    format_weekday = week_day(slot['weekday'])
                    add_appointment = {
                        'date': format_date,
                        'day_of_week': format_weekday,
                        'start_time': slot['start_time'],
                        'end_time': slot['end_time']
                    }
                    available_appointment_slots.append(add_appointment)
                return jsonify(
                    available_appointment_slots=available_appointment_slots
                )
            else:
                api.abort(code=404, message="Error retrieving appointment. Partnership account not found.")
        else:
            api.abort(code=401)


@ns.route('/<int:paid>/appointment/<uuid:appsid>')
@api.doc(responses={200: 'OK',
                    400: 'Error performing operation.',
                    401: 'Unauthorized request.'},
         params={'appsid': 'The appointment SID',
                 'paid': 'The partnership account Id'})
class ApiAppointmentsSpecific(Resource):
    @api.expect(serializers.appointment_edit, validate=True)
    @api.response(200, 'Appointment successfully updated.')
    @requires_auth
    def put(self, paid, appsid):
        """
        Updates an appointment for a partnership account or partnership account user.

        <p>The Appointments API PUT endpoint can be used to update an appointment for a specific partnership account or
        partnership account user.</p>
        <br />
        <p>
        You require a valid partnership account id and appointment sid to
        make a successful request. A response code will be returned
        on a successful request.
        </p>
        """
        if rest_is_partnership and rest_partnership is not None:
            # Find and get the partnership account
            if rest_partnership.partner_type == 'system':
                partnership_account = PartnershipAccount \
                    .query.filter(PartnershipAccount.id == paid).first()
            else:
                partnership_account = PartnershipAccount.query \
                    .filter(and_(PartnershipAccount.id == paid,
                                 PartnershipAccount.partnership_id == rest_partnership.id)).first()

            if partnership_account is not None:
                appointment = Appointment.query\
                    .filter(and_(Appointment.partnership_account_id == partnership_account.id,
                                 Appointment.sid == appsid)).first()
                appointment_slot = AppointmentSlot.query\
                    .filter(AppointmentSlot.appointment_id == appointment.id).first()

                if appointment is not None:
                    received = request.json

                    if 'appointment_date' in received:
                        date = received['appointment_date']
                    else:
                        date = appointment.appointment_date
                    if 'appointment_start_time' in received:
                        start_time = received['appointment_start_time']
                    else:
                        start_time = appointment_slot.start_time
                    if 'user_id' in received:
                        user_id = received['user_id']
                    else:
                        user_id = appointment.user_id
                    if 'appointment_type' in received:
                        appointment_type = received['appointment_type']
                    else:
                        appointment_type = appointment.appointment_type

                    result = Appointment.update(appointment.id, appointment_slot.id, partnership_account.id,
                                                date, start_time, appointment_type, user_id)
                    if result is True:
                        return True, 204
                    elif result == -1:
                        api.abort(code=400, message="Error updating appointment. The provided date and time is"
                                                    " not available.")
                    else:
                        return api.abort(code=404, message="Error updating appointment.")
                else:
                    api.abort(code=404, message="Error updating appointment. Appointment not found.")
            else:
                api.abort(code=404, message="Error retrieving appointment. Partnership account not found.")
        else:
            api.abort(code=401)

    @api.response(200, 'Appointment successfully deleted.')
    @requires_auth
    def delete(self, paid, appsid):
        """
        Removes an appointment for a partnership account or partnership account user.

        <p>The Appointment API DELETE endpoint can be used to deactive an appointment for a specific partnership
        account or partnership account user.</p>
        <br />
        <p>
        You require a valid partnership account id and a appointment sid to
        make a successful request. A response code will be returned
        on a successful request.
        </p>
        """
        if rest_is_partnership and rest_partnership is not None:
            # Find and get the partnership account
            if rest_partnership.partner_type == 'system':
                partnership_account = PartnershipAccount \
                    .query.filter(PartnershipAccount.id == paid).first()
            else:
                partnership_account = PartnershipAccount.query \
                    .filter(and_(PartnershipAccount.id == paid,
                                 PartnershipAccount.partnership_id == rest_partnership.id)).first()

            if partnership_account is not None:
                appointment = Appointment.query.filter(Appointment.sid == appsid).first()

                if appointment is not None:
                    result = Appointment.deactivate(appointment.id, partnership_account.id)

                    if result:
                        return True, 204
                    else:
                        return api.abort(code=400, message="Error deactivating appointment.")
                else:
                    api.abort(code=404, message="Error deactivating appointment. Appointment not found.")
            else:
                api.abort(code=404, message="Error retrieving appointment. Partnership account not found.")
        else:
            api.abort(code=401)

    @api.response(200, 'Appointment successfully deleted.')
    @requires_auth
    def get(self, paid, appsid):
        """
        Retrieve the details for a specific appointment for a partnership account or partnership account user.

        <p>The Appointment API GET endpoint can be used to find and return the details for an appointment
        for a specific partnership account or partnership account user.
        account or partnership account user.</p>
        <br />
        <p>
        You require a valid partnership account id and a appointment sid 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">
        {
          "appointment_date": "2021-04-25",
          "appointment_sid": "6fe5051b-f1cd-987a-2243-b2d89d897843",
          "appointment_start_time": "10:00",
          "appointment_type": "general",
          "apppointment_end_time": "10:30",
          "contact_id": 40,
          "day_of_week": "sunday",
          "deactivated_on": null,
          "is_deactivated": false,
          "partnership_account_id": 1,
          "user_id": null
        }
        </pre>
        </p>
        """
        if rest_is_partnership and rest_partnership is not None:
            # Find and get the partnership account
            if rest_partnership.partner_type == 'system':
                partnership_account = PartnershipAccount \
                    .query.filter(PartnershipAccount.id == paid).first()
            else:
                partnership_account = PartnershipAccount.query \
                    .filter(and_(PartnershipAccount.id == paid,
                                 PartnershipAccount.partnership_id == rest_partnership.id)).first()

            if partnership_account is not None:
                appointment = Appointment.query.filter(Appointment.sid == appsid).first()

                if appointment is not None:
                    appointment_slot = AppointmentSlot.query\
                        .filter(AppointmentSlot.appointment_id == appointment.id).first()
                    format_date = str(appointment.appointment_date)[:-9]
                    weekday = week_day((appointment.appointment_date.weekday() + 1) % 7)
                    return jsonify(
                        appointment_sid=appointment.sid,
                        appointment_date=format_date,
                        day_of_week=weekday,
                        appointment_start_time=appointment_slot.start_time,
                        apppointment_end_time=appointment_slot.end_time,
                        appointment_type=appointment.appointment_type,
                        contact_id=appointment.contact_id,
                        partnership_account_id=appointment.partnership_account_id,
                        user_id=appointment.user_id,
                        is_deactivated=appointment.is_deactivated,
                        deactivated_on=appointment.deactivated_on,
                    )
                else:
                    api.abort(code=404, message="The appointment was not found.")
            else:
                api.abort(code=404, message="Error retrieving the appointment. Partnership account not found.")
        else:
            api.abort(code=401)