public IEnumerable <EfficiencyRecord> Compute([NotNull] IEnumerable <EfficiencyShift> shifts, LaborRate[] laborRates, DateTime start, DateTime end) { // TODO: This can be configurable var breakdownInterval = TimeSpan.FromHours(1); var results = new List <EfficiencyRecord>(); foreach (var shift in shifts) { Transaction shiftFirstTransaction = null; Transaction shiftLastTransaction = null; var shiftEfficiencyResults = new List <EfficiencyRecord>(); var shiftIntervals = DateRangeList.GenerateAscending(shift.StartTime, shift.EndTime, breakdownInterval, nearest: true); foreach (var shiftInterval in shiftIntervals) { var intervalTransactions = _transactionProvider.FindTransactions(shift.SiteCode, shift.SiteEmployeeCodes, shiftInterval.Start, shiftInterval.End.AddMilliseconds(-1) //use EndOf.AddMilliseconds(-1) here so that we don't count same transaction for different intervals ).ToArray(); if (intervalTransactions.Any()) { if (shiftFirstTransaction == null) { shiftFirstTransaction = intervalTransactions.First(); // For the first transaction in a shift the transition time is not relevant, since // we count it in the time to first transaction. shiftFirstTransaction.TransitionTimeSeconds = 0; } shiftLastTransaction = intervalTransactions.Last(); } var intervalEfficiencyResults = HandleInterval(shift, shiftInterval, intervalTransactions, laborRates); shiftEfficiencyResults.AddRange(intervalEfficiencyResults); } shiftEfficiencyResults.ForEach(x => x.LastTransactionDate = shiftLastTransaction?.TransactionDate); //set time clocked-in for shifts with no transactions if (shiftEfficiencyResults.All(x => x.QuantityProcessed == 0)) { foreach (var effitem in shiftEfficiencyResults) { effitem.TimeClockedIn = TimeSpan.FromSeconds(Convert.ToInt32((effitem.IntervalEndTime - effitem.IntervalStartTime).TotalSeconds)); } } //set time to first for shifts with no transactions that count towards efficiency if (shiftEfficiencyResults.All(x => x.QuantityProcessed == 0 && x.TimeEarned.TotalSeconds == 0)) { foreach (var effitem in shiftEfficiencyResults) { effitem.ShiftTimeToFirstTransaction = effitem.TimeClockedIn; effitem.IsNonProductiveClockIn = shift.IsTransactional; } } if (shiftEfficiencyResults.Any(x => x.QuantityProcessed > 0)) { // Set the time to the first and last transactions for this shift var firstTransactionEfficiencyRecords = shiftEfficiencyResults.Where(e => (e.TransactionTypeCode == null || e.TransactionTypeCode.IgnoreCaseEquals(shiftFirstTransaction?.TransactionTypeCode)) && e.IntervalStartTime <= shiftFirstTransaction?.TransactionDate); if (firstTransactionEfficiencyRecords != null && shiftFirstTransaction != null && firstTransactionEfficiencyRecords.Any()) { foreach (var item in firstTransactionEfficiencyRecords) { var timeClockedIn = TimeSpan.FromSeconds(Convert.ToInt32((item.IntervalEndTime - item.IntervalStartTime).TotalSeconds)); var shiftTimeToFirstTransaction = item.QuantityProcessed == 0 ? timeClockedIn : item.IntervalStartToFirstTransaction; item.ShiftTimeToFirstTransaction = shift.IsTransactional ? shiftTimeToFirstTransaction : new TimeSpan(); item.TimeClockedIn += shiftTimeToFirstTransaction; item.TransactionTypeCode = item.TransactionTypeCode ?? shiftFirstTransaction.TransactionTypeCode; } } var lastTransactionEfficiencyRecords = shiftEfficiencyResults.Where(e => (e.TransactionTypeCode == null || e.TransactionTypeCode.IgnoreCaseEquals(shiftLastTransaction?.TransactionTypeCode)) && e.IntervalEndTime >= shiftLastTransaction?.TransactionDate); if (lastTransactionEfficiencyRecords != null && shiftLastTransaction != null && lastTransactionEfficiencyRecords.Any()) { foreach (var item in lastTransactionEfficiencyRecords) { var timeClockedIn = TimeSpan.FromSeconds(Convert.ToInt32((item.IntervalEndTime - item.IntervalStartTime).TotalSeconds)); var shiftTimeAfterLastTransaction = item.QuantityProcessed == 0 ? timeClockedIn : item.LastTransactionToIntervalEnd; item.ShiftTimeAfterLastTransaction = shift.IsTransactional && !shift.IsClockedIn ? shiftTimeAfterLastTransaction : new TimeSpan(); item.TimeClockedIn += shiftTimeAfterLastTransaction; item.TransactionTypeCode = item.TransactionTypeCode ?? shiftLastTransaction.TransactionTypeCode; } } } results.AddRange(shiftEfficiencyResults .Where(x => x.TransactionTypeCode != null || x.TimeClockedIn.TotalSeconds != 0)); //filter out rows that don't have any seconds clocked in and ttype is null } SetStartEndShiftTime(results, start, end); return(results); }