Esempio n. 1
0
    def post(self):
        my_print("==> In AppointmentDraftPost, POST /appointments/draft")
        json_data = request.get_json()
        
        office_id = json_data.get('office_id')
        service_id = json_data.get('service_id')
        start_time = parse(json_data.get('start_time'))
        end_time = parse(json_data.get('end_time'))
        office = Office.find_by_id(office_id)
        service = Service.query.get(int(service_id)) if service_id else None

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

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

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



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

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

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

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

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

        return {"appointment": result.data, "warning" : warning}, 201
Esempio n. 2
0
    def get_available_slots(office: Office, days: [datetime], format_time: bool = True, service: Service = None):
        """Return the available slots for the office"""
        try:
            available_slots_per_day = {}
            if office.appointments_enabled_ind == 0:
                return available_slots_per_day

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

            return AvailabilityService.prune_appointments(available_slots_per_day)

        except exc.SQLAlchemyError as e:
            print(e)
            return {'message': 'API is down'}, 500
Esempio n. 3
0
    def get_available_slots(office: Office,
                            days: [datetime],
                            format_time: bool = True):
        """Return the available slots for the office"""
        try:
            available_slots_per_day = {}
            if office.appointments_enabled_ind == 0:
                return available_slots_per_day

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

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

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

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

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

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

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

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

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

            return AvailabilityService.prune_appointments(
                available_slots_per_day)

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