Exemplo n.º 1
0
        ///<summary>Gets up to 30 days of open time slots based on the RecallType passed in.
        ///Open time slots are found by looping through operatories flagged for Web Sched and finding openings that can hold the RecallType.
        ///The RecallType passed in must be a valid recall type.
        ///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 pass in a recall object in order to consider all other recalls due for the patient.  This will potentially affect the time pattern.
        ///Throws exceptions.</summary>
        public static List <TimeSlot> GetAvailableWebSchedTimeSlots(RecallType recallType, List <Provider> listProviders, Clinic clinic
                                                                    , DateTime dateStart, DateTime dateEnd, Recall recallCur = null, Logger.IWriteLine log = null)
        {
            //No need to check RemotingRole; no call to db.
            if (recallType == null)           //Validate that recallType is not null.
            {
                throw new ODException(Lans.g("WebSched", "The recall appointment you are trying to schedule is no longer available.") + "\r\n"
                                      + Lans.g("WebSched", "Please call us to schedule your appointment."));
            }
            //Get all the Operatories that are flagged for Web Sched.
            List <Operatory> listOperatories = Operatories.GetOpsForWebSched();

            if (listOperatories.Count < 1)             //This is very possible for offices that aren't set up the way that we expect them to be.
            {
                throw new ODException(Lans.g("WebSched", "There are no operatories set up for Web Sched.") + "\r\n"
                                      + Lans.g("WebSched", "Please call us to schedule your appointment."), ODException.ErrorCodes.NoOperatoriesSetup);
            }
            log?.WriteLine("listOperatories:\r\n\t" + string.Join(",\r\n\t", listOperatories.Select(x => x.OperatoryNum + " - " + x.Abbrev)), LogLevel.Verbose);
            List <long>     listProvNums  = listProviders.Select(x => x.ProvNum).Distinct().ToList();
            List <Schedule> listSchedules = Schedules.GetSchedulesAndBlockoutsForWebSched(listProvNums, dateStart, dateEnd, true
                                                                                          , (clinic == null) ? 0 : clinic.ClinicNum, log);

            log?.WriteLine("listSchedules:\r\n\t" + string.Join(",\r\n\t", listSchedules.Select(x => x.ScheduleNum + " - " + x.SchedDate + " " + x.StartTime))
                           , LogLevel.Verbose);
            string timePatternRecall = recallType.TimePattern;

            //Apparently scheduling this one recall can potentially schedule a bunch of other recalls at the same time.
            //We need to potentially bloat our time pattern based on the other recalls that are due for this specific patient.
            if (recallCur != null)
            {
                Patient       patCur      = Patients.GetPat(recallCur.PatNum);
                List <Recall> listRecalls = Recalls.GetList(recallCur.PatNum);
                timePatternRecall = Recalls.GetRecallTimePattern(recallCur, listRecalls, patCur, new List <string>());
            }
            string timePatternAppointment = RecallTypes.ConvertTimePattern(timePatternRecall);

            return(GetTimeSlotsForRange(dateStart, dateEnd, timePatternAppointment, listProvNums, listOperatories, listSchedules, clinic, log: log,
                                        isDoubleBookingAllowed: PrefC.GetInt(PrefName.WebSchedRecallDoubleBooking) == 0));//is double booking allowed according to the preference
        }
Exemplo n.º 2
0
        ///<summary>Gets up to 30 days of open time slots based on the recall passed in.
        ///Open time slots are found by looping through operatories flagged for Web Sched and finding openings that can hold the recall.
        ///The amount of time required to be considered "available" is dictated by the RecallType associated to the recall passed in.
        ///Throws exceptions.</summary>
        public static List <TimeSlot> GetAvailableWebSchedTimeSlots(long recallNum, DateTime dateStart, DateTime dateEnd, long provNum = 0,
                                                                    bool allowOtherProv = true, Logger.IWriteLine log = null)
        {
            //No need to check RemotingRole; no call to db.
            Clinic clinic = Clinics.GetClinicForRecall(recallNum);
            Recall recall = Recalls.GetRecall(recallNum);

            if (recall == null)
            {
                throw new ODException(Lans.g("WebSched", "The recall appointment you are trying to schedule is no longer available.") + "\r\n"
                                      + Lans.g("WebSched", "Please call us to schedule your appointment."));
            }
            List <Provider> listProviders = Providers.GetProvidersForWebSched(recall.PatNum);

            if (provNum > 0 && !allowOtherProv)
            {
                listProviders = listProviders.FindAll(x => x.ProvNum == provNum);
            }
            log?.WriteLine("listProviders:\r\n\t" + string.Join(",\r\n\t", listProviders.Select(x => x.ProvNum + " - " + x.Abbr)), LogLevel.Verbose);
            RecallType recallType = RecallTypes.GetFirstOrDefault(x => x.RecallTypeNum == recall.RecallTypeNum);

            return(GetAvailableWebSchedTimeSlots(recallType, listProviders, clinic, dateStart, dateEnd, recall, log));
        }
Exemplo n.º 3
0
        ///<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());
        }
Exemplo n.º 4
0
 ///<summary>Write to _logStr and the test output window.</summary>
 protected void LogWriteLine(string log)
 {
     _log.WriteLine(log, LogLevel.Verbose);
 }
Exemplo n.º 5
0
        /// <summary>This method grabs all unread webmails, and creates/modifies/deletes alerts for the providers and linked users the webmails are addressed to.</summary>
        public static void CreateAlertsForNewWebmail(Logger.IWriteLine log)
        {
            //This method first collect all unread webmails, and counts how many each provider has.
            //It then fetches all WebMailRecieved alerts, and will create/modify alerts for each provider who was counted before.
            //Finally, it will sync the alerts in the database with the ones we created.
            //If the user has unread webmail and an existing alert, it is modified.
            //If the user has unread webmail and no existing alert, an alert is created.
            //If the user has no unread webmail and an existing alert, it will be deleted.
            //Key: ProvNum, Value: Number of unread webmails
            Dictionary <long, long> dictRecievedCount = EmailMessages.GetProvUnreadWebMailCount();

            log.WriteLine("Collected Webmails for the following providers (ProvNum: # Webmails): "
                          + String.Join(", ", dictRecievedCount.Select(x => POut.Long(x.Key) + ":" + POut.Long(x.Value))), LogLevel.Verbose);
            //This list contains every single WebMailRecieved alert and is synced with listAlerts later.
            List <AlertItem> listOldAlerts = AlertItems.RefreshForType(AlertType.WebMailRecieved);

            log.WriteLine("Fetched current alerts for users: " + String.Join(", ", listOldAlerts.Select(x => POut.Long(x.UserNum))), LogLevel.Verbose);
            //If a user doesn't have any unread webmail, they won't be placed on this list, and any alert they have in listOldAlerts will be deleted.
            List <AlertItem> listAlerts = new List <AlertItem>();
            List <long>      listChangedAlertItemNums = new List <long>();

            //Go through each provider value, and create/update alerts for each patnum under that provider.
            //There will only be a value if they have atleast 1 unread webmail.
            foreach (KeyValuePair <long, long> kvp in dictRecievedCount)
            {
                List <Userod> listUsers = Providers.GetAttachedUsers(kvp.Key);
                //Go through each usernum and create/update their alert item.
                foreach (long usernum in listUsers.Select(x => x.UserNum))
                {
                    AlertItem alertForUser = listOldAlerts.FirstOrDefault(x => x.UserNum == usernum);
                    //If an alert doesn't exist for the user, we'll create it.
                    if (alertForUser == null)
                    {
                        alertForUser             = new AlertItem();
                        alertForUser.Type        = AlertType.WebMailRecieved;
                        alertForUser.FormToOpen  = FormType.FormEmailInbox;
                        alertForUser.Actions     = ActionType.MarkAsRead | ActionType.OpenForm; //Removed delete because the alert will just be re-added next time it checks.
                        alertForUser.Severity    = SeverityType.Normal;
                        alertForUser.ClinicNum   = -1;                                          //The alert is user dependent, not clinic dependent.
                        alertForUser.UserNum     = usernum;
                        alertForUser.Description = POut.Long(kvp.Value);
                        listAlerts.Add(alertForUser);
                        log.WriteLine("Created webmail alert for user " + POut.Long(usernum), LogLevel.Verbose);
                    }
                    else
                    {
                        //If the alert already exists, we'll be updating it and usually mark it as unread.
                        AlertItem selectedAlert = alertForUser.Copy();
                        long      previousValue = PIn.Long(selectedAlert.Description);
                        //We only need to modify the alert if the amount of unread webmails changed.
                        if (previousValue != kvp.Value)
                        {
                            selectedAlert.Description = POut.Long(kvp.Value);
                            //If the new value is greater, the user has recieved more webmails so we want to mark the alert as "Unread".
                            if (previousValue < kvp.Value)
                            {
                                listChangedAlertItemNums.Add(selectedAlert.AlertItemNum);
                            }
                        }
                        listAlerts.Add(selectedAlert);
                        log.WriteLine("Modified webmail alert for user " + POut.Long(usernum), LogLevel.Verbose);
                    }
                }
            }
            //Push our changes to the database.
            AlertItems.Sync(listAlerts, listOldAlerts);
            List <AlertItem> listDeletedAlerts = listOldAlerts.Where(x => !listAlerts.Any(y => y.AlertItemNum == x.AlertItemNum)).ToList();

            log.WriteLine("Deleted webmail alerts for users: " + String.Join(", ", listDeletedAlerts.Select(x => POut.Long(x.UserNum))), LogLevel.Verbose);
            //Make sure to mark alerts that were deleted, modified (not created) and increased as unread.
            listChangedAlertItemNums.AddRange(listDeletedAlerts.Select(x => x.AlertItemNum));
            AlertReads.DeleteForAlertItems(listChangedAlertItemNums);
        }