public ActionResult <List <Clinician> > GetWorkingClinician(string shopCode, DateTime date, string employeeTypeCodes = null)
        {
            string[] employeeTypes = null;
            if (!string.IsNullOrWhiteSpace(employeeTypeCodes))
            {
                employeeTypes = employeeTypeCodes.Split("|");
            }

            List <Clinician> Result = new List <Clinician>();
            var predicate           = PredicateBuilder.New <CM_S_EMPLOYEE>(true);

            if (employeeTypes == null)
            {
                employeeTypes = new string[] { "SRO" }
            }
            ;                                                           //{ "SRO", "AUD", "CSO" };


            List <string> AllowedTypes = new List <string>();

            employeeTypes.ToList().ForEach(E => AllowedTypes.Add("'" + E + "'"));

            StringBuilder SQL = new StringBuilder("SELECT DISTINCT E.* FROM CM_S_EMPLOYEE E");

            SQL.Append(" JOIN AG_B_EMPLOYEE_WORKING_HOURS WH ON E.SHOP_CODE=WH.SHOP_CODE AND E.EMPLOYEE_CODE=WH.EMPLOYEE_CODE");
            SQL.Append($" WHERE E.SHOP_CODE IN ('*', '{shopCode}') AND WH.DT_VALID <= '{date:yyyy-MM-dd}' AND E.EMPLOYEE_TYPE_CODE IN (" + string.Join(", ", AllowedTypes) + ")");
            SQL.Append(" AND (E.DT_START IS NULL OR E.DT_START <= CAST(GETDATE() AS DATE)) AND (E.DT_END IS NULL OR E.DT_END >= CAST(GETDATE() AS DATE))");

            List <CM_S_EMPLOYEE> employees = DBContext.CM_S_EMPLOYEE.FromSql(SQL.ToString()).ToList();
            List <AG_B_EMPLOYEE_WORKING_HOURS> workingHours = GetEmployeeValidWorkingHours(shopCode, employees, date);

            foreach (CM_S_EMPLOYEE item in employees)
            {
                if (workingHours.Any(E => E.EMPLOYEE_CODE == item.EMPLOYEE_CODE && SlotHelper.BynaryCheck(E, date)))
                {
                    Clinician model = EntityMapper.Map <Clinician, CM_S_EMPLOYEE>(DBContext, item);
                    Result.Add(model);
                }
            }

            return(Result);
        }
    }
        protected List <AvailabilitySlot> GetAvailableSlots(string shopCode, IEnumerable <CM_S_EMPLOYEE> employees, DateTime date, string serviceCode, short slotSize, IEnumerable <AG_B_EMPLOYEE_WORKING_HOURS> workingHours)
        {
            const string defaultDescription = "Available";

            if (shopCode == null)
            {
                throw new ArgumentNullException(nameof(shopCode), "shopCode cannot be null");
            }
            if (serviceCode == null)
            {
                throw new ArgumentNullException(nameof(serviceCode), "ServiceCode cannot be null");
            }
            if (employees == null)
            {
                throw new ArgumentNullException(nameof(employees), "employees cannot be null");
            }
            if (workingHours == null)
            {
                throw new ArgumentNullException(nameof(workingHours), "workingHours cannot be null");
            }

            //Load special availability types
            string[] specialAvailabilityServiceCodes = FoxDataService.GetSpecialAvailabilityServiceCodes(shopCode);
            string[] unavailabilityServiceCodes      = FoxDataService.GetUnavailabilityServiceCodes(shopCode);

            List <AvailabilitySlot> Result  = new List <AvailabilitySlot>();
            AG_S_SERVICE            service = DBContext.AG_S_SERVICE.FirstOrDefault(E => E.SERVICE_CODE == serviceCode);

            if (service == null)
            {
                throw new ArgumentException($"No service found for service code: {serviceCode}");
            }

            if (employees.Any())
            {
                List <AvailabilitySlot> availableSlots = new List <AvailabilitySlot>();

                //Add slots for each employee
                foreach (CM_S_EMPLOYEE employee in employees)
                {
                    foreach (AG_B_EMPLOYEE_WORKING_HOURS workingHour in workingHours.Where(E => E.EMPLOYEE_CODE == employee.EMPLOYEE_CODE && E.DT_VALID <= date && SlotHelper.BynaryCheck(E, date)).OrderByDescending(E => E.DT_VALID).OrderBy(E => E.START_HOUR))
                    {
                        //Create standard availability slots
                        availableSlots.AddRange(SlotHelper.CreateSlots(date, workingHour.START_HOUR.GetValueOrDefault(), workingHour.END_HOUR.GetValueOrDefault(), slotSize, employee.EMPLOYEE_CODE, employee.EMPLOYEE_DESCR, defaultDescription, service.BACKGROUND_COLOR.GetValueOrDefault(), service.FOREGROUND_COLOR.GetValueOrDefault()));
                    }
                }

                string[] employeeCodes = employees.Select(E => E.EMPLOYEE_CODE).Distinct().ToArray();

                //Add special availability
                AG_B_APPOINTMENT[] specials = DBContext.AG_B_APPOINTMENT.Where(E => E.APPOINTMENT_SHOP_CODE == shopCode && employeeCodes.Contains(E.EMPLOYEE_CODE) && AllowedAppointmentStatus.Contains(E.STATUS_CODE) && specialAvailabilityServiceCodes.Contains(E.AG_S_SERVICE.SERVICE_TYPE_CODE) && E.DT_APPOINTMENT.Date == date.Date).ToArray();
                foreach (AG_B_APPOINTMENT special in specials)
                {
                    CM_S_EMPLOYEE           employee     = employees.FirstOrDefault(E => E.EMPLOYEE_CODE == special.EMPLOYEE_CODE);
                    List <AvailabilitySlot> specialSlots = SlotHelper.CreateSlots(special.DT_APPOINTMENT.Date, special.DT_APPOINTMENT.TimeOfDay, special.DT_APPOINTMENT.AddMinutes(special.DURATION.GetValueOrDefault()).TimeOfDay, slotSize, employee?.EMPLOYEE_CODE, employee?.EMPLOYEE_DESCR, defaultDescription, service.BACKGROUND_COLOR.GetValueOrDefault(), service.FOREGROUND_COLOR.GetValueOrDefault());
                    foreach (AvailabilitySlot slot in specialSlots)
                    {
                        if (!availableSlots.Contains(slot))
                        {
                            availableSlots.Add(slot);
                        }
                    }
                }
                //Get already booked slots
                AG_B_APPOINTMENT[] appointments = DBContext.AG_B_APPOINTMENT.Where(E => E.APPOINTMENT_SHOP_CODE == shopCode && AllowedAppointmentStatus.Contains(E.STATUS_CODE) && employeeCodes.Contains(E.EMPLOYEE_CODE) && !specialAvailabilityServiceCodes.Contains(E.AG_S_SERVICE.SERVICE_TYPE_CODE) && E.DT_APPOINTMENT.Date == date.Date).ToArray();
                //Get special unavailability
                AG_B_APPOINTMENT[]      unavailabilties = DBContext.AG_B_APPOINTMENT.Where(E => E.APPOINTMENT_SHOP_CODE == shopCode && AllowedAppointmentStatus.Contains(E.STATUS_CODE) && employeeCodes.Contains(E.EMPLOYEE_CODE) && unavailabilityServiceCodes.Contains(E.AG_S_SERVICE.SERVICE_TYPE_CODE) && E.DT_APPOINTMENT.Date == date.Date).ToArray();
                List <AG_B_APPOINTMENT> busySlots       = new List <AG_B_APPOINTMENT>(appointments);
                busySlots.AddRange(unavailabilties);
                //Remove busy slots
                if (busySlots.Any())
                {
                    foreach (AG_B_APPOINTMENT appointment in busySlots)
                    {
                        availableSlots.RemoveAll(E => E.ResourceId == appointment.EMPLOYEE_CODE &&
                                                 ((appointment.DT_APPOINTMENT >= E.Start && appointment.DT_APPOINTMENT < E.End) ||
                                                  (appointment.DT_APPOINTMENT.AddMinutes(appointment.DURATION.GetValueOrDefault()) > E.Start && appointment.DT_APPOINTMENT.AddMinutes(appointment.DURATION.GetValueOrDefault()) < E.End) ||
                                                  (appointment.DT_APPOINTMENT <= E.Start && appointment.DT_APPOINTMENT.AddMinutes(appointment.DURATION.GetValueOrDefault()) > E.End)));
                    }
                }

                if (employeeCodes.Any() && availableSlots.Any())
                {
                    Result.AddRange(availableSlots.OrderBy(E => E.ResourceId).OrderBy(E => E.Start).Distinct(new AvailabilitySlotComparer()));
                }
                else
                {
                    Result.AddRange(availableSlots);
                }
            }

            return(Result);
        }