Esempio n. 1
0
class AppointmentList(Resource):

    appointment_schema = AppointmentSchema(many=True)

    @oidc.accept_token(require_token=True)
    @has_any_role(roles=[Role.internal_user.value])
    def get(self):

        csr = CSR.find_by_username(g.oidc_token_info['username'])
        appt_limit_int = int(appt_limit)
        # today's date and time
        dt = datetime.now()
        upper_dt = dt - timedelta(days=appt_limit_int)
        filter_date = pytz.utc.localize(upper_dt)
        # print("filter_date",filter_date)
        try:
            appointments = Appointment.query.filter_by(office_id=csr.office_id)\
                                            .filter(Appointment.start_time >= filter_date)\
                                            .all()
            result = self.appointment_schema.dump(appointments)

            return {"appointments": result.data, "errors": result.errors}, 200

        except exc.SQLAlchemyError as error:
            logging.error(error, exc_info=True)
            return {"message": "API is down"}, 500
class AppointmentPut(Resource):

    appointment_schema = AppointmentSchema()

    @oidc.accept_token(require_token=True)
    def put(self, id):

        print("==> In Python PUT /appointments/<id>/ endpoint")

        csr = CSR.find_by_username(g.oidc_token_info['username'])

        json_data = request.get_json()

        if not json_data:
            return  {"message": "No input data received for updating an appointment"}

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

        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()

        result = self.appointment_schema.dump(appointment)

        return {"appointment": result.data,
                    "errors": result.errors}, 200
class AppointmentPost(Resource):

    appointment_schema = AppointmentSchema()

    @oidc.accept_token(require_token=True)
    @api_call_with_retry
    def post(self):

        csr = CSR.find_by_username(g.oidc_token_info['username'])
        json_data = request.get_json()

        if not json_data:
            return {
                "message": "No input data received for creating an appointment"
            }, 400

        appointment, warning = self.appointment_schema.load(json_data)

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

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

            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
class AppointmentDraftDelete(Resource):

    appointment_schema = AppointmentSchema()

    def delete(self, id):

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

        return {}, 204
class AppointmentDraftFlush(Resource):

    appointment_schema = AppointmentSchema(many=True)

    # This call is intentionally unauthenticated, as all it does is delete
    # all drafts older than 30min.  It takes no input params and can be called
    # as much as we want since it's idempotent (except for current time)
    def post(self):
        my_print(
            "==> In AppointmentDraftFlush, POST /appointments/draft/flush")
        draft_ids = Appointment.delete_expired_drafts()
        return {"deleted_draft_ids": draft_ids}, 200
class AppointmentDelete(Resource):
    appointment_schema = AppointmentSchema()

    @oidc.accept_token(require_token=True)
    @has_any_role(
        roles=[Role.internal_user.value, Role.online_appointment_user.value])
    def delete(self, id):

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

        csr = None if is_public_user() else CSR.find_by_username(
            g.oidc_token_info['username'])

        user: PublicUser = PublicUser.find_by_username(
            g.oidc_token_info['username']) if is_public_user() else None
        if is_public_user():
            # Check if it's a public user
            citizen = Citizen.find_citizen_by_id(appointment.citizen_id)
            if not citizen or citizen.citizen_id != appointment.citizen_id:
                abort(403)

        # Must call this prior to deleting from DB, so cannot
        # combine with repeated is_draft check below
        if not appointment.is_draft:
            SnowPlow.snowplow_appointment(None, csr, appointment,
                                          'appointment_delete')

        db.session.delete(appointment)
        db.session.commit()

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

        # Do not log snowplow events or send emails if it's a draft.
        if not appointment.is_draft:

            # If the appointment is public user's and if staff deletes it send email
            if csr:
                office = Office.find_by_id(appointment.office_id)

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

        return {}, 204
Esempio n. 7
0
class AppointmentDraftDelete(Resource):

    appointment_schema = AppointmentSchema()

    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
class AppointmentDraftDelete(Resource):

    appointment_schema = AppointmentSchema()

    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
Esempio n. 9
0
class CitizenRemoveFromQueue(Resource):

    citizen_schema = CitizenSchema()
    appointment_schema = AppointmentSchema()

    @jwt.has_one_of_roles([Role.internal_user.value])
    @api_call_with_retry
    def post(self, id):
        csr = CSR.find_by_username(g.jwt_oidc_token_info['username'])
        citizen = Citizen.query.filter_by(citizen_id=id).first()
        active_service_request = citizen.get_active_service_request()

        my_print("==> POST /citizens/" + str(citizen.citizen_id) +
                 '/remove_from_queue, Ticket: ' + citizen.ticket_number)

        if active_service_request is None:
            return {"message": "Citizen has no active service requests"}

        appointment = Appointment.query.filter_by(citizen_id=id) \
            .filter_by(office_id=csr.office_id) \
            .filter(Appointment.checked_in_time.isnot(None)) \
            .first_or_404()

        # This "un-check-in"s the appointment, returning it to calendar and removing from the queue.
        appointment.checked_in_time = None

        # Delete all "periods", FKs on service req
        active_service_request.remove_from_queue()
        # Delete the service req.
        db.session.delete(active_service_request)
        db.session.commit()

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

        socketio.emit('update_customer_list', {}, room=csr.office_id)
        socketio.emit('citizen_invited', {},
                      room='sb-%s' % csr.office.office_number)
        result = self.appointment_schema.dump(appointment)

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

        return {
            "appointment": result,
            "errors": self.appointment_schema.validate(appointment)
        }, 200
Esempio n. 10
0
class AppointmentPost(Resource):

    appointment_schema = AppointmentSchema()
    citizen_schema = CitizenSchema()

    @oidc.accept_token(require_token=True)
    @api_call_with_retry
    def post(self):

        csr = CSR.find_by_username(g.oidc_token_info['username'])

        #  Create a citizen for later use.
        citizen = self.citizen_schema.load({}).data
        citizen.office_id = csr.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()

        json_data = request.get_json()

        if not json_data:
            return {
                "message": "No input data received for creating an appointment"
            }, 400

        appointment, warning = self.appointment_schema.load(json_data)

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

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

            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
Esempio n. 11
0
class AppointmentDelete(Resource):

    appointment_schema = AppointmentSchema()

    @oidc.accept_token(require_token=True)
    def delete(self, id):

        csr = CSR.find_by_username(g.oidc_token_info["username"])

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

        db.session.delete(appointment)
        db.session.commit()
        return {}, 204
Esempio n. 12
0
class UserAppointments(Resource):
    appointments_schema = AppointmentSchema(many=True)

    @jwt.has_one_of_roles([Role.online_appointment_user.value])
    def get(self):

        # Get all appointments for the citizen
        try:
            appointments = PublicUser.find_appointments_by_username(
                g.jwt_oidc_token_info['username'])

            result = self.appointments_schema.dump(appointments)
            return {'appointments': result}

        except exc.SQLAlchemyError as e:
            print(e)
            return {'message': 'API is down'}, 500
Esempio n. 13
0
class AppointmentDelete(Resource):

    appointment_schema = AppointmentSchema()

    @oidc.accept_token(require_token=True)
    @has_any_role(
        roles=[Role.internal_user.value, Role.online_appointment_user.value])
    def delete(self, id):

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

        csr = None if is_public_user() else CSR.find_by_username(
            g.oidc_token_info['username'])

        user: PublicUser = PublicUser.find_by_username(
            g.oidc_token_info['username']) if is_public_user() else None
        if is_public_user():
            # Check if it's a public user
            citizen = Citizen.find_citizen_by_id(appointment.citizen_id)
            if not citizen or citizen.citizen_id != appointment.citizen_id:
                abort(403)

        SnowPlow.snowplow_appointment(None, csr, appointment,
                                      'appointment_delete')

        db.session.delete(appointment)
        db.session.commit()

        # If the appointment is public user's and if staff deletes it send email
        if csr:
            office = Office.find_by_id(appointment.office_id)

            # Send blackout 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_cancel_email_contents(
                                appointment, user, office, office.timezone))
            thread.daemon = True
            thread.start()

        return {}, 204
class AppointmentDelete(Resource):

    appointment_schema = AppointmentSchema()

    @oidc.accept_token(require_token=True)
    def delete(self, id):

        print("==> In the Python DELETE /appointments/<id>/ endpoint")

        csr = CSR.find_by_username(g.oidc_token_info['username'])

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

        db.session.delete(appointment)
        db.session.commit()
        return {}, 204
class AppointmentRecurringDelete(Resource):

    appointment_schema = AppointmentSchema()

    @oidc.accept_token(require_token=True)
    def delete(self, id):

        csr = CSR.find_by_username(g.oidc_token_info['username'])

        appointments = Appointment.query.filter_by(recurring_uuid=id)\
                                        .filter_by(office_id=csr.office_id)\
                                        .all()

        for appointment in appointments:
            db.session.delete(appointment)
            db.session.commit()

        return {}, 204
Esempio n. 16
0
class AppointmentRecurringPut(Resource):

    appointment_schema = AppointmentSchema()

    @jwt.has_one_of_roles([Role.internal_user.value])
    def put(self, id):

        csr = CSR.find_by_username(g.jwt_oidc_token_info['username'])

        json_data = request.get_json()

        if not json_data:
            return {
                "message":
                "No input data received for updating an series of appointments"
            }

        appointments = Appointment.query.filter_by(recurring_uuid=id)\
                                  .filter_by(office_id=csr.office_id)\
                                  .all()

        for appointment in appointments:

            appointment = self.appointment_schema.load(json_data,
                                                       instance=appointment,
                                                       partial=True)
            warning = self.appointment_schema.validate(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(appointments)

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

        return {
            "appointments": result,
            "errors": self.appointment_schema.validate(appointments)
        }, 200
class AppointmentList(Resource):

    appointment_schema = AppointmentSchema(many=True)

    @oidc.accept_token(require_token=True)
    def get(self):

        csr = CSR.find_by_username(g.oidc_token_info["username"])

        try:
            appointments = Appointment.query.filter_by(office_id=csr.office_id).all()
            result = self.appointment_schema.dump(appointments)

            return {"appointments": result.data,
                    "errors": result.errors}, 200

        except exc.SQLAlchemyError as error:
            logging.error(error, exc_info=True)
            return {"message": "API is down"}, 500
Esempio n. 18
0
class AppointmentDetail(Resource):

    appointment_schema = AppointmentSchema()

    @oidc.accept_token(require_token=True)
    def get(self, id):

        csr = CSR.find_by_username(g.oidc_token_info['username'])

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

            result = self.appointment_schema.dump(appointment)

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

        except exc.SQLAlchemyError as error:
            logging.error(error, exc_info=True)
            return {"message": "API is down"}, 500
class AppointmentRecurringDelete(Resource):
    #for stat- to avoid multiple api call
    #delete STAT from all office

    appointment_schema = AppointmentSchema()

    @jwt.has_one_of_roles([Role.internal_user.value])
    def delete(self, id):

        appointments = Appointment.query.filter_by(recurring_uuid=id)\
                                        .all()

        for appointment in appointments:
            db.session.delete(appointment)
            db.session.commit()

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


        return {}, 204
Esempio n. 20
0
class AppointmentRecurringDelete(Resource):

    appointment_schema = AppointmentSchema()

    @jwt.has_one_of_roles([Role.internal_user.value])
    def delete(self, id):

        csr = CSR.find_by_username(g.jwt_oidc_token_info['username'])

        appointments = Appointment.query.filter_by(recurring_uuid=id)\
                                        .filter_by(office_id=csr.office_id)\
                                        .all()

        for appointment in appointments:
            db.session.delete(appointment)
            db.session.commit()

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

        return {}, 204
class CitizenRemoveFromQueue(Resource):

    citizen_schema = CitizenSchema()
    appointment_schema = AppointmentSchema()

    @oidc.accept_token(require_token=True)
    @has_any_role(roles=[Role.internal_user.value])
    @api_call_with_retry
    def post(self, id):
        csr = CSR.find_by_username(g.oidc_token_info['username'])
        citizen = Citizen.query.filter_by(citizen_id=id).first()
        active_service_request = citizen.get_active_service_request()

        my_print("==> POST /citizens/" + str(citizen.citizen_id) +
                 '/remove_from_queue, Ticket: ' + citizen.ticket_number)

        if active_service_request is None:
            return {"message": "Citizen has no active service requests"}

        appointment = Appointment.query.filter_by(citizen_id=id) \
            .filter_by(office_id=csr.office_id) \
            .filter(Appointment.checked_in_time.isnot(None)) \
            .first_or_404()

        # This "un-check-in"s the appointment, returning it to calendar and removing from the queue.
        appointment.checked_in_time = None
        db.session.commit()

        # ARC - Is below necessary? Think not.  Causes issue when re-checking in a removed one.
        # It DOES remove from queue, but stops it from being re-added?
        # active_service_request.remove_from_queue()

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

        result = self.appointment_schema.dump(appointment)

        return {"appointment": result.data, "errors": result.errors}, 200
Esempio n. 22
0
class AppointmentRecurringPut(Resource):

    appointment_schema = AppointmentSchema()

    @oidc.accept_token(require_token=True)
    @has_any_role(roles=[Role.internal_user.value])
    def put(self, id):

        csr = CSR.find_by_username(g.oidc_token_info['username'])

        json_data = request.get_json()

        if not json_data:
            return {
                "message":
                "No input data received for updating an series of appointments"
            }

        appointments = Appointment.query.filter_by(recurring_uuid=id)\
                                  .filter_by(office_id=csr.office_id)\
                                  .all()

        for appointment in appointments:

            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()

        result = self.appointment_schema.dump(appointments)

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

        return {"appointments": result.data, "errors": result.errors}, 200
Esempio n. 23
0
class AppointmentPut(Resource):

    appointment_schema = AppointmentSchema()

    @oidc.accept_token(require_token=True)
    def put(self, id):

        csr = CSR.find_by_username(g.oidc_token_info['username'])

        json_data = request.get_json()

        if not json_data:
            return {"message": "No input data received for updating an appointment"}

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

        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()

        #   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
class AppointmentDetail(Resource):

    appointment_schema = AppointmentSchema()

    @jwt.has_one_of_roles([Role.internal_user.value])
    def get(self, id):

        csr = CSR.find_by_username(g.jwt_oidc_token_info['username'])

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

            result = self.appointment_schema.dump(appointment)

            return {
                "appointment": result,
                "errors": self.appointment_schema.validate(appointment)
            }, 200

        except exc.SQLAlchemyError as error:
            logging.error(error, exc_info=True)
            return {"message": "API is down"}, 500
Esempio n. 25
0
class AppointmentPut(Resource):
    appointment_schema = AppointmentSchema()

    @oidc.accept_token(require_token=True)
    @has_any_role(roles=[Role.internal_user.value, Role.online_appointment_user.value])
    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
Esempio n. 26
0
class AppointmentPost(Resource):

    appointment_schema = AppointmentSchema()
    citizen_schema = CitizenSchema()

    @oidc.accept_token(require_token=True)
    @api_call_with_retry
    @has_any_role(roles=[Role.internal_user.value, Role.online_appointment_user.value])
    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
Esempio n. 27
0
class AppointmentPut(Resource):
    appointment_schema = AppointmentSchema()

    @oidc.accept_token(require_token=True)
    @has_any_role(
        roles=[Role.internal_user.value, Role.online_appointment_user.value])
    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
class AppointmentDraftPost(Resource):
    appointment_schema = AppointmentSchema()
    citizen_schema = CitizenSchema()

    # Un-authenticated call as it can happen before user has logged in
    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, 'jwt_oidc_token_info')
                and hasattr(g.jwt_oidc_token_info, 'username')):
            user = PublicUser.find_by_username(
                g.jwt_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, 'jwt_oidc_token_info')):
            csr = CSR.find_by_username(g.jwt_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

        warning = self.appointment_schema.validate(json_data)
        appointment = 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)

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

        return {"appointment": result, "warning": warning}, 201
Esempio n. 29
0
class WalkinDetail(Resource):

    citizen_schema = CitizenSchema()
    citizens_schema = CitizenSchema(many=True)
    appointment_schema = AppointmentSchema(many=True)
    office_schema = OfficeSchema()

    def get(self, id):
        try:
            citizen = Citizen.query.filter_by(walkin_unique_id=id).join(CitizenState)\
                                            .filter(CitizenState.cs_state_name == 'Active')\
                                            .order_by(Citizen.citizen_id.desc()).first()
            if citizen:
                res_list = []
                # office time zone
                local_timezone = self.get_my_office_timezone(citizen=citizen)

                # am i on hold
                am_on_hold = self.am_i_on_hold(citizen)

                show_estimate = application.config.get(
                    'SHOW_ESTIMATE_TIME_WALKIN', False)

                # result= all citizen in q
                result = self.get_all_citizen_in_q(citizen=citizen)
                # process result
                booked_check_app, walkin_app = self.process_all_citizen_in_q(
                    result, citizen, am_on_hold, local_timezone)

                # get all app from agenda panel
                result_in_book = self.get_all_app_from_agenda_panel(
                    citizen=citizen)
                # processing agenda panel appointmnets:
                booked_not_checkin = self.process_agenda_panel(
                    result_in_book, local_timezone)

                # sorting-maintaing the order group
                # serving people dont want see
                res_list = tuple(booked_check_app + booked_not_checkin +
                                 walkin_app)

                return {
                    'citizen': res_list,
                    'show_estimate': show_estimate
                }, 200
            return {}
        except exc.SQLAlchemyError as e:
            print(e)
            return {'message': 'API is down'}, 500

    def get_my_office_timezone(self, citizen=False, office=False):
        office_id = False
        local_timezone = False
        if citizen:
            office_id = citizen.office_id
        if office:
            office_id = office.office_id
        if office_id:
            my_office = Office.query.filter_by(office_id=office_id).first()
            my_office_data = self.office_schema.dump(my_office)
            if my_office_data:
                my_time_zone = my_office_data['timezone']['timezone_name']
                local_timezone = pytz.timezone(my_time_zone)
        return local_timezone

    def am_i_on_hold(self, citizen):
        my_result = self.citizen_schema.dump(citizen)
        am_on_hold = False
        citizen_service_reqs = my_result.get('service_reqs', [])
        for j in citizen_service_reqs:
            my_served_period = sorted(j['periods'],
                                      key=lambda x: x['period_id'],
                                      reverse=True)[0]
            if my_served_period and (my_served_period['ps']['ps_name']
                                     == 'On hold'):
                am_on_hold = True
        return am_on_hold

    def get_all_citizen_in_q(self, citizen=False, office=False):
        office_id = False
        result = []
        if citizen:
            office_id = citizen.office_id
        if office:
            office_id = office.office_id
        if office_id:
            all_citizen_in_q = Citizen.query.filter_by(office_id=office_id) \
                                            .join(CitizenState)\
                                            .filter(CitizenState.cs_state_name == 'Active')\
                                            .order_by(Citizen.priority) \
                                            .join(Citizen.service_reqs).all()
            result = self.citizens_schema.dump(all_citizen_in_q)
        return result

    def process_all_citizen_in_q(self, result, citizen, am_on_hold,
                                 local_timezone):
        booked_check_app = []
        walkin_app = []
        for each in result:
            data_dict = {}
            if bool(each.get('service_reqs', False)):
                for i in each['service_reqs']:
                    served_period = sorted(i['periods'],
                                           key=lambda x: x['period_id'],
                                           reverse=True)[0]
                    if served_period and (not (served_period['time_end']) and
                                          (served_period['ps']['ps_name']
                                           in ('Waiting', 'Invited'))):
                        not_booked_flag = False
                        data_dict = {}
                        data_dict['ticket_number'] = each.get(
                            'ticket_number', '')
                        data_dict['walkin_unique_id'] = each.get(
                            'walkin_unique_id', '')
                        if (each.get('citizen_comments', '')):
                            if '|||' in each['citizen_comments']:
                                data_dict['flag'] = 'booked_app'
                                booked_check_app.append(data_dict)
                                data_dict = {}
                                break
                            else:
                                not_booked_flag = True
                        else:
                            not_booked_flag = True
                        if (not_booked_flag
                                and each.get('cs', False)) and each['cs'].get(
                                    'cs_state_name', '') == 'Active':
                            each_time_obj = datetime.strptime(
                                each['start_time'], '%Y-%m-%dT%H:%M:%SZ')
                            # start
                            local_datetime_start = each_time_obj.replace(
                                tzinfo=pytz.utc).astimezone(local_timezone)
                            #end
                            local_datetime_end = citizen.start_time.replace(
                                tzinfo=pytz.utc).astimezone(local_timezone)
                            if am_on_hold or local_datetime_start <= local_datetime_end:
                                data_dict['flag'] = 'walkin_app'
                                walkin_app.append(data_dict)
                                data_dict = {}
                                break
        return booked_check_app, walkin_app

    def get_all_app_from_agenda_panel(self, citizen=False, office=False):
        office_id = False
        result_in_book = []
        if citizen:
            office_id = citizen.office_id
        if office:
            office_id = office.office_id
        if office_id:
            past_hour = datetime.utcnow() - timedelta(minutes=15)
            future_hour = datetime.utcnow() + timedelta(minutes=15)
            local_past = pytz.utc.localize(past_hour)
            local_future = pytz.utc.localize(future_hour)
            # getting agenda panel app
            appointments = Appointment.query.filter_by(office_id=office_id)\
                                        .filter(Appointment.start_time <= local_future)\
                                        .filter(Appointment.start_time >= local_past)\
                                        .filter(Appointment.checked_in_time == None)\
                                        .order_by(Appointment.start_time)\
                                        .all()
            result_in_book = self.appointment_schema.dump(appointments)
        return result_in_book

    def process_agenda_panel(self, result_in_book, local_timezone):
        booked_not_checkin = []
        for app in result_in_book:
            if not (app.get('is_draft', True)) and (app.get(
                    'blackout_flag',
                    'N') == 'N') and not (app.get('stat_flag', True)):
                data_dict = {}
                data_dict['flag'] = 'agenda_panel'
                data_dict['start_time'] = app.get('start_time', '')
                if data_dict['start_time'] and local_timezone:
                    if (len(data_dict['start_time']) >=
                            3) and ':' in data_dict['start_time'][-3]:
                        data_dict['start_time'] = '{}{}'.format(
                            data_dict['start_time'][:-3],
                            data_dict['start_time'][-2:])
                    utc_datetime = datetime.strptime(data_dict['start_time'],
                                                     '%Y-%m-%dT%H:%M:%S%z')
                    local_datetime = utc_datetime.replace(tzinfo=pytz.utc)
                    local_datetime = local_datetime.astimezone(local_timezone)
                    data_dict['start_time'] = local_datetime.strftime(
                        "%m/%d/%Y, %H:%M:%S")
                booked_not_checkin.append(data_dict)
        return booked_not_checkin
Esempio n. 30
0
class AppointmentPost(Resource):
    appointment_schema = AppointmentSchema()
    citizen_schema = CitizenSchema()

    @oidc.accept_token(require_token=True)
    @api_call_with_retry
    @has_any_role(
        roles=[Role.internal_user.value, Role.online_appointment_user.value])
    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