From ba37e11381cef7efdfaa98a38501a8f5a5275baf Mon Sep 17 00:00:00 2001 From: Aleksander Lund Date: Tue, 12 Dec 2023 07:53:36 +0100 Subject: [PATCH] Cleanup and fixes to notifications --- .../ApiClients/IOrgApiClient.cs | 18 +-- .../ScheduledReportContentBuilderFunction.cs | 118 +++++++----------- .../ScheduledReportTimerTriggerFunction.cs | 3 +- 3 files changed, 55 insertions(+), 84 deletions(-) diff --git a/src/backend/function/Fusion.Resources.Functions/ApiClients/IOrgApiClient.cs b/src/backend/function/Fusion.Resources.Functions/ApiClients/IOrgApiClient.cs index 866448585..2b39ef392 100644 --- a/src/backend/function/Fusion.Resources.Functions/ApiClients/IOrgApiClient.cs +++ b/src/backend/function/Fusion.Resources.Functions/ApiClients/IOrgApiClient.cs @@ -58,16 +58,16 @@ public class ApiInstanceSnapshot } } -public enum ChangeType +public class ChangeType { - PositionInstanceCreated, - PersonAssignedToPosition, - PositionInstanceAllocationStateChanged, - PositionInstanceAppliesToChanged, - PositionInstanceAppliesFromChanged, - PositionInstanceParentPositionIdChanged, - PositionInstancePercentChanged, - PositionInstanceLocationChanged + public static string PositionInstanceCreated = "PositionInstanceCreated"; + public static string PersonAssignedToPosition = "PersonAssignedToPosition"; + public static string PositionInstanceAllocationStateChanged = "PositionInstanceAllocationStateChanged"; + public static string PositionInstanceAppliesToChanged = "PositionInstanceAppliesToChanged"; + public static string PositionInstanceAppliesFromChanged = "PositionInstanceAppliesFromChanged"; + public static string PositionInstanceParentPositionIdChanged = "PositionInstanceParentPositionIdChanged"; + public static string PositionInstancePercentChanged = "PositionInstancePercentChanged"; + public static string PositionInstanceLocationChanged = "PositionInstanceLocationChanged"; } #endregion \ No newline at end of file diff --git a/src/backend/function/Fusion.Resources.Functions/Functions/Notifications/ScheduledReportContentBuilderFunction.cs b/src/backend/function/Fusion.Resources.Functions/Functions/Notifications/ScheduledReportContentBuilderFunction.cs index 4dc2a2e98..7025083e9 100644 --- a/src/backend/function/Fusion.Resources.Functions/Functions/Notifications/ScheduledReportContentBuilderFunction.cs +++ b/src/backend/function/Fusion.Resources.Functions/Functions/Notifications/ScheduledReportContentBuilderFunction.cs @@ -25,6 +25,7 @@ public class ScheduledReportContentBuilderFunction private readonly INotificationApiClient _notificationsClient; private readonly IResourcesApiClient _resourceClient; private readonly IOrgClient _orgClient; + private static readonly string formatDoubleToHaveOneDecimal = "F1"; public ScheduledReportContentBuilderFunction(ILogger logger, IResourcesApiClient resourcesApiClient, @@ -58,7 +59,7 @@ public async Task RunAsync( switch (dto.Role) { case NotificationRoleType.ResourceOwner: - await BuildContentForResourceOwner(azureUniqueId, dto.FullDepartment); + await BuildContentForResourceOwner(azureUniqueId, dto.FullDepartment, dto.DepartmentSapId); break; case NotificationRoleType.TaskOwner: await BuildContentForTaskOwner(azureUniqueId); @@ -89,91 +90,65 @@ private async Task BuildContentForTaskOwner(Guid azureUniqueId) throw new NotImplementedException(); } - private async Task BuildContentForResourceOwner(Guid azureUniqueId, string fullDepartment) + private async Task BuildContentForResourceOwner(Guid azureUniqueId, string fullDepartment, string departmentSapId) { var threeMonthsFuture = DateTime.UtcNow.AddMonths(3); var today = DateTime.UtcNow; var sevenDaysAgo = DateTime.UtcNow.AddDays(-7); - // Get all requests for specific Department regardsless of state + // Get all requests for department regardsless of state var departmentRequests = await _resourceClient.GetAllRequestsForDepartment(fullDepartment); - - // Get all the personnel for the specific department + // Get all the personnel for the department var personnelForDepartment = await _resourceClient.GetAllPersonnelForDepartment(fullDepartment); + // Adding leave personnelForDepartment = await GetPersonnelLeave(personnelForDepartment); - //1.Number of personnel: - //number of personnel in the department(one specific org unit) - // Hvordan finne? - // Hent ut alle ressursene for en gitt avdeling - // OK? + //1.Number of personnel var numberOfPersonnel = personnelForDepartment.Count(); //2.Capacity in use: - //capacity in use by %. - //Calculated by total current workload for all personnel / (100 % workload x number of personnel - (total % leave)), - //a.e.g. 10 people in department: 800 % current workload / (1000 % -120 % leave) = 91 % capacity in use - // OK - Inkluderer nå leave.. var percentageOfTotalCapacity = FindTotalCapacityIncludingLeave(personnelForDepartment.ToList()); - // 3.New requests last week: - // number of requests received last 7 days - // Notat: En request kan ha blitt opprettet for 7 dager siden, men ikke oversendt til ressurseiere - Det kan være - // Inkluderer foreløpig alle requestene uavhengig av hvilken state de er - var numberOfRequestsLastWeek = departmentRequests.Where(req => req.Created > sevenDaysAgo && !req.IsDraft).Count(); - - + // 3.New requests last week (7 days) + var numberOfRequestsLastWeek = departmentRequests.Count(req => !req.Type.Equals("ResourceOwnerChange") && req.Created > sevenDaysAgo && !req.IsDraft); - //4.Open request: - //number of requests with no proposed candidate - // Only to include those requests which have state approval (this means that the resource owner needs to process the requests in some way) - // OK? - Må sjekkes og finne noen som har approval... - var totalNumberOfOpenRequests = departmentRequests.Count(req => !req.HasProposedPerson && !req.State.Contains(RequestState.completed.ToString())); + //4.Open request (no proposedPerson) + var totalNumberOfOpenRequests = departmentRequests.Count(req => !req.Type.Equals("ResourceOwnerChange") && !req.HasProposedPerson && !req.State.Equals("completed", StringComparison.OrdinalIgnoreCase)); - //5.Requests with start-date < 3 months: - //number of requests with start date within less than 3 months - // Filter to only inlclude the ones that have start-date in less than 3 months and start-date after today and is not complete and has no proposedPerson assigned to them + //5.Requests with start-date < 3 months var numberOfDepartmentRequestWithLessThanThreeMonthsBeforeStartAndNoNomination = departmentRequests .Count(x => x.OrgPositionInstance != null && x.State != null && - !x.State.Contains(RequestState.completed.ToString()) && + !x.State.Equals("completed") && (x.OrgPositionInstance.AppliesFrom < threeMonthsFuture && x.OrgPositionInstance.AppliesFrom > today) && !x.HasProposedPerson); //6.Requests with start-date > 3 months: - //number of requests with start date later than next 3 months - // Filter to only include the ones that have start-date in more than 3 months AND state not completed var numberOfDepartmentRequestWithMoreThanThreeMonthsBeforeStart = departmentRequests .Count(x => x.OrgPositionInstance != null && x.State != null && - !x.State.Contains(RequestState.completed.ToString()) && + !x.State.Contains("completed") && x.OrgPositionInstance.AppliesFrom > threeMonthsFuture); - // TODO: MÅ TESTES OG VERIFISERES - //7.Average time to handle request: - //average number of days from request created/ sent to candidate is proposed - last 6 months + //7.Average time to handle request (last 3 months): var averageTimeToHandleRequest = CalculateAverageTimeToHandleRequests(departmentRequests); - // TODO: GJENSTÅR //8.Allocation changes awaiting task owner action: //number of allocation changes made by resource owner awaiting task owner action //Må hente ut alle posisjoner som har ressurser for en gitt avdeling og sjekke på om det er gjort endringer her den siste tiden var numberOfAllocationchangesAwaitingTaskOwnerAction = GetchangesAwaitingTaskOwnerAction(departmentRequests); - //9.Project changes affecting next 3 months: MÅ TESTES OG VERIFISERES + //9.Project changes affecting next 3 months //number of project changes(changes initiated by project / task) with a change affecting the next 3 months var numberOfChangesAffectingNextThreeMonths = GetAllChangesForResourceDepartment(personnelForDepartment); - //10.Allocations ending soon with no future allocation: - //list of allocations ending within next 3 months where the person allocated does not continue in the position(i.e.no future splits with the same person allocated) + //10.Allocations ending soon with no future allocation var listOfPersonnelWithoutFutureAllocations = FilterPersonnelWithoutFutureAllocations(personnelForDepartment); - //11.Personnel with more than 100 % workload: -OK - //(as in current pilot, but remove "FTE") list of persons with total allocation > 100 %, total % workload should be visible after person name - // TODO: Fiks formatering og oppdeling av innhold her + //11.Personnel with more than 100 % workload var listOfPersonnelsWithMoreThan100Percent = personnelForDepartment.Where(p => p.PositionInstances.Where(pos => pos.IsActive).Select(pos => pos.Workload).Sum() > 100); var listOfPersonnelForDepartmentWithMoreThan100Percent = @@ -196,7 +171,7 @@ private async Task BuildContentForResourceOwner(Guid azureUniqueId, string fullD PersonnelPositionsEndingWithNoFutureAllocation = listOfPersonnelWithoutFutureAllocations, PersonnelAllocatedMoreThan100Percent = listOfPersonnelForDepartmentWithMoreThan100Percent }, - fullDepartment); + fullDepartment, departmentSapId); var sendNotification = await _notificationsClient.SendNotification( new SendNotificationsRequest() @@ -279,10 +254,10 @@ private int GetAllChangesForResourceDepartment(IEnumerable ev.ChangeType != ChangeType.PositionInstanceCreated.ToString() - && ev.ChangeType != ChangeType.PersonAssignedToPosition.ToString() - && ev.ChangeType != ChangeType.PositionInstanceAllocationStateChanged.ToString() - && ev.ChangeType != ChangeType.PositionInstanceParentPositionIdChanged.ToString() + .Where(ev => ev.ChangeType != ChangeType.PositionInstanceCreated + && ev.ChangeType != ChangeType.PersonAssignedToPosition + && ev.ChangeType != ChangeType.PositionInstanceAllocationStateChanged + && ev.ChangeType != ChangeType.PositionInstanceParentPositionIdChanged && (ev.ChangeType.Equals(ChangeType.PositionInstanceAppliesToChanged) && ev.Instance.AppliesTo < threeMonths)); @@ -292,39 +267,38 @@ private int GetAllChangesForResourceDepartment(IEnumerable listOfRequests) - => listOfRequests.Where((req => req.Type.Equals("ResourceOwnerChange"))).Where(req => req.Workflow.Steps.Any(step => !step.IsCompleted && step.Name.Equals("Accept"))).ToList().Count(); + => listOfRequests.Where((req => req.Type.Equals("ResourceOwnerChange"))).Where(req => req.Workflow.Steps.Any(step => step.Name.Equals("Accept") && !step.IsCompleted)).ToList().Count(); private double CalculateAverageTimeToHandleRequests(IEnumerable listOfRequests) { - // How to calculate: - // for each request: - // Find the workflow "created" and then find the date - // This should mean that task owner have created and sent the request to resource owner - // Find the workflow "proposal" and then find the date - // This should mean that the resource owner have done their bit - // TODO: Maybe we need to consider other states - // FIXME: We may need to filter out requests that are sent from resource-side (aka ResourceOwnerChange) - - - double days = 0; + /* How to calculate: + * + * Find the workflow "created" and then find the date + * This should mean that task owner have created and sent the request to resource owner + * Find the workflow "proposal" and then find the date + * This should mean that the resource owner have done their bit + * TODO: Maybe we need to consider other states + */ + + double days = 0.0; int requestsHandledByResourceOwner = 0; double totalNumberOfDays = 0.0; var threeMonthsAgo = DateTime.UtcNow.AddMonths(-3); - var requestsLastThreemonths = listOfRequests + var requestsLastThreeMonthsWithoutResourceOwnerChangeRequest = listOfRequests .Where(req => req.Created > threeMonthsAgo) .Where(r => r.Workflow is not null) - .Where(r => r.Workflow.Steps is not null); + .Where(r => r.Workflow.Steps is not null).Where((req => !req.Type.Equals("ResourceOwnerChange"))); - foreach (var request in requestsLastThreemonths) + foreach (var request in requestsLastThreeMonthsWithoutResourceOwnerChangeRequest) { - if (request.State.Equals(RequestState.created.ToString())) + if (request.State.Equals("created")) continue; - var dateForCreation = request.Workflow.Steps.FirstOrDefault(step => step.IsCompleted && step.Name.Equals("Created"))?.Completed.Value.DateTime; - var dateForApproval = request.Workflow.Steps.FirstOrDefault(step => step.IsCompleted && step.Name.Equals("Proposed"))?.Completed.Value.DateTime; + var dateForCreation = request.Workflow.Steps.FirstOrDefault(step => step.Name.Equals("Created") && step.IsCompleted)?.Completed.Value.DateTime; + var dateForApproval = request.Workflow.Steps.FirstOrDefault(step => step.Name.Equals("Proposed") && step.IsCompleted)?.Completed.Value.DateTime; if (dateForCreation != null && dateForApproval != null) { requestsHandledByResourceOwner++; @@ -374,14 +348,10 @@ private PersonnelContent CreatePersonnelWithTBEContent(InternalPersonnelPerson p } private static AdaptiveCard ResourceOwnerAdaptiveCardBuilder(ResourceOwnerAdaptiveCardData cardData, - string departmentIdentifier) + string departmentIdentifier, string departmentSapId) { - // Plasser denne en annen plass + // FIXME:Plasser denne en annen plass var baseUri = "https://fusion-s-portal-ci.azurewebsites.net/apps/personnel-allocation/"; - var avdelingsId = "52586050"; // Må finne ut av hvor man får denne ID'en til avdelingen. Mulig man må hente det fra context? - - - var formatDoubleToHaveOneDecimal = "F1"; var card = new AdaptiveCardBuilder() .AddHeading($"**Weekly summary - {departmentIdentifier}**") @@ -392,11 +362,11 @@ private static AdaptiveCard ResourceOwnerAdaptiveCardBuilder(ResourceOwnerAdapti .AddColumnSet(new AdaptiveCardColumn(cardData.NumberOfRequestsStartingInLessThanThreeMonths.ToString(), "Requests with start date < 3 months")) .AddColumnSet(new AdaptiveCardColumn(cardData.NumberOfRequestsStartingInMoreThanThreeMonths.ToString(), "Requests with start date > 3 months")) .AddColumnSet(new AdaptiveCardColumn(cardData.AverageTimeToHandleRequests.ToString(formatDoubleToHaveOneDecimal), "Average time to handle request", "days")) - .AddColumnSet(new AdaptiveCardColumn(cardData.AllocationChangesAwaitingTaskOwnerAction.ToString(), "Allocation changes awaiting task owner action")) // WIP + .AddColumnSet(new AdaptiveCardColumn(cardData.AllocationChangesAwaitingTaskOwnerAction.ToString(), "Allocation changes awaiting task owner action")) .AddColumnSet(new AdaptiveCardColumn(cardData.ProjectChangesAffectingNextThreeMonths.ToString(), "Project changes affecting next 3 months")) .AddListContainer("Allocations ending soon with no future allocation:", cardData.PersonnelPositionsEndingWithNoFutureAllocation, "FullName", "EndingPosition") .AddListContainer("Personnel with more than 100% workload:", cardData.PersonnelAllocatedMoreThan100Percent, "FullName", "TotalWorkload") - .AddActionButton("Go to Personnel allocation app", $"{baseUri}{avdelingsId}") + .AddActionButton("Go to Personnel allocation app", $"{baseUri}/{departmentSapId}") .Build(); return card; diff --git a/src/backend/function/Fusion.Resources.Functions/Functions/Notifications/ScheduledReportTimerTriggerFunction.cs b/src/backend/function/Fusion.Resources.Functions/Functions/Notifications/ScheduledReportTimerTriggerFunction.cs index 2b437ec1b..aceda758c 100644 --- a/src/backend/function/Fusion.Resources.Functions/Functions/Notifications/ScheduledReportTimerTriggerFunction.cs +++ b/src/backend/function/Fusion.Resources.Functions/Functions/Notifications/ScheduledReportTimerTriggerFunction.cs @@ -84,7 +84,8 @@ private async Task SendResourceOwnersToQueue(ServiceBusSender sender) { AzureUniqueId = resourceOwner.AzureUniqueId, FullDepartment = resourceOwner.FullDepartment, - Role = NotificationRoleType.ResourceOwner + Role = NotificationRoleType.ResourceOwner, + DepartmentSapId = resourceOwner.DepartmentSapId }); } catch (Exception e)