/// <summary>
        /// Returns a list of shifts that overlap a given date range based on an employee's time sheet records.
        /// In cases where an employee has transactions that do not fall within a given time sheet, a shift for those
        /// transactions is created and they are bucketed under there.
        /// </summary>
        /// <param name="siteCode"></param>
        /// <param name="startTime"></param>
        /// <param name="endTime"></param>
        /// <returns></returns>
        private IEnumerable <EfficiencyShift> GetShifts(string siteCode, DateTime siteNow, Employee[] employees, IEnumerable <string> siteEmployeeCodes, DateTime startTime, DateTime endTime)
        {
            var tnaEmployeeCodes = new HashSet <string>(employees.Select(r => r.TnaEmployeeCode));

            var extendedTimeSheets = _timesheetCacheProvider.GetCurrent().GetAll(siteCode).ToArray();
            var timeSheets         = extendedTimeSheets
                                     .Where(t => tnaEmployeeCodes.Contains(t.TnaEmployeeCode))
                                     .Where(t => t.OperationalDate >= startTime.Date && t.OperationalDate <= endTime.Date);

            var siteContext     = _siteContextProvider.GetSiteContext(siteCode);
            var businesUnitType = siteContext.BusinessUnit;
            var isLtl           = businesUnitType == BusinessUnitType.LTL;

            //var transactionTypes = _transactionTypeService.List(siteCode, null)
            //let the code crash if the sitecode is not cached so we catch problems early

            var transactionTypes = _cachedLookupService.GetTransactionTypes(siteCode)
                                   .Flatten()
                                   .Select(r => new EfficiencyTransactionType()
            {
                Code     = r.Code,
                Measured = r.Measured
            })
                                   .ToArray();

            //var workCenters = siteContext.GetWorkcenterService().ListIncludeInvalids(siteCode);
            //let the code crash if the sitecode is not cached so we catch problems early
            var workCenters = _cachedLookupService.GetWorkcenters(siteCode);

            foreach (var timeSheet in timeSheets)
            {
                var workCenter = workCenters.FirstOrDefault(x => x.Code.IgnoreCaseEquals(timeSheet.WorkedWorkCenterCode));
                timeSheet.WorkedWorkCenterName      = workCenter?.Name;
                timeSheet.IsTransactionalWorkCenter = workCenter?.IsTransactional;
                timeSheet.WorkcenterType            = workCenter?.WorkcenterType;
            }

            //var employeeShiftSupevisors = _shiftSupervisorService.List(siteCode);
            //let the code crash if the sitecode is not cached so we catch problems early
            var employeeShiftSupevisors = _cachedLookupService.GetEmployeeShiftSupevisors(siteCode);

            var timeSheetShifts = timeSheets.MapTimeSheetShifts(siteCode, siteNow, employees, employeeShiftSupevisors, businesUnitType, siteEmployeeCodes, transactionTypes);
            var orphanShifts    = GetOrphanedShiftsFromTransactions(siteCode, employees, isLtl, startTime.AddHours(-12), endTime.AddHours(12), extendedTimeSheets, transactionTypes, businesUnitType, siteEmployeeCodes, employeeShiftSupevisors);

            return(timeSheetShifts.Concat(orphanShifts));
        }