From 2c0942f8240857e6c9879c69dd6ed2b6aa6be503 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Thu, 26 Mar 2026 15:18:57 -0700 Subject: [PATCH 1/6] [AB#32197] Implement RedStop restriction for application actions --- .../IGrantApplicationAppService.cs | 36 +++++++++---------- .../GrantApplicationAppService.cs | 21 +++++++++-- .../Localization/GrantManager/en.json | 1 + .../ApplicationActionWidget.cs | 3 +- .../ApplicationActionWidgetViewModel.cs | 1 + .../ApplicationActionWidget/Default.cshtml | 11 ++++-- .../ApplicationActionWidget/Default.js | 22 +++++++++--- 7 files changed, 65 insertions(+), 30 deletions(-) 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 234cc5e79d..af8c69ea17 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 @@ -4,23 +4,23 @@ using Unity.Modules.Shared; using Volo.Abp.Application.Dtos; -namespace Unity.GrantManager.GrantApplications +namespace Unity.GrantManager.GrantApplications; + +public interface IGrantApplicationAppService { - public interface IGrantApplicationAppService - { - Task GetApplicationStatusAsync(Guid id); - Task> GetActions(Guid applicationId, bool includeInternal = false); - Task UpdateProjectInfoAsync(Guid id, CreateUpdateProjectInfoDto input); - Task UpdatePartialProjectInfoAsync(Guid id, PartialUpdateDto input); - Task UpdateAssessmentResultsAsync(Guid id, CreateUpdateAssessmentResultsDto input); - Task UpdateSupplierNumberAsync(Guid applicationId, string supplierNumber); - Task> GetAllApplicationsAsync(); - Task> GetApplicationDetailsListAsync(List applicationIds); - Task GetAsync(Guid id); - Task TriggerAction(Guid applicationId, GrantApplicationAction triggerAction); - Task GetAccountCodingIdFromFormIdAsync(Guid formId); - Task HideAIAnalysisItemAsync(Guid applicationId, string itemId); - Task ShowAIAnalysisItemAsync(Guid applicationId, string itemId); - Task> GetListAsync(GrantApplicationListInputDto input); - } + Task GetApplicationStatusAsync(Guid id); + Task> GetActions(Guid applicationId, bool includeInternal = false); + Task UpdateProjectInfoAsync(Guid id, CreateUpdateProjectInfoDto input); + Task UpdatePartialProjectInfoAsync(Guid id, PartialUpdateDto input); + Task UpdateAssessmentResultsAsync(Guid id, CreateUpdateAssessmentResultsDto input); + Task UpdateSupplierNumberAsync(Guid applicationId, string supplierNumber); + Task> GetAllApplicationsAsync(); + Task> GetApplicationDetailsListAsync(List applicationIds); + Task GetAsync(Guid id); + Task TriggerAction(Guid applicationId, GrantApplicationAction triggerAction); + Task GetAccountCodingIdFromFormIdAsync(Guid formId); + Task HideAIAnalysisItemAsync(Guid applicationId, string itemId); + Task ShowAIAnalysisItemAsync(Guid applicationId, string itemId); + Task> GetListAsync(GrantApplicationListInputDto input); + Task IsApplicantRedStop(Guid applicationId); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs index 29d8c8bdf6..88511f4029 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs @@ -14,11 +14,11 @@ using System.Threading.Tasks; using Unity.Flex.WorksheetInstances; using Unity.Flex.Worksheets; +using Unity.GrantManager.AI.Models; +using Unity.GrantManager.AI.Responses; using Unity.GrantManager.Applicants; using Unity.GrantManager.ApplicationForms; using Unity.GrantManager.Applications; -using Unity.GrantManager.AI.Models; -using Unity.GrantManager.AI.Responses; using Unity.GrantManager.Events; using Unity.GrantManager.Flex; using Unity.GrantManager.Identity; @@ -955,6 +955,14 @@ public async Task GetApplicationStatusAsync(Guid id) return form.AccountCodingId; } + public async Task IsApplicantRedStop(Guid applicationId) + { + var application = await applicationRepository.GetAsync(applicationId, true) + ?? throw new EntityNotFoundException($"Application with ID {applicationId} not found."); + var applicant = await applicantRepository.GetAsync(application.ApplicantId); + return applicant.RedStop == true; + } + #region APPLICATION WORKFLOW /// /// Fetches the list of actions and their status context for a given application. @@ -974,9 +982,10 @@ public async Task> GetActions(Guid applicati // NOTE: Authorization is applied on the AppService layer and is false by default // AUTHORIZATION HANDLING + bool isRedStop = await IsApplicantRedStop(applicationId); foreach (var item in actionDtos) { - item.IsPermitted = item.IsPermitted && (await AuthorizationService.IsGrantedAsync(application, GetActionAuthorizationRequirement(item.ApplicationAction))); + item.IsPermitted = !isRedStop && item.IsPermitted && (await AuthorizationService.IsGrantedAsync(application, GetActionAuthorizationRequirement(item.ApplicationAction))); item.IsAuthorized = true; } @@ -1002,6 +1011,12 @@ public async Task TriggerAction(Guid applicationId, GrantAp throw new UnauthorizedAccessException(); } + // RED STOP CHECK: Block all status actions when the applicant has RedStop = true + if (await IsApplicantRedStop(applicationId)) + { + throw new UserFriendlyException(L["GrantApplication:ActionButton.RedStopWarning"]); + } + application = await applicationManager.TriggerAction(applicationId, triggerAction); await LocalEventBus.PublishAsync( 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 00e7708474..4b3ee78d49 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 @@ -269,6 +269,7 @@ "AssessmentResultsView:AssessmentResultsForm.RiskRanking": "Risk Ranking", "GrantApplication:ActionButtonName": "Status Actions", + "GrantApplication:ActionButton.RedStopWarning": "This application is flagged as high risk. Status actions are not permitted at this time.", "Enum:GrantApplicationAction.Open": "Open", "Enum:GrantApplicationAction.Submit": "Submit", "Enum:GrantApplicationAction.Internal_Assign": "Assign", diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationActionWidget/ApplicationActionWidget.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationActionWidget/ApplicationActionWidget.cs index a2371e9cff..0d95c298ec 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationActionWidget/ApplicationActionWidget.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationActionWidget/ApplicationActionWidget.cs @@ -28,7 +28,8 @@ public async Task InvokeAsync(Guid applicationId) var viewModel = new ApplicationActionWidgetViewModel() { ApplicationId = applicationId, - ApplicationActions = await _applicationAppService.GetActions(applicationId) + ApplicationActions = await _applicationAppService.GetActions(applicationId), + IsRedStop = await _applicationAppService.IsApplicantRedStop(applicationId) }; return View(viewModel); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationActionWidget/ApplicationActionWidgetViewModel.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationActionWidget/ApplicationActionWidgetViewModel.cs index c417c1b4af..8d324382dd 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationActionWidget/ApplicationActionWidgetViewModel.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationActionWidget/ApplicationActionWidgetViewModel.cs @@ -8,4 +8,5 @@ public class ApplicationActionWidgetViewModel { public Guid ApplicationId { get; set; } public ListResultDto ApplicationActions { get; set; } = new(); + public bool IsRedStop { get; set; } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationActionWidget/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationActionWidget/Default.cshtml index 43704eaf19..d40c6bf28d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationActionWidget/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationActionWidget/Default.cshtml @@ -2,15 +2,20 @@ @using Unity.GrantManager.Localization; @using Microsoft.Extensions.Localization; @using Unity.GrantManager.Web.Views.Shared.Components.ApplicationActionWidget; +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button; @model ApplicationActionWidgetViewModel; @inject IStringLocalizer L @inject IAuthorizationService AuthorizationService +@{ + var actionButtonType = Model.IsRedStop ? AbpButtonType.Danger : AbpButtonType.Light; +} + + -