def delete(self, id): Appointment.delete_draft([id]) if not application.config['DISABLE_AUTO_REFRESH']: socketio.emit('appointment_delete', id) return {}, 204
def post(self): my_print("==> In AppointmentDraftPost, POST /appointments/draft") json_data = request.get_json() office_id = json_data.get('office_id') service_id = json_data.get('service_id') start_time = parse(json_data.get('start_time')) end_time = parse(json_data.get('end_time')) office = Office.find_by_id(office_id) service = Service.query.get(int(service_id)) if service_id else None # end_time can be null for CSRs when they click; whereas citizens know end-time. if not end_time: end_time = add_delta_to_time(start_time, minutes=office.appointment_duration, timezone=office.timezone.timezone_name) # Unauthenticated requests from citizens won't have name, so we set a fallback if (hasattr(g, 'oidc_token_info') and hasattr(g.oidc_token_info, 'username')): user = PublicUser.find_by_username(g.oidc_token_info['username']) citizen_name = user.display_name else: citizen_name = 'Draft' # Delete all expired drafts before checking availability Appointment.delete_expired_drafts() csr = None if (hasattr(g, 'oidc_token_info')): csr = CSR.find_by_username(g.oidc_token_info['username']) # CSRs are not limited by drafts, can always see other CSRs drafts # This mitigates two CSRs in office creating at same time for same meeting # Ensure there's no race condition when submitting a draft if not csr and not AvailabilityService.has_available_slots( office=office, start_time=start_time, end_time=end_time, service=service): return {"code": "CONFLICT_APPOINTMENT", "message": "Cannot create appointment due to scheduling conflict. Please pick another time."}, 400 # Set draft specific data json_data['is_draft'] = True json_data['citizen_name'] = citizen_name appointment, warning = self.appointment_schema.load(json_data) if warning: logging.warning("WARNING: %s", warning) return {"message": warning}, 422 db.session.add(appointment) db.session.commit() result = self.appointment_schema.dump(appointment) socketio.emit('appointment_create', result.data) return {"appointment": result.data, "warning" : warning}, 201
def delete(self, id): appointment = Appointment.query.filter_by(appointment_id=id)\ .first_or_404() Appointment.delete_draft([id]) socketio.emit('appointment_delete', id) return {}, 204
def delete(self, id): appointment = Appointment.query.filter_by(appointment_id=id)\ .first_or_404() Appointment.delete_draft([id]) if not application.config['DISABLE_AUTO_REFRESH']: socketio.emit('appointment_delete', id) return {}, 204
def post(self): """Create appointment reminders.""" appointments = Appointment.find_next_day_appointments() print('sending {} reminders'.format(len(appointments))) if appointments: for (appointment, office, timezone, user) in appointments: send_reminder = False if user and user.send_reminders: send_reminder = True elif not user and is_valid_email( appointment.contact_information): send_reminder = True if send_reminder: @copy_current_request_context def async_email(subject, email, sender, body): send_email(subject, email, sender, body) thread = Thread(target=async_email, args=get_reminder_email_contents( appointment, user, office, timezone)) thread.daemon = True thread.start()
def get(self): """Return appointment reminders for next day.""" appointments = Appointment.find_next_day_appointments() # Construct a custom response as teh payload can grow in size. reminders = { 'appointments': [] } if appointments: for (appointment, office, timezone, user) in appointments: send_reminder = False if user and user.send_reminders: send_reminder = True elif not user and is_valid_email(appointment.contact_information): send_reminder = True if send_reminder: date, day = formatted_date(appointment.start_time, timezone) reminders['appointments'].append( { 'formatted_date': date, 'day': day, 'email': get_email(user, appointment), 'display_name': appointment.citizen_name, 'location': office.office_name, 'duration': get_duration(appointment.start_time, appointment.end_time), 'telephone': office.telephone } ) return reminders
def post(self): my_print( "==> In AppointmentDraftFlush, POST /appointments/draft/flush") draft_ids = Appointment.delete_expired_drafts() return {"deleted_draft_ids": draft_ids}, 200
def get(self, reminder_type: str = 'email'): """Return appointment reminders for next day.""" appointments = Appointment.find_next_day_appointments() # Construct a custom response as teh payload can grow in size. reminders = {'appointments': []} if appointments: for (appointment, office, timezone, user) in appointments: send_reminder = False if reminder_type == 'email': if user and user.send_email_reminders: send_reminder = True elif not user and is_valid_email( appointment.contact_information): send_reminder = True elif reminder_type == 'sms': if user and user.send_sms_reminders: send_reminder = True user_telephone = user.telephone elif not user and is_valid_phone( appointment.contact_information): send_reminder = True user_telephone = appointment.contact_information if send_reminder: if reminder_type == 'email': date, day = formatted_date(appointment.start_time, timezone) else: date, day = format_sms_date(appointment.start_time, timezone), None office_email_paragraph = appointment.office.office_email_paragraph if office_email_paragraph: office_email_paragraph = office_email_paragraph.replace( '\r\n', '<br />') service_email_paragraph = appointment.service.email_paragraph if service_email_paragraph: service_email_paragraph = service_email_paragraph.replace( '\r\n', '<br />') service_name = appointment.service.external_service_name \ if appointment.service.external_service_name else appointment.service.service_name reminders['appointments'].append({ 'formatted_date': date, 'day': day, 'email': get_email(user, appointment), 'display_name': appointment.citizen_name, 'location': office.office_name, 'duration': get_duration(appointment.start_time, appointment.end_time), 'telephone': office.telephone, 'service_email_paragraph': service_email_paragraph, 'office_email_paragraph': office_email_paragraph, 'service_name': service_name, 'civic_address': appointment.office.civic_address, 'user_telephone': user_telephone, }) return reminders
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
def get_available_slots(office: Office, days: [datetime], format_time: bool = True, service: Service = None): """Return the available slots for the office""" try: available_slots_per_day = {} if office.appointments_enabled_ind == 0: return available_slots_per_day # find appointment duration per office and fetch timeslot master data appointment_duration = office.appointment_duration # If user has passed in service and it has duration, use that instead if (service and service.timeslot_duration): appointment_duration = service.timeslot_duration service_is_dltk = service and service.is_dlkt == YesNo.YES # Dictionary to store the available slots per day tz = pytz.timezone(office.timezone.timezone_name) # today's date and time today = datetime.datetime.now().astimezone(tz) # soonest a citizen can book an appointment soonest_appointment_date = today + datetime.timedelta(minutes = office.soonest_appointment or 0) # Find all appointments between the dates appointments = Appointment.find_appointment_availability(office_id=office.office_id, first_date=today, last_date=days[-1], timezone=office.timezone.timezone_name) grouped_appointments = AvailabilityService.group_appointments(appointments, office.timezone.timezone_name) # For each of the day calculate the slots based on time slots for day_in_month in days: formatted_date = day_in_month.strftime('%m/%d/%Y') available_slots_per_day[formatted_date] = [] for timeslot in office.timeslots: # Calculate the slots per day timeslot_end_time = timeslot.end_time.replace(tzinfo=tz) timeslot_start_time = timeslot.start_time.replace(tzinfo=tz) if day_in_month.isoweekday() in day_indexes(timeslot.day_of_week): start_time = timeslot_start_time end_time = add_delta_to_time(timeslot_start_time, minutes=appointment_duration, timezone=office.timezone.timezone_name) # Cannot exceed office timeslot slots. dlkt_slots = office.number_of_dlkt or 0 if ( dlkt_slots > timeslot.no_of_slots): dlkt_slots = timeslot.no_of_slots # Limit DLKT slots only for DLKT services. no_of_slots = timeslot.no_of_slots while end_time <= timeslot_end_time: slot = { 'start_time': start_time, 'end_time': end_time, 'no_of_slots': no_of_slots, 'no_of_dlkt_slots': dlkt_slots } # Check if today's time is past appointment slot # Arc - also check if in office.soonest_appointment if ((day_in_month.date() == soonest_appointment_date.date() and start_time >= soonest_appointment_date.time()) or day_in_month.date() > soonest_appointment_date.date()): if slot not in available_slots_per_day[formatted_date]: available_slots_per_day[formatted_date].append(slot) start_time = end_time.replace(tzinfo=tz) end_time = add_delta_to_time(end_time, minutes=appointment_duration, timezone=office.timezone.timezone_name) # Sort the slot by time for the day available_slots_per_day[formatted_date].sort(key=lambda x: x['start_time']) # Check if the slots are already booked for actual_slot in available_slots_per_day[formatted_date]: booked_slots = 0 booked_dlkt_slots = 0 for booked_slot in grouped_appointments.get(formatted_date, []): if booked_slot.get('start_time') \ <= actual_slot.get('start_time') \ < booked_slot.get('end_time') \ or \ actual_slot.get('end_time') \ > booked_slot.get('start_time') \ >= actual_slot.get('start_time'): if booked_slot.get('blackout_flag', False): # If it's blackout override the no of slots actual_slot['no_of_slots'] = 0 else: if booked_slot['is_dlkt']: booked_dlkt_slots += 1 else: booked_slots += 1 if service_is_dltk: dlkt_nos = actual_slot['no_of_dlkt_slots'] - booked_dlkt_slots if actual_slot['no_of_slots'] <= (booked_slots + booked_dlkt_slots): actual_slot['no_of_slots'] = 0 elif actual_slot['no_of_slots'] - booked_slots >= dlkt_nos: actual_slot['no_of_slots'] = dlkt_nos else: actual_slot['no_of_slots'] = dlkt_nos - (actual_slot['no_of_slots'] - booked_slots) else: actual_slot['no_of_slots'] = actual_slot['no_of_slots'] - (booked_slots + booked_dlkt_slots) del actual_slot['no_of_dlkt_slots'] # no need to expose if format_time: # If true send formatted time actual_slot['start_time'] = actual_slot['start_time'].strftime('%H:%M') actual_slot['end_time'] = actual_slot['end_time'].strftime('%H:%M') return AvailabilityService.prune_appointments(available_slots_per_day) except exc.SQLAlchemyError as e: print(e) return {'message': 'API is down'}, 500
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
def get_available_slots(office: Office, days: [datetime], format_time: bool = True): """Return the available slots for the office""" try: available_slots_per_day = {} if office.appointments_enabled_ind == 0: return available_slots_per_day # find appointment duration per office and fetch timeslot master data appointment_duration = office.appointment_duration # Dictionary to store the available slots per day tz = pytz.timezone(office.timezone.timezone_name) # today's date and time today = datetime.datetime.now().astimezone(tz) # Find all appointments between the dates appointments = Appointment.find_appointment_availability( office_id=office.office_id, first_date=today, last_date=days[-1], timezone=office.timezone.timezone_name) grouped_appointments = AvailabilityService.group_appointments( appointments, office.timezone.timezone_name) # For each of the day calculate the slots based on time slots for day_in_month in days: formatted_date = day_in_month.strftime('%m/%d/%Y') available_slots_per_day[formatted_date] = [] for timeslot in office.timeslots: # Calculate the slots per day timeslot_end_time = timeslot.end_time.replace(tzinfo=tz) timeslot_start_time = timeslot.start_time.replace( tzinfo=tz) if day_in_month.isoweekday() in day_indexes( timeslot.day_of_week): start_time = timeslot_start_time end_time = add_delta_to_time( timeslot_start_time, minutes=appointment_duration, timezone=office.timezone.timezone_name) # print(start_time, end_time) while end_time <= timeslot_end_time: slot = { 'start_time': start_time, 'end_time': end_time, 'no_of_slots': timeslot.no_of_slots } # Check if today's time is past appointment slot if not (today.date() == day_in_month.date() and today.time() > start_time): available_slots_per_day[formatted_date].append( slot) start_time = end_time.replace(tzinfo=tz) end_time = add_delta_to_time( end_time, minutes=appointment_duration, timezone=office.timezone.timezone_name) # Check if the slots are already booked for actual_slot in available_slots_per_day[formatted_date]: for booked_slot in grouped_appointments.get( formatted_date, []): # print('>>>>>>', booked_slot.get('start_time'), actual_slot.get('start_time'), booked_slot.get('end_time')) # print('<<<<<<', booked_slot.get('end_time'), actual_slot.get('end_time'), # booked_slot.get('start_time')) if booked_slot.get('start_time') \ <= actual_slot.get('start_time') \ < booked_slot.get('end_time') \ or \ booked_slot.get('end_time') \ < actual_slot.get('end_time') \ <= booked_slot.get('start_time'): if booked_slot.get( 'blackout_flag', False ): # If it's blackout override the no of slots actual_slot['no_of_slots'] = 0 else: actual_slot['no_of_slots'] -= 1 if format_time: # If true send formatted time actual_slot['start_time'] = actual_slot[ 'start_time'].strftime('%H:%M') actual_slot['end_time'] = actual_slot[ 'end_time'].strftime('%H:%M') return AvailabilityService.prune_appointments( available_slots_per_day) except exc.SQLAlchemyError as e: print(e) return {'message': 'API is down'}, 500
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
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