From 1c6ed774941a730ba5efa20155bed5ad23be5eb8 Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Mon, 24 Mar 2025 13:14:28 -0700 Subject: [PATCH 1/8] AB#23678 initial commit for batch approvals --- .../ApplicationApprovalDto.cs | 13 +++ .../IApplicationApprovalService.cs | 12 +++ .../ApplicationApprovalAppService.cs | 99 +++++++++++++++++++ .../Localization/GrantManager/en.json | 1 + .../Applications/IApplicationRepository.cs | 1 + .../Repositories/ApplicationRepository.cs | 14 ++- .../ApproveApplicationsModal.cshtml.cs | 55 ++++------- .../Components/ActionBar/Default.cshtml | 7 ++ .../Components/DetailsActionBar/Default.js | 10 +- 9 files changed, 165 insertions(+), 47 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationApprovalDto.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationApprovalService.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationApprovalAppService.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationApprovalDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationApprovalDto.cs new file mode 100644 index 000000000..c3dc5ef21 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationApprovalDto.cs @@ -0,0 +1,13 @@ +using System; + +namespace Unity.GrantManager.GrantApplications +{ + public class ApplicationApprovalDto + { + public Guid ApplicationId { get; set; } + public Guid ApplicationStatusId { get; set; } + public decimal RequestedAmount { get; set; } + public decimal ApprovedAmount { get; set; } + public DateTime? DecisionDate { get; set; } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationApprovalService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationApprovalService.cs new file mode 100644 index 000000000..5ae58706f --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationApprovalService.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Unity.GrantManager.GrantApplications +{ + public interface IApplicationApprovalService + { + Task BulkApproveApplications(Guid[] applicationGuids); + Task> GetApplicationsForBulkApproval(Guid[] applicationGuids); + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationApprovalAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationApprovalAppService.cs new file mode 100644 index 000000000..b59af8a2d --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationApprovalAppService.cs @@ -0,0 +1,99 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization.Infrastructure; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Unity.GrantManager.Applications; +using Unity.GrantManager.Permissions; + +namespace Unity.GrantManager.GrantApplications +{ + [Authorize] + public class ApplicationApprovalService(IApplicationRepository applicationRepository) : GrantManagerAppService, IApplicationApprovalService + { + public Task BulkApproveApplications(Guid[] applicationGuids) + { + throw new NotImplementedException(); + } + + public async Task> GetApplicationsForBulkApproval(Guid[] applicationGuids) + { + var applications = await applicationRepository.GetListByIdsAsync(applicationGuids); + return await FilterBulkApplications([.. applications]); + } + + public async Task> FilterBulkApplications(Application[] applications) + { + var approvals = new List(); + + foreach (var application in applications) + { + if (await ValidateStateChange(application, GrantApplicationAction.Approve)) + { + approvals.Add(new ApplicationApprovalDto() + { + ApplicationId = application.Id, + ApplicationStatusId = application.ApplicationStatusId, + ApprovedAmount = application.ApprovedAmount, + RequestedAmount = application.RequestedAmount, + DecisionDate = application.FinalDecisionDate + }); + } + + } + + return approvals; + } + + /// + /// Validate the state of the application before attempting to change it (TODO: integrate into workflow / statemachine) + /// + /// + /// + /// + private async Task ValidateStateChange(Application application, GrantApplicationAction triggerAction) + { + if (MeetsWorkflowRequirement(application, triggerAction)) + { + Logger.LogWarning("Approval requested for application in invalid state for approval: {ApplicationId}", application.Id); + return false; + } + + if (!await AuthorizationService.IsGrantedAsync(application, GetActionAuthorizationRequirement(triggerAction))) + { + Logger.LogWarning("Approval requested for application with insufficient permissions: {ApplicationId}", application.Id); + return false; + } + + return true; + } + + /// + /// Inline explicit validation of status check for bulk application approval + /// + /// + /// + /// + private static bool MeetsWorkflowRequirement(Application application, GrantApplicationAction triggerAction) + { + if (triggerAction != GrantApplicationAction.Approve) + { + return false; + } + + if (application.ApplicationStatus.StatusCode == GrantApplicationState.ASSESSMENT_COMPLETED) + { + return false; + } + + return true; + } + + private static OperationAuthorizationRequirement GetActionAuthorizationRequirement(GrantApplicationAction triggerAction) + { + return new OperationAuthorizationRequirement { Name = $"{GrantApplicationPermissions.Applications.Default}.{triggerAction}" }; + // this should allow anyone for now, but needs to change to a specific permission + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json index 72408204c..97a48d3fa 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json @@ -70,6 +70,7 @@ "ApplicationList:OpenButton": "Open", "ApplicationList:AssignButton": "Assign", + "ApplicationList:ApproveButton": "Approve", "ApplicationList:StatusButton": "Status", "ApplicationList:ApprovedButton": "Approved", "ApplicationList:NotApprovedButton": "Not approved", diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/IApplicationRepository.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/IApplicationRepository.cs index 58deb0040..653fe193b 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/IApplicationRepository.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/IApplicationRepository.cs @@ -10,4 +10,5 @@ public interface IApplicationRepository : IRepository { Task WithBasicDetailsAsync(Guid id); Task>> WithFullDetailsGroupedAsync(int skipCount, int maxResultCount, string? sorting = null); + Task> GetListByIdsAsync(Guid[] ids); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs index ac26cbe80..048dfe3b5 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs @@ -1,3 +1,4 @@ +using Amazon.S3.Model; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; @@ -56,8 +57,17 @@ public async Task WithBasicDetailsAsync(Guid id) .Include(s => s.Applicant) .ThenInclude(s => s.ApplicantAddresses) .Include(s => s.ApplicantAgent) - .Include(s => s.ApplicationStatus) - .FirstAsync(s => s.Id == id); + .Include(s => s.ApplicationStatus) + .FirstAsync(s => s.Id == id); + } + + public async Task> GetListByIdsAsync(Guid[] ids) + { + return await (await GetQueryableAsync()) + .AsNoTracking() + .Include(s => s.ApplicationStatus) + .Where(s => ids.Contains(s.Id)) + .ToListAsync(); } /// diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml.cs index 9f2b89d38..dc2d6625e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; @@ -11,58 +10,37 @@ namespace Unity.GrantManager.Web.Pages.Approve; -public class ApproveApplicationsModalModel : AbpPageModel +public class ApproveApplicationsModalModel(IApplicationApprovalService applicationApprovalService) : AbpPageModel { - - [BindProperty] - public string SelectedApplicationIds { get; set; } = ""; [BindProperty] - public string OperationStatusCode { get; set; } = ""; - [TempData] - public string PopupMessage { get; set; } = ""; - [TempData] - public string PopupTitle { get; set; } = ""; + public string? SelectedApplicationIds { get; set; } - public List StatusList { get; set; } = new(); + [BindProperty] + public string? OperationStatusCode { get; set; } - private readonly IApplicationStatusService _statusService; - private readonly GrantApplicationAppService _applicationService; + [TempData] + public string? PopupMessage { get; set; } - public ApproveApplicationsModalModel(IApplicationStatusService statusService, GrantApplicationAppService applicationService) - { - _statusService = statusService; - _applicationService = applicationService; - } + [TempData] + public string? PopupTitle { get; set; } - public void OnGet(string applicationIds, string operation, string message, string title) + public async void OnGet(string applicationIds, string operation, string message, string title) { SelectedApplicationIds = applicationIds; OperationStatusCode = operation; PopupMessage = message; PopupTitle = title; + + // Load the applications by Id + var applications = await applicationApprovalService.GetApplicationsForBulkApproval(ParseApplicationIds()); } public async Task OnPostAsync() { try { - Guid statusId; - var statuses = await _statusService.GetListAsync(); - var approvedStatus = statuses.FirstOrDefault(status => status.StatusCode == OperationStatusCode); - if (approvedStatus != null) - { - statusId = approvedStatus.Id; - } - else - { - throw new ArgumentException(OperationStatusCode + " status code is not found in the database!"); - } - - var applicationIds = JsonConvert.DeserializeObject>(SelectedApplicationIds); - if (null != applicationIds) - { - await _applicationService.UpdateApplicationStatus(applicationIds.ToArray(), statusId); - } + // Fire off request to approve the applications + var result = await applicationApprovalService.BulkApproveApplications(ParseApplicationIds()); } catch (Exception ex) { @@ -70,4 +48,9 @@ public async Task OnPostAsync() } return NoContent(); } + + private Guid[] ParseApplicationIds() + { + return JsonConvert.DeserializeObject(SelectedApplicationIds ?? string.Empty) ?? []; + } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.cshtml index 7cd4272f3..2c17a050d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.cshtml @@ -31,6 +31,13 @@ class="custom-table-btn flex-none btn btn-secondary action-bar-btn-unavailable" text="@L["ApplicationList:AssignButton"].Value" /> + @if (true) { // Validate against required permission + + } Date: Fri, 4 Apr 2025 12:32:39 -0700 Subject: [PATCH 2/8] AB#23678 approvals modal wip --- .../ApplicationApprovalDto.cs | 13 -- .../GrantApplicationBatchApprovalDto.cs | 25 ++++ .../IApplicationApprovalService.cs | 2 +- .../IGrantApplicationAppService.cs | 1 + .../ApplicationApprovalAppService.cs | 118 +++++++++++++----- .../Repositories/ApplicationRepository.cs | 1 + .../Approve/ApproveApplicationsModal.cshtml | 72 +++++++++-- .../ApproveApplicationsModal.cshtml.cs | 57 +++++++-- .../Shared/Components/ActionBar/ActionBar.cs | 2 + .../Shared/Components/ActionBar/Default.js | 57 +++------ 10 files changed, 245 insertions(+), 103 deletions(-) delete mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationApprovalDto.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationBatchApprovalDto.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationApprovalDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationApprovalDto.cs deleted file mode 100644 index c3dc5ef21..000000000 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationApprovalDto.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Unity.GrantManager.GrantApplications -{ - public class ApplicationApprovalDto - { - public Guid ApplicationId { get; set; } - public Guid ApplicationStatusId { get; set; } - public decimal RequestedAmount { get; set; } - public decimal ApprovedAmount { get; set; } - public DateTime? DecisionDate { get; set; } - } -} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationBatchApprovalDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationBatchApprovalDto.cs new file mode 100644 index 000000000..257d71a14 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationBatchApprovalDto.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +namespace Unity.GrantManager.GrantApplications +{ + public class GrantApplicationBatchApprovalDto + { + public GrantApplicationBatchApprovalDto() + { + ValidationMessages = []; + ReferenceNo = string.Empty; + ApplicantName = string.Empty; + } + + public List ValidationMessages { get; set; } + public bool IsValid => ValidationMessages.Count == 0; + + public Guid ApplicationId { get; set; } + public decimal ApprovedAmount { get; set; } + public decimal RequestedAmount { get; set; } + public DateTime? FinalDecisionDate { get; set; } + public string ReferenceNo { get; set; } + public string ApplicantName { get; set; } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationApprovalService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationApprovalService.cs index 5ae58706f..a7bb44e88 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationApprovalService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationApprovalService.cs @@ -7,6 +7,6 @@ namespace Unity.GrantManager.GrantApplications public interface IApplicationApprovalService { Task BulkApproveApplications(Guid[] applicationGuids); - Task> GetApplicationsForBulkApproval(Guid[] applicationGuids); + Task> GetApplicationsForBulkApproval(Guid[] applicationGuids); } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IGrantApplicationAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IGrantApplicationAppService.cs index d38aac16c..bff3976b9 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IGrantApplicationAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IGrantApplicationAppService.cs @@ -18,5 +18,6 @@ public interface IGrantApplicationAppService : ICommentsService Task> GetApplicationDetailsListAsync(List applicationIds); Task GetAsync(Guid id); Task> GetListAsync(PagedAndSortedResultRequestDto input); + Task TriggerAction(Guid applicationId, GrantApplicationAction triggerAction); } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationApprovalAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationApprovalAppService.cs index b59af8a2d..bf295b5dd 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationApprovalAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationApprovalAppService.cs @@ -6,63 +6,116 @@ using System.Threading.Tasks; using Unity.GrantManager.Applications; using Unity.GrantManager.Permissions; +using Volo.Abp.Uow; namespace Unity.GrantManager.GrantApplications { [Authorize] - public class ApplicationApprovalService(IApplicationRepository applicationRepository) : GrantManagerAppService, IApplicationApprovalService + public class ApplicationApprovalService(IApplicationRepository applicationRepository, + IGrantApplicationAppService grantApplicationsService, // Services should not inject services in same module! + IUnitOfWorkManager unitofWorkManager) : GrantManagerAppService, IApplicationApprovalService { - public Task BulkApproveApplications(Guid[] applicationGuids) + /// + /// Bulk approve applications + /// + /// + /// + public async Task BulkApproveApplications(Guid[] applicationGuids) { - throw new NotImplementedException(); + // We read and write individually here to make sure all applications trigger ther approval correctly as a best effort per application + foreach (var applicationId in applicationGuids) + { + try + { + using var uow = unitofWorkManager.Begin(requiresNew: true); + await grantApplicationsService.TriggerAction(applicationId, GrantApplicationAction.Approve); + await uow.CompleteAsync(); + } + catch (Exception ex) + { + // Log the error and continue with the next application + Logger.LogError(ex, "Error approving application with ID: {ApplicationId}", applicationId); + // Add to error list or handle as needed + } + } + + return await Task.FromResult(true); } - public async Task> GetApplicationsForBulkApproval(Guid[] applicationGuids) + /// + /// Get applications for bulk approval with addeded on validation information + /// + /// + /// + public async Task> GetApplicationsForBulkApproval(Guid[] applicationGuids) { var applications = await applicationRepository.GetListByIdsAsync(applicationGuids); - return await FilterBulkApplications([.. applications]); + return await ValidateBulkApplications([.. applications]); } - public async Task> FilterBulkApplications(Application[] applications) + + /// + /// Add validations to the applications + /// + /// + /// + private async Task> ValidateBulkApplications(Application[] applications) { - var approvals = new List(); + var applicationsForApproval = new List(); foreach (var application in applications) { - if (await ValidateStateChange(application, GrantApplicationAction.Approve)) - { - approvals.Add(new ApplicationApprovalDto() - { - ApplicationId = application.Id, - ApplicationStatusId = application.ApplicationStatusId, - ApprovedAmount = application.ApprovedAmount, - RequestedAmount = application.RequestedAmount, - DecisionDate = application.FinalDecisionDate - }); - } + List validationMessages = await RunValidations(application); + applicationsForApproval.Add(new GrantApplicationBatchApprovalDto() + { + ApplicationId = application.Id, + ApprovedAmount = application.ApprovedAmount, + RequestedAmount = application.RequestedAmount, + FinalDecisionDate = application.FinalDecisionDate, + ReferenceNo = application.ReferenceNo, + ValidationMessages = validationMessages, + ApplicantName = application.Applicant.ApplicantName ?? string.Empty + }); } - return approvals; + return applicationsForApproval; + } + + /// + /// Run the validations for the application + /// + /// + /// + private async Task> RunValidations(Application application) + { + var validWorkflow = MeetsWorkflowRequirement(application, GrantApplicationAction.Approve); + var authorized = await MeetsAuthorizationRequirement(application, GrantApplicationAction.Approve); + var validationMessages = new List(); + + if (!validWorkflow) + validationMessages.Add("Invalid workflow status for approval."); + if (!authorized) + validationMessages.Add("Insufficient permissions for approval."); + + return validationMessages; } /// - /// Validate the state of the application before attempting to change it (TODO: integrate into workflow / statemachine) + /// Inline explicit validation of status check for bulk application approval /// /// /// /// - private async Task ValidateStateChange(Application application, GrantApplicationAction triggerAction) + private static bool MeetsWorkflowRequirement(Application application, GrantApplicationAction triggerAction) { - if (MeetsWorkflowRequirement(application, triggerAction)) + if (triggerAction != GrantApplicationAction.Approve) { - Logger.LogWarning("Approval requested for application in invalid state for approval: {ApplicationId}", application.Id); return false; } - if (!await AuthorizationService.IsGrantedAsync(application, GetActionAuthorizationRequirement(triggerAction))) + if (application.ApplicationStatus.StatusCode != GrantApplicationState.ASSESSMENT_COMPLETED) { - Logger.LogWarning("Approval requested for application with insufficient permissions: {ApplicationId}", application.Id); return false; } @@ -75,21 +128,22 @@ private async Task ValidateStateChange(Application application, GrantAppli /// /// /// - private static bool MeetsWorkflowRequirement(Application application, GrantApplicationAction triggerAction) + private async Task MeetsAuthorizationRequirement(Application application, GrantApplicationAction triggerAction) { - if (triggerAction != GrantApplicationAction.Approve) - { - return false; - } - - if (application.ApplicationStatus.StatusCode == GrantApplicationState.ASSESSMENT_COMPLETED) + if (!await AuthorizationService.IsGrantedAsync(application, GetActionAuthorizationRequirement(triggerAction))) { + Logger.LogWarning("Approval requested for application with insufficient permissions: {ApplicationId}", application.Id); return false; } return true; } + /// + /// Check the authorization requirement + /// + /// + /// private static OperationAuthorizationRequirement GetActionAuthorizationRequirement(GrantApplicationAction triggerAction) { return new OperationAuthorizationRequirement { Name = $"{GrantApplicationPermissions.Applications.Default}.{triggerAction}" }; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs index 048dfe3b5..e3cbf0a98 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs @@ -66,6 +66,7 @@ public async Task> GetListByIdsAsync(Guid[] ids) return await (await GetQueryableAsync()) .AsNoTracking() .Include(s => s.ApplicationStatus) + .Include(s => s.Applicant) .Where(s => ids.Contains(s.Id)) .ToListAsync(); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml index 93f891b0b..1f8fcddd2 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml @@ -1,23 +1,75 @@ @page @model Unity.GrantManager.Web.Pages.Approve.ApproveApplicationsModalModel +@using Microsoft.Extensions.Localization +@using Unity.GrantManager.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@inject IStringLocalizer L + @{ Layout = null; } - -
- - + + + - @Model.PopupMessage - - + + + @for (var i = 0; i < Model.BulkApplicationApprovals?.Count; i++) + { +
+ + +
@Model.BulkApplicationApprovals[i].ApplicantName (@Model.BulkApplicationApprovals[i].ReferenceNo)
+
+ + + +
+ + + + + + @for (var j = 0; j < Model.BulkApplicationApprovals[i].Errors?.Length; j++) + { + + Note: @Model.BulkApplicationApprovals[i].Errors[j] + + } +
+ } +
+
- - + + + + - +
+ + diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml.cs index dc2d6625e..f06af9390 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml.cs @@ -3,7 +3,6 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Unity.GrantManager.GrantApplications; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; @@ -16,23 +15,47 @@ public class ApproveApplicationsModalModel(IApplicationApprovalService applicati public string? SelectedApplicationIds { get; set; } [BindProperty] - public string? OperationStatusCode { get; set; } + public List? BulkApplicationApprovals { get; set; } [TempData] - public string? PopupMessage { get; set; } + public List? PopupMessages { get; set; } [TempData] public string? PopupTitle { get; set; } - public async void OnGet(string applicationIds, string operation, string message, string title) + [TempData] + public bool Invalid { get; set; } + + private const int _maxBatchCount = 50; + + public async void OnGet(string applicationIds) { SelectedApplicationIds = applicationIds; - OperationStatusCode = operation; - PopupMessage = message; - PopupTitle = title; + PopupMessages = []; + Invalid = false; + BulkApplicationApprovals = []; + + Guid[] applicationGuids = ParseApplicationIds(); + + if (!ValidCount(applicationGuids)) + { + PopupMessages.Add($"You can only approve {_maxBatchCount} applications at a time. Please select fewer applications."); + Invalid = true; + } // Load the applications by Id var applications = await applicationApprovalService.GetApplicationsForBulkApproval(ParseApplicationIds()); + + foreach (var application in applications) + { + BulkApplicationApprovals.Add(new BulkApplicationApproval() + { + ApplicationId = application.ApplicationId, + ReferenceNo = application.ReferenceNo, + Errors = [.. application.ValidationMessages], + ApplicantName = application.ApplicantName + }); + } } public async Task OnPostAsync() @@ -53,4 +76,24 @@ private Guid[] ParseApplicationIds() { return JsonConvert.DeserializeObject(SelectedApplicationIds ?? string.Empty) ?? []; } + + private static bool ValidCount(Guid[] applicationGuids) + { + // Soft check in the UI for max approvals in one batch, this is subject to be tweaked later after performance testing + return applicationGuids.Length <= _maxBatchCount; + } + + public class BulkApplicationApproval + { + public BulkApplicationApproval() + { + Errors = []; + } + + public Guid ApplicationId { get; set; } + public string ReferenceNo { get; set; } = string.Empty; + public string ApplicantName { get; set; } = string.Empty; + public bool Disabled => Errors.Length > 0; + public string[] Errors { get; set; } + } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/ActionBar.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/ActionBar.cs index 7a33c5e5e..1dccaaab9 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/ActionBar.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/ActionBar.cs @@ -43,6 +43,8 @@ public override void ConfigureBundle(BundleConfigurationContext context) .AddIfNotContains("/libs/jquery-maskmoney/dist/jquery.maskMoney.min.js"); context.Files .AddIfNotContains("/Pages/PaymentApprovals/UpdatePaymentRequestStatusModal.js"); + context.Files + .AddIfNotContains("/Pages/Approve/ApproveApplicationsPollingModal.js"); } } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.js index 411bfaa9c..e6a288aab 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.js @@ -12,14 +12,12 @@ $(function () { let approveApplicationsModal = new abp.ModalManager({ viewUrl: 'Approve/ApproveApplicationsModal' }); - let dontApproveApplicationsModal = new abp.ModalManager({ - viewUrl: 'Approve/ApproveApplicationsModal' + let approveApplicationsPollingModal = new abp.ModalManager({ + viewUrl: 'Approve/ApproveApplicationsPollingModal' }); - let tagApplicationModal = new abp.ModalManager({ viewUrl: 'ApplicationTags/ApplicationTagsSelectionModal', }); - let applicationPaymentRequestModal = new abp.ModalManager({ viewUrl: 'PaymentRequests/CreatePaymentRequests', }); @@ -131,26 +129,21 @@ $(function () { ); PubSub.publish("refresh_application_list"); }); - approveApplicationsModal.onResult(function () { - abp.notify.success( - 'The application/s has been successfully approved', - 'Approve Applications' - ); - PubSub.publish("refresh_application_list"); + + // Batch Approval Start + $('#approveApplications').on("click", function () { + approveApplicationsModal.open({ + applicationIds: JSON.stringify(selectedApplicationIds) + }); }); - dontApproveApplicationsModal.onResult(function () { - abp.notify.success( - 'The application/s has now been disapproved', - 'Not Approve Applications' - ); - PubSub.publish("refresh_application_list"); + approveApplicationsModal.onResult(function (output) { + console.log(output); + approveApplicationsPollingModal.open({ batchId: '123' }); }); approveApplicationsModal.onClose(function () { PubSub.publish("refresh_application_list"); }); - dontApproveApplicationsModal.onClose(function () { - PubSub.publish("refresh_application_list"); - }); + // Batch Approval End PubSub.subscribe("select_application", (msg, data) => { selectedApplicationIds.push(data.id); @@ -190,22 +183,6 @@ $(function () { applicationIds: JSON.stringify(selectedApplicationIds), }); }); - $('#approveApplications').click(function () { - approveApplicationsModal.open({ - applicationIds: JSON.stringify(selectedApplicationIds), - operation: 'GRANT_APPROVED', - message: 'Are you sure you want to approve the selected application/s?', - title: 'Approve Applications', - }); - }); - $('#dontApproveApplications').click(function () { - dontApproveApplicationsModal.open({ - applicationIds: JSON.stringify(selectedApplicationIds), - operation: 'GRANT_NOT_APPROVED', - message: 'Are you sure you want to disapprove the selected application/s?', - title: 'Not Approve Applications', - }); - }); $('#applicationLink').click(function () { const summaryCanvas = document.getElementById('applicationAsssessmentSummary'); @@ -232,24 +209,24 @@ $(function () { if (selectedApplicationIds.length == 0) { $('*[data-selector="applications-table-actions"]').prop('disabled', true); $('*[data-selector="applications-table-actions"]').addClass('action-bar-btn-unavailable'); - $('.action-bar').removeClass('active'); + $('.action-bar').removeClass('active'); const summaryCanvas = document.getElementById('applicationAsssessmentSummary'); summaryCanvas.classList.remove('show'); - } - else { + } + else { $('*[data-selector="applications-table-actions"]').prop('disabled', false); $('*[data-selector="applications-table-actions"]').removeClass('action-bar-btn-unavailable'); $('.action-bar').addClass('active'); $('#externalLink').addClass('action-bar-btn-unavailable'); $('#applicationLink').addClass('action-bar-btn-unavailable'); - + if (selectedApplicationIds.length == 1) { $('#externalLink').removeClass('action-bar-btn-unavailable'); $('#applicationLink').removeClass('action-bar-btn-unavailable'); - summaryWidgetManager.refresh(); + summaryWidgetManager.refresh(); } } } From 88f325a8514ca5e684c2039a5449aefdada02786 Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Wed, 9 Apr 2025 16:44:23 -0700 Subject: [PATCH 3/8] AB#23678 further updates to batch approval --- .../GrantApplications/BulkApprovalDto.cs | 29 +++++++++++++++++++ .../appsettings.Development.json | 2 +- .../Unity.GrantManager.Web/appsettings.json | 2 +- 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/BulkApprovalDto.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/BulkApprovalDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/BulkApprovalDto.cs new file mode 100644 index 000000000..5e08208af --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/BulkApprovalDto.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace Unity.GrantManager.GrantApplications +{ + public class BulkApprovalDto + { + public BulkApprovalDto() + { + ValidationMessages = []; + ReferenceNo = string.Empty; + ApplicantName = string.Empty; + FormName = string.Empty; + ApplicationStatus = string.Empty; + } + + public List ValidationMessages { get; set; } + public bool IsValid { get;set; } + + public Guid ApplicationId { get; set; } + public decimal ApprovedAmount { get; set; } + public decimal RequestedAmount { get; set; } + public DateTime? FinalDecisionDate { get; set; } + public string ReferenceNo { get; set; } + public string ApplicantName { get; set; } + public string FormName { get; set; } + public string ApplicationStatus { get; set; } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.Development.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.Development.json index 5e137c87a..61ffb75e0 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.Development.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.Development.json @@ -17,7 +17,7 @@ } }, "Intake": { - "BaseUri": "https://chefs-test.apps.silver.devops.gov.bc.ca/app/api/v1", + "BaseUri": "https://chefs-dev.apps.silver.devops.gov.bc.ca/app/api/v1", "FormId": "", "ApiKey": "", "BearerTokenPlaceholder": "", diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json index f93917fa2..408eac87d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json @@ -101,7 +101,7 @@ "Host": "127.0.0.1", "InstanceName": "redis", "KeyPrefix": "Unity", - "Password": "******", + "Password": "SUPER_SECRET_PASSWORD", "Port": 6379 }, "Logging": { From cfdfbb3ec55f8378fd8f64d525fcbc6b19f00591 Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Wed, 9 Apr 2025 16:46:31 -0700 Subject: [PATCH 4/8] AB#23678 batch approval notes / validation handling --- .../BulkApprovalResultDto.cs | 13 ++ .../GrantApplicationBatchApprovalDto.cs | 25 --- .../IApplicationApprovalService.cs | 12 -- .../IBulkApprovalsAppService.cs | 12 ++ ...pService.cs => BulkApprovalsAppService.cs} | 87 +++++--- .../Localization/GrantManager/en.json | 6 +- .../Applications/Application.cs | 8 + .../Repositories/ApplicationRepository.cs | 1 + .../Approve/ApproveApplicationsModal.cshtml | 75 ------- .../ApproveApplicationsModal.cshtml.cs | 99 --------- .../Approvals/ApproveApplicationsModal.cshtml | 105 ++++++++++ .../ApproveApplicationsModal.cshtml.cs | 188 ++++++++++++++++++ .../Approvals/ApproveApplicationsModal.css | 95 +++++++++ .../Approvals/ApproveApplicationsModal.js | 99 +++++++++ .../ApproveApplicationsSummaryModal.cshtml | 50 +++++ .../ApproveApplicationsSummaryModal.cshtml.cs | 51 +++++ .../Pages/GrantApplications/Index.cshtml | 2 + .../Pages/GrantApplications/Index.js | 3 +- .../Shared/Components/ActionBar/ActionBar.cs | 2 - .../Shared/Components/ActionBar/Default.js | 12 +- .../appsettings.Development.json | 2 +- .../Unity.GrantManager.Web/appsettings.json | 2 +- 22 files changed, 699 insertions(+), 250 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/BulkApprovalResultDto.cs delete mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationBatchApprovalDto.cs delete mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationApprovalService.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IBulkApprovalsAppService.cs rename applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/{ApplicationApprovalAppService.cs => BulkApprovalsAppService.cs} (55%) delete mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml delete mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.css create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.js create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsSummaryModal.cshtml create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsSummaryModal.cshtml.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/BulkApprovalResultDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/BulkApprovalResultDto.cs new file mode 100644 index 000000000..c3720fe7d --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/BulkApprovalResultDto.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Unity.GrantManager.GrantApplications +{ + public class BulkApprovalResultDto + { + public List Successes { get; set; } = []; + + [JsonProperty("failures")] + public List> Failures { get; set; } = []; + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationBatchApprovalDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationBatchApprovalDto.cs deleted file mode 100644 index 257d71a14..000000000 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationBatchApprovalDto.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Unity.GrantManager.GrantApplications -{ - public class GrantApplicationBatchApprovalDto - { - public GrantApplicationBatchApprovalDto() - { - ValidationMessages = []; - ReferenceNo = string.Empty; - ApplicantName = string.Empty; - } - - public List ValidationMessages { get; set; } - public bool IsValid => ValidationMessages.Count == 0; - - public Guid ApplicationId { get; set; } - public decimal ApprovedAmount { get; set; } - public decimal RequestedAmount { get; set; } - public DateTime? FinalDecisionDate { get; set; } - public string ReferenceNo { get; set; } - public string ApplicantName { get; set; } - } -} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationApprovalService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationApprovalService.cs deleted file mode 100644 index a7bb44e88..000000000 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationApprovalService.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Unity.GrantManager.GrantApplications -{ - public interface IApplicationApprovalService - { - Task BulkApproveApplications(Guid[] applicationGuids); - Task> GetApplicationsForBulkApproval(Guid[] applicationGuids); - } -} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IBulkApprovalsAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IBulkApprovalsAppService.cs new file mode 100644 index 000000000..1340a7c73 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IBulkApprovalsAppService.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Unity.GrantManager.GrantApplications +{ + public interface IBulkApprovalsAppService + { + Task BulkApproveApplications(List batchApplicationsToApprove); + Task> GetApplicationsForBulkApproval(Guid[] applicationGuids); + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationApprovalAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs similarity index 55% rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationApprovalAppService.cs rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs index bf295b5dd..ec8c4b741 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationApprovalAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs @@ -3,43 +3,81 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Unity.GrantManager.Applications; +using Unity.GrantManager.Events; using Unity.GrantManager.Permissions; +using Volo.Abp.EventBus.Local; using Volo.Abp.Uow; namespace Unity.GrantManager.GrantApplications { [Authorize] - public class ApplicationApprovalService(IApplicationRepository applicationRepository, - IGrantApplicationAppService grantApplicationsService, // Services should not inject services in same module! - IUnitOfWorkManager unitofWorkManager) : GrantManagerAppService, IApplicationApprovalService + public class BulkApprovalsAppService(IApplicationRepository applicationRepository, + IApplicationManager applicationManager, + ILocalEventBus localEventBus, + IUnitOfWorkManager unitofWorkManager) : GrantManagerAppService, IBulkApprovalsAppService { /// /// Bulk approve applications /// - /// + /// /// - public async Task BulkApproveApplications(Guid[] applicationGuids) + public async Task BulkApproveApplications(List batchApplicationsToApprove) { + var bulkApprovalResult = new BulkApprovalResultDto(); + + // Need to Look at refactoring this into the single control flow for workflow approvals + var approvalAction = GrantApplicationAction.Approve; + // We read and write individually here to make sure all applications trigger ther approval correctly as a best effort per application - foreach (var applicationId in applicationGuids) + foreach (var applicationToUpdateAndApprove in batchApplicationsToApprove) { + Application? application = null; + try { - using var uow = unitofWorkManager.Begin(requiresNew: true); - await grantApplicationsService.TriggerAction(applicationId, GrantApplicationAction.Approve); - await uow.CompleteAsync(); + // Fields to update + using var uowFields = unitofWorkManager.Begin(requiresNew: true); + application = await applicationRepository.GetAsync(applicationToUpdateAndApprove.ApplicationId); + + application.ValidateAndChangeFinalDecisionDate(applicationToUpdateAndApprove.FinalDecisionDate); + application.ValidateMinAndChangeApprovedAmount(applicationToUpdateAndApprove.ApprovedAmount); + application.ApprovedAmount = applicationToUpdateAndApprove.ApprovedAmount; + + if (!await AuthorizationService.IsGrantedAsync(application, GetActionAuthorizationRequirement(GrantApplicationAction.Approve))) + { + throw new UnauthorizedAccessException(); + } + + _ = await applicationManager.TriggerAction(application.Id, GrantApplicationAction.Approve); + + await localEventBus.PublishAsync( + new ApplicationChangedEvent + { + Action = approvalAction, + ApplicationId = application.Id + } + ); + + await uowFields.CompleteAsync(); + + bulkApprovalResult.Successes.Add(application.ReferenceNo); } catch (Exception ex) { // Log the error and continue with the next application - Logger.LogError(ex, "Error approving application with ID: {ApplicationId}", applicationId); + Logger.LogError(ex, "Error approving application with ID: {ApplicationId} and ReferenceNo: {ReferenceNo}", + applicationToUpdateAndApprove.ApplicationId, + applicationToUpdateAndApprove.ReferenceNo); + // Add to error list or handle as needed + bulkApprovalResult.Failures.Add(new KeyValuePair(application?.ReferenceNo ?? string.Empty, ex.Message)); } } - return await Task.FromResult(true); + return bulkApprovalResult; } /// @@ -47,7 +85,7 @@ public async Task BulkApproveApplications(Guid[] applicationGuids) /// /// /// - public async Task> GetApplicationsForBulkApproval(Guid[] applicationGuids) + public async Task> GetApplicationsForBulkApproval(Guid[] applicationGuids) { var applications = await applicationRepository.GetListByIdsAsync(applicationGuids); return await ValidateBulkApplications([.. applications]); @@ -59,23 +97,26 @@ public async Task> GetApplicationsForBulk ///
/// /// - private async Task> ValidateBulkApplications(Application[] applications) + private async Task> ValidateBulkApplications(Application[] applications) { - var applicationsForApproval = new List(); + var applicationsForApproval = new List(); foreach (var application in applications) { - List validationMessages = await RunValidations(application); + List<(bool, string)> validationMessages = await RunValidations(application); - applicationsForApproval.Add(new GrantApplicationBatchApprovalDto() + applicationsForApproval.Add(new BulkApprovalDto() { ApplicationId = application.Id, ApprovedAmount = application.ApprovedAmount, RequestedAmount = application.RequestedAmount, FinalDecisionDate = application.FinalDecisionDate, ReferenceNo = application.ReferenceNo, - ValidationMessages = validationMessages, - ApplicantName = application.Applicant.ApplicantName ?? string.Empty + ValidationMessages = validationMessages.Select(s => s.Item2).ToList(), + ApplicantName = application.Applicant.ApplicantName ?? string.Empty, + ApplicationStatus = application.ApplicationStatus.InternalStatus, + FormName = application.ApplicationForm?.ApplicationFormName ?? string.Empty, + IsValid = !validationMessages.Exists(s => s.Item1) }); } @@ -86,17 +127,17 @@ private async Task> ValidateBulkApplicati /// Run the validations for the application /// /// - /// - private async Task> RunValidations(Application application) + /// A tuple with validation messages and if it should trigger a invalid state for the record + private async Task> RunValidations(Application application) { var validWorkflow = MeetsWorkflowRequirement(application, GrantApplicationAction.Approve); var authorized = await MeetsAuthorizationRequirement(application, GrantApplicationAction.Approve); - var validationMessages = new List(); + var validationMessages = new List<(bool, string)>(); if (!validWorkflow) - validationMessages.Add("Invalid workflow status for approval."); + validationMessages.Add(new(true, "INVALID_STATUS")); if (!authorized) - validationMessages.Add("Insufficient permissions for approval."); + validationMessages.Add(new(true, "INVALID_PERMISSIONS")); return validationMessages; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json index 7f87e3917..6acf2e052 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json @@ -376,7 +376,11 @@ "ApplicationContact:Email": "Email", "ApplicationContact:MobilePhone": "Mobile Phone Number", "ApplicationContact:WorkPhone": "Work Phone Number", - "ApplicationContact:Delete": "Delete This Contact" + "ApplicationContact:Delete": "Delete This Contact", + + "ApplicationBatchApprovalRequest:Title": "Approve Applications", + "ApplicationBatchApprovalRequest:SubmitButtonText": "Approve", + "ApplicationBatchApprovalRequest:CancelButtonText": "Cancel" } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/Application.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/Application.cs index 1291424ab..56770a47b 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/Application.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/Application.cs @@ -192,4 +192,12 @@ public void ValidateAndChangeFinalDecisionDate(DateTime? finalDecisionDate) FinalDecisionDate = finalDecisionDate; } } + + public void ValidateMinAndChangeApprovedAmount(decimal approvedAmount) + { + if ((ApprovedAmount != approvedAmount) && approvedAmount <= 0m) + { + throw new BusinessException("Approved amount cannot be 0."); + } + } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs index e3cbf0a98..c7a07a555 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs @@ -67,6 +67,7 @@ public async Task> GetListByIdsAsync(Guid[] ids) .AsNoTracking() .Include(s => s.ApplicationStatus) .Include(s => s.Applicant) + .Include(s => s.ApplicationForm) .Where(s => ids.Contains(s.Id)) .ToListAsync(); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml deleted file mode 100644 index 1f8fcddd2..000000000 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml +++ /dev/null @@ -1,75 +0,0 @@ -@page -@model Unity.GrantManager.Web.Pages.Approve.ApproveApplicationsModalModel -@using Microsoft.Extensions.Localization -@using Unity.GrantManager.Localization -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@inject IStringLocalizer L - -@{ - Layout = null; -} - -
- - - - - - @for (var i = 0; i < Model.BulkApplicationApprovals?.Count; i++) - { -
- - -
@Model.BulkApplicationApprovals[i].ApplicantName (@Model.BulkApplicationApprovals[i].ReferenceNo)
-
- - - -
- - - - - - @for (var j = 0; j < Model.BulkApplicationApprovals[i].Errors?.Length; j++) - { - - Note: @Model.BulkApplicationApprovals[i].Errors[j] - - } -
- } -
-
-
- - - - - - - -
-
- - diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml.cs deleted file mode 100644 index f06af9390..000000000 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Approve/ApproveApplicationsModal.cshtml.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Unity.GrantManager.GrantApplications; -using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; - -namespace Unity.GrantManager.Web.Pages.Approve; - -public class ApproveApplicationsModalModel(IApplicationApprovalService applicationApprovalService) : AbpPageModel -{ - [BindProperty] - public string? SelectedApplicationIds { get; set; } - - [BindProperty] - public List? BulkApplicationApprovals { get; set; } - - [TempData] - public List? PopupMessages { get; set; } - - [TempData] - public string? PopupTitle { get; set; } - - [TempData] - public bool Invalid { get; set; } - - private const int _maxBatchCount = 50; - - public async void OnGet(string applicationIds) - { - SelectedApplicationIds = applicationIds; - PopupMessages = []; - Invalid = false; - BulkApplicationApprovals = []; - - Guid[] applicationGuids = ParseApplicationIds(); - - if (!ValidCount(applicationGuids)) - { - PopupMessages.Add($"You can only approve {_maxBatchCount} applications at a time. Please select fewer applications."); - Invalid = true; - } - - // Load the applications by Id - var applications = await applicationApprovalService.GetApplicationsForBulkApproval(ParseApplicationIds()); - - foreach (var application in applications) - { - BulkApplicationApprovals.Add(new BulkApplicationApproval() - { - ApplicationId = application.ApplicationId, - ReferenceNo = application.ReferenceNo, - Errors = [.. application.ValidationMessages], - ApplicantName = application.ApplicantName - }); - } - } - - public async Task OnPostAsync() - { - try - { - // Fire off request to approve the applications - var result = await applicationApprovalService.BulkApproveApplications(ParseApplicationIds()); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error updating application statuses"); - } - return NoContent(); - } - - private Guid[] ParseApplicationIds() - { - return JsonConvert.DeserializeObject(SelectedApplicationIds ?? string.Empty) ?? []; - } - - private static bool ValidCount(Guid[] applicationGuids) - { - // Soft check in the UI for max approvals in one batch, this is subject to be tweaked later after performance testing - return applicationGuids.Length <= _maxBatchCount; - } - - public class BulkApplicationApproval - { - public BulkApplicationApproval() - { - Errors = []; - } - - public Guid ApplicationId { get; set; } - public string ReferenceNo { get; set; } = string.Empty; - public string ApplicantName { get; set; } = string.Empty; - public bool Disabled => Errors.Length > 0; - public string[] Errors { get; set; } - } -} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml new file mode 100644 index 000000000..535754c84 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml @@ -0,0 +1,105 @@ +@page +@model Unity.GrantManager.Web.Pages.GrantApplications.Approvals.ApproveApplicationsModalModel +@using Microsoft.Extensions.Localization +@using Unity.GrantManager.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@inject IStringLocalizer L + +@{ + Layout = null; +} + +
+ + + + + + + @for (var i = 0; i < Model.BulkApplicationApprovals?.Count; i++) + { +
+ + +
+
@Model.BulkApplicationApprovals[i].ReferenceNo
+
@Model.BulkApplicationApprovals[i].ApplicantName
+
@string.Format("({0})", @Model.BulkApplicationApprovals[i].FormName)
+
@Model.BulkApplicationApprovals[i].ApplicationStatus
+
+
+ + + +
+ + + + + + + + + + + + + + + + + + + @for (var j = 0; j < Model.BulkApplicationApprovals[i].Notes?.Count; j++) + { + + Note: @Model.BulkApplicationApprovals[i].Notes[j].Key + + } + +
+ } +
+
+
+ + + + + + + +
+
+ + diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml.cs new file mode 100644 index 000000000..cbb574d3b --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml.cs @@ -0,0 +1,188 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading.Tasks; +using Unity.GrantManager.GrantApplications; +using Unity.Modules.Shared.Utils; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Unity.GrantManager.Web.Pages.GrantApplications.Approvals; + +public class ApproveApplicationsModalModel(IBulkApprovalsAppService bulkApprovalsAppService, + BrowserUtils browserUtils) : AbpPageModel +{ + [BindProperty] + public List? BulkApplicationApprovals { get; set; } + + [TempData] + public List? PopupMessages { get; set; } + + [TempData] + public string? PopupTitle { get; set; } + + [TempData] + public int ApplicationsCount { get; set; } + + [TempData] + public bool Invalid { get; set; } + + private const int _maxBatchCount = 50; + + public async void OnGet(string applicationIds) + { + PopupMessages = []; + Invalid = false; + BulkApplicationApprovals = []; + + Guid[] applicationGuids = ParseApplicationIds(applicationIds); + + if (!ValidCount(applicationGuids)) + { + PopupMessages.Add($"You can only approve {_maxBatchCount} applications at a time. Please select fewer applications."); + Invalid = true; + } + + // Load the applications by Id + var applications = await bulkApprovalsAppService.GetApplicationsForBulkApproval(applicationGuids); + var offsetMinutes = browserUtils.GetBrowserOffset(); + + foreach (var application in applications) + { + var bulkApproval = new BulkApplicationApproval + { + ApplicationId = application.ApplicationId, + ReferenceNo = application.ReferenceNo, + ApplicantName = application.ApplicantName, + DecisionDate = application.FinalDecisionDate ?? DateTime.UtcNow.AddMinutes(-offsetMinutes), + RequestedAmount = application.RequestedAmount, + ApprovedAmount = application.ApprovedAmount == 0m ? application.RequestedAmount : application.ApprovedAmount, + ApplicationStatus = application.ApplicationStatus, + FormName = application.FormName, + IsValid = application.IsValid, + Notes = SetNotesForApplication(application) + }; + + BulkApplicationApprovals.Add(bulkApproval); + } + + Invalid = applications.Exists(s => !s.IsValid); + ApplicationsCount = applications.Count; + } + + private static List> SetNotesForApplication(BulkApprovalDto application) + { + var notes = new List> + { + new("DECISION_DATE_DEFAULTED", false), + new("APPROVED_AMOUNT_DEFAULTED", false), + new("INVALID_STATUS", false), + new("INVALID_PERMISSIONS", false), + new("INVALID_APPROVED_AMOUNT", false) + }; + + + if (application.FinalDecisionDate == null) + { + notes[0] = new KeyValuePair("DECISION_DATE_DEFAULTED", true); + } + + if (application.ApprovedAmount == 0m) + { + notes[1] = new KeyValuePair("APPROVED_AMOUNT_DEFAULTED", true); + } + + foreach (var validation in application.ValidationMessages) + { + for (int i = 2; i < notes.Count; i++) + { + if (notes[i].Key == validation) + { + notes[i] = new KeyValuePair(notes[i].Key, true); + } + } + } + + return notes; + } + + public async Task OnPostAsync() + { + try + { + if (BulkApplicationApprovals == null) return NoContent(); + + var approvalRequests = MapBulkApprovalRequests(); + + // Fire off request to approve the applications + var result = await bulkApprovalsAppService.BulkApproveApplications(approvalRequests); + + // Return the result out + return new OkObjectResult(result); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error updating application statuses"); + } + + return NoContent(); + } + + private List MapBulkApprovalRequests() + { + var bulkApprovals = new List(); + foreach (var application in BulkApplicationApprovals ?? []) + { + bulkApprovals.Add(new BulkApprovalDto() + { + ApplicantName = application.ApplicantName, + ApplicationId = application.ApplicationId, + ApprovedAmount = application.ApprovedAmount, + FinalDecisionDate = application.DecisionDate, + ReferenceNo = application.ReferenceNo, + RequestedAmount = application.RequestedAmount, + ValidationMessages = [] + }); + } + + return bulkApprovals; + } + + private static Guid[] ParseApplicationIds(string applicationIds) + { + return JsonConvert.DeserializeObject(applicationIds ?? string.Empty) ?? []; + } + + private static bool ValidCount(Guid[] applicationGuids) + { + // Soft check in the UI for max approvals in one batch, this is subject to be tweaked later after performance testing + return applicationGuids.Length <= _maxBatchCount; + } + + public class BulkApplicationApproval + { + public BulkApplicationApproval() + { + Notes = []; + } + + public Guid ApplicationId { get; set; } + public string ReferenceNo { get; set; } = string.Empty; + public string ApplicantName { get; set; } = string.Empty; + public string FormName { get; set; } = string.Empty; + public string ApplicationStatus { get; set; } = string.Empty; + + [DisplayName("Requested Amount")] + public decimal RequestedAmount { get; set; } = 0m; + + [DisplayName("Approved Amount")] + public decimal ApprovedAmount { get; set; } = 0m; + + [DisplayName("Decision Date")] + public DateTime DecisionDate { get; set; } + public bool IsValid { get; set; } + public List> Notes { get; set; } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.css new file mode 100644 index 000000000..764b85572 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.css @@ -0,0 +1,95 @@ +.batch-approval-modal-body { + max-height: 65vh; + overflow: auto; +} + +.batch-approval-container { + border: var(--bs-border-width) solid; + border-color: var(--bs-border-color); +} + +.bulk-approval-row-header { + padding-bottom: 0 !important; +} + +.bulk-approval-remove-row { + min-width: 55px +} + + + +/* This is for the summary modal*/ + +.table-container { + max-height: 65vh; + overflow-y: auto; +} + +.custom-table { + width: 100%; + border-collapse: collapse; +} + +.centered-icon { + text-align: center; + padding-bottom: 0.85rem; +} + +.custom-table thead th { + position: sticky; + top: 0; + background-color: white; + z-index: 1; + font-size: 1rem; + padding-bottom: 1rem; +} + +.custom-table .col-5 { + width: 5%; +} + +.custom-table .col-20 { + width: 20%; +} + +.custom-table .col-75 { + width: 75%; +} + +.approval-details-header { + display: flex; + align-items: center; +} + + .approval-details-header + .reference-no { + font-weight: 700; + } + + .approval-details-header + .applicant-name { + font-weight: 400; + color: var(--bc-type-color-secondary); + margin-left: 0.75rem; + padding-right: 0.75rem !important; + } + + .approval-details-header + .form-name { + font-weight: 400; + color: var(--bc-type-color-secondary); + font-size: 0.8rem; + } + + .approval-details-header + .application-status { + font-weight: 700; + color: var(--bc-colors-blue-text-links); + text-transform: uppercase; + border: 3px solid var(--bc-colors-blue-text-links); + border-radius: 1rem; + font-size: 0.8rem; + padding: 0.125rem 0.5rem; + margin: 0 0.5rem; + margin-top: 2px; + } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.js new file mode 100644 index 000000000..4dc39fb12 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.js @@ -0,0 +1,99 @@ +let approveApplicationsSummaryModal = new abp.ModalManager({ + viewUrl: 'GrantApplications/Approvals/ApproveApplicationsSummaryModal' +}); +function removeApplicationApproval(applicationId) { + $('#' + applicationId).remove(); + let applicationsCount = $('#ApplicationsCount').val(); + $('#ApplicationsCount').val(applicationsCount - 1); + runValidations(); +} + +function approvedAmountUpdated(event) { + const input = event.target; + const value = parseFloat(input.value.replace(/,/g, '')); + + setNote(event.target, '_APPROVED_AMOUNT_DEFAULTED', false); + + if (isNaN(value) || value <= 0) { + setNote(event.target, '_INVALID_APPROVED_AMOUNT', true); + } else { + setNote(event.target, '_INVALID_APPROVED_AMOUNT', false); + } + runValidations(); +} + +function decisionDateUpdated(event) { + setNote(event.target, '_DECISION_DATE_DEFAULTED', false) + runValidations(); +} + +function setNote(target, note, visible) { + const input = target; + const containerId = input.closest('.batch-approval-container').id; + const noteField = $('#' + containerId + note); + + if (noteField.length) { + if (visible) { + noteField.css('display', 'block'); + } else { + noteField.css('display', 'none'); + } + } +} + +function runValidations() { + let isValid = true; + let itemCount = 0; + + $('#bulkApprovalForm input[name="BulkApplicationApprovals.Index"]').each(function () { + itemCount++; + let index = $(this).val(); + let approvedAmount = parseFloat($('#bulkApprovalForm input[name="BulkApplicationApprovals[' + index + '].ApprovedAmount"]').val().replace(/,/g, '')); + let decisionDate = new Date($('#bulkApprovalForm input[name="BulkApplicationApprovals[' + index + '].DecisionDate"]').val()); + let isValidField = $('#bulkApprovalForm input[name="BulkApplicationApprovals[' + index + '].IsValid"]').val(); + + if (isValidField.toLowerCase() !== 'true' || isNaN(approvedAmount) || approvedAmount <= 0 || isNaN(decisionDate.getTime()) || decisionDate > new Date()) { + isValid = false; + } + }); + + if (itemCount === 0) { + isValid = false; + } + + if (isValid) { + enableBulkApprovalSubmit(); + } else { + disableBulkApprovalSubmit(); + } +} + +function enableBulkApprovalSubmit() { + $("#approveApplicationsModal") + .find('#btnSubmitBatchApproval').prop("disabled", false); +} + +function disableBulkApprovalSubmit() { + $("#approveApplicationsModal") + .find('#btnSubmitBatchApproval').prop("disabled", true); +} + +function closeApprovals() { + $('#approveApplicationsModal').modal('hide'); +} + +function handleBulkApplicationsApprovalResponse(response) { + let transformedFailures = response.responseText.failures.map(failure => { + return { + Key: failure.key, + Value: failure.value + }; + }); + + let summaryJson = JSON.stringify( + { + Successes: response.responseText.successes, + Failures: transformedFailures + }); + approveApplicationsSummaryModal.open({ summaryJson: summaryJson }); +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsSummaryModal.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsSummaryModal.cshtml new file mode 100644 index 000000000..8887cd259 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsSummaryModal.cshtml @@ -0,0 +1,50 @@ +@page +@model Unity.GrantManager.Web.Pages.GrantApplications.Approvals.ApproveApplicationsSummaryModalModel +@using Microsoft.Extensions.Localization +@using Unity.GrantManager.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@inject IStringLocalizer L + +@{ + Layout = null; +} + + + + Approval Summary + + +
+ + + + + + + + @for (var i = 0; i < Model.BulkApprovalResults?.Count; i++) + { + + + + + + } + +
Reference No:Status:
+ @if (Model.BulkApprovalResults[i].IsSuccess) + { + + } + else + { + + } + + + + +
+
+
+
\ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsSummaryModal.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsSummaryModal.cshtml.cs new file mode 100644 index 000000000..66142c569 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsSummaryModal.cshtml.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using Unity.GrantManager.GrantApplications; + +namespace Unity.GrantManager.Web.Pages.GrantApplications.Approvals +{ + public class ApproveApplicationsSummaryModalModel : PageModel + { + [BindProperty] + public List? BulkApprovalResults { get; set; } + + public void OnGet(string summaryJson) + { + var items = new List(); + + var result = JsonSerializer.Deserialize(summaryJson); + + foreach (var item in result?.Successes ?? []) + { + items.Add(new BulkApprovalItemResult + { + ReferenceNo = item, + Message = "Success", + IsSuccess = true + }); + } + + foreach (var item in result?.Failures ?? []) + { + items.Add(new BulkApprovalItemResult + { + ReferenceNo = item.Key, + Message = item.Value, + IsSuccess = false + }); + } + + BulkApprovalResults = [.. items.OrderBy(s => s.ReferenceNo)]; + } + + public class BulkApprovalItemResult + { + public string ReferenceNo { get; set; } = string.Empty; + public string Message { get; set; } = string.Empty; + public bool IsSuccess { get; set; } + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.cshtml index 92d946308..1f308cda2 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.cshtml @@ -25,6 +25,7 @@ + } else { @@ -34,6 +35,7 @@ @section styles { + }
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.js index f9815ab58..49fe1d888 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.js @@ -237,8 +237,7 @@ data: 'referenceNo', name: 'referenceNo', className: 'data-table-header', - render: function (data, type, row) { - console.log(row); + render: function (data, type, row) { return `${data}`; }, index: 2 diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/ActionBar.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/ActionBar.cs index 1dccaaab9..7a33c5e5e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/ActionBar.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/ActionBar.cs @@ -43,8 +43,6 @@ public override void ConfigureBundle(BundleConfigurationContext context) .AddIfNotContains("/libs/jquery-maskmoney/dist/jquery.maskMoney.min.js"); context.Files .AddIfNotContains("/Pages/PaymentApprovals/UpdatePaymentRequestStatusModal.js"); - context.Files - .AddIfNotContains("/Pages/Approve/ApproveApplicationsPollingModal.js"); } } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.js index e6a288aab..1613786cf 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.js @@ -10,10 +10,7 @@ $(function () { viewUrl: 'StatusUpdate/StatusUpdateModal' }); let approveApplicationsModal = new abp.ModalManager({ - viewUrl: 'Approve/ApproveApplicationsModal' - }); - let approveApplicationsPollingModal = new abp.ModalManager({ - viewUrl: 'Approve/ApproveApplicationsPollingModal' + viewUrl: 'GrantApplications/Approvals/ApproveApplicationsModal' }); let tagApplicationModal = new abp.ModalManager({ viewUrl: 'ApplicationTags/ApplicationTagsSelectionModal', @@ -136,11 +133,8 @@ $(function () { applicationIds: JSON.stringify(selectedApplicationIds) }); }); - approveApplicationsModal.onResult(function (output) { - console.log(output); - approveApplicationsPollingModal.open({ batchId: '123' }); - }); - approveApplicationsModal.onClose(function () { + approveApplicationsModal.onResult(function (_, response) { + handleBulkApplicationsApprovalResponse(response); PubSub.publish("refresh_application_list"); }); // Batch Approval End diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.Development.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.Development.json index 61ffb75e0..5e137c87a 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.Development.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.Development.json @@ -17,7 +17,7 @@ } }, "Intake": { - "BaseUri": "https://chefs-dev.apps.silver.devops.gov.bc.ca/app/api/v1", + "BaseUri": "https://chefs-test.apps.silver.devops.gov.bc.ca/app/api/v1", "FormId": "", "ApiKey": "", "BearerTokenPlaceholder": "", diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json index 408eac87d..f93917fa2 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json @@ -101,7 +101,7 @@ "Host": "127.0.0.1", "InstanceName": "redis", "KeyPrefix": "Unity", - "Password": "SUPER_SECRET_PASSWORD", + "Password": "******", "Port": 6379 }, "Logging": { From 2ff653c1cdd49b40e7e8a4068de8b084aa03f99d Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Tue, 15 Apr 2025 16:53:12 -0700 Subject: [PATCH 5/8] AB#23678 summary block and logic added --- .../BulkApprovalsAppService.cs | 12 ++++ .../GrantApplications/BatchApprovalConsts.cs | 7 ++ .../Localization/GrantManager/en.json | 9 ++- .../Approvals/ApproveApplicationsModal.cshtml | 26 +++++-- .../ApproveApplicationsModal.cshtml.cs | 71 +++++++++++-------- .../Approvals/ApproveApplicationsModal.css | 23 +++++- .../Approvals/ApproveApplicationsModal.js | 22 ++++++ 7 files changed, 133 insertions(+), 37 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/GrantApplications/BatchApprovalConsts.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs index ec8c4b741..93eba622c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs @@ -8,6 +8,7 @@ using Unity.GrantManager.Applications; using Unity.GrantManager.Events; using Unity.GrantManager.Permissions; +using Volo.Abp; using Volo.Abp.EventBus.Local; using Volo.Abp.Uow; @@ -31,6 +32,12 @@ public async Task BulkApproveApplications(List BatchApprovalConsts.MaxBatchCount) + { + throw new UserFriendlyException(L["ApplicationBatchApprovalRequest:MaxCountExceeded", BatchApprovalConsts.MaxBatchCount].Value); + } + // We read and write individually here to make sure all applications trigger ther approval correctly as a best effort per application foreach (var applicationToUpdateAndApprove in batchApplicationsToApprove) { @@ -42,6 +49,11 @@ public async Task BulkApproveApplications(List 50; + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json index a7d1c557c..86e427e00 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json @@ -416,7 +416,12 @@ "ApplicationBatchApprovalRequest:Title": "Approve Applications", "ApplicationBatchApprovalRequest:SubmitButtonText": "Approve", - "ApplicationBatchApprovalRequest:CancelButtonText": "Cancel" + "ApplicationBatchApprovalRequest:CancelButtonText": "Cancel", + "ApplicationBatchApprovalRequest:DecisionDateDefaulted": "Decision Date has been defaulted", + "ApplicationBatchApprovalRequest:ApprovedAmountDefaulted": "Approved Amount has been defaulted", + "ApplicationBatchApprovalRequest:InvalidStatus": "The assessment for the selected item is not in the Assessment Completed state", + "ApplicationBatchApprovalRequest:InvalidPermissions": "Invalid permissions", + "ApplicationBatchApprovalRequest:InvalidApprovedAmount": "Invalid Approved Amount, it must be greater than 0.00", + "ApplicationBatchApprovalRequest:MaxCountExceeded": "You have exceeded the maximum number of items for bulk approval. Please reduce the number to {0} or fewer" } } - diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml index 535754c84..d06d85447 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml @@ -1,6 +1,7 @@ @page @model Unity.GrantManager.Web.Pages.GrantApplications.Approvals.ApproveApplicationsModalModel @using Microsoft.Extensions.Localization +@using Unity.GrantManager.GrantApplications @using Unity.GrantManager.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @inject IStringLocalizer L @@ -12,10 +13,11 @@
- - - + + + + @for (var i = 0; i < Model.BulkApplicationApprovals?.Count; i++) {
@@ -44,7 +46,6 @@ - @@ -57,8 +58,18 @@ @for (var j = 0; j < Model.BulkApplicationApprovals[i].Notes?.Count; j++) { - - Note: @Model.BulkApplicationApprovals[i].Notes[j].Key + + + @if (Model.BulkApplicationApprovals[i].Notes[j].IsError) + { + Error + } + else + { + Note + } + @Model.BulkApplicationApprovals[i].Notes[j].Description + } @@ -66,6 +77,9 @@ } +
+ Error @Model.MaxBatchCountExceededError +
? BulkApplicationApprovals { get; set; } [TempData] - public List? PopupMessages { get; set; } + public int ApplicationsCount { get; set; } [TempData] - public string? PopupTitle { get; set; } + public bool Invalid { get; set; } [TempData] - public int ApplicationsCount { get; set; } + public int MaxBatchCount { get; set; } [TempData] - public bool Invalid { get; set; } + public string? MaxBatchCountExceededError { get; set; } - private const int _maxBatchCount = 50; + [TempData] + public bool MaxBatchCountExceeded { get; set; } public async void OnGet(string applicationIds) { - PopupMessages = []; - Invalid = false; + MaxBatchCount = BatchApprovalConsts.MaxBatchCount; BulkApplicationApprovals = []; + MaxBatchCountExceededError = L["ApplicationBatchApprovalRequest:MaxCountExceeded", BatchApprovalConsts.MaxBatchCount.ToString()].Value; Guid[] applicationGuids = ParseApplicationIds(applicationIds); if (!ValidCount(applicationGuids)) { - PopupMessages.Add($"You can only approve {_maxBatchCount} applications at a time. Please select fewer applications."); - Invalid = true; + MaxBatchCountExceeded = true; } // Load the applications by Id @@ -68,40 +70,37 @@ public async void OnGet(string applicationIds) BulkApplicationApprovals.Add(bulkApproval); } - Invalid = applications.Exists(s => !s.IsValid); + Invalid = applications.Exists(s => !s.IsValid) || MaxBatchCountExceeded; ApplicationsCount = applications.Count; } - private static List> SetNotesForApplication(BulkApprovalDto application) + private List SetNotesForApplication(BulkApprovalDto application) { - var notes = new List> + var notes = new List { - new("DECISION_DATE_DEFAULTED", false), - new("APPROVED_AMOUNT_DEFAULTED", false), - new("INVALID_STATUS", false), - new("INVALID_PERMISSIONS", false), - new("INVALID_APPROVED_AMOUNT", false) + new("DECISION_DATE_DEFAULTED", false, L.GetString("ApplicationBatchApprovalRequest:DecisionDateDefaulted"), false), + new("APPROVED_AMOUNT_DEFAULTED", false, L.GetString("ApplicationBatchApprovalRequest:ApprovedAmountDefaulted"), false), + new("INVALID_STATUS", false, L.GetString("ApplicationBatchApprovalRequest:InvalidStatus"), true), + new("INVALID_PERMISSIONS", false, L.GetString("ApplicationBatchApprovalRequest:InvalidPermissions"), true), + new("INVALID_APPROVED_AMOUNT", false, L.GetString("ApplicationBatchApprovalRequest:InvalidApprovedAmount"), true) }; - if (application.FinalDecisionDate == null) { - notes[0] = new KeyValuePair("DECISION_DATE_DEFAULTED", true); + notes[0] = new ApprovalNote(notes[0].Key, true, notes[0].Description, notes[0].IsError); } if (application.ApprovedAmount == 0m) { - notes[1] = new KeyValuePair("APPROVED_AMOUNT_DEFAULTED", true); + notes[0] = new ApprovalNote(notes[1].Key, true, notes[1].Description, notes[1].IsError); } foreach (var validation in application.ValidationMessages) { - for (int i = 2; i < notes.Count; i++) + var index = notes.FindIndex(note => note.Key == validation); + if (index != -1) { - if (notes[i].Key == validation) - { - notes[i] = new KeyValuePair(notes[i].Key, true); - } + notes[index] = new ApprovalNote(validation, true, notes[index].Description, notes[index].IsError); } } @@ -155,10 +154,10 @@ private static Guid[] ParseApplicationIds(string applicationIds) return JsonConvert.DeserializeObject(applicationIds ?? string.Empty) ?? []; } - private static bool ValidCount(Guid[] applicationGuids) + private bool ValidCount(Guid[] applicationGuids) { // Soft check in the UI for max approvals in one batch, this is subject to be tweaked later after performance testing - return applicationGuids.Length <= _maxBatchCount; + return applicationGuids.Length <= MaxBatchCount; } public class BulkApplicationApproval @@ -183,6 +182,22 @@ public BulkApplicationApproval() [DisplayName("Decision Date")] public DateTime DecisionDate { get; set; } public bool IsValid { get; set; } - public List> Notes { get; set; } + public List Notes { get; set; } + } + + public class ApprovalNote + { + public ApprovalNote(string key, bool active, string description, bool isError) + { + Key = key; + Active = active; + Description = description; + IsError = isError; + } + + public string Key { get; set; } + public bool Active { get; set; } + public string Description { get; set; } + public bool IsError { get; set; } } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.css index 764b85572..0055174e3 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.css +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.css @@ -1,4 +1,4 @@ -.batch-approval-modal-body { +.batch-approval-card { max-height: 65vh; overflow: auto; } @@ -93,3 +93,24 @@ margin: 0 0.5rem; margin-top: 2px; } + + +.approval-note-prefix { + font-weight: 700; + color: var(--bc-colors-blue-text-links); + text-transform: uppercase; + border: 3px solid var(--bc-colors-blue-text-links); + border-radius: 1rem; + font-size: 0.8rem; + padding: 0.025rem 0.5rem; + line-height: 1.75rem; +} + +.approval-note-error { + color: var(--lpx-danger); + border-color: var(--lpx-danger); +} + +.batch-approval-summary { + text-align: center; +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.js index 4dc39fb12..668f40e7c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.js @@ -61,6 +61,13 @@ function runValidations() { isValid = false; } + if (!validBatchCount()) { + isValid = false; + setMaxCountError(true); + } else { + setMaxCountError(false); + } + if (isValid) { enableBulkApprovalSubmit(); } else { @@ -68,6 +75,21 @@ function runValidations() { } } +function setMaxCountError(visible) { + const summary = $('#batch-approval-summary'); + if (visible) { + summary.css('display', 'block'); + } else { + summary.css('display', 'none'); + } +} + +function validBatchCount() { + let applicationsCount = $('#ApplicationsCount').val(); + let maxBatchCount = $('#MaxBatchCount').val(); + return applicationsCount <= maxBatchCount; +} + function enableBulkApprovalSubmit() { $("#approveApplicationsModal") .find('#btnSubmitBatchApproval').prop("disabled", false); From fb75be199e67aa5485ebbfaa197656c767f5d97e Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Wed, 16 Apr 2025 08:47:00 -0700 Subject: [PATCH 6/8] AB#23678 - SQ cleanup --- .../GrantApplications/BulkApprovalsAppService.cs | 6 ------ .../Repositories/ApplicationRepository.cs | 1 - .../Approvals/ApproveApplicationsModal.cshtml | 3 +-- .../Approvals/ApproveApplicationsSummaryModal.cshtml | 2 +- 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs index 93eba622c..c27a9eaf3 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs @@ -32,12 +32,6 @@ public async Task BulkApproveApplications(List BatchApprovalConsts.MaxBatchCount) - { - throw new UserFriendlyException(L["ApplicationBatchApprovalRequest:MaxCountExceeded", BatchApprovalConsts.MaxBatchCount].Value); - } - // We read and write individually here to make sure all applications trigger ther approval correctly as a best effort per application foreach (var applicationToUpdateAndApprove in batchApplicationsToApprove) { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs index c7a07a555..9aa9ac181 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs @@ -1,4 +1,3 @@ -using Amazon.S3.Model; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml index d06d85447..708a33620 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml @@ -1,9 +1,8 @@ @page @model Unity.GrantManager.Web.Pages.GrantApplications.Approvals.ApproveApplicationsModalModel @using Microsoft.Extensions.Localization -@using Unity.GrantManager.GrantApplications @using Unity.GrantManager.Localization -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal + @inject IStringLocalizer L @{ diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsSummaryModal.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsSummaryModal.cshtml index 8887cd259..e01459415 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsSummaryModal.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsSummaryModal.cshtml @@ -15,7 +15,7 @@
- +
From 659dfa28f7dad9a1caa22b707c59efff891555e4 Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Wed, 16 Apr 2025 09:18:52 -0700 Subject: [PATCH 7/8] AB#23678 - more SQ cleanup --- .../GrantApplications/BulkApprovalsAppService.cs | 1 - .../Approvals/ApproveApplicationsModal.cshtml.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs index c27a9eaf3..95ad1310e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/BulkApprovalsAppService.cs @@ -8,7 +8,6 @@ using Unity.GrantManager.Applications; using Unity.GrantManager.Events; using Unity.GrantManager.Permissions; -using Volo.Abp; using Volo.Abp.EventBus.Local; using Volo.Abp.Uow; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml.cs index 9b2e6392f..3b6a696dd 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Approvals/ApproveApplicationsModal.cshtml.cs @@ -1,4 +1,3 @@ -using Amazon.S3.Model; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; From eaf17163ab15436394545d78af5d47068fa58a7b Mon Sep 17 00:00:00 2001 From: Patrick Lavoie Date: Thu, 17 Apr 2025 08:42:40 -0700 Subject: [PATCH 8/8] AB#28209 - Localize and Color Payment Status in Payment Info Tab --- .../Localization/Payments/en.json | 19 ++++++- .../Pages/PaymentRequests/Index.js | 52 +------------------ .../Shared/Components/PaymentInfo/Default.js | 40 +++++++++++++- 3 files changed, 58 insertions(+), 53 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Localization/Payments/en.json b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Localization/Payments/en.json index 0493edd6f..1173ab47f 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Localization/Payments/en.json +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Localization/Payments/en.json @@ -102,13 +102,28 @@ "ApplicationPaymentStatusRequest:L2ApproveOrDeclineTitle": "Approve/Decline L2 Payment Requests", "ApplicationPaymentStatusRequest:L3ApproveOrDeclineTitle": "Approve/Decline L3 Payment Requests", - "Permission:Payments": "Payments", "Permission:Payments.Default": "Payments", "Permission:Payments.L1ApproveOrDecline": "Approve/Decline L1 Payments", "Permission:Payments.L2ApproveOrDecline": "Approve/Decline L2 Payments", "Permission:Payments.L3ApproveOrDecline": "Approve/Decline L3 Payments", "Permission:Payments.RequestPayment": "Request Payment", - "Permission:Payments.EditSupplierInfo": "Update Supplier Info" + "Permission:Payments.EditSupplierInfo": "Update Supplier Info", + + "Enum:PaymentRequestStatus.L1Pending": "L1 Pending", + "Enum:PaymentRequestStatus.L1Approved": "L1 Approved", + "Enum:PaymentRequestStatus.L1Declined": "L1 Declined", + "Enum:PaymentRequestStatus.L2Pending": "L2 Pending", + "Enum:PaymentRequestStatus.L2Approved": "L2 Approved", + "Enum:PaymentRequestStatus.L2Declined": "L2 Declined", + "Enum:PaymentRequestStatus.L3Pending": "L3 Pending", + "Enum:PaymentRequestStatus.L3Approved": "L3 Approved", + "Enum:PaymentRequestStatus.L3Declined": "L3 Declined", + "Enum:PaymentRequestStatus.Submitted": "Submitted to CAS", + "Enum:PaymentRequestStatus.Validated": "Validated", + "Enum:PaymentRequestStatus.NotValidated": "Not Validated", + "Enum:PaymentRequestStatus.Paid": "Paid", + "Enum:PaymentRequestStatus.Failed": "Payment Failed", + "Enum:PaymentRequestStatus.PaymentFailed": "Payment Failed" } } \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js index fec3434e1..ac3452311 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js @@ -424,10 +424,8 @@ $(function () { className: 'data-table-header', index: columnIndex, render: function (data) { - - let statusText = getStatusText(data); let statusColor = getStatusTextColor(data); - return '' + statusText + ''; + return `` + l(`Enum:PaymentRequestStatus.${data}`) + ''; } }; } @@ -653,7 +651,7 @@ $(function () { case "Paid": return "#42814A"; - case "PaymentFailed": + case "Failed": return "#CE3E39"; default: @@ -661,52 +659,6 @@ $(function () { } } - function getStatusText(status) { - - switch (status) { - - case "L1Pending": - return "L1 Pending"; - - case "L1Approved": - return "L1 Approved"; - - case "L1Declined": - return "L1 Declined"; - - case "L2Pending": - return "L2 Pending"; - - case "L2Approved": - return "L2 Approved"; - - case "L2Declined": - return "L2 Declined"; - - case "L3Pending": - return "L3 Pending"; - - case "L3Approved": - return "L3 Approved"; - - case "L3Declined": - return "L3 Declined"; - - case "Submitted": - return "Submitted to CAS"; - - case "Paid": - return "Paid"; - - case "PaymentFailed": - return "Payment Failed"; - - - default: - return "Created"; - } - } - $('.select-all-payments').click(function () { if ($(this).is(':checked')) { dataTable.rows({ 'page': 'current' }).select(); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/PaymentInfo/Default.js b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/PaymentInfo/Default.js index a8b4ff430..dcb1baa68 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/PaymentInfo/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/PaymentInfo/Default.js @@ -244,7 +244,11 @@ name: 'status', data: 'status', className: 'data-table-header', - index: 3 + index: 3, + render: function (data) { + let statusColor = getPaymentStatusTextColor(data); + return `` + l(`Enum:PaymentRequestStatus.${data}`) + ''; + } }; } @@ -431,3 +435,37 @@ function enablePaymentInfoSaveBtn() { function nullToEmpty(value) { return value == null ? '' : value; } + +function getPaymentStatusTextColor(status) { + switch (status) { + case "L1Pending": + return "#053662"; + + case "L1Declined": + return "#CE3E39"; + + case "L2Pending": + return "#053662"; + + case "L2Declined": + return "#CE3E39"; + + case "L3Pending": + return "#053662"; + + case "L3Declined": + return "#CE3E39"; + + case "Submitted": + return "#5595D9"; + + case "Paid": + return "#42814A"; + + case "Failed": + return "#CE3E39"; + + default: + return "#053662"; + } +}
Reference No: