예제 #1
0
        ///<summary>Adds valid time slots to listAvailableTimeSlots if the time slot found does NOT already exist within the list.
        ///This is a helper method to better break up the complexity of GetAvailableWebSchedTimeSlots() so that it is easier to follow.
        ///Make sure that timePattern is always passed in utilizing 5 minute increments (no conversion will be applied to the pattern passed in).
        ///Optionally set defNumApptType if looking for time slots for New Pat Appt which will apply the DefNum to all time slots found.</summary>
        private static void AddTimeSlotsFromSchedule(List <TimeSlot> listAvailableTimeSlots, Schedule schedule, long operatoryNum
                                                     , TimeSpan timeSchedStart, TimeSpan timeSchedStop, List <Schedule> listBlockouts
                                                     , Dictionary <DateTime, List <ApptSearchProviderSchedule> > dictProvSchedules, List <Appointment> listApptsForOps, string timePattern
                                                     , long defNumApptType = 0)
        {
            //No need to check RemotingRole; no call to db and this is a private method.
            //Figure out how large of a time slot we need to find in order to consider this time slot "available".
            int      apptLengthMins = timePattern.Length * 5;
            int      timeIncrement  = PrefC.GetInt(PrefName.AppointmentTimeIncrement);
            DateTime dateSched      = schedule.SchedDate;
            //Start going through this operatories schedule according to the time increment, looking for a gap that can handle apptLengthMins.
            TimeSpan timeSlotStart = new TimeSpan(timeSchedStart.Ticks);

            //Start looking for collisions AFTER the start time.
            //Stop as soon as the slots stop time meets or passes the sched stop time.
            //Iterate through the schedule via the time increment preference.
            for (TimeSpan timeSlotStop = timeSchedStart.Add(new TimeSpan(0, timeIncrement, 0))
                 ; timeSlotStop <= timeSchedStop
                 ; timeSlotStop = timeSlotStop.Add(new TimeSpan(0, timeIncrement, 0)))
            {
                //Check to see if we've found an opening.
                TimeSpan timeSpanCur = timeSlotStop - timeSlotStart;
                //Check to see if there is an appointment or a blockout that collides with this blockout.
                bool isOverlapping = false;
                //First we'll look at blockouts because it should be quicker than looking at the appointments
                foreach (Schedule blockout in listBlockouts)
                {
                    if (dateSched.Date != blockout.SchedDate)
                    {
                        continue;                        //Blockout is not on the same day that we are looking at.
                    }
                    if (!blockout.Ops.Exists(x => x == operatoryNum))
                    {
                        continue;                        //Blockout is not present in this operatory.
                    }
                    //Same operatory and day, check if the times overlap.
                    //Create new TimeSpans in order to remove the date portion from the blockouts.
                    TimeSpan timeBlockoutStart = new TimeSpan(blockout.StartTime.Hours, blockout.StartTime.Minutes, 0);
                    TimeSpan timeBlockoutStop  = new TimeSpan(blockout.StopTime.Hours, blockout.StopTime.Minutes, 0);
                    if (IsTimeOverlapping(timeSlotStart, timeSlotStop, timeBlockoutStart, timeBlockoutStop))
                    {
                        isOverlapping = true;
                        break;
                    }
                }
                if (isOverlapping)                 //This check is here so that we don't waste time looping through appointments if we don't need to.
                //There was a collision, set the time slot start time to the stop time and continue from there.
                {
                    timeSlotStart = timeSlotStop;
                    continue;
                }
                //Next we'll look for overlapping appointments
                foreach (Appointment appointment in listApptsForOps)
                {
                    if (appointment.Op != operatoryNum)
                    {
                        continue;
                    }
                    if (dateSched.Date != appointment.AptDateTime.Date)
                    {
                        continue;                        //Appt is not on the same day that we are looking at.
                    }
                    //Same operatory and day, check if the times overlap.
                    TimeSpan timeApptStart = appointment.AptDateTime.TimeOfDay;
                    TimeSpan timeApptStop  = appointment.AptDateTime.AddMinutes(appointment.Pattern.Length * 5).TimeOfDay;
                    if (IsTimeOverlapping(timeSlotStart, timeSlotStop, timeApptStart, timeApptStop))
                    {
                        isOverlapping = true;
                        break;
                    }
                }
                if (isOverlapping)
                {
                    //There was a collision, set the time slot start time to the stop time and continue from there.
                    timeSlotStart = timeSlotStop;
                    continue;
                }
                if (timeSpanCur.TotalMinutes >= apptLengthMins)
                {
                    //We just found an opening.  Make sure we don't already have this time slot available.
                    DateTime dateTimeSlotStart = new DateTime(dateSched.Year, dateSched.Month, dateSched.Day, timeSlotStart.Hours, timeSlotStart.Minutes, 0);
                    DateTime dateTimeSlotStop  = new DateTime(dateSched.Year, dateSched.Month, dateSched.Day, timeSlotStop.Hours, timeSlotStop.Minutes, 0);
                    TimeSlot timeSlot          = new TimeSlot(dateTimeSlotStart, dateTimeSlotStop, operatoryNum, schedule.ProvNum, defNumApptType);
                    if (!listAvailableTimeSlots.Any(x => (x.DateTimeStart == dateTimeSlotStart && x.DateTimeStop == dateTimeSlotStop &&
                                                          x.ProvNum == schedule.ProvNum))) //We will return multiple time slots for the same time for different providers.
                    {
                        //This time slot is not already in our list of available time slots, check for double booking.
                        if (dictProvSchedules.ContainsKey(dateSched.Date))
                        {
                            long recallProvNum = schedule.ProvNum;
                            if (IsApptPatternDoubleBooked(dictProvSchedules[dateSched.Date], recallProvNum, timePattern, dateTimeSlotStart))
                            {
                                //There is a double booking conflict.  Do not add this time slot as a possibility.
                                //However, at this point we know that there are no appointment conflicts for the current time slot, only a double booking conflict.
                                //The appointment needs to scoot within the operatory to hopefully find the first available opening (unit test 86).
                                timeSlotStart = timeSlotStart.Add(new TimeSpan(0, timeIncrement, 0));
                                continue;
                            }
                        }
                        //There are no collisions with this provider's schedule, add it to our list of available time slots.
                        listAvailableTimeSlots.Add(timeSlot);
                    }
                    else
                    {
                        //We have found a time slot in another operatory that matches the necessary criteria.
                        //Check to see if this operatory should be considered before the previously found operatory.
                        TimeSlot  timeSlotCur  = listAvailableTimeSlots.First(x => (x.DateTimeStart == dateTimeSlotStart && x.DateTimeStop == dateTimeSlotStop));
                        Operatory operatoryIn  = Operatories.GetOperatory(operatoryNum);
                        Operatory operatoryCur = Operatories.GetOperatory(timeSlotCur.OperatoryNum);
                        if (operatoryIn.ItemOrder < operatoryCur.ItemOrder)
                        {
                            timeSlotCur.OperatoryNum = operatoryIn.OperatoryNum;
                        }
                    }
                    //Continue looking for more open slots starting at the end of this time slot.
                    //E.g. we just found 9:30 AM to 10:00 AM.  We need to continue from 10:00 AM.
                    timeSlotStart = timeSlotStop;
                    continue;
                }
            }
        }
예제 #2
0
        ///<summary>Adds valid time slots to listAvailableTimeSlots if the time slot found does NOT already exist within the list.
        ///This is a helper method to better break up the complexity of GetAvailableWebSchedTimeSlots() so that it is easier to follow.
        ///Make sure that timePattern is always passed in utilizing 5 minute increments (no conversion will be applied to the pattern passed in).
        ///Optionally set defNumApptType if looking for time slots for New Pat Appt which will apply the DefNum to all time slots found.</summary>
        private static void AddTimeSlotsFromSchedule(List <TimeSlot> listAvailableTimeSlots, Schedule schedule, long operatoryNum
                                                     , TimeSpan timeSchedStart, TimeSpan timeSchedStop, List <Schedule> listBlockouts
                                                     , Dictionary <DateTime, List <ApptSearchProviderSchedule> > dictProvSchedules, List <Appointment> listApptsForOps, string timePattern
                                                     , long defNumApptType = 0, bool isDoubleBookingAllowed = true)
        {
            //No need to check RemotingRole; no call to db and this is a private method.
            //Figure out how large of a time slot we need to find in order to consider this time slot "available".
            int      apptLengthMins = timePattern.Length * 5;
            int      timeIncrement  = PrefC.GetInt(PrefName.AppointmentTimeIncrement);
            DateTime dateSched      = schedule.SchedDate;
            //Filter out all blockouts that are not pertinent to this dateSched and operatoryNum combo.
            List <Schedule> listBlockoutsForDateAndOp = listBlockouts.FindAll(x => x.SchedDate.Date == dateSched.Date && x.Ops.Contains(operatoryNum));
            //Filter out all appointments that are not pertinent to this dateSched and operatoryNum combo.
            List <Appointment> listApptsForDateAndOp = listApptsForOps.FindAll(x => x.AptDateTime.Date == dateSched.Date && x.Op == operatoryNum);
            //Start going through this operatory's schedule according to the time increment, looking for a gap that can handle apptLengthMins.
            TimeSpan timeSlotStart = new TimeSpan(timeSchedStart.Ticks);
            //Make a list of all perfect world appointment starting times that we will use within our time slot finding loop to make our slot finding
            //more predictable and user friendly.  This is mainly for the scenario where offices manually schedule strange appointments throughout the day.
            //E.g. Searching for hour long time slots, a 15 min appt was manually scheduled for 09:55 - 10:10 which throws off the nice "on the hour" slots.
            //We want to have logic that will prefer to return time slots on the hour.  E.g. 8 - 9, 10:10 - 11: 10, 11 - 12 (note the overlap), 12 - 13...
            List <TimeSpan> listPerfectSlotStarts = new List <TimeSpan>();

            //The first perfect time slot start will always be when our schedule starts.
            for (TimeSpan timeSlotPerfect = new TimeSpan(timeSchedStart.Ticks)
                 ; timeSlotPerfect <= new TimeSpan(timeSchedStop.Ticks)
                 ; timeSlotPerfect = timeSlotPerfect.Add(new TimeSpan(0, apptLengthMins, 0)))
            {
                listPerfectSlotStarts.Add(timeSlotPerfect);
            }
            //Start looking for collisions AFTER the start time.
            //Stop as soon as the slots stop time meets or passes the sched stop time.
            //Iterate through the schedule via the time increment preference.
            for (TimeSpan timeSlotStop = timeSchedStart.Add(new TimeSpan(0, timeIncrement, 0))
                 ; timeSlotStop <= timeSchedStop
                 ; timeSlotStop = timeSlotStop.Add(new TimeSpan(0, timeIncrement, 0)))
            {
                //Check to see if we've found an opening.
                TimeSpan timeSpanCur = timeSlotStop - timeSlotStart;
                //Check to see if there is an appointment or a blockout that collides with this blockout.
                bool isOverlapping = false;
                #region Blockout Collisions
                TimeSpan timeBlockoutStart = new TimeSpan();
                TimeSpan timeBlockoutStop  = new TimeSpan();
                //First we'll look at blockouts because it should be quicker than looking at the appointments
                foreach (Schedule blockout in listBlockoutsForDateAndOp)
                {
                    //Create new TimeSpans in order to remove the date portion from the blockouts.
                    timeBlockoutStart = new TimeSpan(blockout.StartTime.Hours, blockout.StartTime.Minutes, 0);
                    timeBlockoutStop  = new TimeSpan(blockout.StopTime.Hours, blockout.StopTime.Minutes, 0);
                    if (IsTimeOverlapping(timeSlotStart, timeSlotStop, timeBlockoutStart, timeBlockoutStop))
                    {
                        isOverlapping = true;
                        break;
                    }
                }
                if (isOverlapping)                 //This check is here so that we don't waste time looping through appointments if we don't need to.
                //There was a collision with a blockout.  Set the time slot start time to the stop time of the blockout and continue from there.
                {
                    timeSlotStart = timeBlockoutStop;
                    continue;
                }
                #endregion
                #region Appointment Collisions
                TimeSpan timeApptStart = new TimeSpan();
                TimeSpan timeApptStop  = new TimeSpan();
                //Next we'll look for overlapping appointments
                foreach (Appointment appointment in listApptsForDateAndOp)
                {
                    timeApptStart = appointment.AptDateTime.TimeOfDay;
                    timeApptStop  = appointment.AptDateTime.AddMinutes(appointment.Pattern.Length * 5).TimeOfDay;
                    if (IsTimeOverlapping(timeSlotStart, timeSlotStop, timeApptStart, timeApptStop))
                    {
                        isOverlapping = true;
                        break;
                    }
                }
                if (isOverlapping)
                {
                    //There was a collision with an appointment.  Set the time slot start time to the stop time of the appointment and continue from there.
                    timeSlotStart = timeApptStop;
                    continue;
                }
                #endregion
                #region Opening Found
                if (timeSpanCur.TotalMinutes >= apptLengthMins)
                {
                    //We just found an opening.  Make sure we don't already have this time slot available.
                    DateTime dateTimeSlotStart = new DateTime(dateSched.Year, dateSched.Month, dateSched.Day, timeSlotStart.Hours, timeSlotStart.Minutes, 0);
                    DateTime dateTimeSlotStop  = new DateTime(dateSched.Year, dateSched.Month, dateSched.Day, timeSlotStop.Hours, timeSlotStop.Minutes, 0);
                    TimeSlot timeSlot          = new TimeSlot(dateTimeSlotStart, dateTimeSlotStop, operatoryNum, schedule.ProvNum, defNumApptType);
                    if (!listAvailableTimeSlots.Any(x => (x.DateTimeStart == dateTimeSlotStart && x.DateTimeStop == dateTimeSlotStop &&
                                                          x.ProvNum == schedule.ProvNum))) //We will return multiple time slots for the same time for different providers.
                    {
                        //This time slot is not already in our list of available time slots, check for double booking.
                        if (dictProvSchedules.ContainsKey(dateSched.Date))
                        {
                            long recallProvNum = schedule.ProvNum;
                            if (IsApptTimeSlotDoubleBooked(dictProvSchedules[dateSched.Date], listApptsForDateAndOp, recallProvNum, timePattern, dateTimeSlotStart
                                                           , defNumApptType, isDoubleBookingAllowed))
                            {
                                //There is a double booking conflict.  Do not add this time slot as a possibility.
                                //However, at this point we know that there are no appointment conflicts for the current time slot, only a double booking conflict.
                                //The appointment needs to scoot within the operatory to hopefully find the first available opening (unit test 86).
                                timeSlotStart = timeSlotStart.Add(new TimeSpan(0, timeIncrement, 0));
                                continue;
                            }
                        }
                        //There are no collisions with this provider's schedule, add it to our list of available time slots.
                        listAvailableTimeSlots.Add(timeSlot);
                        //Check to see if the time slot that was just added started on a "perfect starting time".
                        //If it didn't, we need to backtrack to the most recent "perfect starting time" and continue from there.
                        if (!listPerfectSlotStarts.Contains(timeSlotStart))
                        {
                            //Find the most recent "perfect starting time" that corresponds to the timeSlotStop that was just found.
                            //We then need to set both timeSlotStart AND timeSlotStop to the closest "perfect starting time" and continue from there.
                            //E.g. If apptLengthMins is 60 minutes, odds are we are looking for appointments that start on the hour.
                            //So if we just found an opening that started at 10:10 then we need to backtrack from the stopping time of 11:10 and find the closest
                            //"perfect starting time", 11:00 in this case, and continue searching from there.
                            timeSlotStart = listPerfectSlotStarts.Last(x => x.Subtract(timeSlotStop).TotalMinutes < 0);
                            timeSlotStop  = timeSlotStart;
                        }
                    }
                    else
                    {
                        //We have found a time slot in another operatory that matches the necessary criteria.
                        //Check to see if this operatory should be considered before the previously found operatory.
                        TimeSlot  timeSlotCur  = listAvailableTimeSlots.First(x => (x.DateTimeStart == dateTimeSlotStart && x.DateTimeStop == dateTimeSlotStop));
                        Operatory operatoryIn  = Operatories.GetOperatory(operatoryNum);
                        Operatory operatoryCur = Operatories.GetOperatory(timeSlotCur.OperatoryNum);
                        if (operatoryIn.ItemOrder < operatoryCur.ItemOrder)
                        {
                            timeSlotCur.OperatoryNum = operatoryIn.OperatoryNum;
                        }
                    }
                    //Continue looking for more open slots starting at the end of this time slot.
                    //E.g. we just found 9:30 AM to 10:00 AM.  We need to continue from 10:00 AM.
                    timeSlotStart = timeSlotStop;
                    continue;
                }
                #endregion
            }
        }