///<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; } } }