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