diff --git a/applications/Unity.AutoUI/cypress/e2e/chefsdata.cy.ts b/applications/Unity.AutoUI/cypress/e2e/chefsdata.cy.ts index 3ffd75071..48432f83d 100644 --- a/applications/Unity.AutoUI/cypress/e2e/chefsdata.cy.ts +++ b/applications/Unity.AutoUI/cypress/e2e/chefsdata.cy.ts @@ -1,14 +1,19 @@ +/// describe('Unity Login and check data from CHEFS', () => { + it('Verify Login', () => { cy.login() }) // 19.) Verify that the info panel populates with mapped data it('Verify the UI is populated with valid data from CHEFS', () => { + cy.getSubmissionDetail('confirmationID').then(id => {cy.log(`Confirmation ID: ${id}`);}); + cy.get('#search').should('exist').clear(); // Ensure the field exists and clear its contents cy.get('#search').click() // click the search field cy.getSubmissionDetail('confirmationID').then(id => cy.get('#search').type(id)); // Fetch the confirmation ID and type it into the search field cy.getSubmissionDetail('confirmationID').then(id => cy.contains('tr', id).find('.checkbox-select').click()); // Fetch the confirmation ID, find its row, and click the checkbox + cy.get('#applicationLink').should('exist').click() // open the info panel // 19.) Verify that the info panel populates with mapped data // Category: AutoUI @@ -153,17 +158,15 @@ describe('Unity Login and check data from CHEFS', () => { }) // 25 Verify that the Payment Info tab populates with mapped data cy.get('#nav-payment-info-tab').should('exist').click() // open the Payment Info tab - // Requested Amount: 89000.00 - cy.get('#RequestedAmount').should('have.value', '89000.00') + // Requested Amount: 89,000.00 + cy.get('#RequestedAmount').should('have.value', '89,000.00') // 26.) Verify that the Submission tab populates with all form data cy.get('#nav-summery-tab').should('exist').click() // open the Submission tab - cy.getSubmissionDetail('formObjectID').then(formId => { // Fetch formObjectID - let headers = ['1. INTRODUCTION','2. ELIGIBILITY','3. APPLICANT INFORMATION','4. PROJECT INFORMATION','5. PROJECT TIMELINES','6. PROJECT BUDGET','7. ATTESTATION']; // Define headers - headers.forEach((header, index) => { // Iterate over headers - cy.get(`#${formId} > div:nth-child(${index + 1}) > div.card-header.bg-default > h4`) // Select header element - .should('contain', header) // Assert header text - .click(); // Click header to expand/collapse - }); + const headers = ['1. INTRODUCTION', '2. ELIGIBILITY', '3. APPLICANT INFORMATION','4. PROJECT INFORMATION', '5. PROJECT TIMELINES', '6. PROJECT BUDGET', '7. ATTESTATION']; + headers.forEach(header => { + cy.contains('h4', header) + .should('exist') + .click(); }); }) it('Verify Logout', () => { diff --git a/applications/Unity.AutoUI/cypress/e2e/lists.cy.ts b/applications/Unity.AutoUI/cypress/e2e/lists.cy.ts index e3777f552..5bb645d36 100644 --- a/applications/Unity.AutoUI/cypress/e2e/lists.cy.ts +++ b/applications/Unity.AutoUI/cypress/e2e/lists.cy.ts @@ -1,10 +1,11 @@ describe('Grant Manager Login and List Navigation', () => { + it('Verify Login', () => { cy.login() }) // 12.) Ensure all of the lists are populated. it('Verify Applications, Roles, Users, Intakes, Forms, Dashboard lists are populated', () => { - // 12.) Verify Default Grant Program tenant is selected. + // Verify Default Grant Program tenant is selected. cy.get('.unity-user-initials').should('exist').click() cy.get('#user-dropdown .btn-dropdown span').should('contain', 'Default Grants Program') // 13.) Applications diff --git a/applications/Unity.AutoUI/cypress/e2e/login.cy.ts b/applications/Unity.AutoUI/cypress/e2e/login.cy.ts index f410431dd..8dc67c30f 100644 --- a/applications/Unity.AutoUI/cypress/e2e/login.cy.ts +++ b/applications/Unity.AutoUI/cypress/e2e/login.cy.ts @@ -1,4 +1,5 @@ describe('Grant Manager Login and Logout', () => { + it('Verify Default Grant Program tenant is selected.', () => { cy.login() cy.get('.unity-user-initials').should('exist').click() diff --git a/applications/Unity.AutoUI/cypress/e2e/navigation.cy.ts b/applications/Unity.AutoUI/cypress/e2e/navigation.cy.ts index ec55a29b0..bb41f556c 100644 --- a/applications/Unity.AutoUI/cypress/e2e/navigation.cy.ts +++ b/applications/Unity.AutoUI/cypress/e2e/navigation.cy.ts @@ -1,4 +1,5 @@ describe('Grant Manager Login and Top Navigation', () => { + it('Verify Login', () => { cy.login() }) diff --git a/applications/Unity.AutoUI/cypress/fixtures/submissions.json b/applications/Unity.AutoUI/cypress/fixtures/submissions.json index c91c1fd9b..d84fb5785 100644 --- a/applications/Unity.AutoUI/cypress/fixtures/submissions.json +++ b/applications/Unity.AutoUI/cypress/fixtures/submissions.json @@ -13,7 +13,7 @@ { "unityEnv": "TEST", "confirmationID": "68090C5E", - "formObjectID": "efkdeiq" + "formObjectID": "evigmss" }, { "unityEnv": "UAT", diff --git a/applications/Unity.AutoUI/cypress/support/commands.ts b/applications/Unity.AutoUI/cypress/support/commands.ts index 24ffbce82..b302bf72e 100644 --- a/applications/Unity.AutoUI/cypress/support/commands.ts +++ b/applications/Unity.AutoUI/cypress/support/commands.ts @@ -75,21 +75,22 @@ Cypress.Commands.add('getMetabaseDetail', (key: string) => { }); Cypress.Commands.add('metabaseLogin', () => { - cy.getMetabaseDetail('baseURL').then(baseURL => {cy.visit(baseURL); // Visit the URL fetched from metabase.json - cy.get('#root > div > div > main > div > div.emotion-iq817s.euvero02 > div > div.emotion-1spv9yy > div > form > div:nth-child(1) > div.emotion-17sifsc.edcfyzd6 > input[name="username"]') - .should('exist') - .click(); - cy.get('#root > div > div > main > div > div.emotion-iq817s.euvero02 > div > div.emotion-1spv9yy > div > form > div:nth-child(1) > div.emotion-17sifsc.edcfyzd6 > input[name="username"]') - .type('iDontHave@ValidEmail.com'); // the test account doesn't have an email address - cy.get('#root > div > div > main > div > div.emotion-iq817s.euvero02 > div > div.emotion-1spv9yy > div > form > div:nth-child(2) > div.emotion-17sifsc.edcfyzd6 > input[name="password"]') + cy.getMetabaseDetail('baseURL').then((baseURL) => { + cy.visit(baseURL); + + // Target the username field using its `name` attribute + cy.get('input[name="username"]') .should('exist') - .click(); - cy.get('#root > div > div > main > div > div.emotion-iq817s.euvero02 > div > div.emotion-1spv9yy > div > form > div:nth-child(2) > div.emotion-17sifsc.edcfyzd6 > input[name="password"]') + .click() + .type('iDontHave@ValidEmail.com'); // Placeholder email address + + // Target the password field using its `name` attribute + cy.get('input[name="password"]') .should('exist') - .type('pointless'); // there's no point adding a valid password yet because the test account doesn't have an email address - //.type(Cypress.env('test1password')) + .click() + .type('pointless'); // Placeholder password }); - }); +}); interface chefsDetail { unityEnv: string; diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Scoresheets/QuestionDto.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Scoresheets/QuestionDto.cs index affa91464..3708cad7c 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Scoresheets/QuestionDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Scoresheets/QuestionDto.cs @@ -12,7 +12,6 @@ public class QuestionDto : ExtensibleEntityDto public virtual string Name { get; set; } = string.Empty; public virtual string Label { get; set; } = string.Empty; public virtual string? Description { get; set; } - public virtual bool Enabled { get; private set; } public virtual QuestionType Type { get; set; } public virtual uint Order { get; set; } diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/WorksheetInstances/CustomFieldValue.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/WorksheetInstances/CustomFieldValue.cs index 578fc8d78..fb86dfe94 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/WorksheetInstances/CustomFieldValue.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Domain/WorksheetInstances/CustomFieldValue.cs @@ -5,7 +5,7 @@ namespace Unity.Flex.Domain.WorksheetInstances { - public class CustomFieldValue : FullAuditedEntity, IMultiTenant + public class CustomFieldValue : AuditedEntity, IMultiTenant { [Column(TypeName = "jsonb")] public virtual string CurrentValue { get; private set; } = "{}"; diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/FlexWebModule.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/FlexWebModule.cs index e89257b32..2b8ccff96 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/FlexWebModule.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/FlexWebModule.cs @@ -1,12 +1,10 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.DependencyInjection; using Unity.Flex.Localization; -using Unity.Flex.Web.Menus; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AutoMapper; using Volo.Abp.Modularity; -using Volo.Abp.UI.Navigation; using Volo.Abp.VirtualFileSystem; namespace Unity.Flex.Web; @@ -33,7 +31,7 @@ public override void PreConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context) { - + Configure(options => { diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/Components/DataGrid/DataGridReadService.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/Components/DataGrid/DataGridReadService.cs index 07e3ed7cf..a08a9e245 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/Components/DataGrid/DataGridReadService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/Components/DataGrid/DataGridReadService.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Text.Json; using System.Threading.Tasks; -using Unity.Flex.Web.Views.Shared.Components.DataGridWidget; using Unity.Flex.Web.Views.Shared.Components.WorksheetInstanceWidget.ViewModels; using Unity.Flex.WorksheetInstances; using Unity.Flex.Worksheets; diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/Components/DataGrid/DataGridWriteService.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/Components/DataGrid/DataGridWriteService.cs index e244649d7..6a1440df9 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/Components/DataGrid/DataGridWriteService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/Components/DataGrid/DataGridWriteService.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Unity.Flex.Web.Views.Shared.Components.DataGridWidget; using Unity.Flex.WorksheetInstances; using Unity.Flex.Worksheets; using Unity.Flex.Worksheets.Definitions; diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Unity.Flex.Web.csproj b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Unity.Flex.Web.csproj index eef11a58b..ddc37cbf5 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Unity.Flex.Web.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Unity.Flex.Web.csproj @@ -1,172 +1,64 @@ - + - - net8.0 - enable - $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; - true - Library - Unity.Flex.Web - true - + + net8.0 + enable + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + true + Library + Unity.Flex.Web + true + - - - - - - - + + + + + + + - - - + + + - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - Always - - - Always - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + true + PreserveNewest + + - - - - - - - - true - PreserveNewest - - - - - - - all - runtime; build; native; contentfiles; analyzers - - + + + + all + runtime; build; native; contentfiles; analyzers + + diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/BCAddressWidget/Default.css b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/BCAddressWidget/Default.css index 5f282702b..19a76f130 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/BCAddressWidget/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/BCAddressWidget/Default.css @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CurrencyDefinitionWidget/Default.css b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CurrencyDefinitionWidget/Default.css index 5f282702b..19a76f130 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CurrencyDefinitionWidget/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CurrencyDefinitionWidget/Default.css @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CurrencyWidget/Default.css b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CurrencyWidget/Default.css index 5f282702b..19a76f130 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CurrencyWidget/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CurrencyWidget/Default.css @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/Default.cshtml index 9317675fe..c8d596a93 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/Default.cshtml @@ -1,5 +1,4 @@ -@using Unity.Flex.Web.Views.Shared.Components; -@using Unity.Flex.Web.Views.Shared.Components.CheckboxGroupDefinitionWidget +@using Unity.Flex.Web.Views.Shared.Components.CheckboxGroupDefinitionWidget @using Unity.Flex.Web.Views.Shared.Components.CurrencyDefinitionWidget @using Unity.Flex.Web.Views.Shared.Components.CustomFieldDefinitionWidget; @using Unity.Flex.Web.Views.Shared.Components.DataGridDefinitionWidget diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/Default.css b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/Default.css index 5f282702b..19a76f130 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomFieldDefinitionWidget/Default.css @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomTabWidget/Default.css b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomTabWidget/Default.css index 5f282702b..19a76f130 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomTabWidget/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/CustomTabWidget/Default.css @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DateWidget/Default.css b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DateWidget/Default.css index 5f282702b..19a76f130 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DateWidget/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DateWidget/Default.css @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/NumericDefinitionWidget/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/NumericDefinitionWidget/Default.cshtml index 0ce1381b2..45171d84d 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/NumericDefinitionWidget/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/NumericDefinitionWidget/Default.cshtml @@ -1,6 +1,4 @@ -@using Unity.Flex.Web.Views.Shared.Components; -@using Unity.Flex.Web.Views.Shared.Components.NumericDefinitionWidget; -@using Unity.Flex.Worksheets.Definitions; +@using Unity.Flex.Web.Views.Shared.Components.NumericDefinitionWidget; @model NumericDefinitionViewModel; diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/NumericDefinitionWidget/Default.css b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/NumericDefinitionWidget/Default.css index 5f282702b..19a76f130 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/NumericDefinitionWidget/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/NumericDefinitionWidget/Default.css @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionSelectListWidget/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionSelectListWidget/Default.cshtml index aa077f19d..7c477202f 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionSelectListWidget/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionSelectListWidget/Default.cshtml @@ -1,5 +1,4 @@ -@using Unity.Flex.Web.Views.Shared.Components; -@using Unity.Flex.Web.Views.Shared.Components.QuestionSelectListWidget; +@using Unity.Flex.Web.Views.Shared.Components.QuestionSelectListWidget; @using Unity.Flex.Worksheets.Definitions; @using Unity.Flex; @using Unity.Flex.Scoresheets.Enums; diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionYesNoDefinitionWidget/Default.css b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionYesNoDefinitionWidget/Default.css index 5f282702b..19a76f130 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionYesNoDefinitionWidget/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/QuestionYesNoDefinitionWidget/Default.css @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/RadioDefinitionWidget/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/RadioDefinitionWidget/Default.cshtml index c9b57e630..1f5295287 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/RadioDefinitionWidget/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/RadioDefinitionWidget/Default.cshtml @@ -1,6 +1,4 @@ -@using Unity.Flex.Web.Views.Shared.Components; -@using Unity.Flex.Web.Views.Shared.Components.RadioDefinitionWidget; -@using Unity.Flex.Worksheets.Definitions; +@using Unity.Flex.Web.Views.Shared.Components.RadioDefinitionWidget; @model RadioDefinitionViewModel diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/SelectListWidget/Default.css b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/SelectListWidget/Default.css index 5f282702b..19a76f130 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/SelectListWidget/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/SelectListWidget/Default.css @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextDefinitionWidget/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextDefinitionWidget/Default.cshtml index 363c9738e..75f9c994b 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextDefinitionWidget/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextDefinitionWidget/Default.cshtml @@ -1,6 +1,4 @@ -@using Unity.Flex.Web.Views.Shared.Components; -@using Unity.Flex.Web.Views.Shared.Components.TextDefinitionWidget; -@using Unity.Flex.Worksheets.Definitions; +@using Unity.Flex.Web.Views.Shared.Components.TextDefinitionWidget; @model TextDefinitionViewModel; diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextDefinitionWidget/Default.css b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextDefinitionWidget/Default.css index 5f282702b..19a76f130 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextDefinitionWidget/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/TextDefinitionWidget/Default.css @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetInstanceWidget/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetInstanceWidget/Default.cshtml index 2f14d17df..9078c5ed9 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetInstanceWidget/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/WorksheetInstanceWidget/Default.cshtml @@ -4,7 +4,6 @@ @using Unity.Flex.Web.Views.Shared.Components.TextAreaWidget @using Unity.Flex.Web.Views.Shared.Components.WorksheetInstanceWidget.ViewModels; @using Unity.Flex.Worksheets; -@using Unity.Flex.Web.Views.Shared.Components; @using Unity.Flex.Web.Views.Shared.Components.CheckboxWidget; @using Unity.Flex.Web.Views.Shared.Components.YesNoWidget; @using Unity.Flex.Web.Views.Shared.Components.CurrencyWidget; diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/YesNoWidget/Default.css b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/YesNoWidget/Default.css index 5f282702b..19a76f130 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/YesNoWidget/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/YesNoWidget/Default.css @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/CreateModal.cshtml b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/CreateModal.cshtml index 8cd601e76..eef21fdfd 100644 --- a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/CreateModal.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/CreateModal.cshtml @@ -1,14 +1,15 @@ @page @using Microsoft.AspNetCore.Mvc.Localization @using Microsoft.Extensions.Localization -@using Microsoft.Extensions.Logging +@using Unity.Identity.Web.Pages.Identity.Roles @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Abp.Data @using Volo.Abp.Identity.Localization -@using Unity.Identity.Web.Pages.Identity.Roles @using Volo.Abp.Localization @using Volo.Abp.ObjectExtending -@using Volo.Abp.Data + @model CreateModalModel + @inject IHtmlLocalizer L @inject IStringLocalizerFactory StringLocalizerFactory @{ diff --git a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/EditModal.cshtml b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/EditModal.cshtml index a596d6f26..4ecd338f1 100644 --- a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/EditModal.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/EditModal.cshtml @@ -1,13 +1,15 @@ @page @using Microsoft.AspNetCore.Mvc.Localization @using Microsoft.Extensions.Localization +@using Unity.Identity.Web.Pages.Identity.Roles @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Abp.Data @using Volo.Abp.Identity.Localization -@using Unity.Identity.Web.Pages.Identity.Roles @using Volo.Abp.Localization @using Volo.Abp.ObjectExtending -@using Volo.Abp.Data + @model EditModalModel + @inject IHtmlLocalizer L @inject IStringLocalizerFactory StringLocalizerFactory @{ diff --git a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/Index.cshtml b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/Index.cshtml index 40928c600..51e0555b4 100644 --- a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/Index.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/Index.cshtml @@ -1,13 +1,13 @@ @page @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Mvc.Localization -@using Volo.Abp.AspNetCore.Mvc.UI.Layout -@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Pages.Shared.Components.AbpPageToolbar -@using Volo.Abp.Identity -@using Volo.Abp.Identity.Localization @using Unity.Identity.Web.Navigation @using Unity.Identity.Web.Pages.Identity.Roles +@using Volo.Abp.AspNetCore.Mvc.UI.Layout +@using Volo.Abp.Identity.Localization + @model IndexModel + @inject IHtmlLocalizer L @inject IAuthorizationService Authorization @inject IPageLayout PageLayout diff --git a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/index.js b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/index.js index 9af658dda..43693f803 100644 --- a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/index.js +++ b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Roles/index.js @@ -120,6 +120,17 @@ $(function () { $.fn.dataTable.Buttons.defaults.dom.button.className = 'btn flex-none'; let actionButtons = [ + { + text: ' ' + l('NewRole') + '', + titleAttr: l('NewRole'), + id: 'CreateRoleButton', + className: 'btn-light rounded-1', + available: () => abp.auth.isGranted('AbpIdentity.Roles.Create'), + action: (e, dt, node, config) => { + e.preventDefault(); + _createModal.open(); + } + }, ...commonTableActionButtons(l('Roles')) ]; @@ -143,6 +154,7 @@ $(function () { }; }, actionButtons, + serverSideEnabled: false, pagingEnabled: true, reorderEnabled: false, languageSetValues: {}, diff --git a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/EditModal.cshtml b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/EditModal.cshtml index 6dc992012..f5a488198 100644 --- a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/EditModal.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/EditModal.cshtml @@ -1,14 +1,15 @@ @page @using Microsoft.AspNetCore.Mvc.Localization @using Microsoft.Extensions.Localization +@using Unity.Identity.Web.Pages.Identity.Users @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Abp.Data @using Volo.Abp.Identity.Localization -@using Unity.Identity.Web.Pages.Identity.Users @using Volo.Abp.Localization @using Volo.Abp.ObjectExtending -@using Volo.Abp.Data -@using Volo.Abp.Identity + @model EditModalModel + @inject IHtmlLocalizer L @inject IStringLocalizerFactory StringLocalizerFactory @{ diff --git a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/ImportModal.cshtml b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/ImportModal.cshtml index 92356b2d6..f257daa8e 100644 --- a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/ImportModal.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/ImportModal.cshtml @@ -2,17 +2,13 @@ @using Microsoft.AspNetCore.Mvc.Localization @using Microsoft.Extensions.Localization @using Unity.GrantManager.Localization; -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@using Volo.Abp.Identity.Localization @using Unity.Identity.Web.Pages.Identity.Users -@using Volo.Abp.Localization -@using Volo.Abp.ObjectExtending -@using Volo.Abp.Data -@using Volo.Abp.Identity +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal + @model ImportModalModel + @inject IHtmlLocalizer L @inject IStringLocalizerFactory StringLocalizerFactory - @{ Layout = null; } diff --git a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/Index.cshtml b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/Index.cshtml index c92827008..37a9c8a2e 100644 --- a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/Index.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/Index.cshtml @@ -1,14 +1,13 @@ @page @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Mvc.Localization -@using Unity.GrantManager.Localization; -@using Volo.Abp.AspNetCore.Mvc.UI.Layout -@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Pages.Shared.Components.AbpPageToolbar -@using Volo.Abp.Identity -@using Volo.Abp.Identity.Localization +@using Unity.GrantManager.Localization @using Unity.Identity.Web.Navigation @using Unity.Identity.Web.Pages.Identity.Users +@using Volo.Abp.AspNetCore.Mvc.UI.Layout + @model IndexModel + @inject IHtmlLocalizer L @inject IAuthorizationService Authorization @inject IPageLayout PageLayout diff --git a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/index.js b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/index.js index c8ba8d0b2..6cfa429cc 100644 --- a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/index.js +++ b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Pages/Identity/Users/index.js @@ -259,6 +259,7 @@ $(function () { data: {}, responseCallback: tableResponseCallback, actionButtons, + serverSideEnabled: false, pagingEnabled: true, reorderEnabled: false, languageSetValues: {}, diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Emails/EmailCommentDto.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Emails/EmailCommentDto.cs index deaf855df..c62bddaff 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Emails/EmailCommentDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Emails/EmailCommentDto.cs @@ -11,4 +11,5 @@ public class EmailCommentDto public string Body { get; set; } = string.Empty; public string ApplicationId { get; set; } = string.Empty; public List MentionNamesEmail { get; set; } = []; + public string? EmailTemplateName { get; set; } = string.Empty; } \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Emails/EmailHistoryDto.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Emails/EmailHistoryDto.cs index fdf8b3eae..e885a6fdb 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Emails/EmailHistoryDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Emails/EmailHistoryDto.cs @@ -13,6 +13,7 @@ public class EmailHistoryDto : ExtensibleAuditedEntityDto public DateTime? SentDateTime { get; set; } public string Body { get; set; } = string.Empty; public EmailHistoryUserDto? SentBy { get; set; } + public string TemplateName { get; set; } = string.Empty; } public class EmailHistoryUserDto : EntityDto diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/EmailNotificationService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/EmailNotificationService.cs index 328d1d34b..e7687324f 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/EmailNotificationService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/EmailNotificationService.cs @@ -98,14 +98,14 @@ public async Task DeleteEmail(Guid id) await _emailLogsRepository.DeleteAsync(id); } - public async Task UpdateEmailLog(Guid emailId, string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status) + public async Task UpdateEmailLog(Guid emailId, string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status,string? emailTemplateName) { if (string.IsNullOrEmpty(emailTo)) { return null; } - var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "text"); + var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName); EmailLog emailLog = await _emailLogsRepository.GetAsync(emailId); emailLog = UpdateMappedEmailLog(emailLog, emailObject); emailLog.ApplicationId = applicationId; @@ -117,19 +117,19 @@ public async Task DeleteEmail(Guid id) return loggedEmail; } - public async Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom) + public async Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? emailTemplateName) { - return await InitializeEmailLog(emailTo, body, subject, applicationId, emailFrom, EmailStatus.Initialized); + return await InitializeEmailLog(emailTo, body, subject, applicationId, emailFrom, EmailStatus.Initialized, emailTemplateName); } [RemoteService(false)] - public async Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status) + public async Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName) { if (string.IsNullOrEmpty(emailTo)) { return null; } - var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "text"); + var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName); EmailLog emailLog = new EmailLog(); emailLog = UpdateMappedEmailLog(emailLog, emailObject); emailLog.ApplicationId = applicationId; @@ -203,7 +203,7 @@ public async Task SendCommentNotification(EmailCommentDto i foreach (var email in input.MentionNamesEmail) { var toEmail = email; - res = await SendEmailNotification(toEmail, htmlBody, subject, fromEmail, "html"); + res = await SendEmailNotification(toEmail, htmlBody, subject, fromEmail, "html", input.EmailTemplateName); } } else @@ -236,7 +236,7 @@ public async Task SendCommentNotification(EmailCommentDto i /// From Email Address /// Type of body email: html or text /// HttpResponseMessage indicating the result of the operation - public async Task SendEmailNotification(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType) + public async Task SendEmailNotification(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName) { try { @@ -250,7 +250,7 @@ public async Task SendEmailNotification(string emailTo, str } // Send the email using the CHES client service - var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, emailBodyType); + var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, emailBodyType, emailTemplateName); var response = await _chesClientService.SendAsync(emailObject); // Assuming SendAsync returns a HttpResponseMessage or equivalent: @@ -328,7 +328,7 @@ public async Task SendEmailToQueue(EmailLog emailLog) await _emailQueueService.SendToEmailEventQueueAsync(emailNotificationEvent); } - protected virtual async Task GetEmailObjectAsync(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType) + protected virtual async Task GetEmailObjectAsync(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName) { List toList = new(); string[] emails = emailTo.Split([',', ';'], StringSplitOptions.RemoveEmptyEntries); @@ -349,7 +349,8 @@ protected virtual async Task GetEmailObjectAsync(string emailTo, string priority = "normal", subject, tag = "tag", - to = toList + to = toList, + templateName = emailTemplateName, }; return emailObject; } @@ -360,7 +361,8 @@ protected virtual EmailLog UpdateMappedEmailLog(EmailLog emailLog, dynamic email emailLog.Subject = emailDynamicObject.subject; emailLog.BodyType = emailDynamicObject.bodyType; emailLog.FromAddress = emailDynamicObject.from; - emailLog.ToAddress = ((List)emailDynamicObject.to).FirstOrDefault() ?? ""; + emailLog.ToAddress = String.Join(",", emailDynamicObject.to); + emailLog.TemplateName = emailDynamicObject.templateName; return emailLog; } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/IEmailNotificationService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/IEmailNotificationService.cs index 939f37038..a4304c06c 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/IEmailNotificationService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/IEmailNotificationService.cs @@ -10,12 +10,12 @@ namespace Unity.Notifications.EmailNotifications { public interface IEmailNotificationService : IApplicationService { - Task UpdateEmailLog(Guid emailId, string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status); - Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status); - Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom); + Task UpdateEmailLog(Guid emailId, string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName); + Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName); + Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? emailTemplateName); Task GetEmailLogById(Guid id); Task SendCommentNotification(EmailCommentDto input); - Task SendEmailNotification(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType); + Task SendEmailNotification(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName); Task SendEmailToQueue(EmailLog emailLog); string GetApprovalBody(); string GetDeclineBody(); diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationEvent.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationEvent.cs index 8dd53b747..a502e50a8 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationEvent.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationEvent.cs @@ -20,5 +20,6 @@ public class EmailNotificationEvent [JsonConverter(typeof(JsonStringEnumConverter))] public EmailAction Action { get; set; } + public string? EmailTemplateName { get; set; } = string.Empty; } } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationHandler.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationHandler.cs index c940f24fa..f924ad56a 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationHandler.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationHandler.cs @@ -25,7 +25,7 @@ public async Task HandleEventAsync(EmailNotificationEvent eventData) } } - private async Task InitializeAndSendEmailToQueue(string emailTo, string body, string subject, Guid applicationId, string? emailFrom) + private async Task InitializeAndSendEmailToQueue(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? emailTemplateName) { EmailLog emailLog = await InitializeEmail( emailTo, @@ -33,12 +33,13 @@ private async Task InitializeAndSendEmailToQueue(string emailTo, string body, st subject, applicationId, emailFrom, - EmailStatus.Initialized); + EmailStatus.Initialized, + emailTemplateName); await emailNotificationService.SendEmailToQueue(emailLog); } - private async Task InitializeEmail(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string status) + private async Task InitializeEmail(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string status, string? emailTemplateName) { EmailLog emailLog = await emailNotificationService.InitializeEmailLog( emailTo, @@ -46,7 +47,8 @@ private async Task InitializeEmail(string emailTo, string body, string subject, applicationId, emailFrom, - status) ?? throw new UserFriendlyException("Unable to Initialize Email Log"); + status, + emailTemplateName) ?? throw new UserFriendlyException("Unable to Initialize Email Log"); return emailLog; } @@ -58,10 +60,11 @@ private async Task EmailNotificationEventAsync(EmailNotificationEvent eventData) switch (eventData.Action) { case EmailAction.SendFailedSummary: - foreach (string emailToAddress in eventData.EmailAddressList) - { - await InitializeAndSendEmailToQueue(emailToAddress, eventData.Body, FAILED_PAYMENTS_SUBJECT, eventData.ApplicationId, eventData.EmailFrom); - } + + string emailToAddress = String.Join(",", eventData.EmailAddressList); + + await InitializeAndSendEmailToQueue(emailToAddress, eventData.Body, FAILED_PAYMENTS_SUBJECT, eventData.ApplicationId, eventData.EmailFrom,eventData.EmailTemplateName); + break; case EmailAction.SendCustom: @@ -79,11 +82,12 @@ private async Task EmailNotificationEventAsync(EmailNotificationEvent eventData) private async Task HandleSendCustomEmail(EmailNotificationEvent eventData) { - foreach (string emailToAddress in eventData.EmailAddressList) - { + + + string emailToAddress = String.Join(",", eventData.EmailAddressList); if (eventData.Id == Guid.Empty) { - await InitializeAndSendEmailToQueue(emailToAddress, eventData.Body, eventData.Subject, eventData.ApplicationId, eventData.EmailFrom); + await InitializeAndSendEmailToQueue(emailToAddress, eventData.Body, eventData.Subject, eventData.ApplicationId, eventData.EmailFrom,eventData.EmailTemplateName); } else { @@ -94,7 +98,8 @@ private async Task HandleSendCustomEmail(EmailNotificationEvent eventData) eventData.Subject, eventData.ApplicationId, eventData.EmailFrom, - EmailStatus.Initialized); + EmailStatus.Initialized, + eventData.EmailTemplateName); if (emailLog != null) { @@ -105,13 +110,15 @@ private async Task HandleSendCustomEmail(EmailNotificationEvent eventData) throw new UserFriendlyException("Unable to update Email Log"); } } - } + } private async Task HandleSaveDraftEmail(EmailNotificationEvent eventData) { - foreach (string emailToAddress in eventData.EmailAddressList) - { + + + string emailToAddress = String.Join(",", eventData.EmailAddressList); + if (eventData.Id != Guid.Empty) { await emailNotificationService.UpdateEmailLog( @@ -121,7 +128,8 @@ await emailNotificationService.UpdateEmailLog( eventData.Subject, eventData.ApplicationId, eventData.EmailFrom, - EmailStatus.Draft); + EmailStatus.Draft, + eventData.EmailTemplateName); } else { @@ -131,9 +139,10 @@ await InitializeEmail( eventData.Subject, eventData.ApplicationId, eventData.EmailFrom, - EmailStatus.Draft); + EmailStatus.Draft, + eventData.EmailTemplateName); } - } + } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/RabbitMQ/EmailConsumer.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/RabbitMQ/EmailConsumer.cs index 75b31e973..2c901596c 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/RabbitMQ/EmailConsumer.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/RabbitMQ/EmailConsumer.cs @@ -63,7 +63,8 @@ private async Task ProcessEmailLogAsync(EmailLog emailLog, EmailNotificationEven emailLog.ToAddress, emailLog.Body, emailLog.Subject, - emailLog.FromAddress, "text"); + emailLog.FromAddress, "html", + emailLog.TemplateName); // Update the response emailLog.ChesResponse = JsonConvert.SerializeObject(response); emailLog.ChesStatus = response.StatusCode.ToString(); diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Emails/EmailLog.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Emails/EmailLog.cs index e9c3af640..ae9b3c16b 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Emails/EmailLog.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Emails/EmailLog.cs @@ -30,4 +30,5 @@ public class EmailLog : AuditedAggregateRoot, IMultiTenant public string Status { get; set; } = ""; public DateTime? SendOnDateTime { get; set; } public DateTime? SentDateTime { get; set; } + public string TemplateName { get; set; } = ""; } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/NotificationsDataSeedContributor.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/NotificationsDataSeedContributor.cs index 3537418f1..1865654f9 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/NotificationsDataSeedContributor.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/NotificationsDataSeedContributor.cs @@ -27,7 +27,7 @@ public async Task SeedAsync(DataSeedContext context) new EmailTempateVariableDto { Name = "Applicant name", Token = "applicant_name", MapTo = "applicant.applicantName" }, new EmailTempateVariableDto { Name = "Submission #", Token = "submission_number", MapTo = "referenceNo" }, new EmailTempateVariableDto { Name = "Submission Date", Token = "submission_date", MapTo = "submissionDate" }, - new EmailTempateVariableDto { Name = "Category", Token = "category", MapTo = "category" }, + new EmailTempateVariableDto { Name = "Category", Token = "category", MapTo = "applicationForm.category" }, new EmailTempateVariableDto { Name = "Status", Token = "status", MapTo = "status" }, new EmailTempateVariableDto { Name = "Approved Amount", Token = "approved_amount", MapTo = "approvedAmount" }, new EmailTempateVariableDto { Name = "Approval date", Token = "approval_date", MapTo = "finalDecisionDate" }, @@ -54,6 +54,11 @@ await _templateVariablesRepository.InsertAsync( autoSave: true ); } + else if (existingVariable.Token == "category" && existingVariable.MapTo == "category") + { + existingVariable.MapTo = "applicationForm.category"; + await _templateVariablesRepository.UpdateAsync(existingVariable, autoSave: true); + } } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.cshtml index 277bab907..e088a067d 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.cshtml @@ -5,52 +5,63 @@
- - -
-

Notifications Configuration

-
-
-
- - - - - - - - - - - -
-
- - -
-
-
-
-
- -
-

Email Templates

+
+

Notifications

+
+ - - -
-
-
+ +
\ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingPageContributor.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingPageContributor.cs index a7c0007bd..ae976271e 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingPageContributor.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingPageContributor.cs @@ -21,7 +21,8 @@ public override Task ConfigureAsync(SettingPageCreationContext context) new SettingPageGroup( "GrantManager.Notifications", "Notifications", - typeof(NotificationsSettingViewComponent) + typeof(NotificationsSettingViewComponent), + order: 2 ) ); diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/test/Unity.Notifications.TestBase/NotificationsDataSeedContributor.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/test/Unity.Notifications.TestBase/NotificationsDataSeedContributor.cs index 00c1f0da4..16002012e 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/test/Unity.Notifications.TestBase/NotificationsDataSeedContributor.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/test/Unity.Notifications.TestBase/NotificationsDataSeedContributor.cs @@ -1,23 +1,20 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Unity.Notifications.Templates; +using System.Threading.Tasks; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; -using Volo.Abp.Domain.Repositories; namespace Unity.Notifications; public class NotificationsDataSeedContributor : IDataSeedContributor, ITransientDependency -{ +{ private readonly ICurrentTenant _currentTenant; public NotificationsDataSeedContributor(ICurrentTenant currentTenant) - { + { _currentTenant = currentTenant; } - public Task SeedAsync(DataSeedContext context) + public Task SeedAsync(DataSeedContext context) { using (_currentTenant.Change(context?.TenantId)) { diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/PaymentDetailsDto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/PaymentDetailsDto.cs index ffbee9ed2..236c93d00 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/PaymentDetailsDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/PaymentDetailsDto.cs @@ -1,6 +1,7 @@ using System; using System.Collections.ObjectModel; using Unity.Payments.Enums; +using Unity.Payments.PaymentTags; using Unity.Payments.Suppliers; using Volo.Abp.Application.Dtos; @@ -28,6 +29,7 @@ public class PaymentDetailsDto : AuditedEntityDto public string ReferenceNumber { get; set; } = string.Empty; public string SubmissionConfirmationCode { get; set; } = string.Empty; public SiteDto? Site { get; set; } + public Collection PaymentTags { get; set; } = []; public Collection ExpenseApprovals { get; set; } = []; } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/PaymentRequestDto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/PaymentRequestDto.cs index 8a9ec2abf..3b3c09073 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/PaymentRequestDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/PaymentRequestDto.cs @@ -1,6 +1,7 @@ using System; using System.Collections.ObjectModel; using Unity.Payments.Enums; +using Unity.Payments.PaymentTags; using Unity.Payments.Suppliers; using Volo.Abp.Application.Dtos; @@ -33,7 +34,7 @@ public class PaymentRequestDto : AuditedEntityDto public string SubmissionConfirmationCode { get; set; } = string.Empty; public string? ErrorSummary { get; set; } public PaymentUserDto? CreatorUser { get; set; } - + public Collection PaymentTags { get; set; } public Collection ExpenseApprovals { get; set; } public static explicit operator PaymentRequestDto(CreatePaymentRequestDto v) diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentTags/IPaymentTagAppService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentTags/IPaymentTagAppService.cs new file mode 100644 index 000000000..3758a57ec --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentTags/IPaymentTagAppService.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace Unity.Payments.PaymentTags +{ + public interface IPaymentTagAppService : IApplicationService + { + Task> GetListAsync(); + Task> GetListWithPaymentRequestIdsAsync(List ids); + Task CreateorUpdateTagsAsync(Guid id, PaymentTagDto input); + Task GetPaymentTagsAsync(Guid id); + + Task> GetTagSummaryAsync(); + Task GetMaxRenameLengthAsync(string originalTag); + Task> RenameTagAsync(string originalTag, string replacementTag); + Task DeleteTagAsync(string deleteTag); + } +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentTags/PaymentTagDto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentTags/PaymentTagDto.cs new file mode 100644 index 000000000..b3d80bf7d --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentTags/PaymentTagDto.cs @@ -0,0 +1,11 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Unity.Payments.PaymentTags; + +[Serializable] +public class PaymentTagDto : AuditedEntityDto +{ + public Guid PaymentRequestId { get; set; } + public string Text { get; set; } = string.Empty; +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentTags/TagSummaryCountDto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentTags/TagSummaryCountDto.cs new file mode 100644 index 000000000..a8d97e36d --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentTags/TagSummaryCountDto.cs @@ -0,0 +1,6 @@ +namespace Unity.Payments.PaymentTags; +public class TagSummaryCountDto +{ + public required string Text { get; set; } + public required int Count { get; set; } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Exceptions/ErrorConsts.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Exceptions/ErrorConsts.cs index 7f6407d4e..f9ab61c36 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Exceptions/ErrorConsts.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Exceptions/ErrorConsts.cs @@ -7,5 +7,6 @@ public static class ErrorConsts public const string ConfigurationExists = "Unity.Payments:Errors:ConfigurationExists"; public const string ConfigurationDoesNotExist = "Unity.Payments:Errors:ConfigurationDoesNotExist"; public const string InvalidAccountCodingField = "Unity.Payments:Errors:InvalidAccountCodingFiled"; + public const string L2ApproverRestriction = "Unity.Payments:Errors:L2ApproverRestriction"; } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/PaymentRequest.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/PaymentRequest.cs index 961a4f003..0b7f017f2 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/PaymentRequest.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/PaymentRequest.cs @@ -9,6 +9,7 @@ using Volo.Abp; using Unity.Payments.Domain.Exceptions; using Unity.Payments.PaymentRequests; +using Unity.Payments.Domain.PaymentTags; namespace Unity.Payments.Domain.PaymentRequests { @@ -51,7 +52,7 @@ public virtual Site Site public virtual string RequesterName { get; private set; } = string.Empty; public virtual string BatchName { get; private set; } = string.Empty; public virtual decimal BatchNumber { get; private set; } = 0; - + public virtual Collection? PaymentTags { get; set; } public virtual Collection ExpenseApprovals { get; private set; } public virtual bool IsApproved { get => ExpenseApprovals.All(s => s.Status == ExpenseApprovalStatus.Approved); } @@ -62,6 +63,7 @@ public virtual Site Site protected PaymentRequest() { ExpenseApprovals = []; + PaymentTags = []; /* This constructor is for ORMs to be used while getting the entity from the database. */ } @@ -97,6 +99,7 @@ public PaymentRequest(Guid id, CreatePaymentRequestDto createPaymentRequestDto) SubmissionConfirmationCode = createPaymentRequestDto.SubmissionConfirmationCode; BatchName = createPaymentRequestDto.BatchName; BatchNumber = createPaymentRequestDto.BatchNumber; + PaymentTags = null; ExpenseApprovals = GenerateExpenseApprovals(createPaymentRequestDto.Amount, createPaymentRequestDto.PaymentThreshold); ValidatePaymentRequest(); } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentTags/IPaymentTagRepository.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentTags/IPaymentTagRepository.cs new file mode 100644 index 000000000..b816b887c --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentTags/IPaymentTagRepository.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace Unity.Payments.Domain.PaymentTags +{ + public interface IPaymentTagRepository : IRepository + { + Task> GetTagsByPaymentRequestIdAsync(Guid paymentRequestId); + Task> GetTagSummary(); + Task GetMaxRenameLengthAsync(string originalTag); + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentTags/PaymentTag.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentTags/PaymentTag.cs new file mode 100644 index 000000000..1fb6ab453 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentTags/PaymentTag.cs @@ -0,0 +1,28 @@ +using System; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Unity.Payments.Domain.PaymentTags +{ + public class PaymentTag : AuditedAggregateRoot, IMultiTenant + { + public Guid? TenantId { get; set; } + public Guid PaymentRequestId { get; set; } + public string Text { get; set; } = string.Empty; + + protected PaymentTag() + { + /* This constructor is for ORMs to be used while getting the entity from the database. */ + } + + public PaymentTag(Guid id, + Guid paymentRequestId, + string text) + : base(id) + { + PaymentRequestId = paymentRequestId; + Text = text; + } + + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentTags/TagSummaryCount.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentTags/TagSummaryCount.cs new file mode 100644 index 000000000..4421ed473 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentTags/TagSummaryCount.cs @@ -0,0 +1,6 @@ +namespace Unity.Payments.Domain.PaymentTags; +public class TagSummaryCount(string name, int count) +{ + public string Text { get; set; } = name; + public int Count { get; set; } = count; +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/IPaymentsDbContext.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/IPaymentsDbContext.cs index 7f01a1727..025c1daf7 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/IPaymentsDbContext.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/IPaymentsDbContext.cs @@ -3,6 +3,7 @@ using Unity.Payments.Domain.PaymentConfigurations; using Unity.Payments.Domain.PaymentRequests; using Unity.Payments.Domain.Suppliers; +using Unity.Payments.Domain.PaymentTags; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; @@ -17,4 +18,5 @@ public interface IPaymentsDbContext : IEfCoreDbContext public DbSet Suppliers { get; } public DbSet Sites { get; } public DbSet PaymentConfigurations { get; } + public DbSet PaymentTags { get; } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContext.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContext.cs index 781f5b4a7..8318dcf86 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContext.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContext.cs @@ -2,6 +2,7 @@ using Unity.Payments.Domain; using Unity.Payments.Domain.PaymentConfigurations; using Unity.Payments.Domain.PaymentRequests; +using Unity.Payments.Domain.PaymentTags; using Unity.Payments.Domain.Suppliers; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; @@ -17,6 +18,7 @@ public class PaymentsDbContext : AbpDbContext, IPaymentsDbCon public DbSet Suppliers { get;set; } public DbSet PaymentConfigurations { get;set; } public DbSet Sites { get; set; } + public DbSet PaymentTags { get; set; } public PaymentsDbContext(DbContextOptions options) : base(options) diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContextModelCreatingExtensions.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContextModelCreatingExtensions.cs index 549ef78eb..f5778160e 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContextModelCreatingExtensions.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContextModelCreatingExtensions.cs @@ -5,6 +5,7 @@ using Unity.Payments.Domain; using Unity.Payments.Domain.Suppliers; using Unity.Payments.Domain.PaymentConfigurations; +using Unity.Payments.Domain.PaymentTags; namespace Unity.Payments.EntityFrameworkCore; @@ -71,5 +72,16 @@ public static void ConfigurePayments( b.ConfigureByConvention(); }); + + modelBuilder.Entity(b => + { + b.ToTable(PaymentsDbProperties.DbTablePrefix + "PaymentTags", + PaymentsDbProperties.DbSchema); + + b.ConfigureByConvention(); + b.Property(x => x.Text) + .IsRequired() + .HasMaxLength(250); + }); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/PaymentTagRepository.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/PaymentTagRepository.cs new file mode 100644 index 000000000..cd8476016 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/PaymentTagRepository.cs @@ -0,0 +1,70 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Unity.Payments.Domain.PaymentTags; +using Unity.Payments.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Unity.Payments.Repositories +{ + public class PaymentTagRepository : EfCoreRepository, IPaymentTagRepository + { + public PaymentTagRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + public async Task> GetTagsByPaymentRequestIdAsync(Guid paymentRequestId) + { + var dbSet = await GetDbSetAsync(); + return await dbSet.Where(p => p.PaymentRequestId.Equals(paymentRequestId)).ToListAsync(); + } + + public virtual async Task> GetTagSummary() + { + var dbSet = await GetDbSetAsync(); + var results = dbSet + .AsNoTracking() + .AsEnumerable() // Forces client-side evaluation + .SelectMany(tag => tag.Text.Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(t => t.Trim())) + .GroupBy(tag => tag) + .Select(group => new TagSummaryCount( + group.Key, + group.Count() + )).ToList(); + + return results; + } + + /// + /// For a given Tag, finds the maximum length available for renaming. + /// + /// The tag to be replaced. + /// The maximum length available for renaming + public virtual async Task GetMaxRenameLengthAsync(string originalTag) + { + var dbContext = await GetDbContextAsync(); + var entityType = dbContext.Model.FindEntityType(typeof(PaymentTag)); + var property = entityType?.FindProperty(nameof(PaymentTag.Text)); + + int maxColumnLength = property?.GetMaxLength() ?? 0; + + var dbSet = await GetDbSetAsync(); + int? maxTagSetLength = await dbSet + .AsNoTracking() + .Where(t => t.Text.Contains(originalTag)) + .Select(t => t.Text.Length) + .OrderByDescending(len => len) + .FirstOrDefaultAsync(); + + if (maxTagSetLength == null || maxTagSetLength == 0) + { + return maxColumnLength; + } + + return maxColumnLength + originalTag.Length - maxTagSetLength.Value; + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Events/DeleteTagEto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Events/DeleteTagEto.cs new file mode 100644 index 000000000..bba118980 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Events/DeleteTagEto.cs @@ -0,0 +1,9 @@ +using System; + +namespace Unity.Payments.Events; + +[Serializable] +public class DeleteTagEto +{ + public required string TagName { get; set; } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Events/RenameTagEto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Events/RenameTagEto.cs new file mode 100644 index 000000000..0a14e85d6 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Events/RenameTagEto.cs @@ -0,0 +1,10 @@ +using System; + +namespace Unity.Payments.Events; + +[Serializable] +public class RenameTagEto +{ + public required string originalTagName { get; set; } + public required string replacementTagName { get; set; } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Handlers/DeleteTagHandler.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Handlers/DeleteTagHandler.cs new file mode 100644 index 000000000..512341d83 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Handlers/DeleteTagHandler.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Unity.Payments.Events; +using Unity.Payments.PaymentTags; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus; + +namespace Unity.Payments.Handlers; +public class DeleteTagHandler(PaymentTagAppService paymentTagAppService) : + ILocalEventHandler, + ITransientDependency +{ + public async Task HandleEventAsync(DeleteTagEto eventData) + { + await paymentTagAppService.DeleteTagAsync(eventData.TagName); + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Handlers/RenameTagHandler.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Handlers/RenameTagHandler.cs new file mode 100644 index 000000000..147a73e6b --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Handlers/RenameTagHandler.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Unity.Payments.Events; +using Unity.Payments.PaymentTags; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus; + +namespace Unity.Payments.Handlers; +public class RenameTagHandler(PaymentTagAppService paymentTagAppService) : + ILocalEventHandler, + ITransientDependency +{ + public async Task HandleEventAsync(RenameTagEto eventData) + { + await paymentTagAppService.RenameTagAsync(eventData.originalTagName, eventData.replacementTagName); + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/PaymentRequestAppService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/PaymentRequestAppService.cs index 579c77ff0..cd98b311b 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/PaymentRequestAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/PaymentRequestAppService.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Unity.Payment.Shared; +using Unity.Payments.Domain.Exceptions; using Unity.Payments.Domain.PaymentConfigurations; using Unity.Payments.Domain.PaymentRequests; using Unity.Payments.Domain.Services; @@ -95,7 +96,7 @@ public virtual async Task> CreateAsync(List> UpdateStatusAsync(List r.IsApprove).Select(x => x.PaymentRequestId).ToList(); + var approvalList = await _paymentRequestsRepository.GetListAsync(x => approvalRequests.Contains(x.Id), includeDetails: true); + + // Rule AB#26693: Reject Payment Request update batch if violates L1 and L2 separation of duties + if (approvalList.Any( + x => x.Status == PaymentRequestStatus.L2Pending + && CurrentUser.Id == x.ExpenseApprovals.FirstOrDefault(y => y.Type == ExpenseApprovalType.Level1)?.DecisionUserId)) + { + throw new BusinessException( + code: ErrorConsts.L2ApproverRestriction, + message: L[ErrorConsts.L2ApproverRestriction]); + } + foreach (var dto in paymentRequests) { try @@ -206,6 +221,7 @@ public virtual async Task> UpdateStatusAsync(List DetermineTriggerActionAsync( UpdatePaymentStatusRequestDto dto, PaymentRequest payment, @@ -216,7 +232,7 @@ private async Task DetermineTriggerActionAsync( return dto.IsApprove ? PaymentApprovalAction.L1Approve : PaymentApprovalAction.L1Decline; } - if (await CanPerformLevel2ActionAsync(payment)) + if (await CanPerformLevel2ActionAsync(payment, dto.IsApprove)) { if (dto.IsApprove) { @@ -241,9 +257,18 @@ private async Task CanPerformLevel1ActionAsync(PaymentRequestStatus status return await _permissionChecker.IsGrantedAsync(PaymentsPermissions.Payments.L1ApproveOrDecline) && level1Approvals.Contains(status); } - private async Task CanPerformLevel2ActionAsync(PaymentRequest payment) + private async Task CanPerformLevel2ActionAsync(PaymentRequest payment, bool IsApprove) { List level2Approvals = new() { PaymentRequestStatus.L2Pending, PaymentRequestStatus.L2Declined }; + + // Rule AB#26693: Reject Payment Request update if violates L1 and L2 separation of duties + var IsSameApprover = CurrentUser.Id == payment.ExpenseApprovals.FirstOrDefault(x => x.Type == ExpenseApprovalType.Level1)?.DecisionUserId; + if (IsSameApprover && IsApprove) + { + throw new BusinessException( + code: ErrorConsts.L2ApproverRestriction, + message: L[ErrorConsts.L2ApproverRestriction]); + } return await _permissionChecker.IsGrantedAsync(PaymentsPermissions.Payments.L2ApproveOrDecline) && level2Approvals.Contains(payment.Status); } @@ -289,10 +314,16 @@ public async Task> GetListAsync(PagedAndSorted var totalCount = await _paymentRequestsRepository.GetCountAsync(); using (_dataFilter.Disable()) { - var payments = await _paymentRequestsRepository + await _paymentRequestsRepository .GetPagedListAsync(input.SkipCount, input.MaxResultCount, input.Sorting ?? string.Empty, includeDetails: true); - var mappedPayments = await MapToDtoAndLoadDetailsAsync(payments); + // Include PaymentTags in the query + var paymentsQueryable = await _paymentRequestsRepository.GetQueryableAsync(); + var paymentsWithTags = await paymentsQueryable + .Include(pr => pr.PaymentTags) + .ToListAsync(); + + var mappedPayments = await MapToDtoAndLoadDetailsAsync(paymentsWithTags); ApplyErrorSummary(mappedPayments); @@ -383,6 +414,7 @@ public async Task> GetListByPaymentIdsAsync(List p var payments = await paymentsQueryable .Where(e => paymentIds.Contains(e.Id)) .Include(pr => pr.Site) + .Include(x => x.ExpenseApprovals) .ToListAsync(); return ObjectMapper.Map, List>(payments); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentTags/PaymentTagAppService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentTags/PaymentTagAppService.cs new file mode 100644 index 000000000..0cf887143 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentTags/PaymentTagAppService.cs @@ -0,0 +1,200 @@ +using Microsoft.AspNetCore.Authorization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Unity.Modules.Shared; +using Unity.Payments.Domain.PaymentTags; +using Volo.Abp; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Features; + +namespace Unity.Payments.PaymentTags +{ + [RequiresFeature("Unity.Payments")] + [Authorize] + public class PaymentTagAppService : PaymentsAppService, IPaymentTagAppService + { + private readonly IPaymentTagRepository _paymentTagRepository; + public PaymentTagAppService(IPaymentTagRepository paymentTagRepository) + { + _paymentTagRepository = paymentTagRepository; + } + public async Task> GetListAsync() + { + var paymentTags = await _paymentTagRepository.GetListAsync(); + return ObjectMapper.Map, List>(paymentTags.OrderBy(t => t.Id).ToList()); + } + public async Task> GetListWithPaymentRequestIdsAsync(List ids) + { + var tags = await _paymentTagRepository.GetListAsync(e => ids.Contains(e.PaymentRequestId)); + + return ObjectMapper.Map, List>(tags.OrderBy(t => t.Id).ToList()); + } + public async Task GetPaymentTagsAsync(Guid id) + { + var paymentTags = await _paymentTagRepository.FirstOrDefaultAsync(s => s.PaymentRequestId == id); + + if (paymentTags == null) return null; + + return ObjectMapper.Map(paymentTags); + } + public async Task CreateorUpdateTagsAsync(Guid id, PaymentTagDto input) + { + var paymentTag = await _paymentTagRepository.FirstOrDefaultAsync(e => e.PaymentRequestId == id); + + // Sanitize input tag text string + var tagInput = input.Text.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToHashSet(); + input.Text = string.Join(',', tagInput.OrderBy(t => t, StringComparer.InvariantCultureIgnoreCase)); + + if (paymentTag == null) + { + var newTag = await _paymentTagRepository.InsertAsync(new PaymentTag( + Guid.NewGuid(), // Generate a new ID for the PaymentTag + input.PaymentRequestId, + input.Text + ), + autoSave: true + ); + + return ObjectMapper.Map(newTag); + } + else + { + paymentTag.Text = input.Text; + await _paymentTagRepository.UpdateAsync(paymentTag, autoSave: true); + return ObjectMapper.Map(paymentTag); + } + } + + [Authorize(UnitySelector.SettingManagement.Tags.Default)] + public async Task> GetTagSummaryAsync() + { + var tagSummary = ObjectMapper.Map, List>( + await _paymentTagRepository.GetTagSummary()); + + return new PagedResultDto( + tagSummary.Count, + tagSummary + ); + } + + /// + /// For a given Tag, finds the maximum length available for renaming. + /// + /// The tag to be replaced. + /// The maximum length available for renaming + [Authorize(UnitySelector.SettingManagement.Tags.Update)] + public async Task GetMaxRenameLengthAsync(string originalTag) + { + Check.NotNullOrWhiteSpace(originalTag, nameof(originalTag)); + return await _paymentTagRepository.GetMaxRenameLengthAsync(originalTag); + } + + [Authorize(UnitySelector.SettingManagement.Tags.Update)] + public async Task> RenameTagAsync(string originalTag, string replacementTag) + { + Check.NotNullOrWhiteSpace(originalTag, nameof(originalTag)); + Check.NotNullOrWhiteSpace(replacementTag, nameof(replacementTag)); + + // Remove commas and trim whitespace from tags + originalTag = originalTag.Replace(",", string.Empty).Trim(); + replacementTag = replacementTag.Replace(",", string.Empty).Trim(); + + if (string.Equals(originalTag, replacementTag, StringComparison.InvariantCultureIgnoreCase)) + { + throw new BusinessException("Cannot update a tag to itself."); + } + + var paymentRequestTags = await _paymentTagRepository + .GetListAsync(e => e.Text.Contains(originalTag)); + + if (paymentRequestTags.Count == 0) + return []; + + int maxRemainingLength = await GetMaxRenameLengthAsync(originalTag); + if (replacementTag.Length > maxRemainingLength) + { + throw new ArgumentOutOfRangeException( + nameof(replacementTag), + $"String length exceeds maximum allowed length of {maxRemainingLength}. Actual length: {replacementTag.Length}" + ); + } + + var updatedTags = new List(paymentRequestTags.Count); + + foreach (var item in paymentRequestTags) + { + // Split and trim tags, use case-insensitive HashSet for matching + var tagSet = new HashSet( + item.Text.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries), + StringComparer.InvariantCultureIgnoreCase); + + // Only replace if the original tag exists (case-insensitive) + if (tagSet.Remove(originalTag)) + { + tagSet.Add(replacementTag); // No effect if replacement already exists + item.Text = string.Join(',', tagSet.OrderBy(t => t, StringComparer.InvariantCultureIgnoreCase)); + updatedTags.Add(item); + } + } + + if (updatedTags.Count > 0) + { + await _paymentTagRepository.UpdateManyAsync(updatedTags, autoSave: true); + } + + return [.. updatedTags.Select(x => x.Id)]; + } + + /// + /// Deletes a tag from all application tags. Only whole-word tags are removed; substring matches are ignored. + /// + /// String of tag to be deleted. + [Authorize(UnitySelector.SettingManagement.Tags.Delete)] + public async Task DeleteTagAsync(string deleteTag) + { + Check.NotNullOrWhiteSpace(deleteTag, nameof(deleteTag)); + + // Remove commas from the originalTag and replacementTag + deleteTag = deleteTag.Replace(",", string.Empty).Trim(); + + var paymentRequestTags = await _paymentTagRepository.GetListAsync(e => e.Text.Contains(deleteTag)); + + var updatedTags = new List(); + var deletedTags = new List(); + + foreach (var item in paymentRequestTags) + { + var tagSet = new HashSet( + item.Text.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries), + StringComparer.InvariantCultureIgnoreCase); + + // Only replace whole word tags - skip substring matches + if (tagSet.Remove(deleteTag)) + { + if (tagSet.Count > 0) + { + item.Text = string.Join(',', tagSet.OrderBy(t => t, StringComparer.InvariantCultureIgnoreCase)); + updatedTags.Add(item); + } + else + { + deletedTags.Add(item); + } + } + } + + if (deletedTags.Count > 0) + { + await _paymentTagRepository.DeleteManyAsync(deletedTags, autoSave: true); + } + + if (updatedTags.Count > 0) + { + await _paymentTagRepository.UpdateManyAsync(updatedTags, autoSave: true); + } + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentsApplicationAutoMapperProfile.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentsApplicationAutoMapperProfile.cs index 01c06379b..114c6e93c 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentsApplicationAutoMapperProfile.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentsApplicationAutoMapperProfile.cs @@ -1,9 +1,11 @@ using AutoMapper; -using Unity.Payments.PaymentRequests; -using Unity.Payments.Domain.PaymentRequests; using Unity.Payments.Domain.PaymentConfigurations; +using Unity.Payments.Domain.PaymentRequests; +using Unity.Payments.Domain.PaymentTags; using Unity.Payments.Domain.Suppliers; using Unity.Payments.PaymentConfigurations; +using Unity.Payments.PaymentRequests; +using Unity.Payments.PaymentTags; using Unity.Payments.Suppliers; using Volo.Abp.Users; @@ -16,7 +18,8 @@ public PaymentsApplicationAutoMapperProfile() CreateMap() .ForMember(dest => dest.ErrorSummary, options => options.Ignore()) .ForMember(dest => dest.Site, opt => opt.MapFrom(src => src.Site)) - .ForMember(dest => dest.CreatorUser, opt => opt.Ignore()); + .ForMember(dest => dest.CreatorUser, opt => opt.Ignore()) + .ForMember(dest => dest.PaymentTags, opt => opt.MapFrom(src => src.PaymentTags)); CreateMap() .ForMember(dest => dest.Site, opt => opt.MapFrom(src => src.Site)); @@ -27,5 +30,8 @@ public PaymentsApplicationAutoMapperProfile() CreateMap(); CreateMap(); CreateMap(); + CreateMap(); + + CreateMap(); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Suppliers/SupplierAppService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Suppliers/SupplierAppService.cs index cbb6c1ca2..c5b4334dc 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Suppliers/SupplierAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Suppliers/SupplierAppService.cs @@ -1,18 +1,17 @@ -using System; -using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System; +using System.Collections.Generic; using System.Linq; -using Volo.Abp.Features; +using System.Threading.Tasks; using Unity.Payments.Domain.Suppliers; using Unity.Payments.Domain.Suppliers.ValueObjects; -using Unity.Payments.Permissions; -using System.Collections.Generic; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.Features; namespace Unity.Payments.Suppliers { [RequiresFeature("Unity.Payments")] - public class SupplierAppService(ISupplierRepository supplierRepository, + public class SupplierAppService(ISupplierRepository supplierRepository, ISiteAppService siteAppService) : PaymentsAppService, ISupplierAppService { protected ILogger logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); @@ -65,11 +64,15 @@ public virtual async Task UpdateAsync(Guid id, UpdateSupplierDto up } public virtual async Task GetAsync(Guid id) - { try { + { + try + { var result = await supplierRepository.GetAsync(id); if (result == null) return null; return ObjectMapper.Map(result); - } catch (Exception ex) { + } + catch (Exception ex) + { logger.LogError(ex, "Error fetching supplier"); return null; } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Unity.Payments.Application.csproj b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Unity.Payments.Application.csproj index 2c4c0eadd..6fa1a9eec 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Unity.Payments.Application.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Unity.Payments.Application.csproj @@ -29,6 +29,8 @@ + + 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 1173ab47f..27dff2071 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 @@ -9,6 +9,7 @@ "Unity.Payments:Errors:ConfigurationExists": "Configuration already exitst", "Unity.Payments:Errors:ConfigurationDoesNotExist": "Configuration does not exits", "Unity.Payments:Errors:InvalidAccountCodingFiled": "Invalid account coding field {field} : {length}", + "Unity.Payments:Errors:L2ApproverRestriction": "You cannot approve one or more selected payments as you have already approved them as an L1 Approver.", "Setting:Payments.ThresholdAmount.DisplayName": "Payment threshold amount", "Setting:Payments.ThresholdAmount.Description": "Payment threshold that triggers additional approvals and checks", @@ -21,6 +22,9 @@ "ApplicationPaymentRequest:SubmitButtonText": "Submit Payment Requests", "ApplicationPaymentRequest:CancelButtonText": "Cancel", "ApplicationPaymentRequest:Validations:RemainingAmountExceeded": "Cannot add a payment that exceeds the remaining amount of ", + "ApplicationPaymentRequest:Validations:L2ApproverRestriction": "You cannot approve this payment as you have already approved it as an L1 Approver.", + "ApplicationPaymentRequest:Validations:L2ApproverRestrictionBatch": "Highlighted payments were already approved with L1 permission. L1 and L2 approvers must be different individuals", + "ApplicationPaymentTable:BatchNumber": "Batch Number", "ApplicationPaymentTable:TotalAmount": "Total Amount", diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/PaymentsApprovalModel.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/PaymentsApprovalModel.cs index 1c9e092ac..45c3925de 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/PaymentsApprovalModel.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/PaymentsApprovalModel.cs @@ -1,12 +1,16 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; using System; using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using Unity.Payments.Enums; +using Unity.Payments.Localization; +using Volo.Abp.Users; namespace Unity.Payments.Web.Pages.PaymentApprovals { - public class PaymentsApprovalModel + public class PaymentsApprovalModel : IValidatableObject { [Required] public Guid Id { get; set; } @@ -32,8 +36,6 @@ public class PaymentsApprovalModel public bool isPermitted { get; set; } - public List ErrorList { get; set; } = new List { }; - public bool IsL3ApprovalRequired { get; set; } public PaymentRequestStatus ToStatus { get; set; } @@ -42,5 +44,27 @@ public class PaymentsApprovalModel public string ToStatusText { get; set; } = string.Empty; + public Guid? PreviousL1Approver { get; set; } + + public bool IsApproval { get; set; } + public bool IsValid { get; set; } = false; + public Guid CurrentUser { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + var currentUser = validationContext.GetRequiredService(); + var localizer = validationContext.GetRequiredService>(); + + // Rule AB#26693: Reject Payment Request update batch if violates L1 and L2 separation of duties + if (IsApproval + && Status == PaymentRequestStatus.L2Pending + && PreviousL1Approver == currentUser.Id) + { + yield return new ValidationResult( + errorMessage: localizer["ApplicationPaymentRequest:Validations:L2ApproverRestriction"], + memberNames: [nameof(PreviousL1Approver)] + ); + } + } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/UpdatePaymentRequestStatus.cshtml b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/UpdatePaymentRequestStatus.cshtml index fcc80acfc..6b4c1dd81 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/UpdatePaymentRequestStatus.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/UpdatePaymentRequestStatus.cshtml @@ -1,11 +1,12 @@ @page @using Microsoft.Extensions.Localization -@using Unity.Payments.Localization; -@using Volo.Abp.AspNetCore.Mvc.UI.Layout; -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal; +@using Unity.Payments.Localization +@using Unity.Payments.Web.Pages.PaymentApprovals +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal + @model Unity.Payments.Web.Pages.PaymentApprovals.UpdatePaymentRequestStatus + @inject IStringLocalizer L -@using Unity.Payments.Web.Pages.PaymentApprovals; @{ Layout = null; } @@ -14,8 +15,16 @@ int uniqueIndex = 0; } +@functions +{ + public static string GetContainerValidationState(bool isValid) + { + return isValid ? "single-payment" : "single-payment bg-danger-subtle border-danger-subtle"; + } +} + -
+ @if (Model.IsApproval) { @@ -32,7 +41,7 @@ - @if (Model.PaymentGroupings.Count >= 1) + @if (Model.PaymentGroupings.Count > 0) { @for (int k = 0; k < Model.PaymentGroupings.Count; k++) { @@ -45,7 +54,7 @@ @for (int i = 0; i < Model.PaymentGroupings[k].Items.Count; i++) { -
+
@@ -57,9 +66,21 @@ - -
@Model.PaymentGroupings[k].Items[i].ApplicantName (@Model.PaymentGroupings[k].Items[i].InvoiceNumber)
- + + + + +
+
+ @Model.PaymentGroupings[k].Items[i].ApplicantName (@Model.PaymentGroupings[k].Items[i].InvoiceNumber) +
+
+
+ +
+
@@ -81,24 +102,34 @@ uniqueIndex++; } - - - @{ - var fromStatusText = UpdatePaymentRequestStatus.GetStatusText(@Model.PaymentGroupings[k].Items[0].Status); - var fromStatusTextColor = UpdatePaymentRequestStatus.GetStatusTextColor(@Model.PaymentGroupings[k].Items[0].Status); - } - @fromStatusText - - - - - - - @{ - var toStatusText = UpdatePaymentRequestStatus.GetStatusText(@Model.PaymentGroupings[k].ToStatus); - var toStatusTextColor = UpdatePaymentRequestStatus.GetStatusTextColor(@Model.PaymentGroupings[k].ToStatus); - } - @toStatusText + + +
+ @{ + var fromStatusText = UpdatePaymentRequestStatus.GetStatusText(@Model.PaymentGroupings[k].Items[0].Status); + var fromStatusTextColor = UpdatePaymentRequestStatus.GetStatusTextColor(@Model.PaymentGroupings[k].Items[0].Status); + } + @fromStatusText +
+
+ @if (Model.PaymentGroupings[k].Items.Any(x => !x.IsValid)) + { + + } + else + { + + + + } +
+
+ @{ + var toStatusText = UpdatePaymentRequestStatus.GetStatusText(@Model.PaymentGroupings[k].ToStatus); + var toStatusTextColor = UpdatePaymentRequestStatus.GetStatusTextColor(@Model.PaymentGroupings[k].ToStatus); + } + @toStatusText +
@@ -110,13 +141,16 @@

No Payments Selected

} - - Note: Only payments in @Model.FromStatusText status will appear in this list + + @if(ModelState.ContainsKey(nameof(PaymentsApprovalModel.PreviousL1Approver)) + && ModelState[nameof(PaymentsApprovalModel.PreviousL1Approver)]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) + { + @L["ApplicationPaymentRequest:Validations:L2ApproverRestrictionBatch"] + } - @@ -134,6 +168,10 @@ +@section Scripts { + +} + '); - newTab.document.write(''); - newTab.document.write(''); - newTab.document.write(''); - newTab.document.write(''); + const jqueryScript = doc.createElement('script'); + jqueryScript.src = '/libs/jquery/jquery.js'; - let newHiddenInput = $(''); - // Set attributes for the hidden input - newHiddenInput.attr({ - 'type': 'hidden', - 'name': 'ApplicationFormSubmissionId', - 'value': submissionId - }); + const formioScript = doc.createElement('script'); + formioScript.src = '/libs/formiojs/formio.form.min.js'; - let inputToStore = newHiddenInput.prop('outerHTML'); - newTab.document.write(inputToStore); - newTab.document.write(divToStore); - newTab.document.write(''); - newTab.onload = function () { - let script = newTab.document.createElement('script'); - script.src = '/Pages/GrantApplications/loadPrint.js'; - script.onload = function () { - newTab.executeOperations(data); + const bootstrapCSS = doc.createElement('link'); + bootstrapCSS.rel = 'stylesheet'; + bootstrapCSS.href = '/libs/bootstrap-4/dist/css/bootstrap.min.css'; - }; + const formioCSS = doc.createElement('link'); + formioCSS.rel = 'stylesheet'; + formioCSS.href = '/libs/formiojs/formio.form.css'; - newTab.document.head.appendChild(script); + head.appendChild(jqueryScript); + head.appendChild(formioScript); + head.appendChild(bootstrapCSS); + head.appendChild(formioCSS); + + // BODY + const body = doc.body; + + // Hidden input + const hiddenInput = doc.createElement('input'); + hiddenInput.type = 'hidden'; + hiddenInput.name = 'ApplicationFormSubmissionId'; + hiddenInput.value = submissionId; + body.appendChild(hiddenInput); + // Placeholder div + const formContainer = doc.createElement('div'); + formContainer.id = 'new-rendering'; + formContainer.textContent = 'Loading form...'; + body.appendChild(formContainer); + + // Load your custom script after Form.io is ready + formioScript.onload = () => { + const customScript = doc.createElement('script'); + customScript.src = '/Pages/GrantApplications/loadPrint.js'; + customScript.onload = function () { + // Call your global executeOperations function + newTab.executeOperations(data); + }; + head.appendChild(customScript); }; + }); - newTab.document.close(); - } function openScoreSheetDataInNewTab(assessmentScoresheet) { let newTab = window.open('', '_blank'); @@ -463,12 +496,12 @@ $(function () { } } }; - + const assessmentResultObserver = new MutationObserver(widgetCallback); - if (assessmentResultTargetNode) { + if (assessmentResultTargetNode) { assessmentResultObserver.observe(assessmentResultTargetNode, widgetConfig); - } + } PubSub.subscribe( 'application_status_changed', diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.cshtml.cs index 563fd0b5c..12e8b574f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.cshtml.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.cshtml.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Unity.Modules.Shared.Permissions; using Volo.Abp.Identity; using Volo.Abp.Identity.Integration; @@ -35,6 +36,13 @@ public async Task OnGetAsync() { try { + + if (User.IsInRole(IdentityConsts.ITAdminRoleName)) + { + Response.Redirect("/TenantManagement/Tenants"); + return; + } + var users = (await _identityUserLookupAppService.SearchAsync(new UserLookupSearchInputDto())).Items; AssigneeList ??= new List(); foreach (var user in users.OrderBy(s => s.UserName)) 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 ec3c150ab..b6d598137 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 @@ -81,7 +81,7 @@ let responseCallback = function (result) { return { recordsTotal: result.totalCount, - recordsFiltered: result.items.length, + recordsFiltered: result.totalCount, data: result.items }; }; @@ -96,6 +96,7 @@ data: {}, responseCallback, actionButtons, + serverSideEnabled: false, pagingEnabled: true, reorderEnabled: true, languageSetValues, @@ -778,7 +779,7 @@ data: 'organizationType', className: 'data-table-header', render: function (data) { - return data ?? ''; + return getFullType(data) ?? ''; }, index: 39 } @@ -1082,6 +1083,47 @@ } } + function getFullType(code) { + const companyTypes = [ + { code: "BC", name: "BC Company" }, + { code: "CP", name: "Cooperative" }, + { code: "GP", name: "General Partnership" }, + { code: "S", name: "Society" }, + { code: "SP", name: "Sole Proprietorship" }, + { code: "A", name: "Extraprovincial Company" }, + { code: "B", name: "Extraprovincial" }, + { code: "BEN", name: "Benefit Company" }, + { code: "C", name: "Continuation In" }, + { code: "CC", name: "BC Community Contribution Company" }, + { code: "CS", name: "Continued In Society" }, + { code: "CUL", name: "Continuation In as a BC ULC" }, + { code: "EPR", name: "Extraprovincial Registration" }, + { code: "FI", name: "Financial Institution" }, + { code: "FOR", name: "Foreign Registration" }, + { code: "LIB", name: "Public Library Association" }, + { code: "LIC", name: "Licensed (Extra-Pro)" }, + { code: "LL", name: "Limited Liability Partnership" }, + { code: "LLC", name: "Limited Liability Company" }, + { code: "LP", name: "Limited Partnership" }, + { code: "MF", name: "Miscellaneous Firm" }, + { code: "PA", name: "Private Act" }, + { code: "PAR", name: "Parish" }, + { code: "QA", name: "CO 1860" }, + { code: "QB", name: "CO 1862" }, + { code: "QC", name: "CO 1878" }, + { code: "QD", name: "CO 1890" }, + { code: "QE", name: "CO 1897" }, + { code: "REG", name: "Registraton (Extra-pro)" }, + { code: "ULC", name: "BC Unlimited Liability Company" }, + { code: "XCP", name: "Extraprovincial Cooperative" }, + { code: "XL", name: "Extrapro Limited Liability Partnership" }, + { code: "XP", name: "Extraprovincial Limited Partnership" }, + { code: "XS", name: "Extraprovincial Society" } + ]; + const match = companyTypes.find(entry => entry.code === code); + return match ? match.name : "Unknown"; + } + window.addEventListener('resize', () => { }); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ScoresheetPrint.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ScoresheetPrint.css index 3e7218020..b2cd89f79 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ScoresheetPrint.css +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ScoresheetPrint.css @@ -18,12 +18,10 @@ button { display: none; } - .user-role-full { margin-left: 20px; } - .form-control:disabled, .form-control[readonly] { background-color: #ffffff; @@ -40,22 +38,10 @@ button { overflow-anchor: none; } -&:hover { - z-index: 2; -} - -&:focus { - z-index: 3; - outline: 0; - -} - - .accordion-header { margin-bottom: 0; } - #assessment-scoresheet #section-button::after { -webkit-filter: grayscale(1) invert(0); filter: grayscale(1) invert(0); @@ -69,4 +55,4 @@ button { #assessment-scoresheet .preview-btn:not(.collapsed) { background-color: #007bff; color: #ffffff; -} \ No newline at end of file +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantPrograms/Index.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantPrograms/Index.cshtml index 9f858a354..71b133178 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantPrograms/Index.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantPrograms/Index.cshtml @@ -1,11 +1,13 @@ @page +@using Microsoft.Extensions.Localization @using Unity.GrantManager.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Layout -@using Unity.GrantManager.Web.Pages.Intakes -@using Microsoft.Extensions.Localization + @model Unity.GrantManager.Web.Pages.GrantPrograms.IndexModel + @inject IStringLocalizer L @inject IPageLayout PageLayout + @{ PageLayout.Content.MenuItemName = "GrantManager.GrantPrograms"; PageLayout.Content.Title = L["GrantPrograms"].Value; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantPrograms/Index.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantPrograms/Index.js index b3a341d57..1def43a2f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantPrograms/Index.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantPrograms/Index.js @@ -54,6 +54,7 @@ data: {}, responseCallback, actionButtons: [...commonTableActionButtons('Grant Programs')], + serverSideEnabled: false, pagingEnabled: true, reorderEnabled: false, languageSetValues: {}, diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Intakes/Index.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Intakes/Index.js index 04bb0b22d..bac333052 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Intakes/Index.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Intakes/Index.js @@ -100,6 +100,7 @@ data: {}, responseCallback, actionButtons, + serverSideEnable: false, pagingEnabled: true, reorderEnabled: false, languageSetValues: {}, diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/PaymentHistory/Details.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/PaymentHistory/Details.js index affacd784..97264e856 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/PaymentHistory/Details.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/PaymentHistory/Details.js @@ -33,6 +33,7 @@ data: inputAction, responseCallback, actionButtons, + serverSideEnabled: false, pagingEnabled: true, reorderEnabled: true, languageSetValues: {}, diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/SettingManagement/TagManagement/RenameTagModal.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/SettingManagement/TagManagement/RenameTagModal.cshtml new file mode 100644 index 000000000..99b743841 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/SettingManagement/TagManagement/RenameTagModal.cshtml @@ -0,0 +1,29 @@ +@page +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal + +@model Unity.GrantManager.Web.Pages.SettingManagement.TagManagement.RenameTagModal +@{ + Layout = null; +} + +@if (Model.ViewModel == null) +{ + throw new InvalidOperationException("ViewModel cannot be null."); +} + + +
+ + + + + + + + + + + +
+ diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/SettingManagement/TagManagement/RenameTagModal.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/SettingManagement/TagManagement/RenameTagModal.cshtml.cs new file mode 100644 index 000000000..f4208b99f --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/SettingManagement/TagManagement/RenameTagModal.cshtml.cs @@ -0,0 +1,64 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Unity.GrantManager.GrantApplications; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Volo.Abp.Validation; + +namespace Unity.GrantManager.Web.Pages.SettingManagement.TagManagement; + +public class RenameTagModal(IApplicationTagsService applicationTagService) : AbpPageModel +{ + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string SelectedTagText { get; set; } = string.Empty; + + [BindProperty] + public RenameTagViewModel? ViewModel { get; set; } + + public void OnGet() + { + ViewModel = new RenameTagViewModel + { + OriginalTag = SelectedTagText, + ReplacementTag = SelectedTagText + }; + } + + public class RenameTagViewModel + { + [Required] + [HiddenInput] + public required string OriginalTag { get; set; } + + [DisplayName("New Tag Name")] + [Required(ErrorMessage = "Replacement tag is required")] + [RegularExpression(@"^[^\s,]+$", ErrorMessage = "Tag cannot contain spaces or commas.")] + [MaxLength(250)] + public required string ReplacementTag { get; set; } + } + + public async Task OnPostAsync() + { + if (ViewModel == null) return NoContent(); + + if (ViewModel.OriginalTag == ViewModel.ReplacementTag) + { + throw new AbpValidationException("New tag cannot be the same as the original tag."); + } + + try + { + await applicationTagService.RenameTagGlobalAsync(ViewModel.OriginalTag, ViewModel.ReplacementTag); + } + catch (Exception ex) + { + Logger.LogError(ex, message: "Error updating application tags"); + } + + return NoContent(); + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Unity.GrantManager.Web.csproj b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Unity.GrantManager.Web.csproj index 36fff14b4..d81ca7c12 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Unity.GrantManager.Web.csproj +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Unity.GrantManager.Web.csproj @@ -41,6 +41,12 @@ Always + + Always + + + Always + Always diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/BackgroundJobsPageContributor.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/BackgroundJobsPageContributor.cs index f5d6acbd5..187d2d191 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/BackgroundJobsPageContributor.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/BackgroundJobsPageContributor.cs @@ -20,7 +20,8 @@ public override Task ConfigureAsync(SettingPageCreationContext context) new SettingPageGroup( "GrantManager.BackgroundJobs", "Background Jobs", - typeof(BackgroundJobsViewComponent) + typeof(BackgroundJobsViewComponent), + order: 1 ) ); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/BackgroundJobsSettingGroup/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/BackgroundJobsSettingGroup/Default.js new file mode 100644 index 000000000..436c3baea --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/BackgroundJobsSettingGroup/Default.js @@ -0,0 +1 @@ +abp.log.debug('BackgroundJobs initialized!'); \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/TagManagement/TagManagement.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/TagManagement/TagManagement.js new file mode 100644 index 000000000..9697cb08b --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/TagManagement/TagManagement.js @@ -0,0 +1,265 @@ +const TagTypes = {}; +let userCanUpdate = abp.auth.isGranted('Unity.GrantManager.SettingManagement.Tags.Update'); +let userCanDelete = abp.auth.isGranted('Unity.GrantManager.SettingManagement.Tags.Delete'); + +function defineTagSummaryColumnDefs() { + // Define columns - start with fixed columns + const columnDefs = [ + { + title: "Tags", + name: 'text', + data: 'text' + } + ]; + + // Add a column for each tag type + Object.values(TagTypes).forEach(tagType => { + columnDefs.push({ + title: `${tagType.name} Count`, + name: `${tagType.name.toLowerCase()}Count`, + data: `tagTypeCounts.${tagType.name}`, + defaultContent: 0, + render: function (data) { + return data || 0; + } + }); + }); + + columnDefs.push({ + title: "Count", + name: 'totalCount', + data: 'totalCount' + }); + + // Add the actions column + columnDefs.push({ + title: "Actions", + name: 'actions', + data: 'text', + orderable: false, + render: function (data, type, row) { + let $buttonWrapper = $('
').addClass('d-flex flex-nowrap gap-1'); + + let $editButton = $('
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentResults/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentResults/Default.cshtml index 3423042e8..043c6880b 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentResults/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentResults/Default.cshtml @@ -1,17 +1,15 @@ -@using Microsoft.AspNetCore.Authorization; -@using Unity.Flex.Web.Views.Shared.Components.WorksheetInstanceWidget -@using Unity.GrantManager.Flex -@using Unity.GrantManager.Localization +@using Microsoft.AspNetCore.Authorization @using Microsoft.Extensions.Localization -@using Unity.GrantManager.Settings -@using Unity.GrantManager.Web.Views.Shared.Components.AssessmentResults; +@using Unity.GrantManager.Localization +@using Unity.GrantManager.Web.Views.Shared.Components.AssessmentResults @using Unity.Modules.Shared @using Volo.Abp.Authorization.Permissions +@model AssessmentResultsPageModel + @inject IAuthorizationService AuthorizationService @inject IStringLocalizer L @inject IPermissionChecker PermissionChecker -@model AssessmentResultsPageModel diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CommentsWidget/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CommentsWidget/Default.cshtml index a6c905c01..5bc2c51d1 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CommentsWidget/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CommentsWidget/Default.cshtml @@ -1,12 +1,12 @@ -@using System.Text.Json -@using Unity.GrantManager.Comments; -@using Unity.GrantManager.Web.Views.Shared.Components.CommentsWidget; -@using Volo.Abp.AspNetCore.Mvc.UI.Layout; -@using Unity.GrantManager.Web.Pages.GrantApplications; -@model CommentsWidgetViewModel @using Microsoft.AspNetCore.Mvc.Localization -@using Unity.GrantManager.Localization; @using Microsoft.Extensions.Localization +@using System.Text.Json +@using Unity.GrantManager.Comments +@using Unity.GrantManager.Localization +@using Unity.GrantManager.Web.Views.Shared.Components.CommentsWidget + +@model CommentsWidgetViewModel + @inject IHtmlLocalizer L @inject IStringLocalizerFactory StringLocalizerFactory @{ diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/DetailsActionBar/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/DetailsActionBar/Default.cshtml index 701d14121..e43cb8926 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/DetailsActionBar/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/DetailsActionBar/Default.cshtml @@ -17,17 +17,5 @@ text="@L["ApplicationList:ManageTagButton"].Value" icon-type="Other" button-type="Light" /> - - @* - - *@
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.cshtml index 33e6e8679..832884db3 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.cshtml @@ -75,6 +75,16 @@ + + + + + + + + + +