/// <summary>
        /// Loads and returns all the cached schedules for the team between and including the past
        /// and future weeks.
        /// </summary>
        /// <param name="teamId">The ID of the team to return the cached schedules for.</param>
        /// <param name="pastWeeks">The number of past weeks to return.</param>
        /// <param name="futureWeeks">The number of future weeks to return.</param>
        /// <param name="startDayOfWeek">The start day of the week for the team.</param>
        /// <param name="scheduleCacheService">The schedule cache service to use to load the schedules.</param>
        /// <param name="timeService">The time service to use to get the current times.</param>
        /// <returns>The array of cached schedules.</returns>
        internal static async Task <CacheModel <ShiftModel>[]> LoadSchedulesAsync(string teamId, int pastWeeks, int futureWeeks, DayOfWeek startDayOfWeek, IScheduleCacheService scheduleCacheService, ISystemTimeService timeService)
        {
            var loadScheduleTasks = timeService.UtcNow
                                    .Range(pastWeeks, futureWeeks, startDayOfWeek)
                                    .Select(w => scheduleCacheService.LoadScheduleAsync(teamId, w));

            return(await Task.WhenAll(loadScheduleTasks).ConfigureAwait(false));
        }
Exemple #2
0
        public async Task <ResultModel> Run([ActivityTrigger] WeekModel weekModel, ILogger log)
        {
            log.LogWeek(weekModel);

            // initialise source service
            var credentials = await _secretsService.GetCredentialsAsync(weekModel.TeamId);

            _scheduleSourceService.SetCredentials(weekModel.TeamId, credentials);

            // get the current set of shifts from JDA
            var shifts = await _scheduleSourceService.ListWeekShiftsAsync(weekModel.TeamId, weekModel.StoreId, weekModel.StartDate, weekModel.TimeZoneInfoId);

            log.LogShifts(weekModel, shifts);

            // get the last saved set of shifts
            var savedShifts = await _scheduleCacheService.LoadScheduleAsync(weekModel.TeamId, weekModel.StartDate);

            // compute the delta
            var delta = _scheduleDeltaService.ComputeDelta(savedShifts.Tracked, shifts);

            log.LogFullDelta(weekModel, delta);

            if (delta.HasChanges)
            {
                delta.RemoveSkipped(savedShifts.Skipped);
                delta.ApplyMaximum(_options.MaximumDelta);

                log.LogPartialDelta(weekModel, delta);

                var allShifts = delta.All;

                // set teams employee
                var employeeLookup = BuildEmployeeLookup(savedShifts.Tracked);
                await SetTeamsEmployeeIds(allShifts.Where(s => string.IsNullOrEmpty(s.TeamsEmployeeId)), employeeLookup, weekModel, log);

                // set job & department name
                var jobLookup = BuildJobLookup(savedShifts.Tracked);
                await SetJobAndDepartmentName(allShifts, jobLookup, weekModel, log);

                // set teams schedule group (N.B this must be set after teams employee id and jobs)
                var groupLookup = BuildScheduleGroupLookup(savedShifts.Tracked);
                await SetTeamsSchedulingGroupId(allShifts.Where(s => string.IsNullOrEmpty(s.TeamsSchedulingGroupId)), groupLookup, weekModel, log);
                await AddEmployeesToSchedulingGroups(delta, groupLookup, weekModel, log);

                // update teams
                await ApplyDeltaAsync(weekModel, delta, log);

                log.LogAppliedDelta(weekModel, delta);

                // apply the final delta to the savedShifts
                delta.ApplyChanges(savedShifts.Tracked);
                delta.ApplySkipped(savedShifts.Skipped);

                await _scheduleCacheService.SaveScheduleAsync(weekModel.TeamId, weekModel.StartDate, savedShifts);
            }

            return(delta.AsResult());
        }
Exemple #3
0
        protected async Task <ShiftModel> GetOpenShift(string teamId, string openShiftId)
        {
            var loadScheduleTasks = DateTime.UtcNow
                                    .Range(_teamOptions.PastWeeks, _teamOptions.FutureWeeks, _teamOptions.StartDayOfWeek)
                                    .Select(w => _scheduleCacheService.LoadScheduleAsync(GetSaveScheduleId(teamId), w));

            var cacheModels = await Task.WhenAll(loadScheduleTasks).ConfigureAwait(false);

            return(cacheModels
                   .SelectMany(c => c.Tracked)
                   .FirstOrDefault(s => s.TeamsShiftId == openShiftId));
        }
        public override async Task <IActionResult> HandleRequest(ChangeRequest changeRequest, ChangeItemRequest changeItemRequest, ChangeResponse changeResponse, string entityId, ILogger log, IDurableOrchestrationClient starter)
        {
            var connectionModel = await _scheduleConnectorService.GetConnectionAsync(entityId).ConfigureAwait(false);

            var loadScheduleTasks = DateTime.UtcNow
                                    .Range(0, _teamOptions.FutureWeeks, _teamOptions.StartDayOfWeek) // pastWeek = 0 as we don't swap shifts from previous weeks
                                    .Select(w => _scheduleCacheService.LoadScheduleAsync(entityId, w));

            var cacheModels = await Task.WhenAll(loadScheduleTasks).ConfigureAwait(false);

            var fromShift = cacheModels.SelectMany(c => c.Tracked).FirstOrDefault(s => s.TeamsShiftId == changeItemRequest.Id);

            if (fromShift == null)
            {
                return(new ChangeErrorResult(changeResponse, changeItemRequest, ErrorCodes.SenderShiftNotFound, _stringLocalizer[ErrorCodes.SenderShiftNotFound], true));
            }

            var user = await _cacheService.GetKeyAsync <EmployeeModel>(ApplicationConstants.TableNameEmployees, fromShift.WfmEmployeeId).ConfigureAwait(false);

            if (user == null)
            {
                return(new ChangeErrorResult(changeResponse, changeItemRequest, ErrorCodes.UserCredentialsNotFound, _stringLocalizer[ErrorCodes.UserCredentialsNotFound], true));
            }

            try
            {
                var wfmMatches = await _wfmDataService.GetEligibleTargetsForShiftSwap(fromShift, user, connectionModel.WfmBuId).ConfigureAwait(false);

                var matches = new List <string>();

                foreach (var wfmShiftId in wfmMatches)
                {
                    var swappableTeamsShift = cacheModels.SelectMany(c => c.Tracked).FirstOrDefault(s => s.WfmShiftId == wfmShiftId);
                    if (swappableTeamsShift != null)
                    {
                        matches.Add(swappableTeamsShift.TeamsShiftId);
                    }
                }

                return(new ChangeSuccessResult(changeResponse, changeItemRequest, matches));
            }
            catch (WfmException wex)
            {
                return(WfmErrorToActionResult(wex.Error, changeItemRequest, changeResponse));
            }
        }
Exemple #5
0
        private async Task <List <ShiftModel> > GetCachedShiftsAsync(ConnectionModel connection, DateTime weekStartDate)
        {
            var cachedShifts = await _scheduleCacheService.LoadScheduleAsync(connection.TeamId, weekStartDate).ConfigureAwait(false);

            return(cachedShifts.Tracked.OrderBy(s => s.StartDate).ToList());
        }
Exemple #6
0
        public override async Task <IActionResult> HandleRequest(ChangeRequest changeRequest, ChangeItemRequest changeItemRequest, ChangeResponse changeResponse, string teamId, ILogger log, IDurableOrchestrationClient starter)
        {
            // get the open shift from the change item
            var openShift = changeItemRequest.Body.ToObject <OpenShiftResponse>();
            // get the proposed shift to be created from the open shift
            var proposedShift = changeRequest.Requests
                                .Where(r => r.Method.Equals("POST", StringComparison.OrdinalIgnoreCase))
                                .Select(r => r.Body.ToObject <ShiftResponse>())
                                .First();

            var connectionModel = await _scheduleConnectorService.GetConnectionAsync(teamId).ConfigureAwait(false);

            // get the corresponding open shift from cache
            var localStartDate = openShift.SharedOpenShift.StartDateTime.ApplyTimeZoneOffset(connectionModel.TimeZoneInfoId);
            var weekStartDate  = localStartDate.StartOfWeek(_teamOptions.StartDayOfWeek);
            var scheduleId     = teamId + ApplicationConstants.OpenShiftsSuffix;
            var cacheModel     = await _scheduleCacheService.LoadScheduleAsync(scheduleId, weekStartDate).ConfigureAwait(false);

            var assignedShift = cacheModel.Tracked.FirstOrDefault(o => o.TeamsShiftId == openShift.Id);

            if (assignedShift == null)
            {
                // we didn't find an open shift with this ID
                return(new ChangeErrorResult(changeResponse, changeItemRequest, ErrorCodes.NoOpenShiftsFound, _stringLocalizer[ErrorCodes.NoOpenShiftsFound]));
            }

            // get the employee object for the manager who initiated the change
            var manager = await _cacheService.GetKeyAsync <EmployeeModel>(ApplicationConstants.TableNameEmployees, openShift.LastModifiedBy.User.Id).ConfigureAwait(false);

            if (manager == null)
            {
                return(new ChangeErrorResult(changeResponse, changeItemRequest, ErrorCodes.UserCredentialsNotFound, _stringLocalizer[ErrorCodes.UserCredentialsNotFound]));
            }

            // get the employee object for the user the open shift is assigned to
            var employee = await _cacheService.GetKeyAsync <EmployeeModel>(ApplicationConstants.TableNameEmployees, proposedShift.UserId).ConfigureAwait(false);

            if (employee == null)
            {
                return(new ChangeErrorResult(changeResponse, changeItemRequest, ErrorCodes.UserCredentialsNotFound, _stringLocalizer[ErrorCodes.UserCredentialsNotFound]));
            }

            var wfmResponse = await _wfmActionService.ManagerAssignOpenShiftAsync(assignedShift, manager, employee, connectionModel.WfmBuId, connectionModel.TimeZoneInfoId, log).ConfigureAwait(false);

            if (!wfmResponse.Success)
            {
                return(new ChangeErrorResult(changeResponse, changeItemRequest, wfmResponse.Error.Code, wfmResponse.Error.Message));
            }

            var policy = GetConflictRetryPolicy(_teamOptions.RetryMaxAttempts, _teamOptions.RetryIntervalSeconds);

            // as it has been assigned successfully, decrement the quantity
            assignedShift.Quantity--;

            // update the open shift in the cache
            await policy.ExecuteAsync(() => UpdateCachedOpenShiftsAsync(scheduleId, assignedShift, weekStartDate)).ConfigureAwait(false);

            // convert the open shift to a shift and add it to the week shifts cache
            assignedShift.Quantity        = 1;
            assignedShift.WfmEmployeeId   = employee.WfmEmployeeId;
            assignedShift.TeamsEmployeeId = employee.TeamsEmployeeId;
            assignedShift.TeamsShiftId    = proposedShift.Id;
            assignedShift.WfmShiftId      = wfmResponse.NewEntityId;

            await policy.ExecuteAsync(() => UpdateCachedShiftsAsync(teamId, assignedShift, weekStartDate)).ConfigureAwait(false);

            // finally, set up a deferred action to share the schedule
            var deferredActionModel = new DeferredActionModel
            {
                ActionType     = DeferredActionModel.DeferredActionType.ShareTeamSchedule,
                DelaySeconds   = _teamOptions.DelayedActionSeconds,
                ShareStartDate = openShift.SharedOpenShift.StartDateTime.Date,
                ShareEndDate   = openShift.SharedOpenShift.EndDateTime.Date.AddHours(23).AddMinutes(59),
                TeamId         = teamId
            };
            await starter.StartNewAsync(nameof(DeferredActionOrchestrator), deferredActionModel).ConfigureAwait(false);

            return(new ChangeSuccessResult(changeResponse));
        }
Exemple #7
0
 protected override async Task <CacheModel <ShiftModel> > GetSavedRecordsAsync(TeamActivityModel activityModel, ILogger log)
 {
     return(await _scheduleCacheService.LoadScheduleAsync(GetSaveScheduleId(activityModel.TeamId), activityModel.StartDate).ConfigureAwait(false));
 }