public async Task <IActionResult> ClockOut(int timeEntryid)
        {
            var entry = await _timeEntryRepository.GetTimeEntry(timeEntryid);

            //ensure the entry belongs to the current user and that the current user is still in the specified patrol
            if (User.PatrolIds().Any(x => x == entry.PatrolId) &&
                (entry.UserId == User.UserId() ||
                 User.RoleInPatrol(entry.PatrolId).CanMaintainTimeClock()))   //or the user is an admin
            {
                var result = await _timeClockService.ClockOut(timeEntryid);

                return(Ok(result));
            }
            else
            {
                return(Forbid());
            }
        }
 public IActionResult List()
 {
     return(Ok(_repository.GetTimeEntry()));
 }
        public async Task <CurrentTimeEntryDto> ClockOut(int timeEntryId, DateTime?now = null)
        {
            if (!now.HasValue)
            {
                now = _systemClock.UtcNow.UtcDateTime;
            }

            var result = new CurrentTimeEntryDto();
            var entry  = await _timeEntryRepository.GetTimeEntry(timeEntryId);

            result.TimeEntry = entry;

            //make sure it's not already been clocked out
            if (!entry.ClockOut.HasValue)
            {
                entry.ClockOut        = now.Value;
                entry.DurationSeconds = (int)(entry.ClockOut.Value - entry.ClockIn).TotalSeconds;
                await _timeEntryRepository.UpdateTimeEntry(entry);

                //if scheduling is enabled, complete entry=>schedule relating/calculating
                //TODO: all of this work can be done async from the clock out action, ideally in some fire-and-forget event system.
                var patrol = await _patrolRepository.GetPatrol(entry.PatrolId);

                if (patrol.EnableScheduling)
                {
                    //track all seconds that need to be allocated from the current time entry
                    int unallocatedSeconds = entry.DurationSeconds.Value;
                    //track what has been allocated thus far
                    int allocatedSeconds = 0;

                    var shifts = (await _shiftRepository.GetScheduledShiftAssignments(entry.PatrolId, entry.UserId, entry.ClockIn, entry.ClockOut)).ToList();
                    shifts = shifts.OrderBy(x => x.StartsAt).ToList();

                    var timeEntryScheduledShiftAssignments = new List <TimeEntryScheduledShiftAssignment>();
                    timeEntryScheduledShiftAssignments.AddRange(await _timeEntryRepository.GetScheduledShiftAssignmentsForTimeEntry(entry.Id));
                    //reset all existing allocations to 0
                    foreach (var existing in timeEntryScheduledShiftAssignments)
                    {
                        existing.DurationSeconds = 0;
                    }

                    for (int i = 0; i < shifts.Count && unallocatedSeconds > 0; i++)
                    {
                        var shift = shifts[i];

                        //time entries related to this shift
                        var otherEntryAllocatedScheduledShiftAssignments = (await _timeEntryRepository.GetScheduledShiftAssignmentsForScheduledShiftAssignment(shift.Id)).ToList();
                        otherEntryAllocatedScheduledShiftAssignments = otherEntryAllocatedScheduledShiftAssignments.Where(x => x.TimeEntryId != entry.Id).ToList();

                        //get the schedule entry so we can see how much can be allocated
                        var scheduledShift = await _shiftRepository.GetScheduledShift(shift.ScheduledShiftId);

                        result.ScheduledShift = scheduledShift;

                        //see if this schedule entry is already covered by previous time entries
                        int previouslyAllocatedShiftSeconds = otherEntryAllocatedScheduledShiftAssignments.Sum(x => x.DurationSeconds);
                        if (previouslyAllocatedShiftSeconds < scheduledShift.DurationSeconds)
                        {
                            //if not, allocate what we can to this shift
                            //offsetting by allocatedSeconds and previouslyAllocatedShiftSeconds prevents overlapping shifts from both being allocated to the same time
                            var allocateStart = entry.ClockIn + new TimeSpan(0, 0, allocatedSeconds) > scheduledShift.StartsAt + new TimeSpan(0, 0, previouslyAllocatedShiftSeconds) ? entry.ClockIn + new TimeSpan(0, 0, allocatedSeconds) : scheduledShift.StartsAt + new TimeSpan(0, 0, previouslyAllocatedShiftSeconds);
                            var allocateEnd   = entry.ClockOut.Value > scheduledShift.EndsAt ? scheduledShift.EndsAt : entry.ClockOut.Value;

                            //TODO: we could check for existing time entries that overlap between allocateStart/End, but that really shouldn't happen
                            //since a user can't have multiple ongoing time entries

                            var maximumOverlappedAvailableAllocationSeconds = (int)(allocateEnd - allocateStart).TotalSeconds;
                            var neededSeconds = scheduledShift.DurationSeconds - previouslyAllocatedShiftSeconds;

                            //figure out how much to allocate based on overlap and what's remaining in the shift
                            var secondsToAllocate = maximumOverlappedAvailableAllocationSeconds > neededSeconds ? neededSeconds : maximumOverlappedAvailableAllocationSeconds;
                            //figure out how much to allocate based on what's remaining in the time entry
                            secondsToAllocate = secondsToAllocate > unallocatedSeconds ? unallocatedSeconds : secondsToAllocate;

                            //do the allocation
                            //find a entryscheduledshiftassignment to alloate with, or make one
                            var timeEntryScheduledShiftAssignment = timeEntryScheduledShiftAssignments.SingleOrDefault(x => x.ScheduledShiftAssignmentId == shift.Id);
                            if (timeEntryScheduledShiftAssignment == null)
                            {
                                timeEntryScheduledShiftAssignment = new TimeEntryScheduledShiftAssignment()
                                {
                                    ScheduledShiftAssignmentId = shift.Id,
                                    TimeEntryId     = entry.Id,
                                    DurationSeconds = secondsToAllocate,
                                };
                                await _timeEntryRepository.InsertTimeEntryScheduledShiftAssignment(timeEntryScheduledShiftAssignment);

                                otherEntryAllocatedScheduledShiftAssignments.Add(timeEntryScheduledShiftAssignment);
                            }
                            else
                            {
                                timeEntryScheduledShiftAssignment.DurationSeconds = secondsToAllocate;
                                await _timeEntryRepository.UpdateTimeEntryScheduledShiftAssignment(timeEntryScheduledShiftAssignment);
                            }

                            //adjust running totals
                            unallocatedSeconds = unallocatedSeconds - secondsToAllocate;
                            allocatedSeconds   = allocatedSeconds + secondsToAllocate;

                            result.TimeEntryScheduledShiftAssignment = timeEntryScheduledShiftAssignment;
                        }
                        else
                        {
                            //_logger.LogDebug("Previously allocated > current duration");
                        }

                        if (result.ScheduledShift.ShiftId.HasValue)
                        {
                            result.Shift = await _shiftRepository.GetShift(result.ScheduledShift.ShiftId.Value);
                        }
                        if (result.ScheduledShift.GroupId.HasValue)
                        {
                            result.Group = await _groupRepository.GetGroup(result.ScheduledShift.GroupId.Value);
                        }
                    }

                    //there's no scenario where this should happen, the only way it could occur is if a schedule change
                    //occurred mid-shift, which we do not allow.  But just in case, clean it up anyway
                    foreach (var timeEntryScheduledShiftAssignment in timeEntryScheduledShiftAssignments)
                    {
                        if (timeEntryScheduledShiftAssignment.DurationSeconds == 0)
                        {
                            await _timeEntryRepository.DeleteTimeEntryScheduledShiftAssignment(timeEntryScheduledShiftAssignment);
                        }
                    }
                }
            }

            return(result);
        }