public DutyRosterService(SheriffDbContext db, IConfiguration configuration, ShiftService shiftService, ILogger <DutyRosterService> logger) { Db = db; ShiftService = shiftService; OvertimeHoursPerDay = double.Parse(configuration.GetNonEmptyValue("OvertimeHoursPerDay")); Logger = logger; }
private async Task HandleShiftAdjustmentsAndOvertime(int locationId, DateTimeOffset targetDate, string timezone, IEnumerable <Guid?> sheriffsForDuties) { var startRange = targetDate.ConvertToTimezone(timezone).DateOnly(); var endRange = startRange.TranslateDateForDaylightSavings(timezone, hoursToShift: 24); var shiftExpansions = new List <ShiftAdjustment>(); foreach (var sheriff in sheriffsForDuties.Where(sheriff => sheriff != null)) { var shiftsForDay = await Db.Shift.Where(s => s.ExpiryDate == null && s.SheriffId == sheriff && s.LocationId == locationId && s.StartDate >= startRange && s.StartDate < endRange).ToListAsync(); if (shiftsForDay.Count == 0) { continue; } var referenceShift = shiftsForDay.First(); var earliestShift = shiftsForDay.FirstOrDefault(s => s.StartDate == shiftsForDay.Min(s2 => s2.StartDate)); var latestShift = shiftsForDay.FirstOrDefault(s => s.EndDate == shiftsForDay.Max(s2 => s2.EndDate)); var dutySlotsForSheriffOnDay = await Db.DutySlot.Where(s => s.ExpiryDate == null && s.SheriffId == sheriff && s.LocationId == locationId && s.StartDate >= startRange && s.StartDate < endRange).ToListAsync(); var earliestDutySlot = dutySlotsForSheriffOnDay.FirstOrDefault(ds => ds.StartDate == dutySlotsForSheriffOnDay.Min(ds2 => ds2.StartDate)); var latestDutySlot = dutySlotsForSheriffOnDay.FirstOrDefault(ds => ds.EndDate == dutySlotsForSheriffOnDay.Max(ds2 => ds2.EndDate)); if (latestShift !.EndDate < latestDutySlot?.EndDate) { await Db.Shift.AddAsync(new Shift { SheriffId = sheriff.Value, Timezone = referenceShift.Timezone, LocationId = referenceShift.LocationId, StartDate = latestShift.EndDate, EndDate = latestDutySlot.EndDate }); shiftExpansions.Add(new ShiftAdjustment { SheriffId = sheriff.Value, Date = earliestDutySlot.StartDate, Timezone = earliestShift.Timezone }); } if (earliestShift !.StartDate > earliestDutySlot?.StartDate) { await Db.Shift.AddAsync(new Shift { SheriffId = sheriff.Value, Timezone = referenceShift.Timezone, LocationId = referenceShift.LocationId, StartDate = earliestDutySlot.StartDate, EndDate = earliestShift.StartDate }); shiftExpansions.Add(new ShiftAdjustment { SheriffId = sheriff.Value, Date = earliestDutySlot.StartDate, Timezone = earliestShift.Timezone }); } } if (!shiftExpansions.Any()) { return; } await Db.SaveChangesAsync(); foreach (var shiftExpansion in shiftExpansions.Distinct()) { await ShiftService.CalculateOvertimeHoursForSheriffOnDay( shiftExpansion.SheriffId, shiftExpansion.Date, shiftExpansion.Timezone); } }
/// <summary> /// This can be used to add / update / remove slots. /// </summary> public async Task <List <Duty> > UpdateDuties(List <Duty> duties, bool overrideValidation) { if (!duties.Any()) { throw new BusinessLayerException("Didn't provide any duties."); } await CheckForDutySlotOverlap(duties, overrideValidation); var dutyIds = duties.SelectToList(duty => duty.Id); var savedDuties = Db.Duty.AsSingleQuery() .Include(d => d.DutySlots) .Where(s => dutyIds.Contains(s.Id) && s.ExpiryDate == null); var shiftIds = duties.SelectMany(d => d.DutySlots).SelectDistinctToList(ds => ds.ShiftId); var shifts = Db.Shift.Where(s => shiftIds.Contains(s.Id)); foreach (var duty in duties) { var savedDuty = await savedDuties.FirstOrDefaultAsync(s => s.Id == duty.Id); savedDuty.ThrowBusinessExceptionIfNull($"{nameof(Duty)} with the id: {duty.Id} could not be found. "); duty.Timezone.GetTimezone().ThrowBusinessExceptionIfNull($"A valid {nameof(duty.Timezone)} must be provided."); Db.Entry(savedDuty !).CurrentValues.SetValues(duty); Db.Entry(savedDuty).Property(x => x.LocationId).IsModified = false; Db.Entry(savedDuty).Property(x => x.ExpiryDate).IsModified = false; foreach (var dutySlot in duty.DutySlots) { var savedDutySlot = await Db.DutySlot .FirstOrDefaultAsync(ds => ds.Id == dutySlot.Id && ds.ExpiryDate == null); dutySlot.LocationId = duty.LocationId; dutySlot.DutyId = duty.Id; dutySlot.Timezone = duty.Timezone; dutySlot.Duty = null; dutySlot.Sheriff = null; dutySlot.Shift = null; dutySlot.Location = null; dutySlot.IsOvertime = false; var shift = shifts.FirstOrDefault(s => s.Id == dutySlot.ShiftId && s.ExpiryDate == null); if (shift != null) { if (shift.StartDate > dutySlot.StartDate) { shift.StartDate = dutySlot.StartDate; shift.IsOvertime = ShiftService.IsShiftOvertime(shift.StartDate, shift.EndDate, shift.Timezone, OvertimeHours); dutySlot.IsOvertime = shift.IsOvertime; } if (shift.EndDate < dutySlot.EndDate) { shift.EndDate = dutySlot.EndDate; shift.IsOvertime = ShiftService.IsShiftOvertime(shift.StartDate, shift.EndDate, shift.Timezone, OvertimeHours); dutySlot.IsOvertime = shift.IsOvertime; } } if (savedDutySlot == null) { await Db.DutySlot.AddAsync(dutySlot); } else { Db.Entry(savedDutySlot).CurrentValues.SetValues(dutySlot); } } Db.RemoveRange(savedDuty.DutySlots.Where(ds => duty.DutySlots.All(d => d.Id != ds.Id))); } await Db.SaveChangesAsync(); return(await savedDuties.ToListAsync()); }