///<summary></summary> public static List <DateTime> GetSearchResults(long aptNum, DateTime afterDate, List <long> providerNums, int resultCount, TimeSpan beforeTime, TimeSpan afterTime) { if (beforeTime == TimeSpan.FromSeconds(0)) //if they didn't set a before time, set it to a large timespan so that we can use the same logic for checking appointment times. { beforeTime = TimeSpan.FromHours(25); //bigger than any time of day. } SearchBehaviorCriteria SearchType = (SearchBehaviorCriteria)PrefC.GetInt(PrefName.AppointmentSearchBehavior); List <DateTime> retVal = new List <DateTime>(); DateTime dayEvaluating = afterDate.AddDays(1); Appointment appointmentToAdd = Appointments.GetOneApt(aptNum); List <DateTime> potentialProvAppointmentTime; List <DateTime> potentialOpAppointmentTime; List <Operatory> opsListAll = Operatories.GetDeepCopy(); //all operatory Numbers List <Schedule> scheduleListAll = Schedules.GetTwoYearPeriod(dayEvaluating); // Schedules for the given day. List <Appointment> appointmentListAll = Appointments.GetForPeriodList(dayEvaluating, dayEvaluating.AddYears(2)); List <ScheduleOp> schedOpListAll = ScheduleOps.GetForSchedList(scheduleListAll); List <ApptSearchProviderSchedule> provScheds = new List <ApptSearchProviderSchedule>(); //Provider Bar, ProviderSched Bar, Date and Provider List <ApptSearchOperatorySchedule> operatrorySchedules = new List <ApptSearchOperatorySchedule>(); //filtered based on SearchType List <long> operatoryNums = new List <long>(); //more usefull than a list of operatories. for (int i = 0; i < opsListAll.Count; i++) { operatoryNums.Add(opsListAll[i].OperatoryNum); } while (retVal.Count < resultCount && dayEvaluating < afterDate.AddYears(2)) { potentialOpAppointmentTime = new List <DateTime>(); //clear or create //Providers------------------------------------------------------------------------------------------------------------------------------------- potentialProvAppointmentTime = new List <DateTime>(); //clear or create provScheds = Appointments.GetApptSearchProviderScheduleForProvidersAndDate(providerNums, dayEvaluating, scheduleListAll, appointmentListAll); for (int i = 0; i < provScheds.Count; i++) { for (int j = 0; j < 288; j++) //search every 5 minute increment per day { if (j + appointmentToAdd.Pattern.Length > 288) { break; } if (potentialProvAppointmentTime.Contains(dayEvaluating.AddMinutes(j * 5))) { continue; } bool addDateTime = true; for (int k = 0; k < appointmentToAdd.Pattern.Length; k++) { if ((provScheds[i].ProvBar[j + k] == false && appointmentToAdd.Pattern[k] == 'X') || provScheds[i].ProvSchedule[j + k] == false) { addDateTime = false; break; } } if (addDateTime) { potentialProvAppointmentTime.Add(dayEvaluating.AddMinutes(j * 5)); } } } if (SearchType == SearchBehaviorCriteria.ProviderTimeOperatory) //Handle Operatories here---------------------------------------------------------------------------- { operatrorySchedules = GetAllForDate(dayEvaluating, scheduleListAll, appointmentListAll, schedOpListAll, operatoryNums, providerNums); potentialOpAppointmentTime = new List <DateTime>(); //create or clear //for(int j=0;j<operatrorySchedules.Count;j++) {//for each operatory for (int i = 0; i < 288; i++) //search every 5 minute increment per day { if (i + appointmentToAdd.Pattern.Length > 288) //skip if appointment would span across midnight { break; } for (int j = 0; j < operatrorySchedules.Count; j++) //for each operatory //if(potentialOpAppointmentTime.Contains(dayEvaluating.AddMinutes(i*5))) {//skip if we already have this dateTime // break; //} { bool addDateTime = true; for (int k = 0; k < appointmentToAdd.Pattern.Length; k++) //check appointment against operatories { if (operatrorySchedules[j].OperatorySched[i + k] == false) { addDateTime = false; break; } } if (!addDateTime) { continue; } if (addDateTime) // && SearchType==SearchBehaviorCriteria.ProviderTimeOperatory) {//check appointment against providers available for the given operatory { bool provAvail = false; for (int k = 0; k < providerNums.Count; k++) { if (!operatrorySchedules[j].ProviderNums.Contains(providerNums[k])) { continue; } provAvail = true; for (int m = 0; m < appointmentToAdd.Pattern.Length; m++) { if ((provScheds[k].ProvBar[i + m] == false && appointmentToAdd.Pattern[m] == 'X') || provScheds[k].ProvSchedule[i + m] == false) //if provider bar time slot { provAvail = false; break; } } if (provAvail) //found a provider with an available operatory { break; } } if (provAvail && addDateTime) //operatory and provider are available { potentialOpAppointmentTime.Add(dayEvaluating.AddMinutes(i * 5)); } } else //not using SearchBehaviorCriteria.ProviderTimeOperatory { if (addDateTime) { potentialOpAppointmentTime.Add(dayEvaluating.AddMinutes(i * 5)); } } } } } //At this point the potentialOpAppointmentTime is already filtered and only contains appointment times that match both provider time and operatory time. switch (SearchType) { case SearchBehaviorCriteria.ProviderTime: //Add based on provider bars for (int i = 0; i < potentialProvAppointmentTime.Count; i++) { if (potentialProvAppointmentTime[i].TimeOfDay > beforeTime || potentialProvAppointmentTime[i].TimeOfDay < afterTime) { continue; } retVal.Add(potentialProvAppointmentTime[i]); //add one for this day break; //stop looking through potential times for today. } break; case SearchBehaviorCriteria.ProviderTimeOperatory: //add based on provider bar and operatory bar for (int i = 0; i < potentialOpAppointmentTime.Count; i++) { if (potentialOpAppointmentTime[i].TimeOfDay > beforeTime || potentialOpAppointmentTime[i].TimeOfDay < afterTime) { continue; } retVal.Add(potentialOpAppointmentTime[i]); //add one for this day break; //stop looking through potential times for today. } break; } dayEvaluating = dayEvaluating.AddDays(1); } return(retVal); }
///<summary>Gets open time slots based on the parameters passed in. ///Open time slots are found by looping through the passed in operatories and finding openings that can hold the entire appointment. ///Make sure that timePattern is always passed in utilizing 5 minute increments (no conversion will be applied to the pattern passed in). ///Providers passed in will be the only providers considered when looking for available time slots. ///Passing in a null clinic will only consider operatories with clinics set to 0 (unassigned). ///The timeslots on and between the Start and End dates passed in will be considered and potentially returned as available. ///Optionally set defNumApptType if looking for time slots for New Pat Appt which will apply the DefNum to all time slots found. ///Throws exceptions.</summary> public static List <TimeSlot> GetTimeSlotsForRange(DateTime dateStart, DateTime dateEnd, string timePattern, List <long> listProvNums , List <Operatory> listOperatories, List <Schedule> listSchedules, Clinic clinic, long defNumApptType = 0, Logger.IWriteLine log = null) { //No need to check RemotingRole; no call to db. //Order the operatories passed in by their ItemOrder just in case they were passed in all jumbled up. List <long> listOpNums = listOperatories.OrderBy(x => x.ItemOrder).Select(x => x.OperatoryNum).Distinct().ToList(); //Remove all schedules that fall outside of the date range passed in. Only consider the date right now, the time portion is handled later. listSchedules.RemoveAll(x => !x.SchedDate.Date.Between(dateStart.Date, dateEnd.Date)); List <Schedule> listProviderSchedules = listSchedules.FindAll(x => x.BlockoutType == 0); List <Schedule> listBlockoutSchedules = listSchedules.FindAll(x => x.BlockoutType > 0); //Get every single appointment for all operatories within our start and end dates for double booking and overlapping consideration. List <Appointment> listApptsForOps = Appointments.GetAppointmentsForOpsByPeriod(Operatories.GetDeepCopy(true).Select(x => x.OperatoryNum).ToList() , dateStart, dateEnd, log, listSchedules.GroupBy(x => x.ProvNum).Select(x => x.Key).ToList()); log?.WriteLine("listProviderSchedules:\r\n\t" + string.Join(",\r\n\t", listProviderSchedules.Select(x => x.ScheduleNum + " - " + x.SchedDate.ToShortDateString() + " " + x.StartTime)), LogLevel.Verbose); log?.WriteLine("listBlockoutSchedules:\r\n\t" + string.Join(",\r\n\t", listBlockoutSchedules.Select(x => x.ScheduleNum + " - " + x.SchedDate.ToShortDateString() + " " + x.StartTime)), LogLevel.Verbose); log?.WriteLine("listApptsForOps:\r\n\t" + string.Join(",\r\n\t", listApptsForOps.Select(x => x.AptNum + " - " + x.AptDateTime + " OpNum: " + x.Op)), LogLevel.Verbose); //We need to be conscious of double booking possibilities. Go get provider schedule information for the date range passed in. Dictionary <DateTime, List <ApptSearchProviderSchedule> > dictProvSchedules = Appointments.GetApptSearchProviderScheduleForProvidersAndDate( listProvNums, dateStart, dateEnd, listProviderSchedules, listApptsForOps); //Split up the operatory specific provider schedules from the dynamic ones because each will have different operatory logic. List <Schedule> listProviderSchedulesWithOp = listProviderSchedules.FindAll(x => x.Ops.Intersect(listOpNums).ToList().Count > 0); List <ScheduleOp> listScheduleOps = ScheduleOps.GetForSchedList(listProviderSchedules); //Now we need to get the dynamic schedules (not assigned to a specific operatory). List <Schedule> listProviderDynamicSchedules = listProviderSchedules.FindAll(x => !listScheduleOps.Exists(y => y.ScheduleNum == x.ScheduleNum)); //Now that we have found all possible valid schedules, find all the unique time slots from them. List <Schedule> listProviderSchedulesAll = new List <Schedule>(listProviderSchedulesWithOp); listProviderSchedulesAll.AddRange(listProviderDynamicSchedules); listProviderSchedulesAll = listProviderSchedulesAll.OrderBy(x => x.SchedDate).ToList(); List <TimeSlot> listAvailableTimeSlots = new List <TimeSlot>(); List <DateTime> listUniqueDays = new List <DateTime>(); int timeIncrement = PrefC.GetInt(PrefName.AppointmentTimeIncrement); //Loop through all schedules five minutes at a time to find time slots large enough that have no appointments and no blockouts within them. foreach (Schedule schedule in listProviderSchedulesAll) { DateTime dateSched = schedule.SchedDate; //Straight up ignore schedules in the past. This should not be possible but this is just in case. if (dateSched.Date < DateTime.Today) { continue; } if (!listUniqueDays.Contains(dateSched)) { listUniqueDays.Add(dateSched); } TimeSpan timeSchedStart = schedule.StartTime; TimeSpan timeSchedStop = schedule.StopTime; //Now, make sure that the start time is set to a starting time that makes sense with the appointment time increment preference. int minsOver = (timeSchedStart.Minutes) % timeIncrement; if (minsOver > 0) { int minsToAdd = timeIncrement - minsOver; timeSchedStart = timeSchedStart.Add(new TimeSpan(0, minsToAdd, 0)); } //Double check that we haven't pushed the start time past the stop time. if (timeSchedStart >= timeSchedStop) { continue; } //Figure out all possible operatories for this particular schedule. List <Operatory> listOpsForSchedule = new List <Operatory>(); if (schedule.Ops.Count > 0) { listOpsForSchedule = listOperatories.FindAll(x => schedule.Ops.Exists(y => y == x.OperatoryNum)); } else //Dynamic schedule. Figure out what operatories this provider is part of that are associated to the corresponding eService. //Get all of the valid operatories that this provider is associated with. { listOpsForSchedule = listOperatories.FindAll(x => x.ProvDentist == schedule.ProvNum || x.ProvHygienist == schedule.ProvNum); } if (PrefC.HasClinicsEnabled) { //Skip this schedule entry if the operatory's clinic does not match the patient's clinic. if (clinic == null) { //If a clinic was not passed in, ONLY consider unassigned operatories listOpsForSchedule = listOpsForSchedule.FindAll(x => x.ClinicNum == 0); } else { //If a valid clinic was passed in, make sure the operatory has a matching clinic. listOpsForSchedule = listOpsForSchedule.FindAll(x => x.ClinicNum == clinic.ClinicNum); } } if (listOpsForSchedule.Count == 0) { continue; //No valid operatories for this schedule. } log?.WriteLine("schedule: " + schedule.ScheduleNum + "\tlistOpsForSchedule:\r\n\t" + string.Join(",\r\n\t", listOpsForSchedule.Select(x => x.OperatoryNum + " - " + x.Abbrev)), LogLevel.Verbose); //The list of operatories has been filtered above so we need to find ALL available time slots for this schedule in all operatories. foreach (Operatory op in listOpsForSchedule) { AddTimeSlotsFromSchedule(listAvailableTimeSlots, schedule, op.OperatoryNum, timeSchedStart, timeSchedStop , listBlockoutSchedules, dictProvSchedules, listApptsForOps, timePattern, defNumApptType); } } //Remove any time slots that start before right now (just in case the consuming method is looking for slots for today). listAvailableTimeSlots.RemoveAll(x => x.DateTimeStart.Date == DateTime.Now.Date && x.DateTimeStart.TimeOfDay < DateTime.Now.TimeOfDay); //Order the entire list of available time slots so that they are displayed to the user in sequential order. //We need to do this because we loop through each provider's schedule one at a time and add openings as we find them. //Then order by operatory.ItemOrder in order to preserve old behavior (filling up the schedule via operatories from the left to the right). return(listAvailableTimeSlots.OrderBy(x => x.DateTimeStart) //listOpNums was ordered by ItemOrder at the top of this method so we can trust that it is in the correct order. .ThenBy(x => listOpNums.IndexOf(x.OperatoryNum)) .ToList()); }