def delete(self, id):

        Appointment.delete_draft([id])
        if not application.config['DISABLE_AUTO_REFRESH']:
            socketio.emit('appointment_delete', id)

        return {}, 204
예제 #2
0
    def post(self):
        my_print("==> In AppointmentDraftPost, POST /appointments/draft")
        json_data = request.get_json()
        
        office_id = json_data.get('office_id')
        service_id = json_data.get('service_id')
        start_time = parse(json_data.get('start_time'))
        end_time = parse(json_data.get('end_time'))
        office = Office.find_by_id(office_id)
        service = Service.query.get(int(service_id)) if service_id else None

        # end_time can be null for CSRs when they click; whereas citizens know end-time.
        if not end_time:
            end_time = add_delta_to_time(start_time, minutes=office.appointment_duration, timezone=office.timezone.timezone_name)

        # Unauthenticated requests from citizens won't have name, so we set a fallback
        if (hasattr(g, 'oidc_token_info') and hasattr(g.oidc_token_info, 'username')):
            user = PublicUser.find_by_username(g.oidc_token_info['username'])
            citizen_name = user.display_name
        else:
            citizen_name = 'Draft'

        # Delete all expired drafts before checking availability
        Appointment.delete_expired_drafts()



        csr = None
        if (hasattr(g, 'oidc_token_info')):
            csr = CSR.find_by_username(g.oidc_token_info['username'])

        # CSRs are not limited by drafts,  can always see other CSRs drafts
        # This mitigates two CSRs in office creating at same time for same meeting
        # Ensure there's no race condition when submitting a draft
        if not csr and not AvailabilityService.has_available_slots(
                                    office=office, 
                                    start_time=start_time, 
                                    end_time=end_time, 
                                    service=service):
                    return {"code": "CONFLICT_APPOINTMENT",
                            "message": "Cannot create appointment due to scheduling conflict.  Please pick another time."}, 400
        
        # Set draft specific data
        json_data['is_draft'] = True
        json_data['citizen_name'] = citizen_name

        appointment, warning = self.appointment_schema.load(json_data)
        
        if warning:
            logging.warning("WARNING: %s", warning)
            return {"message": warning}, 422

        db.session.add(appointment)
        db.session.commit()
        
        result = self.appointment_schema.dump(appointment)

        socketio.emit('appointment_create', result.data)

        return {"appointment": result.data, "warning" : warning}, 201
예제 #3
0
    def delete(self, id):

        appointment = Appointment.query.filter_by(appointment_id=id)\
                                       .first_or_404()

        Appointment.delete_draft([id])
        socketio.emit('appointment_delete', id)

        return {}, 204
    def delete(self, id):

        appointment = Appointment.query.filter_by(appointment_id=id)\
                                       .first_or_404()

        Appointment.delete_draft([id])
        if not application.config['DISABLE_AUTO_REFRESH']:
            socketio.emit('appointment_delete', id)

        return {}, 204
    def post(self):
        """Create appointment reminders."""
        appointments = Appointment.find_next_day_appointments()
        print('sending {} reminders'.format(len(appointments)))

        if appointments:
            for (appointment, office, timezone, user) in appointments:
                send_reminder = False
                if user and user.send_reminders:
                    send_reminder = True
                elif not user and is_valid_email(
                        appointment.contact_information):
                    send_reminder = True

                if send_reminder:

                    @copy_current_request_context
                    def async_email(subject, email, sender, body):
                        send_email(subject, email, sender, body)

                    thread = Thread(target=async_email,
                                    args=get_reminder_email_contents(
                                        appointment, user, office, timezone))
                    thread.daemon = True
                    thread.start()
예제 #6
0
    def get(self):
        """Return appointment reminders for next day."""
        appointments = Appointment.find_next_day_appointments()

        # Construct a custom response as teh payload can grow in size.
        reminders = {
            'appointments': []
        }

        if appointments:
            for (appointment, office, timezone, user) in appointments:
                send_reminder = False
                if user and user.send_reminders:
                    send_reminder = True
                elif not user and is_valid_email(appointment.contact_information):
                    send_reminder = True

                if send_reminder:
                    date, day = formatted_date(appointment.start_time, timezone)

                    reminders['appointments'].append(
                        {
                            'formatted_date': date,
                            'day': day,
                            'email': get_email(user, appointment),
                            'display_name': appointment.citizen_name,
                            'location': office.office_name,
                            'duration': get_duration(appointment.start_time, appointment.end_time),
                            'telephone': office.telephone
                        }
                    )
        return reminders
 def post(self):
     my_print(
         "==> In AppointmentDraftFlush, POST /appointments/draft/flush")
     draft_ids = Appointment.delete_expired_drafts()
     return {"deleted_draft_ids": draft_ids}, 200
예제 #8
0
    def get(self, reminder_type: str = 'email'):
        """Return appointment reminders for next day."""
        appointments = Appointment.find_next_day_appointments()

        # Construct a custom response as teh payload can grow in size.
        reminders = {'appointments': []}

        if appointments:
            for (appointment, office, timezone, user) in appointments:
                send_reminder = False
                if reminder_type == 'email':
                    if user and user.send_email_reminders:
                        send_reminder = True
                    elif not user and is_valid_email(
                            appointment.contact_information):
                        send_reminder = True
                elif reminder_type == 'sms':
                    if user and user.send_sms_reminders:
                        send_reminder = True
                        user_telephone = user.telephone
                    elif not user and is_valid_phone(
                            appointment.contact_information):
                        send_reminder = True
                        user_telephone = appointment.contact_information

                if send_reminder:
                    if reminder_type == 'email':
                        date, day = formatted_date(appointment.start_time,
                                                   timezone)
                    else:
                        date, day = format_sms_date(appointment.start_time,
                                                    timezone), None

                    office_email_paragraph = appointment.office.office_email_paragraph
                    if office_email_paragraph:
                        office_email_paragraph = office_email_paragraph.replace(
                            '\r\n', '<br />')

                    service_email_paragraph = appointment.service.email_paragraph
                    if service_email_paragraph:
                        service_email_paragraph = service_email_paragraph.replace(
                            '\r\n', '<br />')

                    service_name = appointment.service.external_service_name \
                        if appointment.service.external_service_name else appointment.service.service_name

                    reminders['appointments'].append({
                        'formatted_date':
                        date,
                        'day':
                        day,
                        'email':
                        get_email(user, appointment),
                        'display_name':
                        appointment.citizen_name,
                        'location':
                        office.office_name,
                        'duration':
                        get_duration(appointment.start_time,
                                     appointment.end_time),
                        'telephone':
                        office.telephone,
                        'service_email_paragraph':
                        service_email_paragraph,
                        'office_email_paragraph':
                        office_email_paragraph,
                        'service_name':
                        service_name,
                        'civic_address':
                        appointment.office.civic_address,
                        'user_telephone':
                        user_telephone,
                    })
        return reminders
예제 #9
0
    def post(self):
        my_print("==> In AppointmentPost, POST /appointments/")
        json_data = request.get_json()
        if not json_data:
            return {
                "message": "No input data received for creating an appointment"
            }, 400

        # Should delete draft appointment, and free up slot, before booking.
        # Clear up a draft if one was previously created by user reserving this time.
        if json_data.get('appointment_draft_id'):
            draft_id_to_delete = int(json_data['appointment_draft_id'])
            Appointment.delete_draft([draft_id_to_delete])
            if not application.config['DISABLE_AUTO_REFRESH']:
                socketio.emit('appointment_delete', draft_id_to_delete)

        is_blackout_appt = json_data.get('blackout_flag', 'N') == 'Y'
        csr = None
        user = None
        office = None

        #  Create a citizen for later use.
        citizen = self.citizen_schema.load({}).data

        # Check if the appointment is created by public user. Can't depend on the IDP as BCeID is used by other users as well
        is_public_user_appointment = is_public_user()
        if is_public_user_appointment:
            office_id = json_data.get('office_id')
            service_id = json_data.get('service_id')
            user = PublicUser.find_by_username(g.oidc_token_info['username'])
            # Add values for contact info and notes
            json_data['contact_information'] = user.email
            telephone = f'. Phone: {user.telephone}' if user.telephone else ''
            json_data['comments'] = json_data.get('comments', '') + telephone

            citizen.user_id = user.user_id
            citizen.citizen_name = user.display_name

            office = Office.find_by_id(office_id)
            service = Service.query.get(int(service_id))

            # Validate if the same user has other appointments for same day at same office
            appointments = Appointment.find_by_username_and_office_id(
                office_id=office_id,
                user_name=g.oidc_token_info['username'],
                start_time=json_data.get('start_time'),
                timezone=office.timezone.timezone_name)
            if appointments and len(
                    appointments) >= office.max_person_appointment_per_day:
                return {
                    "code": "MAX_NO_OF_APPOINTMENTS_REACHED",
                    "message": "Maximum number of appointments reached"
                }, 400

            # Check for race condition
            start_time = parse(json_data.get('start_time'))
            end_time = parse(json_data.get('end_time'))
            if not AvailabilityService.has_available_slots(
                    office=office,
                    start_time=start_time,
                    end_time=end_time,
                    service=service):
                return {
                    "code":
                    "CONFLICT_APPOINTMENT",
                    "message":
                    "Cannot create appointment due to scheduling conflict.  Please pick another time."
                }, 400

        else:
            csr = CSR.find_by_username(g.oidc_token_info['username'])
            office_id = csr.office_id
            office = Office.find_by_id(office_id)

        citizen.office_id = office_id
        citizen.qt_xn_citizen_ind = 0
        citizen_state = CitizenState.query.filter_by(
            cs_state_name="Appointment booked").first()
        citizen.cs_id = citizen_state.cs_id
        citizen.start_time = datetime.now()
        citizen.service_count = 1

        db.session.add(citizen)
        db.session.commit()

        appointment, warning = self.appointment_schema.load(json_data)
        if is_public_user_appointment:
            appointment.citizen_name = user.display_name
            appointment.online_flag = True

        if warning:
            logging.warning("WARNING: %s", warning)
            return {"message": warning}, 422

        if appointment.office_id == office_id:
            appointment.citizen_id = citizen.citizen_id
            db.session.add(appointment)
            db.session.commit()

            # Generate CHES token
            try:
                ches_token = generate_ches_token()
            except Exception as exc:
                pprint(f'Error on token generation - {exc}')

            # If staff user is creating a blackout event then send email to all of the citizens with appointments for that period
            if is_blackout_appt:
                appointment_ids_to_delete = []
                appointments_for_the_day = Appointment.get_appointment_conflicts(
                    office_id, json_data.get('start_time'),
                    json_data.get('end_time'))
                for (cancelled_appointment, office, timezone,
                     user) in appointments_for_the_day:
                    if cancelled_appointment.appointment_id != appointment.appointment_id and not cancelled_appointment.checked_in_time:
                        appointment_ids_to_delete.append(
                            cancelled_appointment.appointment_id)

                        # Send blackout email
                        try:
                            pprint(
                                'Sending email for appointment cancellation due to blackout'
                            )
                            send_email(
                                ches_token,
                                *get_blackout_email_contents(
                                    appointment, cancelled_appointment, office,
                                    timezone, user))
                        except Exception as exc:
                            pprint(f'Error on email sending - {exc}')

                # Delete appointments
                if len(appointment_ids_to_delete) > 0:
                    Appointment.delete_appointments(appointment_ids_to_delete)

            else:
                # Send confirmation email
                try:
                    pprint('Sending email for appointment confirmation')
                    send_email(
                        ches_token,
                        *get_confirmation_email_contents(
                            appointment, office, office.timezone, user))
                except Exception as exc:
                    pprint(f'Error on email sending - {exc}')

            SnowPlow.snowplow_appointment(citizen, csr, appointment,
                                          'appointment_create')

            result = self.appointment_schema.dump(appointment)

            if not application.config['DISABLE_AUTO_REFRESH']:
                socketio.emit('appointment_create', result.data)

            return {"appointment": result.data, "errors": result.errors}, 201

        else:
            return {
                "The Appointment Office ID and CSR Office ID do not match!"
            }, 403
예제 #10
0
    def get_available_slots(office: Office, days: [datetime], format_time: bool = True, service: Service = None):
        """Return the available slots for the office"""
        try:
            available_slots_per_day = {}
            if office.appointments_enabled_ind == 0:
                return available_slots_per_day

            # find appointment duration per office and fetch timeslot master data
            appointment_duration = office.appointment_duration

            # If user has passed in service and it has duration, use that instead
            if (service and service.timeslot_duration):
                appointment_duration = service.timeslot_duration

            service_is_dltk = service and service.is_dlkt == YesNo.YES
            
            # Dictionary to store the available slots per day
            tz = pytz.timezone(office.timezone.timezone_name)

            # today's date and time
            today = datetime.datetime.now().astimezone(tz)

            # soonest a citizen can book an appointment
            soonest_appointment_date = today + datetime.timedelta(minutes = office.soonest_appointment or 0)

            # Find all appointments between the dates
            appointments = Appointment.find_appointment_availability(office_id=office.office_id, first_date=today,
                                                                     last_date=days[-1],
                                                                     timezone=office.timezone.timezone_name)
            grouped_appointments = AvailabilityService.group_appointments(appointments, office.timezone.timezone_name)

            # For each of the day calculate the slots based on time slots
            for day_in_month in days:
                formatted_date = day_in_month.strftime('%m/%d/%Y')
                available_slots_per_day[formatted_date] = []
                for timeslot in office.timeslots:
                    # Calculate the slots per day
                    timeslot_end_time = timeslot.end_time.replace(tzinfo=tz)
                    timeslot_start_time = timeslot.start_time.replace(tzinfo=tz)
                    if day_in_month.isoweekday() in day_indexes(timeslot.day_of_week):
                        start_time = timeslot_start_time
                        end_time = add_delta_to_time(timeslot_start_time, minutes=appointment_duration,
                                                     timezone=office.timezone.timezone_name)

                        # Cannot exceed office timeslot slots.
                        dlkt_slots = office.number_of_dlkt  or 0
    
                        if ( dlkt_slots > timeslot.no_of_slots):
                            dlkt_slots = timeslot.no_of_slots
                        

                        # Limit DLKT slots only for DLKT services.
                        no_of_slots = timeslot.no_of_slots

                        while end_time <= timeslot_end_time:
                            slot = {
                                'start_time': start_time,
                                'end_time': end_time,
                                'no_of_slots': no_of_slots,
                                'no_of_dlkt_slots': dlkt_slots
                            }
                            # Check if today's time is past appointment slot
                            # Arc - also check if in office.soonest_appointment
                            if ((day_in_month.date() == soonest_appointment_date.date() and start_time >= soonest_appointment_date.time()) or day_in_month.date() > soonest_appointment_date.date()):
                                if slot not in available_slots_per_day[formatted_date]: 
                                    available_slots_per_day[formatted_date].append(slot)

                            start_time = end_time.replace(tzinfo=tz)
                            end_time = add_delta_to_time(end_time, minutes=appointment_duration,
                                                         timezone=office.timezone.timezone_name)

                # Sort the slot by time for the day
                available_slots_per_day[formatted_date].sort(key=lambda x: x['start_time'])

                # Check if the slots are already booked
                for actual_slot in available_slots_per_day[formatted_date]:
                    booked_slots = 0
                    booked_dlkt_slots = 0
                    for booked_slot in grouped_appointments.get(formatted_date, []):
                        if booked_slot.get('start_time') \
                                <= actual_slot.get('start_time') \
                                < booked_slot.get('end_time') \
                                or \
                                actual_slot.get('end_time') \
                                > booked_slot.get('start_time') \
                                >= actual_slot.get('start_time'):


                            if booked_slot.get('blackout_flag', False):  # If it's blackout override the no of slots
                                actual_slot['no_of_slots'] = 0
                            else:
                                if  booked_slot['is_dlkt']:
                                    booked_dlkt_slots += 1
                                else:   
                                    booked_slots += 1   
                    if service_is_dltk:
                        dlkt_nos = actual_slot['no_of_dlkt_slots'] - booked_dlkt_slots
                        if actual_slot['no_of_slots'] <= (booked_slots + booked_dlkt_slots):
                            actual_slot['no_of_slots'] = 0
                        elif actual_slot['no_of_slots'] - booked_slots >= dlkt_nos:
                            actual_slot['no_of_slots'] = dlkt_nos
                        else: 
                            actual_slot['no_of_slots'] = dlkt_nos - (actual_slot['no_of_slots'] - booked_slots) 
                    else:
                       actual_slot['no_of_slots']  =  actual_slot['no_of_slots'] - (booked_slots + booked_dlkt_slots)

                    del actual_slot['no_of_dlkt_slots'] # no need to expose
                       
                    if format_time:  # If true send formatted time
                        actual_slot['start_time'] = actual_slot['start_time'].strftime('%H:%M')
                        actual_slot['end_time'] = actual_slot['end_time'].strftime('%H:%M')

            return AvailabilityService.prune_appointments(available_slots_per_day)

        except exc.SQLAlchemyError as e:
            print(e)
            return {'message': 'API is down'}, 500
예제 #11
0
    def put(self, id):
        json_data = request.get_json()
        csr = None
        user = None

        if not json_data:
            return {
                "message": "No input data received for updating an appointment"
            }
        is_public_user_appt = is_public_user()

        # Should delete draft appointment, and free up slot, before booking.
        # Clear up a draft if one was previously created by user reserving this time.
        if json_data.get('appointment_draft_id'):
            draft_id_to_delete = int(json_data['appointment_draft_id'])
            Appointment.delete_draft([draft_id_to_delete])
            if not application.config['DISABLE_AUTO_REFRESH']:
                socketio.emit('appointment_delete', draft_id_to_delete)

        if is_public_user_appt:
            office_id = json_data.get('office_id')
            office = Office.find_by_id(office_id)
            # user = PublicUser.find_by_username(g.oidc_token_info['username'])
            # citizen = Citizen.find_citizen_by_username(g.oidc_token_info['username'], office_id)
            # Validate if the same user has other appointments for same day at same office
            appointments = Appointment.find_by_username_and_office_id(
                office_id=office_id,
                user_name=g.oidc_token_info['username'],
                start_time=json_data.get('start_time'),
                timezone=office.timezone.timezone_name,
                appointment_id=id)
            if appointments and len(
                    appointments) >= office.max_person_appointment_per_day:
                return {
                    "code": "MAX_NO_OF_APPOINTMENTS_REACHED",
                    "message": "Maximum number of appoinments reached"
                }, 400

            # Check for race condition
            start_time = parse(json_data.get('start_time'))
            end_time = parse(json_data.get('end_time'))
            service_id = json_data.get('service_id')
            service = Service.query.get(int(service_id))
            if not AvailabilityService.has_available_slots(
                    office=office,
                    start_time=start_time,
                    end_time=end_time,
                    service=service):
                return {
                    "code":
                    "CONFLICT_APPOINTMENT",
                    "message":
                    "Cannot create appointment due to scheduling conflict.  Please pick another time."
                }, 400

        else:
            csr = CSR.find_by_username(g.oidc_token_info['username'])
            office_id = csr.office_id
            office = Office.find_by_id(office_id)

        appointment = Appointment.query.filter_by(appointment_id=id) \
            .filter_by(office_id=office_id) \
            .first_or_404()

        # If appointment is not made by same user, throw error
        if is_public_user_appt:
            citizen = Citizen.find_citizen_by_id(appointment.citizen_id)
            user = PublicUser.find_by_username(g.oidc_token_info['username'])
            # Should just match based on appointment_id and other info.  Can't have proper auth yet.
            if citizen.user_id != user.user_id:
                abort(403)

        appointment, warning = self.appointment_schema.load(
            json_data, instance=appointment, partial=True)

        if warning:
            logging.warning("WARNING: %s", warning)
            return {"message": warning}, 422

        db.session.add(appointment)
        db.session.commit()

        # Send confirmation email
        try:
            pprint('Sending email for appointment update')
            send_email(
                generate_ches_token(),
                *get_confirmation_email_contents(appointment, office,
                                                 office.timezone, user))
        except Exception as exc:
            pprint(f'Error on token generation - {exc}')

        #   Make Snowplow call.
        schema = 'appointment_update'
        if "checked_in_time" in json_data:
            schema = 'appointment_checkin'

        if not appointment.is_draft:
            SnowPlow.snowplow_appointment(None, csr, appointment, schema)

        result = self.appointment_schema.dump(appointment)

        if not application.config['DISABLE_AUTO_REFRESH']:
            socketio.emit('appointment_update', result.data)

        return {"appointment": result.data, "errors": result.errors}, 200
예제 #12
0
    def get_available_slots(office: Office,
                            days: [datetime],
                            format_time: bool = True):
        """Return the available slots for the office"""
        try:
            available_slots_per_day = {}
            if office.appointments_enabled_ind == 0:
                return available_slots_per_day

            # find appointment duration per office and fetch timeslot master data
            appointment_duration = office.appointment_duration

            # Dictionary to store the available slots per day
            tz = pytz.timezone(office.timezone.timezone_name)

            # today's date and time
            today = datetime.datetime.now().astimezone(tz)

            # Find all appointments between the dates
            appointments = Appointment.find_appointment_availability(
                office_id=office.office_id,
                first_date=today,
                last_date=days[-1],
                timezone=office.timezone.timezone_name)
            grouped_appointments = AvailabilityService.group_appointments(
                appointments, office.timezone.timezone_name)

            # For each of the day calculate the slots based on time slots
            for day_in_month in days:
                formatted_date = day_in_month.strftime('%m/%d/%Y')
                available_slots_per_day[formatted_date] = []

                for timeslot in office.timeslots:
                    # Calculate the slots per day
                    timeslot_end_time = timeslot.end_time.replace(tzinfo=tz)
                    timeslot_start_time = timeslot.start_time.replace(
                        tzinfo=tz)
                    if day_in_month.isoweekday() in day_indexes(
                            timeslot.day_of_week):
                        start_time = timeslot_start_time
                        end_time = add_delta_to_time(
                            timeslot_start_time,
                            minutes=appointment_duration,
                            timezone=office.timezone.timezone_name)
                        # print(start_time, end_time)
                        while end_time <= timeslot_end_time:
                            slot = {
                                'start_time': start_time,
                                'end_time': end_time,
                                'no_of_slots': timeslot.no_of_slots
                            }
                            # Check if today's time is past appointment slot
                            if not (today.date() == day_in_month.date()
                                    and today.time() > start_time):
                                available_slots_per_day[formatted_date].append(
                                    slot)

                            start_time = end_time.replace(tzinfo=tz)
                            end_time = add_delta_to_time(
                                end_time,
                                minutes=appointment_duration,
                                timezone=office.timezone.timezone_name)

                # Check if the slots are already booked
                for actual_slot in available_slots_per_day[formatted_date]:
                    for booked_slot in grouped_appointments.get(
                            formatted_date, []):
                        # print('>>>>>>', booked_slot.get('start_time'), actual_slot.get('start_time'), booked_slot.get('end_time'))
                        # print('<<<<<<', booked_slot.get('end_time'), actual_slot.get('end_time'),
                        #       booked_slot.get('start_time'))

                        if booked_slot.get('start_time') \
                                <= actual_slot.get('start_time') \
                                < booked_slot.get('end_time') \
                                or \
                                booked_slot.get('end_time') \
                                < actual_slot.get('end_time') \
                                <= booked_slot.get('start_time'):
                            if booked_slot.get(
                                    'blackout_flag', False
                            ):  # If it's blackout override the no of slots
                                actual_slot['no_of_slots'] = 0
                            else:
                                actual_slot['no_of_slots'] -= 1
                    if format_time:  # If true send formatted time
                        actual_slot['start_time'] = actual_slot[
                            'start_time'].strftime('%H:%M')
                        actual_slot['end_time'] = actual_slot[
                            'end_time'].strftime('%H:%M')

            return AvailabilityService.prune_appointments(
                available_slots_per_day)

        except exc.SQLAlchemyError as e:
            print(e)
            return {'message': 'API is down'}, 500
예제 #13
0
    def put(self, id):
        json_data = request.get_json()
        csr = None
        user = None

        if not json_data:
            return {"message": "No input data received for updating an appointment"}
        is_public_user_appt = is_public_user()
        if is_public_user_appt:
            office_id = json_data.get('office_id')
            office = Office.find_by_id(office_id)
            # user = PublicUser.find_by_username(g.oidc_token_info['username'])
            # citizen = Citizen.find_citizen_by_username(g.oidc_token_info['username'], office_id)
            # Validate if the same user has other appointments for same day at same office
            appointments = Appointment.find_by_username_and_office_id(office_id=office_id,
                                                                      user_name=g.oidc_token_info['username'],
                                                                      start_time=json_data.get('start_time'),
                                                                      timezone=office.timezone.timezone_name,
                                                                      appointment_id=id)
            if appointments and len(appointments) >= office.max_person_appointment_per_day:
                return {"code": "MAX_NO_OF_APPOINTMENTS_REACHED",
                        "message": "Maximum number of appoinments reached"}, 400

            # Check for race condition
            start_time = parse(json_data.get('start_time'))
            end_time = parse(json_data.get('end_time'))
            if not AvailabilityService.has_available_slots(office=office, start_time=start_time, end_time=end_time):
                return {"code": "CONFLICT_APPOINTMENT",
                        "message": "Cannot create appointment due to conflict in time"}, 400

        else:
            csr = CSR.find_by_username(g.oidc_token_info['username'])
            office_id = csr.office_id
            office = Office.find_by_id(office_id)

        appointment = Appointment.query.filter_by(appointment_id=id) \
            .filter_by(office_id=office_id) \
            .first_or_404()

        # If appointment is not made by same user, throw error
        if is_public_user_appt:
            citizen = Citizen.find_citizen_by_id(appointment.citizen_id)
            user = PublicUser.find_by_username(g.oidc_token_info['username'])
            if citizen.user_id != user.user_id:
                abort(403)

        appointment, warning = self.appointment_schema.load(json_data, instance=appointment, partial=True)

        if warning:
            logging.warning("WARNING: %s", warning)
            return {"message": warning}, 422

        db.session.add(appointment)
        db.session.commit()

        # Send confirmation email
        @copy_current_request_context
        def async_email(subject, email, sender, body):
            send_email(subject, email, sender, body)

        thread = Thread(target=async_email, args=get_confirmation_email_contents(appointment, office, office.timezone, user))
        thread.daemon = True
        thread.start()

        #   Make Snowplow call.
        schema = 'appointment_update'
        if "checked_in_time" in json_data:
            schema = 'appointment_checkin'

        SnowPlow.snowplow_appointment(None, csr, appointment, schema)

        result = self.appointment_schema.dump(appointment)

        return {"appointment": result.data,
                "errors": result.errors}, 200
예제 #14
0
    def post(self):
        json_data = request.get_json()
        if not json_data:
            return {"message": "No input data received for creating an appointment"}, 400

        is_blackout_appt = json_data.get('blackout_flag', 'N') == 'Y'
        csr = None
        user = None
        office = None

        #  Create a citizen for later use.
        citizen = self.citizen_schema.load({}).data

        # Check if the appointment is created by public user. Can't depend on the IDP as BCeID is used by other users as well
        is_public_user_appointment = is_public_user()
        if is_public_user_appointment:
            office_id = json_data.get('office_id')
            user = PublicUser.find_by_username(g.oidc_token_info['username'])
            # Add values for contact info and notes
            json_data['contact_information'] = user.email
            telephone = f'. Phone: {user.telephone}' if user.telephone else ''
            json_data['comments'] = json_data.get('comments', '') + telephone

            citizen.user_id = user.user_id
            citizen.citizen_name = user.display_name

            office = Office.find_by_id(office_id)
            # Validate if the same user has other appointments for same day at same office
            appointments = Appointment.find_by_username_and_office_id(office_id=office_id,
                                                                      user_name=g.oidc_token_info['username'],
                                                                      start_time=json_data.get('start_time'),
                                                                      timezone=office.timezone.timezone_name)
            if appointments and len(appointments) >= office.max_person_appointment_per_day:
                return {"code": "MAX_NO_OF_APPOINTMENTS_REACHED", "message": "Maximum number of appointments reached"}, 400

            # Check for race condition
            start_time = parse(json_data.get('start_time'))
            end_time = parse(json_data.get('end_time'))
            if not AvailabilityService.has_available_slots(office=office, start_time=start_time, end_time=end_time):
                return {"code": "CONFLICT_APPOINTMENT",
                        "message": "Cannot create appointment due to conflict in time"}, 400

        else:
            csr = CSR.find_by_username(g.oidc_token_info['username'])
            office_id = csr.office_id
            office = Office.find_by_id(office_id)

        citizen.office_id = office_id
        citizen.qt_xn_citizen_ind = 0
        citizen_state = CitizenState.query.filter_by(cs_state_name="Appointment booked").first()
        citizen.cs_id = citizen_state.cs_id
        citizen.start_time = datetime.now()
        citizen.service_count = 1

        db.session.add(citizen)
        db.session.commit()

        appointment, warning = self.appointment_schema.load(json_data)
        if is_public_user_appointment:
            appointment.citizen_name = user.display_name
            appointment.online_flag = True

        if warning:
            logging.warning("WARNING: %s", warning)
            return {"message": warning}, 422

        if appointment.office_id == office_id:
            appointment.citizen_id = citizen.citizen_id
            db.session.add(appointment)
            db.session.commit()

            # If staff user is creating a blackout event then send email to all of the citizens with appointments for that period
            if is_blackout_appt:
                appointment_ids_to_delete = []
                appointments_for_the_day = Appointment.get_appointment_conflicts(office_id, json_data.get('start_time'),
                                                                                 json_data.get('end_time'))
                for (cancelled_appointment, office, timezone, user) in appointments_for_the_day:
                    if cancelled_appointment.appointment_id != appointment.appointment_id and not cancelled_appointment.checked_in_time:
                        appointment_ids_to_delete.append(cancelled_appointment.appointment_id)

                        # Send blackout email
                        @copy_current_request_context
                        def async_email(subject, email, sender, body):
                            return send_email(subject, email, sender, body)

                        thread = Thread(target=async_email, args=get_blackout_email_contents(appointment, cancelled_appointment, office, timezone, user))
                        thread.daemon = True
                        thread.start()

                # Delete appointments
                if len(appointment_ids_to_delete) > 0:
                    Appointment.delete_appointments(appointment_ids_to_delete)

            else:
                # Send confirmation email
                @copy_current_request_context
                def async_email(subject, email, sender, body):
                    send_email(subject, email, sender, body)

                thread = Thread(target=async_email, args=get_confirmation_email_contents(appointment, office, office.timezone, user))
                thread.daemon = True
                thread.start()

            SnowPlow.snowplow_appointment(citizen, csr, appointment, 'appointment_create')

            result = self.appointment_schema.dump(appointment)

            return {"appointment": result.data,
                    "errors": result.errors}, 201

        else:
            return {"The Appointment Office ID and CSR Office ID do not match!"}, 403