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); }
private static bool IsReworkTransaction(Transaction transaction) { return(transaction.TransactionTypeCode.IgnoreCaseEquals(Constants.TransactionTypes.LOAD) && transaction.QuantityEarned == 0); }
/// <summary> /// Advances the time sheet enumerator so that the current time sheet's punchOutTime > transactionDate. /// If no more time sheets exist that meet that criteria null is returned. /// </summary> /// <param name="enumerator"></param> /// <param name="transaction"></param> /// <returns></returns> internal static (EfficiencyTimeSheet TimeSheet, EfficiencyTimeSheet PreviousTimeSheet) AdvanceTimeSheet([NotNull] SafeEnumerator <EfficiencyTimeSheet> enumerator, Transaction transaction) { var transactionDate = transaction.TransactionDate; if (enumerator.Current?.PunchOutTime == null || enumerator.Current?.PunchOutTime >= transactionDate) { return(enumerator.Current, enumerator.Previous); } while (enumerator.MoveNext()) { var current = enumerator.Current; if (current == null) { continue; } if (current.PunchInTime > current.PunchOutTime) { throw new InvalidOperationException( $"Invalid TimeSheet. {nameof(EfficiencyTimeSheet.PunchInTime)} > {nameof(EfficiencyTimeSheet.PunchOutTime)}"); } if (enumerator.Previous?.PunchInTime > current?.PunchInTime) { throw new ArgumentException("TimeSheets must be ordered by PunchInTime", nameof(enumerator)); } // We found the next time sheet. Break out. if (current.PunchOutTime == null || current.PunchOutTime >= transactionDate) { break; } } return(enumerator.Current, enumerator.Previous); }