From 804cda859f8144a2a96a79d3cd47c85405841eb1 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:16:18 -0700 Subject: [PATCH 01/58] [AB#32424] Add Applicant Portal Settings page and functionality --- .../UX2/Components/Topbar/Default.cshtml | 4 + .../IApplicationStatusService.cs | 2 + ...UpdateApplicationStatusExternalLabelDto.cs | 14 ++++ ...pdateApplicationStatusExternalLabelsDto.cs | 10 +++ .../ApplicationStatusAppService.cs | 12 ++- .../Localization/GrantManager/en.json | 12 ++- .../ApplicantPortalSettings/Index.cshtml | 77 +++++++++++++++++++ .../ApplicantPortalSettings/Index.cshtml.cs | 17 ++++ .../Pages/ApplicantPortalSettings/Index.css | 50 ++++++++++++ .../Pages/ApplicantPortalSettings/Index.js | 52 +++++++++++++ 10 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/UpdateApplicationStatusExternalLabelDto.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/UpdateApplicationStatusExternalLabelsDto.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.css create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.js diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml index 7d6d1c5e13..38fd0f21b0 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml @@ -42,6 +42,10 @@ { Switch Grant Programs } + @if (CurrentTenant.Id != null) + { + Applicant Portal Settings + } @if (await FeatureChecker.IsEnabledAsync("Unity.Payments") && isAuthorizedForPaymentConfiguration) { Payments Configuration diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationStatusService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationStatusService.cs index b28cb95405..fb8dafc378 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationStatusService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationStatusService.cs @@ -7,4 +7,6 @@ namespace Unity.GrantManager.GrantApplications; public interface IApplicationStatusService : IApplicationService { Task> GetListAsync(); + + Task UpdateExternalStatusLabelsAsync(UpdateApplicationStatusExternalLabelsDto input); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/UpdateApplicationStatusExternalLabelDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/UpdateApplicationStatusExternalLabelDto.cs new file mode 100644 index 0000000000..28cb19e244 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/UpdateApplicationStatusExternalLabelDto.cs @@ -0,0 +1,14 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Unity.GrantManager.GrantApplications; + +public class UpdateApplicationStatusExternalLabelDto +{ + [Required] + public Guid Id { get; set; } + + [Required] + [StringLength(ApplicationStatusConsts.MaxNameLength, MinimumLength = 1)] + public string ExternalStatus { get; set; } = string.Empty; +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/UpdateApplicationStatusExternalLabelsDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/UpdateApplicationStatusExternalLabelsDto.cs new file mode 100644 index 0000000000..a622b32d6a --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/UpdateApplicationStatusExternalLabelsDto.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Unity.GrantManager.GrantApplications; + +public class UpdateApplicationStatusExternalLabelsDto +{ + [Required] + public List Statuses { get; set; } = []; +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationStatusAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationStatusAppService.cs index 57ea951e99..00a84af7b5 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationStatusAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationStatusAppService.cs @@ -19,10 +19,20 @@ public ApplicationStatusAppService(IApplicationStatusRepository repository) _applicationStatusRepository = repository; } - public async Task> GetListAsync() + public virtual async Task> GetListAsync() { var statuses = await _applicationStatusRepository.GetListAsync(); return ObjectMapper.Map, List>(statuses.OrderBy(s => s.StatusCode).ToList()); } + + public virtual async Task UpdateExternalStatusLabelsAsync(UpdateApplicationStatusExternalLabelsDto input) + { + foreach (var statusDto in input.Statuses) + { + var status = await _applicationStatusRepository.GetAsync(statusDto.Id); + status.ExternalStatus = statusDto.ExternalStatus; + await _applicationStatusRepository.UpdateAsync(status); + } + } } \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json index fe3e30cd2c..2a982fa99e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json @@ -478,6 +478,16 @@ "ApplicationLinks:Category": "Category", "ApplicationLinks:ID": "ID", "ApplicationLinks:Status": "Status", - "ApplicationLinks:LinkType": "Link Type" + "ApplicationLinks:LinkType": "Link Type", + + "ApplicantPortalSettings:Title": "Applicant Portal Settings", + "ApplicantPortalSettings:ManageStatuses": "Manage Statuses", + "ApplicantPortalSettings:PortalStatusHeading": "Applicant Portal Status", + "ApplicantPortalSettings:InternalStatus": "Internal Status", + "ApplicantPortalSettings:PortalStatusLabel": "Portal Status Label", + "ApplicantPortalSettings:SaveChanges": "Save Changes", + "ApplicantPortalSettings:SaveSuccess": "Portal status labels updated successfully.", + "ApplicantPortalSettings:SaveError": "An error occurred while saving portal status labels.", + "ApplicantPortalSettings:ValidationRequired": "Portal status label cannot be empty." } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml new file mode 100644 index 0000000000..d855430dba --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml @@ -0,0 +1,77 @@ +@page +@using Microsoft.Extensions.Localization +@using Unity.GrantManager.Localization +@using Unity.GrantManager.GrantApplications +@using Volo.Abp.AspNetCore.Mvc.UI.Layout + +@model Unity.GrantManager.Web.Pages.ApplicantPortalSettings.IndexModel + +@inject IStringLocalizer L +@inject IPageLayout PageLayout + +@{ + PageLayout.Content.Title = L["ApplicantPortalSettings:Title"].Value; + ViewBag.PageTitle = L["ApplicantPortalSettings:Title"].Value; +} + +@section styles { + +} +@section scripts { + +} + +
+
    + +
+
+ +
+
+
+
@L["ApplicantPortalSettings:PortalStatusHeading"]
+
+
+
+ + + + + + + + + @for (var i = 0; i < Model.Statuses.Count; i++) + { + var status = Model.Statuses[i]; + + + + + } + +
@L["ApplicantPortalSettings:InternalStatus"]@L["ApplicantPortalSettings:PortalStatusLabel"]
+ + + + +
+
+
+ +
+
+
+
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml.cs new file mode 100644 index 0000000000..41c12d5819 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Authorization; +using System.Collections.Generic; +using System.Threading.Tasks; +using Unity.GrantManager.GrantApplications; + +namespace Unity.GrantManager.Web.Pages.ApplicantPortalSettings; + +[Authorize] +public class IndexModel(IApplicationStatusService applicationStatusService) : GrantManagerPageModel +{ + public IList Statuses { get; set; } = []; + + public async Task OnGetAsync() + { + Statuses = await applicationStatusService.GetListAsync(); + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.css new file mode 100644 index 0000000000..415f86c125 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.css @@ -0,0 +1,50 @@ +.unity-app-main-container { + margin: auto; + display: flex; + justify-content: center; + align-items: flex-start; +} + +#PortalSettingsSideMenu.side-menu { + float: left; + position: absolute; + left: 0px; + top: 150px; +} + +#PortalSettingsSideMenu ul { + padding-left: 0; +} + +#PortalSettingsSideMenu li { + height: 40px; + padding: 10px; + border-radius: 0 100em 100em 0; +} + +#PortalSettingsSideMenu .nav-item { + justify-content: left; +} + +.portal-settings-config { + /*max-height: 80vh;*/ + overflow: auto; +} + +.portal-status-table th { + font-weight: 600; + border-bottom: 2px solid #dee2e6; +} + +.portal-status-table td { + vertical-align: middle; +} + +.portal-status-table label { + margin-bottom: 0; + font-weight: 400; +} + +.portal-status-table .form-control { + max-width: 300px; +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.js new file mode 100644 index 0000000000..986ddea8d5 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.js @@ -0,0 +1,52 @@ +(function ($) { + + const l = abp.localization.getResource('GrantManager'); + + const $form = $('#PortalStatusForm'); + + let portalStatusTable = new DataTable("#PortalStatusTable", { + paging: false, + sort: false, + info: false + }); + + $form.on('submit', function (e) { + e.preventDefault(); + + const statuses = []; + $form.find('tbody tr').each(function () { + const $row = $(this); + const id = $row.find('input[type="hidden"]').val(); + const externalStatus = $row.find('input[type="text"]').val().trim(); + + if (!externalStatus) { + abp.notify.warn(l('ApplicantPortalSettings:ValidationRequired')); + return false; + } + + statuses.push({ + id: id, + externalStatus: externalStatus + }); + }); + + if (statuses.length === 0) { + return; + } + + abp.ui.setBusy($form); + + unity.grantManager.grantApplications.applicationStatus + .updateExternalStatusLabels({ statuses: statuses }) + .then(function () { + abp.notify.success(l('ApplicantPortalSettings:SaveSuccess')); + }) + .catch(function (error) { + abp.notify.error(error.message || l('ApplicantPortalSettings:SaveError')); + }) + .always(function () { + abp.ui.clearBusy($form); + }); + }); + +})(jQuery); From 9b06a141b1f4df94291145930e13c8bcabacc84d Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:47:52 -0700 Subject: [PATCH 02/58] [AB#32424] Optimize external status update and validation --- .../ApplicationStatusAppService.cs | 19 +++++++++++++++---- .../ApplicantPortalSettings/Index.cshtml | 2 +- .../Pages/ApplicantPortalSettings/Index.js | 7 +++++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationStatusAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationStatusAppService.cs index 00a84af7b5..0b1fd27c35 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationStatusAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationStatusAppService.cs @@ -28,11 +28,22 @@ public virtual async Task> GetListAsync() public virtual async Task UpdateExternalStatusLabelsAsync(UpdateApplicationStatusExternalLabelsDto input) { - foreach (var statusDto in input.Statuses) + // Load all statuses in a single query by IDs + var statusIds = input.Statuses.Select(s => s.Id).ToList(); + var statuses = await _applicationStatusRepository.GetListAsync(s => statusIds.Contains(s.Id)); + + // Build a lookup for efficient matching + var statusMap = input.Statuses.ToDictionary(s => s.Id); + + // Update statuses in memory + foreach (var status in statuses) { - var status = await _applicationStatusRepository.GetAsync(statusDto.Id); - status.ExternalStatus = statusDto.ExternalStatus; - await _applicationStatusRepository.UpdateAsync(status); + if (statusMap.TryGetValue(status.Id, out var statusDto)) + { + status.ExternalStatus = statusDto.ExternalStatus; + await _applicationStatusRepository.UpdateAsync(status); + } } + // ABP's UnitOfWork batches all UpdateAsync calls into a single SaveChanges } } \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml index d855430dba..31c98cb790 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml @@ -29,7 +29,7 @@ -
+
@L["ApplicantPortalSettings:PortalStatusHeading"]
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.js index 986ddea8d5..59d4b84e32 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.js @@ -6,7 +6,7 @@ let portalStatusTable = new DataTable("#PortalStatusTable", { paging: false, - sort: false, + ordering: false, info: false }); @@ -14,6 +14,8 @@ e.preventDefault(); const statuses = []; + let hasValidationError = false; + $form.find('tbody tr').each(function () { const $row = $(this); const id = $row.find('input[type="hidden"]').val(); @@ -21,6 +23,7 @@ if (!externalStatus) { abp.notify.warn(l('ApplicantPortalSettings:ValidationRequired')); + hasValidationError = true; return false; } @@ -30,7 +33,7 @@ }); }); - if (statuses.length === 0) { + if (statuses.length === 0 || hasValidationError) { return; } From 103af54ef66d25ddff479032c1f278fe819d739a Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Mon, 13 Apr 2026 12:08:17 -0700 Subject: [PATCH 03/58] [AB#32424] Optimize external status update --- .../GrantApplications/ApplicationStatusAppService.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationStatusAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationStatusAppService.cs index 0b1fd27c35..4427a21044 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationStatusAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationStatusAppService.cs @@ -31,19 +31,15 @@ public virtual async Task UpdateExternalStatusLabelsAsync(UpdateApplicationStatu // Load all statuses in a single query by IDs var statusIds = input.Statuses.Select(s => s.Id).ToList(); var statuses = await _applicationStatusRepository.GetListAsync(s => statusIds.Contains(s.Id)); + var statusMap = statuses.ToDictionary(s => s.Id); - // Build a lookup for efficient matching - var statusMap = input.Statuses.ToDictionary(s => s.Id); - - // Update statuses in memory - foreach (var status in statuses) + foreach (var statusDto in input.Statuses) { - if (statusMap.TryGetValue(status.Id, out var statusDto)) + if (statusMap.TryGetValue(statusDto.Id, out var status)) { status.ExternalStatus = statusDto.ExternalStatus; await _applicationStatusRepository.UpdateAsync(status); } } - // ABP's UnitOfWork batches all UpdateAsync calls into a single SaveChanges } } \ No newline at end of file From 013fecbe46b1d4f3b3d06359aa98984564adfd00 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Mon, 13 Apr 2026 12:14:01 -0700 Subject: [PATCH 04/58] [AB#32424] External Status - Add reset functionality and validation messages --- .../Localization/GrantManager/en.json | 5 +- .../ApplicantPortalSettings/Index.cshtml | 9 +- .../Pages/ApplicantPortalSettings/Index.js | 100 ++++++++++++++++-- 3 files changed, 105 insertions(+), 9 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json index 2a982fa99e..99744e4dcc 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json @@ -486,8 +486,11 @@ "ApplicantPortalSettings:InternalStatus": "Internal Status", "ApplicantPortalSettings:PortalStatusLabel": "Portal Status Label", "ApplicantPortalSettings:SaveChanges": "Save Changes", + "ApplicantPortalSettings:ResetChanges": "Reset", "ApplicantPortalSettings:SaveSuccess": "Portal status labels updated successfully.", "ApplicantPortalSettings:SaveError": "An error occurred while saving portal status labels.", - "ApplicantPortalSettings:ValidationRequired": "Portal status label cannot be empty." + "ApplicantPortalSettings:ValidationRequired": "Portal status label cannot be empty.", + "ApplicantPortalSettings:NoChanges": "No changes to save.", + "ApplicantPortalSettings:ChangesReset": "Changes have been reset to original values." } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml index 31c98cb790..0c59aa2b56 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.cshtml @@ -67,10 +67,15 @@
-
- + +
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.js index 59d4b84e32..fede36f39c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.js @@ -3,6 +3,11 @@ const l = abp.localization.getResource('GrantManager'); const $form = $('#PortalStatusForm'); + const $saveButton = $('#SaveButton'); + const $resetButton = $('#ResetButton'); + + // Store original values on page load + const originalValues = new Map(); let portalStatusTable = new DataTable("#PortalStatusTable", { paging: false, @@ -10,13 +15,79 @@ info: false }); + // Capture original values after DataTable initialization + // Use DataTable's rows().every() to ensure all rows are captured, even if dynamically loaded + portalStatusTable.rows().every(function () { + const $row = $(this.node()); + const id = $row.find('input[type="hidden"]').val(); + const externalStatus = $row.find('input[type="text"]').val().trim(); + originalValues.set(id, externalStatus); + }); + + // Check if any values have changed + function hasChanges() { + let changed = false; + portalStatusTable.$('tbody tr').each(function () { + const $row = $(this); + const id = $row.find('input[type="hidden"]').val(); + const currentValue = $row.find('input[type="text"]').val().trim(); + if (originalValues.get(id) !== currentValue) { + changed = true; + return false; + } + }); + return changed; + } + + // Update button states + function updateButtonStates() { + const changed = hasChanges(); + $saveButton.prop('disabled', !changed); + $resetButton.prop('disabled', !changed); + } + + // Debounce utility to limit how often updateButtonStates is called + function debounce(func, wait) { + let timeout; + return function () { + clearTimeout(timeout); + timeout = setTimeout(func, wait); + }; + } + + // Debounced version of updateButtonStates + const debouncedUpdateButtonStates = debounce(updateButtonStates, 150); + + // Listen for input changes, using debounced handler + $form.on('input', '#PortalStatusTable input[type="text"]', function () { + debouncedUpdateButtonStates(); + }); + + // Reset button handler + $resetButton.on('click', function (e) { + e.preventDefault(); + + portalStatusTable.$('tbody tr').each(function () { + const $row = $(this); + const id = $row.find('input[type="hidden"]').val(); + const originalValue = originalValues.get(id); + $row.find('input[type="text"]').val(originalValue); + }); + + updateButtonStates(); + abp.notify.info(l('ApplicantPortalSettings:ChangesReset')); + }); + + // Initialize button states + updateButtonStates(); + $form.on('submit', function (e) { e.preventDefault(); const statuses = []; let hasValidationError = false; - $form.find('tbody tr').each(function () { + portalStatusTable.$('tbody tr').each(function () { const $row = $(this); const id = $row.find('input[type="hidden"]').val(); const externalStatus = $row.find('input[type="text"]').val().trim(); @@ -27,13 +98,22 @@ return false; } - statuses.push({ - id: id, - externalStatus: externalStatus - }); + // Only include if value has changed + if (originalValues.get(id) !== externalStatus) { + statuses.push({ + id: id, + externalStatus: externalStatus + }); + } }); - if (statuses.length === 0 || hasValidationError) { + if (hasValidationError) { + return; + } + + // Check if there are any changes + if (statuses.length === 0) { + abp.notify.info(l('ApplicantPortalSettings:NoChanges')); return; } @@ -43,6 +123,14 @@ .updateExternalStatusLabels({ statuses: statuses }) .then(function () { abp.notify.success(l('ApplicantPortalSettings:SaveSuccess')); + + // Update original values after successful save + statuses.forEach(function(status) { + originalValues.set(status.id, status.externalStatus); + }); + + // Update button states after save + updateButtonStates(); }) .catch(function (error) { abp.notify.error(error.message || l('ApplicantPortalSettings:SaveError')); From 62e727ff619e876e9c1e0384d6d41b53a56e84b1 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Mon, 13 Apr 2026 12:16:59 -0700 Subject: [PATCH 05/58] [AB#32424] SonarQube Clean-up --- .../Pages/ApplicantPortalSettings/Index.css | 1 - 1 file changed, 1 deletion(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.css index 415f86c125..7bfa365a7a 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.css +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicantPortalSettings/Index.css @@ -27,7 +27,6 @@ } .portal-settings-config { - /*max-height: 80vh;*/ overflow: auto; } From 47a8aa650780a99ec67ca89951b533de289d6a89 Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Mon, 13 Apr 2026 13:58:12 -0700 Subject: [PATCH 06/58] AB#32398 add safe datetime functions for worksheet report config --- .../appsettings.json | 4 +- ...0413203049_SafeDateTimeCasting.Designer.cs | 4941 +++++++++++++++++ .../20260413203049_SafeDateTimeCasting.cs | 50 + .../Scripts/get_worksheet_data.sql | 16 +- .../Scripts/safe_to_date.sql | 46 + .../Scripts/safe_to_timestamp.sql | 56 + ...ty.GrantManager.EntityFrameworkCore.csproj | 10 +- 7 files changed, 5110 insertions(+), 13 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260413203049_SafeDateTimeCasting.Designer.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260413203049_SafeDateTimeCasting.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_date.sql create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_timestamp.sql diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json index 452a5ac1d3..cfb8e65c8e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json @@ -1,7 +1,7 @@ { "ConnectionStrings": { - "Default": "Host=localhost;port=5432;Database=UnityGrantManager;Username=postgres", - "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres" + "Default": "Host=localhost;port=5432;Database=UnityGrantManager;Username=postgres;Password=admin", + "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres;Password=admin" }, "Redis": { "Configuration": "127.0.0.1" diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260413203049_SafeDateTimeCasting.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260413203049_SafeDateTimeCasting.Designer.cs new file mode 100644 index 0000000000..3dd5548f12 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260413203049_SafeDateTimeCasting.Designer.cs @@ -0,0 +1,4941 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Unity.GrantManager.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + [DbContext(typeof(GrantTenantDbContext))] + [Migration("20260413203049_SafeDateTimeCasting")] + partial class SafeDateTimeCasting + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ScoresheetId"); + + b.ToTable("ScoresheetInstances", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("ScoresheetInstanceId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.HasIndex("ScoresheetInstanceId"); + + b.ToTable("Answers", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("SectionId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SectionId"); + + b.ToTable("Questions", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Scoresheets", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ScoresheetId"); + + b.ToTable("ScoresheetSections", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CustomFieldId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("WorksheetInstanceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetInstanceId"); + + b.ToTable("CustomFieldValues", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UiAnchor") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetCorrelationId") + .HasColumnType("uuid"); + + b.Property("WorksheetCorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("WorksheetInstances", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UiAnchor") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetId"); + + b.ToTable("WorksheetLinks", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("SectionId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SectionId"); + + b.ToTable("CustomFields", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Worksheets", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetId"); + + b.ToTable("WorksheetSections", "Flex"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantName") + .IsRequired() + .HasMaxLength(600) + .HasColumnType("character varying(600)"); + + b.Property("ApproxNumberOfEmployees") + .HasColumnType("text"); + + b.Property("AuditComments") + .HasColumnType("text"); + + b.Property("BusinessNumber") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FiscalDay") + .HasColumnType("integer"); + + b.Property("FiscalMonth") + .HasColumnType("text"); + + b.Property("FundingHistoryComments") + .HasColumnType("text"); + + b.Property("IndigenousOrgInd") + .HasColumnType("text"); + + b.Property("IsDuplicated") + .HasColumnType("boolean"); + + b.Property("IssueTrackingComments") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MatchPercentage") + .HasColumnType("numeric"); + + b.Property("NonRegOrgName") + .HasColumnType("text"); + + b.Property("NonRegisteredBusinessName") + .HasColumnType("text"); + + b.Property("OrgName") + .HasColumnType("text"); + + b.Property("OrgNumber") + .HasColumnType("text"); + + b.Property("OrgStatus") + .HasColumnType("text"); + + b.Property("OrganizationSize") + .HasColumnType("text"); + + b.Property("OrganizationType") + .HasColumnType("text"); + + b.Property("RedStop") + .HasColumnType("boolean"); + + b.Property("Sector") + .HasColumnType("text"); + + b.Property("SectorSubSectorIndustryDesc") + .HasColumnType("text"); + + b.Property("SiteId") + .HasColumnType("uuid"); + + b.Property("StartedOperatingDate") + .HasColumnType("date"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SubSector") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UnityApplicantId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantName"); + + b.ToTable("Applicants", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AddressType") + .HasColumnType("integer"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Postal") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("Street") + .HasColumnType("text"); + + b.Property("Street2") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Unit") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicantAddresses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("BceidBusinessGuid") + .HasColumnType("uuid"); + + b.Property("BceidBusinessName") + .HasColumnType("text"); + + b.Property("BceidUserGuid") + .HasColumnType("uuid"); + + b.Property("BceidUserName") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactOrder") + .HasColumnType("integer"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IdentityEmail") + .HasColumnType("text"); + + b.Property("IdentityName") + .HasColumnType("text"); + + b.Property("IdentityProvider") + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsConfirmed") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSubUser") + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("Phone2") + .HasColumnType("text"); + + b.Property("Phone2Extension") + .HasColumnType("text"); + + b.Property("PhoneExtension") + .HasColumnType("text"); + + b.Property("RoleForApplicant") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId") + .IsUnique(); + + b.ToTable("ApplicantAgents", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ApplicantAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AIAnalysis") + .HasColumnType("text"); + + b.Property("AIScoresheetAnswers") + .HasColumnType("jsonb"); + + b.Property("Acquisition") + .HasColumnType("text"); + + b.Property("ApplicantElectoralDistrict") + .HasColumnType("text"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationStatusId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("AssessmentResultDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AssessmentResultStatus") + .HasColumnType("text"); + + b.Property("AssessmentStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Community") + .HasColumnType("text"); + + b.Property("CommunityPopulation") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractExecutionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ContractNumber") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeclineRational") + .HasColumnType("text"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("DueDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DueDiligenceStatus") + .HasColumnType("text"); + + b.Property("EconomicRegion") + .HasColumnType("text"); + + b.Property("ElectoralDistrict") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinalDecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Forestry") + .HasColumnType("text"); + + b.Property("ForestryFocus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LikelihoodOfFunding") + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("NotificationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Payload") + .HasColumnType("jsonb"); + + b.Property("PercentageTotalProjectBudget") + .HasColumnType("double precision"); + + b.Property("Place") + .HasColumnType("text"); + + b.Property("ProjectEndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectFundingTotal") + .HasColumnType("numeric"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProjectStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectSummary") + .HasColumnType("text"); + + b.Property("ProposalDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RecommendedAmount") + .HasColumnType("numeric"); + + b.Property("ReferenceNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("RegionalDistrict") + .HasColumnType("text"); + + b.Property("RequestedAmount") + .HasColumnType("numeric"); + + b.Property("RiskRanking") + .HasColumnType("text"); + + b.Property("SigningAuthorityBusinessPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityCellPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityEmail") + .HasColumnType("text"); + + b.Property("SigningAuthorityFullName") + .HasColumnType("text"); + + b.Property("SigningAuthorityTitle") + .HasColumnType("text"); + + b.Property("SubStatus") + .HasColumnType("text"); + + b.Property("SubmissionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalProjectBudget") + .HasColumnType("numeric"); + + b.Property("TotalScore") + .HasColumnType("integer"); + + b.Property("UnityApplicationId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.HasIndex("ApplicationStatusId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Applications", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssigneeId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Duty") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssigneeId"); + + b.ToTable("ApplicationAssignments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AISummary") + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsFileId") + .HasColumnType("text"); + + b.Property("ChefsSubmissionId") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationChefsFileAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactEmail") + .HasColumnType("text"); + + b.Property("ContactFullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactMobilePhone") + .HasColumnType("text"); + + b.Property("ContactTitle") + .HasColumnType("text"); + + b.Property("ContactType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactWorkPhone") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationContact", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasColumnType("text"); + + b.Property("ApplicationFormDescription") + .HasColumnType("text"); + + b.Property("ApplicationFormName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AttemptedConnectionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("Category") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsCriteriaFormGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ConnectionHttpStatus") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultPaymentGroup") + .HasColumnType("integer"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ElectoralDistrictAddressType") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormHierarchy") + .HasColumnType("integer"); + + b.Property("IntakeId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsDirectApproval") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ParentFormId") + .HasColumnType("uuid"); + + b.Property("Payable") + .HasColumnType("boolean"); + + b.Property("PaymentApprovalThreshold") + .HasColumnType("numeric"); + + b.Property("Prefix") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PreventPayment") + .HasColumnType("boolean"); + + b.Property("RenderFormIoToHtml") + .HasColumnType("boolean"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("SuffixType") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IntakeId"); + + b.HasIndex("ParentFormId"); + + b.ToTable("ApplicationForms", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormVersionId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsSubmissionGuid") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormVersionId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("RenderedHTML") + .HasColumnType("text"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Submission") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormSubmissions", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsFormVersionGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormSchema") + .HasColumnType("jsonb"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubmissionHeaderMapping") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormVersion", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LinkType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Related"); + + b.Property("LinkedApplicationId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("StatusCode") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("StatusCode") + .IsUnique(); + + b.ToTable("ApplicationStatuses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TagId"); + + b.ToTable("ApplicationTags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.ToTable("AssessmentAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("AuditDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AuditNote") + .HasColumnType("text"); + + b.Property("AuditTrackingNumber") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("AuditHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FundingNotes") + .HasColumnType("text"); + + b.Property("FundingYear") + .HasColumnType("text"); + + b.Property("GrantCategory") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OneTimeConsideration") + .HasColumnType("numeric"); + + b.Property("ReconsiderationAmount") + .HasColumnType("numeric"); + + b.Property("RenewedFunding") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalGrantAmount") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("FundingHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IssueDescription") + .HasColumnType("text"); + + b.Property("IssueHeading") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ResolutionNote") + .HasColumnType("text"); + + b.Property("Resolved") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Year") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("IssueTrackings", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ApprovalRecommended") + .HasColumnType("boolean"); + + b.Property("AssessorId") + .HasColumnType("uuid"); + + b.Property("CleanGrowth") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EconomicImpact") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinancialAnalysis") + .HasColumnType("integer"); + + b.Property("InclusiveGrowth") + .HasColumnType("integer"); + + b.Property("IsAiAssessment") + .HasColumnType("boolean"); + + b.Property("IsComplete") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssessorId"); + + b.ToTable("Assessments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicantComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicationComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.HasIndex("CommenterId"); + + b.ToTable("AssessmentComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.Contact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("HomePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MobilePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WorkPhoneExtension") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WorkPhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.ToTable("Contacts", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("RelatedEntityId") + .HasColumnType("uuid"); + + b.Property("RelatedEntityType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Role") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("RelatedEntityType", "RelatedEntityId"); + + b.HasIndex("ContactId", "RelatedEntityType", "RelatedEntityId"); + + b.ToTable("ContactLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.GlobalTag.Tag", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Tags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Identity.Person", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Badge") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcDisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("OidcSub"); + + b.ToTable("Persons", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Intakes.Intake", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Budget") + .HasColumnType("double precision"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IntakeName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("StartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Intakes", (string)null); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("EmailGroupUsers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("BCC") + .IsRequired() + .HasColumnType("text"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CC") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesHttpStatusCode") + .HasColumnType("text"); + + b.Property("ChesMsgId") + .HasColumnType("uuid"); + + b.Property("ChesResponse") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FromAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestIds") + .IsRequired() + .HasColumnType("text"); + + b.Property("Priority") + .IsRequired() + .HasColumnType("text"); + + b.Property("RetryAttempts") + .HasColumnType("integer"); + + b.Property("SendOnDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("SentDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ToAddress") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailLogs", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("EmailLogId") + .HasColumnType("uuid"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EmailLogId"); + + b.HasIndex("S3ObjectKey"); + + b.ToTable("EmailLogAttachments", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.EmailTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BodyHTML") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyText") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("SendFrom") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("EmailTemplates", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Subscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Subscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("SubscriptionGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriberId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("SubscriberId"); + + b.ToTable("SubscriptionGroupSubscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TemplateVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MapTo") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TemplateVariables", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Trigger", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Triggers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriptionGroupId") + .HasColumnType("uuid"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionGroupId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("TriggerId"); + + b.ToTable("TriggerSubscriptions", "Notifications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.AccountCodings.AccountCoding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(35) + .HasColumnType("character varying(35)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MinistryClient") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("Responsibility") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServiceLine") + .IsRequired() + .HasColumnType("text"); + + b.Property("Stob") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AccountCodings", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentConfigurations.PaymentConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultAccountCodingId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentIdPrefix") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("PaymentConfigurations", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DecisionUserId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.ToTable("ExpenseApprovals", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("BatchName") + .IsRequired() + .HasColumnType("text"); + + b.Property("BatchNumber") + .HasColumnType("numeric"); + + b.Property("CasHttpStatusCode") + .HasColumnType("integer"); + + b.Property("CasResponse") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FsbApNotified") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("FsbNotificationEmailLogId") + .HasColumnType("uuid"); + + b.Property("FsbNotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("InvoiceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("InvoiceStatus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsRecon") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("PayeeName") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentDate") + .HasColumnType("text"); + + b.Property("PaymentNumber") + .HasColumnType("text"); + + b.Property("PaymentStatus") + .HasColumnType("text"); + + b.Property("ReferenceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequesterName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SiteId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("SubmissionConfirmationCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("SupplierName") + .HasColumnType("text"); + + b.Property("SupplierNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AccountCodingId"); + + b.HasIndex("FsbNotificationEmailLogId"); + + b.HasIndex("ReferenceNumber") + .IsUnique(); + + b.HasIndex("SiteId"); + + b.ToTable("PaymentRequests", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.HasIndex("TagId"); + + b.ToTable("PaymentTags", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentThresholds.PaymentThreshold", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Threshold") + .HasColumnType("numeric"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("PaymentThresholds", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressLine1") + .HasColumnType("text"); + + b.Property("AddressLine2") + .HasColumnType("text"); + + b.Property("AddressLine3") + .HasColumnType("text"); + + b.Property("BankAccount") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EFTAdvicePref") + .HasColumnType("text"); + + b.Property("EmailAddress") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCas") + .HasColumnType("timestamp without time zone"); + + b.Property("MarkDeletedInUse") + .HasColumnType("boolean"); + + b.Property("Number") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentGroup") + .HasColumnType("integer"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SiteProtected") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("SupplierId"); + + b.ToTable("Sites", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BusinessNumber") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCAS") + .HasColumnType("timestamp without time zone"); + + b.Property("MailingAddress") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("text"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SIN") + .HasColumnType("text"); + + b.Property("StandardIndustryClassification") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("Subcategory") + .HasColumnType("text"); + + b.Property("SupplierProtected") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Suppliers", "Payments"); + }); + + modelBuilder.Entity("Unity.Reporting.Domain.Configuration.ReportColumnsMap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Mapping") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RoleStatus") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ViewStatus") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ReportColumnsMaps", "Reporting"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Instances") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Question", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", null) + .WithMany("Answers") + .HasForeignKey("ScoresheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.ScoresheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Sections") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => + { + b.HasOne("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", null) + .WithMany("Values") + .HasForeignKey("WorksheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Links") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.WorksheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Sections") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicationId"); + + b.Navigation("Applicant"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithOne("ApplicantAgent") + .HasForeignKey("Unity.GrantManager.Applications.ApplicantAgent", "ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", "ApplicationForm") + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationStatus", "ApplicationStatus") + .WithMany("Applications") + .HasForeignKey("ApplicationStatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Applicant"); + + b.Navigation("ApplicationForm"); + + b.Navigation("ApplicationStatus"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationAssignments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Assignee") + .WithMany() + .HasForeignKey("AssigneeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Assignee"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.HasOne("Unity.GrantManager.Intakes.Intake", null) + .WithMany() + .HasForeignKey("IntakeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ParentFormId") + .OnDelete(DeleteBehavior.NoAction); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany("ApplicationLinks") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationTags") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("Assessments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("AssessorId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.HasOne("Unity.GrantManager.Contacts.Contact", null) + .WithMany() + .HasForeignKey("ContactId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.HasOne("Unity.Notifications.EmailGroups.EmailGroup", null) + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.HasOne("Unity.Notifications.Emails.EmailLog", null) + .WithMany() + .HasForeignKey("EmailLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("GroupId"); + + b.HasOne("Unity.Notifications.Templates.Subscriber", "Subscriber") + .WithMany() + .HasForeignKey("SubscriberId"); + + b.Navigation("Subscriber"); + + b.Navigation("SubscriptionGroup"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("SubscriptionGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", "EmailTemplate") + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.Trigger", "Trigger") + .WithMany() + .HasForeignKey("TriggerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EmailTemplate"); + + b.Navigation("SubscriptionGroup"); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", "PaymentRequest") + .WithMany("ExpenseApprovals") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PaymentRequest"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.HasOne("Unity.Payments.Domain.AccountCodings.AccountCoding", "AccountCoding") + .WithMany() + .HasForeignKey("AccountCodingId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Unity.Payments.Domain.Suppliers.Site", "Site") + .WithMany() + .HasForeignKey("SiteId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("AccountCoding"); + + b.Navigation("Site"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", null) + .WithMany("PaymentTags") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.HasOne("Unity.Payments.Domain.Suppliers.Supplier", "Supplier") + .WithMany("Sites") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Navigation("Instances"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Navigation("Values"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Navigation("Links"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Navigation("ApplicantAddresses"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Navigation("ApplicantAddresses"); + + b.Navigation("ApplicantAgent"); + + b.Navigation("ApplicationAssignments"); + + b.Navigation("ApplicationLinks"); + + b.Navigation("ApplicationTags"); + + b.Navigation("Assessments"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Navigation("Applications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Navigation("ExpenseApprovals"); + + b.Navigation("PaymentTags"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Navigation("Sites"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260413203049_SafeDateTimeCasting.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260413203049_SafeDateTimeCasting.cs new file mode 100644 index 0000000000..1915e60563 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260413203049_SafeDateTimeCasting.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.IO; +using System.Reflection; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + /// + public partial class SafeDateTimeCasting : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + var assembly = Assembly.GetExecutingAssembly(); + + // Create the safe_to_timestamp helper function + var safeTimestampResource = "Unity.GrantManager.Scripts.safe_to_timestamp.sql"; + using Stream stream1 = assembly.GetManifestResourceStream(safeTimestampResource) + ?? throw new InvalidOperationException($"Could not find embedded resource: {safeTimestampResource}"); + using StreamReader reader1 = new StreamReader(stream1); + string sql1 = reader1.ReadToEnd(); + migrationBuilder.Sql(sql1); + + // Create the safe_to_date helper function + var safeDateResource = "Unity.GrantManager.Scripts.safe_to_date.sql"; + using Stream stream2 = assembly.GetManifestResourceStream(safeDateResource) + ?? throw new InvalidOperationException($"Could not find embedded resource: {safeDateResource}"); + using StreamReader reader2 = new StreamReader(stream2); + string sql2 = reader2.ReadToEnd(); + migrationBuilder.Sql(sql2); + + // Update the get_worksheet_data function to use safe casting helpers + var worksheetDataResource = "Unity.GrantManager.Scripts.get_worksheet_data.sql"; + using Stream stream3 = assembly.GetManifestResourceStream(worksheetDataResource) + ?? throw new InvalidOperationException($"Could not find embedded resource: {worksheetDataResource}"); + using StreamReader reader3 = new StreamReader(stream3); + string sql3 = reader3.ReadToEnd(); + migrationBuilder.Sql(sql3); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@"DROP FUNCTION IF EXISTS ""Reporting"".safe_to_timestamp(TEXT);"); + migrationBuilder.Sql(@"DROP FUNCTION IF EXISTS ""Reporting"".safe_to_date(TEXT);"); + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/get_worksheet_data.sql b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/get_worksheet_data.sql index 16e5e65112..98d5f5ec33 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/get_worksheet_data.sql +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/get_worksheet_data.sql @@ -100,11 +100,11 @@ BEGIN format('(CASE WHEN ((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L)) IS NULL THEN NULL WHEN ((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L)) ~ ''^-?[0-9]+\.?[0-9]*$'' THEN ((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L))::NUMERIC ELSE NULL END) AS %I', um.field_name, um.field_name, um.field_name, um.column_name) WHEN 'date' THEN - format('(CASE WHEN ((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L)) IS NULL OR trim((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L)) = '''' THEN NULL ELSE ((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L))::DATE END) AS %I', - um.field_name, um.field_name, um.field_name, um.column_name) + format('"Reporting".safe_to_date((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L)) AS %I', + um.field_name, um.column_name) WHEN 'datetime' THEN - format('(CASE WHEN ((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L)) IS NULL OR trim((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L)) = '''' THEN NULL ELSE ((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L))::TIMESTAMP END) AS %I', - um.field_name, um.field_name, um.field_name, um.column_name) + format('"Reporting".safe_to_timestamp((SELECT cell_elem->>''value'' FROM jsonb_array_elements(dg_tbl.dg_data->''cells'') AS cell_elem WHERE cell_elem->>''key'' = %L)) AS %I', + um.field_name, um.column_name) WHEN 'checkbox' THEN -- Check if this is a checkbox group field by looking at the type_path CASE @@ -189,15 +189,11 @@ BEGIN COALESCE(um.clean_data_path, um.property_name), um.column_name) WHEN 'date' THEN - format('(CASE WHEN ((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L)) IS NULL OR trim((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L)) = '''' THEN NULL ELSE ((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L))::DATE END) AS %I', - COALESCE(um.clean_data_path, um.property_name), - COALESCE(um.clean_data_path, um.property_name), + format('"Reporting".safe_to_date((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L)) AS %I', COALESCE(um.clean_data_path, um.property_name), um.column_name) WHEN 'datetime' THEN - format('(CASE WHEN ((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L)) IS NULL OR trim((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L)) = '''' THEN NULL ELSE ((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L))::TIMESTAMP END) AS %I', - COALESCE(um.clean_data_path, um.property_name), - COALESCE(um.clean_data_path, um.property_name), + format('"Reporting".safe_to_timestamp((SELECT v_elem->>''value'' FROM jsonb_array_elements(wi."CurrentValue"->''values'') AS v_elem WHERE v_elem->>''key'' = %L)) AS %I', COALESCE(um.clean_data_path, um.property_name), um.column_name) WHEN 'checkbox' THEN diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_date.sql b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_date.sql new file mode 100644 index 0000000000..c822daeede --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_date.sql @@ -0,0 +1,46 @@ +CREATE OR REPLACE FUNCTION "Reporting".safe_to_date(val text) + RETURNS date + LANGUAGE plpgsql + IMMUTABLE +AS $function$ +DECLARE + normalized text; +BEGIN + IF val IS NULL OR trim(val) = '' THEN + RETURN NULL; + END IF; + + normalized := trim(val); + + -- Try direct cast first (handles ISO 8601 YYYY-MM-DD, etc.) + BEGIN + RETURN normalized::date; + EXCEPTION WHEN OTHERS THEN + NULL; + END; + + -- Try MM/DD/YYYY + BEGIN + RETURN to_date(normalized, 'MM/DD/YYYY'); + EXCEPTION WHEN OTHERS THEN + NULL; + END; + + -- Try DD/MM/YYYY + BEGIN + RETURN to_date(normalized, 'DD/MM/YYYY'); + EXCEPTION WHEN OTHERS THEN + NULL; + END; + + -- Try YYYY/MM/DD + BEGIN + RETURN to_date(normalized, 'YYYY/MM/DD'); + EXCEPTION WHEN OTHERS THEN + NULL; + END; + + -- All attempts failed, return NULL gracefully + RETURN NULL; +END; +$function$; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_timestamp.sql b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_timestamp.sql new file mode 100644 index 0000000000..900c26d5d9 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_timestamp.sql @@ -0,0 +1,56 @@ +CREATE OR REPLACE FUNCTION "Reporting".safe_to_timestamp(val text) + RETURNS timestamp without time zone + LANGUAGE plpgsql + IMMUTABLE +AS $function$ +DECLARE + normalized text; +BEGIN + IF val IS NULL OR trim(val) = '' THEN + RETURN NULL; + END IF; + + -- Normalize: trim whitespace and handle dotted meridian indicators + normalized := trim(val); + normalized := regexp_replace(normalized, '\s*[aA]\.[mM]\.', ' AM', 'g'); + normalized := regexp_replace(normalized, '\s*[pP]\.[mM]\.', ' PM', 'g'); + + -- Try direct cast first (handles ISO 8601, standard YYYY-MM-DD HH24:MI:SS, etc.) + BEGIN + RETURN normalized::timestamp; + EXCEPTION WHEN OTHERS THEN + NULL; + END; + + -- Try MM/DD/YYYY with 12-hour clock + BEGIN + RETURN to_timestamp(normalized, 'MM/DD/YYYY HH12:MI:SS AM'); + EXCEPTION WHEN OTHERS THEN + NULL; + END; + + -- Try MM/DD/YYYY with 24-hour clock + BEGIN + RETURN to_timestamp(normalized, 'MM/DD/YYYY HH24:MI:SS'); + EXCEPTION WHEN OTHERS THEN + NULL; + END; + + -- Try DD/MM/YYYY with 12-hour clock + BEGIN + RETURN to_timestamp(normalized, 'DD/MM/YYYY HH12:MI:SS AM'); + EXCEPTION WHEN OTHERS THEN + NULL; + END; + + -- Try YYYY/MM/DD with 24-hour clock + BEGIN + RETURN to_timestamp(normalized, 'YYYY/MM/DD HH24:MI:SS'); + EXCEPTION WHEN OTHERS THEN + NULL; + END; + + -- All attempts failed, return NULL gracefully + RETURN NULL; +END; +$function$; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Unity.GrantManager.EntityFrameworkCore.csproj b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Unity.GrantManager.EntityFrameworkCore.csproj index af8cbd315a..02e6a2f93a 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Unity.GrantManager.EntityFrameworkCore.csproj +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Unity.GrantManager.EntityFrameworkCore.csproj @@ -16,6 +16,8 @@ + + @@ -35,7 +37,13 @@ Never - Never + Never + + + Never + + + Never From 9ed187e256da0d82e01bb9115885260440b3bacd Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Mon, 13 Apr 2026 15:16:09 -0700 Subject: [PATCH 07/58] AB#32597 - Removed setting constant and definition. --- .../Settings/IAIConfigurationAppService.cs | 4 +- .../Settings/AIConfigurationAppService.cs | 40 +----------------- .../Settings/AISettingDefinitionProvider.cs | 18 +------- .../Localization/AI/en.json | 1 - .../Settings/AISettings.cs | 3 +- .../AISettingGroup/AISettingViewComponent.cs | 15 ++----- .../Assessments/AssessmentAppService.cs | 28 ++++++------- .../RunApplicationAIPipelineJob.cs | 7 ---- ...teAIAssessmentOnScoringGeneratedHandler.cs | 7 ---- .../Pages/GrantApplications/Details.cshtml | 5 +-- .../AssessmentScoresWidgetViewComponent.cs | 8 +--- .../Components/ReviewList/ReviewList.cs | 8 +--- ...ssessmentOnScoringGeneratedHandlerTests.cs | 42 ++++--------------- .../RunApplicationAIPipelineJobTests.cs | 40 +----------------- .../Components/AssessmentScoresWidgetTests.cs | 8 +--- 15 files changed, 37 insertions(+), 197 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/IAIConfigurationAppService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/IAIConfigurationAppService.cs index 8a21259aa6..5d81913dc7 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/IAIConfigurationAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/IAIConfigurationAppService.cs @@ -1,10 +1,8 @@ -using System.Threading.Tasks; using Volo.Abp.Application.Services; namespace Unity.AI.Settings; public interface IAIConfigurationAppService : IApplicationService { - Task GetScoringSettingsAsync(); - Task UpdateScoringSettingsAsync(UpdateAIScoringSettingsDto input); + // Tenant AI configuration methods added in AB#32291 } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AIConfigurationAppService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AIConfigurationAppService.cs index af5dfd02e7..7f90ee64e6 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AIConfigurationAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AIConfigurationAppService.cs @@ -1,44 +1,6 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Unity.AI.Permissions; -using Volo.Abp.MultiTenancy; -using Volo.Abp.Settings; -using Volo.Abp.SettingManagement; - namespace Unity.AI.Settings; public class AIConfigurationAppService : AIAppService, IAIConfigurationAppService { - private readonly ISettingProvider _settingProvider; - private readonly ISettingManager _settingManager; - private readonly ICurrentTenant _currentTenant; - - public AIConfigurationAppService( - ISettingProvider settingProvider, - ISettingManager settingManager, - ICurrentTenant currentTenant) - { - _settingProvider = settingProvider; - _settingManager = settingManager; - _currentTenant = currentTenant; - } - - public virtual async Task GetScoringSettingsAsync() - { - return new AIScoringSettingsDto - { - ScoringAssistantEnabled = await _settingProvider.GetAsync( - AISettings.ScoringAssistantEnabled, defaultValue: false) - }; - } - - [Authorize(AIPermissions.Configuration.ConfigureAI)] - public virtual async Task UpdateScoringSettingsAsync(UpdateAIScoringSettingsDto input) - { - await _settingManager.SetAsync( - AISettings.ScoringAssistantEnabled, - input.ScoringAssistantEnabled.ToString().ToLowerInvariant(), - TenantSettingValueProvider.ProviderName, - _currentTenant.Id?.ToString()); - } + // Tenant AI configuration methods added in AB#32291 } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AISettingDefinitionProvider.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AISettingDefinitionProvider.cs index 5032d042af..03fdd94078 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AISettingDefinitionProvider.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AISettingDefinitionProvider.cs @@ -1,5 +1,3 @@ -using Unity.AI.Localization; -using Volo.Abp.Localization; using Volo.Abp.Settings; namespace Unity.AI.Settings; @@ -8,20 +6,6 @@ public class AISettingDefinitionProvider : SettingDefinitionProvider { public override void Define(ISettingDefinitionContext context) { - context.Add( - new SettingDefinition( - AISettings.ScoringAssistantEnabled, - "false", - L("Setting:AI.ScoringAssistantEnabled"), - isVisibleToClients: false, - isInherited: false, - isEncrypted: false) - .WithProviders(TenantSettingValueProvider.ProviderName) - ); - } - - private static LocalizableString L(string name) - { - return LocalizableString.Create(name); + // Tenant AI configuration settings are defined in AB#32291 } } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json index d6b84d6ca1..adbb81b821 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json @@ -6,7 +6,6 @@ "Permission:AI.ApplicationAnalysis": "AI Application Analysis", "Permission:AI.AttachmentSummary": "AI Attachment Summary", "Permission:AI.ScoringAssistant": "AI Scoring Assistant", - "Setting:AI.ScoringAssistantEnabled": "AI Scoring Assistant", "Permission:AI.ConfigureAI": "AI Configuration", "Permission:AI.Prompts": "AI Prompt Management", "Permission:AI.Prompts.Create": "Create Prompts", diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Settings/AISettings.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Settings/AISettings.cs index d335050785..f751a19ba6 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Settings/AISettings.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Settings/AISettings.cs @@ -1,6 +1,7 @@ namespace Unity.AI.Settings; +#pragma warning disable S2094 public static class AISettings +#pragma warning restore S2094 { - public const string ScoringAssistantEnabled = "GrantManager.AI.ScoringAssistantEnabled"; } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs index 3f880adaa6..d1d3c5e910 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs @@ -1,10 +1,8 @@ using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; -using Unity.AI.Settings; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; -using Volo.Abp.Settings; namespace Unity.AI.Web.Views.Settings.AISettingGroup; @@ -12,17 +10,12 @@ namespace Unity.AI.Web.Views.Settings.AISettingGroup; ScriptTypes = [typeof(AISettingScriptBundleContributor)], AutoInitialize = true )] -public class AISettingViewComponent(ISettingProvider settingProvider) : AbpViewComponent +public class AISettingViewComponent : AbpViewComponent { - public virtual async Task InvokeAsync() + public virtual Task InvokeAsync() { - var model = new AISettingViewModel - { - ScoringAssistantEnabled = await settingProvider.GetAsync( - AISettings.ScoringAssistantEnabled, defaultValue: false) - }; - - return View("~/Views/Settings/AISettingGroup/Default.cshtml", model); + return Task.FromResult( + View("~/Views/Settings/AISettingGroup/Default.cshtml", new AISettingViewModel())); } public class AISettingScriptBundleContributor : BundleContributor diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Assessments/AssessmentAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Assessments/AssessmentAppService.cs index a94fa33b62..1fda6f4f62 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Assessments/AssessmentAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Assessments/AssessmentAppService.cs @@ -6,7 +6,6 @@ using System.Text.Json; using System.Threading.Tasks; using Unity.AI.Permissions; -using Unity.AI.Settings; using Unity.Flex; using Unity.Flex.Scoresheets; using Unity.Flex.Scoresheets.Enums; @@ -94,12 +93,11 @@ public async Task GetDisplayList(Guid applicationId) var assessments = await _assessmentRepository.GetListWithAssessorsAsync(applicationId); var assessmentList = ObjectMapper.Map, List>(assessments); - // If AI Scoring feature is disabled, tenant setting is off, or user lacks permission, filter out AI assessments + // If AI Scoring feature is disabled or user lacks permission, filter out AI assessments var aiScoringEnabled = await _featureChecker.IsEnabledAsync("Unity.AI.Scoring"); - var aiScoringSettingEnabled = aiScoringEnabled && await SettingProvider.GetAsync(AISettings.ScoringAssistantEnabled, defaultValue: false); var canViewAI = await AuthorizationService.IsGrantedAsync(AIPermissions.ScoringAssistant.ScoringAssistantDefault); assessmentList = assessmentList - .Where(a => !a.IsAiAssessment || (aiScoringSettingEnabled && canViewAI)) + .Where(a => !a.IsAiAssessment || (aiScoringEnabled && canViewAI)) .OrderByDescending(a => a.IsAiAssessment) .ThenByDescending(a => a.StartDate) .ToList(); @@ -398,17 +396,17 @@ public async Task UpdateAssessmentScore(AssessmentScoresDto dto) /// /// Thrown when the specified assessment is not an AI assessment. /// - [Authorize(AIPermissions.ScoringAssistant.ScoringAssistantDefault)] - public async Task CloneFromAiAsync(Guid aiAssessmentId) - { - if (!await _featureChecker.IsEnabledAsync("Unity.AI.Scoring")) - { - throw new UserFriendlyException("AI scoring is not enabled."); - } - - var aiAssessment = await _assessmentRepository.GetAsync(aiAssessmentId); - if (!aiAssessment.IsAiAssessment) - { + [Authorize(AIPermissions.ScoringAssistant.ScoringAssistantDefault)] + public async Task CloneFromAiAsync(Guid aiAssessmentId) + { + if (!await _featureChecker.IsEnabledAsync("Unity.AI.Scoring")) + { + throw new UserFriendlyException("AI scoring is not enabled."); + } + + var aiAssessment = await _assessmentRepository.GetAsync(aiAssessmentId); + if (!aiAssessment.IsAiAssessment) + { throw new BusinessException(GrantManagerDomainErrorCodes.CannotCloneNonAiAssessment); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/RunApplicationAIPipelineJob.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/RunApplicationAIPipelineJob.cs index 94459c6da8..29ddaca93d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/RunApplicationAIPipelineJob.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/RunApplicationAIPipelineJob.cs @@ -3,14 +3,12 @@ using System.Threading.Tasks; using Unity.AI; using Unity.AI.Operations; -using Unity.AI.Settings; using Unity.GrantManager.GrantApplications.Automation.Events; using Volo.Abp.BackgroundJobs; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Local; using Volo.Abp.Features; using Volo.Abp.MultiTenancy; -using Volo.Abp.Settings; namespace Unity.GrantManager.GrantApplications.Automation.BackgroundJobs; public class RunApplicationAIPipelineJob( IAttachmentSummaryService attachmentSummaryService, @@ -18,7 +16,6 @@ public class RunApplicationAIPipelineJob( IApplicationScoringService applicationScoringService, IAIService aiService, IFeatureChecker featureChecker, - ISettingProvider settingProvider, ILocalEventBus localEventBus, ICurrentTenant currentTenant, ILogger logger) : AsyncBackgroundJob, ITransientDependency @@ -30,10 +27,6 @@ public override async Task ExecuteAsync(RunApplicationAIPipelineJobArgs args) var attachmentSummariesEnabled = await featureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries"); var applicationAnalysisEnabled = await featureChecker.IsEnabledAsync("Unity.AI.ApplicationAnalysis"); var scoringEnabled = await featureChecker.IsEnabledAsync("Unity.AI.Scoring"); - if (scoringEnabled) - { - scoringEnabled = await settingProvider.GetAsync(AISettings.ScoringAssistantEnabled, defaultValue: false); - } if (!attachmentSummariesEnabled && !applicationAnalysisEnabled && !scoringEnabled) { logger.LogDebug("All AI features are disabled, skipping queued AI generation for application {ApplicationId}.", args.ApplicationId); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Handlers/CreateAIAssessmentOnScoringGeneratedHandler.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Handlers/CreateAIAssessmentOnScoringGeneratedHandler.cs index 599de3d1a9..7e432a5b2d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Handlers/CreateAIAssessmentOnScoringGeneratedHandler.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Handlers/CreateAIAssessmentOnScoringGeneratedHandler.cs @@ -1,21 +1,18 @@ using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; -using Unity.AI.Settings; using Unity.GrantManager.Applications; using Unity.GrantManager.Assessments; using Unity.GrantManager.GrantApplications.Automation.Events; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus; using Volo.Abp.Features; -using Volo.Abp.Settings; using Volo.Abp.Uow; namespace Unity.GrantManager.GrantApplications.Automation.Handlers; public class CreateAIAssessmentOnScoringGeneratedHandler( AssessmentManager assessmentManager, IApplicationRepository applicationRepository, IFeatureChecker featureChecker, - ISettingProvider settingProvider, IUnitOfWorkManager unitOfWorkManager, ILogger logger) : ILocalEventHandler, ITransientDependency { @@ -30,10 +27,6 @@ public async Task HandleEventAsync(ApplicationAIScoringGeneratedEvent eventData) { return; } - if (!await settingProvider.GetAsync(AISettings.ScoringAssistantEnabled, defaultValue: false)) - { - return; - } try { using var uow = unitOfWorkManager.Begin(requiresNew: true); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml index 9782c46b93..514249ed7f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml @@ -14,7 +14,6 @@ @using Volo.Abp.Authorization.Permissions @using Volo.Abp.Features @using Volo.Abp.MultiTenancy -@using Volo.Abp.Settings @* #pragma warning restore S1128 *@ @@ -24,7 +23,6 @@ @inject IFeatureChecker FeatureChecker @inject IPermissionChecker PermissionChecker @inject ICurrentTenant CurrentTenant -@inject ISettingProvider SettingProvider @{ PageLayout.Content.Title = L["Grants"].Value; @@ -35,8 +33,7 @@ var aiApplicationAnalysisEnabled = await FeatureChecker.IsEnabledAsync("Unity.AI.ApplicationAnalysis") && await PermissionChecker.IsGrantedAsync(AIPermissions.ApplicationAnalysis.ApplicationAnalysisDefault); var aiScoringEnabled = await FeatureChecker.IsEnabledAsync("Unity.AI.Scoring") - && await PermissionChecker.IsGrantedAsync(AIPermissions.ScoringAssistant.ScoringAssistantDefault) - && await SettingProvider.GetAsync(Unity.AI.Settings.AISettings.ScoringAssistantEnabled, defaultValue: false); + && await PermissionChecker.IsGrantedAsync(AIPermissions.ScoringAssistant.ScoringAssistantDefault); var flexFeatureEnabled = await FeatureChecker.IsEnabledAsync("Unity.Flex"); } @section styles diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/AssessmentScoresWidgetViewComponent.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/AssessmentScoresWidgetViewComponent.cs index 7d79dce8e7..107aa253bc 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/AssessmentScoresWidgetViewComponent.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/AssessmentScoresWidgetViewComponent.cs @@ -18,10 +18,8 @@ using Unity.GrantManager.Applications; using System.Text.Json; using Unity.AI.Permissions; -using Unity.AI.Settings; using Volo.Abp.Authorization.Permissions; using Volo.Abp.Features; -using Volo.Abp.Settings; namespace Unity.GrantManager.Web.Views.Shared.Components.AssessmentScoresWidget { @@ -35,8 +33,7 @@ public class AssessmentScoresWidgetViewComponent(IAssessmentRepository assessmen IScoresheetInstanceRepository scoresheetInstanceRepository, IApplicationRepository applicationRepository, IFeatureChecker featureChecker, - IPermissionChecker permissionChecker, - ISettingProvider settingProvider) : AbpViewComponent + IPermissionChecker permissionChecker) : AbpViewComponent { public async Task InvokeAsync(Guid assessmentId, Guid currentUserId) { @@ -103,8 +100,7 @@ public async Task InvokeAsync(Guid assessmentId, Guid curr CurrentUserId = currentUserId, AssessorId = assessment.AssessorId, IsAIScoringEnabled = await featureChecker.IsEnabledAsync("Unity.AI.Scoring") && - await permissionChecker.IsGrantedAsync(AIPermissions.ScoringAssistant.ScoringAssistantDefault) && - await settingProvider.GetAsync(AISettings.ScoringAssistantEnabled, defaultValue: false), + await permissionChecker.IsGrantedAsync(AIPermissions.ScoringAssistant.ScoringAssistantDefault), IsAiAssessment = assessment.IsAiAssessment, }; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ReviewList/ReviewList.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ReviewList/ReviewList.cs index cab075b090..429617e305 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ReviewList/ReviewList.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ReviewList/ReviewList.cs @@ -1,12 +1,10 @@ using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using Unity.AI.Permissions; -using Unity.AI.Settings; using Volo.Abp.Authorization.Permissions; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; using Volo.Abp.Features; -using Volo.Abp.Settings; namespace Unity.GrantManager.Web.Views.Shared.Components.ReviewList { @@ -21,16 +19,14 @@ namespace Unity.GrantManager.Web.Views.Shared.Components.ReviewList })] public class ReviewList( IFeatureChecker featureChecker, - IPermissionChecker permissionChecker, - ISettingProvider settingProvider) : AbpViewComponent + IPermissionChecker permissionChecker) : AbpViewComponent { public async Task InvokeAsync() { var scoringFeatureEnabled = await featureChecker.IsEnabledAsync("Unity.AI.Scoring"); ViewBag.IsAIScoringEnabled = scoringFeatureEnabled && - await permissionChecker.IsGrantedAsync(AIPermissions.ScoringAssistant.ScoringAssistantDefault) && - await settingProvider.GetAsync(AISettings.ScoringAssistantEnabled, defaultValue: false); + await permissionChecker.IsGrantedAsync(AIPermissions.ScoringAssistant.ScoringAssistantDefault); return View(); } diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/GrantApplications/Automation/CreateAIAssessmentOnScoringGeneratedHandlerTests.cs b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/GrantApplications/Automation/CreateAIAssessmentOnScoringGeneratedHandlerTests.cs index 68c34bb6d0..a449c5393b 100644 --- a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/GrantApplications/Automation/CreateAIAssessmentOnScoringGeneratedHandlerTests.cs +++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/GrantApplications/Automation/CreateAIAssessmentOnScoringGeneratedHandlerTests.cs @@ -4,14 +4,12 @@ using System; using System.Linq; using System.Threading.Tasks; -using Unity.AI.Settings; using Unity.GrantManager.Applications; using Unity.GrantManager.Assessments; using Unity.GrantManager.GrantApplications.Automation.Events; using Unity.GrantManager.GrantApplications.Automation.Handlers; using Volo.Abp.Domain.Repositories; using Volo.Abp.Features; -using Volo.Abp.Settings; using Volo.Abp.Uow; using Xunit; using Xunit.Abstractions; @@ -29,13 +27,12 @@ public CreateAIAssessmentOnScoringGeneratedHandlerTests(ITestOutputHelper output _applicationRepository = GetRequiredService(); _unitOfWorkManager = GetRequiredService(); } - private CreateAIAssessmentOnScoringGeneratedHandler BuildHandler(IFeatureChecker featureChecker, ISettingProvider settingProvider) + private CreateAIAssessmentOnScoringGeneratedHandler BuildHandler(IFeatureChecker featureChecker) { return new CreateAIAssessmentOnScoringGeneratedHandler( _assessmentManager, _applicationRepository, featureChecker, - settingProvider, _unitOfWorkManager, NullLogger.Instance); } @@ -45,9 +42,7 @@ public async Task HandleEventAsync_Should_Skip_When_Application_Id_Is_Empty() { var featureChecker = Substitute.For(); featureChecker.IsEnabledAsync("Unity.AI.Scoring").Returns(true); - var settingProvider = Substitute.For(); - settingProvider.GetOrNullAsync(AISettings.ScoringAssistantEnabled).Returns("true"); - var handler = BuildHandler(featureChecker, settingProvider); + var handler = BuildHandler(featureChecker); using var uow = _unitOfWorkManager.Begin(); var beforeCount = (await _assessmentRepository.GetQueryableAsync()).Count(a => a.IsAiAssessment); await handler.HandleEventAsync(new ApplicationAIScoringGeneratedEvent { ApplicationId = Guid.Empty }); @@ -60,8 +55,7 @@ public async Task HandleEventAsync_Should_Skip_When_Feature_Disabled() { var featureChecker = Substitute.For(); featureChecker.IsEnabledAsync("Unity.AI.Scoring").Returns(false); - var settingProvider = Substitute.For(); - var handler = BuildHandler(featureChecker, settingProvider); + var handler = BuildHandler(featureChecker); using var uow = _unitOfWorkManager.Begin(); var application = (await _applicationRepository.GetListAsync())[0]; var beforeCount = (await _assessmentRepository.GetQueryableAsync()).Count(a => a.IsAiAssessment); @@ -71,31 +65,11 @@ public async Task HandleEventAsync_Should_Skip_When_Feature_Disabled() } [Fact] [Trait("Category", "Integration")] - public async Task HandleEventAsync_Should_Skip_When_Feature_Enabled_But_Setting_Disabled() + public async Task HandleEventAsync_Should_Create_AI_Assessment_When_Feature_Enabled() { - // Arrange - feature ON but tenant setting OFF var featureChecker = Substitute.For(); featureChecker.IsEnabledAsync("Unity.AI.Scoring").Returns(true); - var settingProvider = Substitute.For(); - settingProvider.GetOrNullAsync(AISettings.ScoringAssistantEnabled).Returns("false"); - var handler = BuildHandler(featureChecker, settingProvider); - using var uow = _unitOfWorkManager.Begin(); - var application = (await _applicationRepository.GetListAsync())[0]; - var beforeCount = (await _assessmentRepository.GetQueryableAsync()).Count(a => a.IsAiAssessment); - await handler.HandleEventAsync(new ApplicationAIScoringGeneratedEvent { ApplicationId = application.Id }); - var afterCount = (await _assessmentRepository.GetQueryableAsync()).Count(a => a.IsAiAssessment); - afterCount.ShouldBe(beforeCount); - } - [Fact] - [Trait("Category", "Integration")] - public async Task HandleEventAsync_Should_Create_AI_Assessment_When_Feature_Enabled_And_Setting_Enabled() - { - // Arrange - feature ON and tenant setting ON - var featureChecker = Substitute.For(); - featureChecker.IsEnabledAsync("Unity.AI.Scoring").Returns(true); - var settingProvider = Substitute.For(); - settingProvider.GetOrNullAsync(AISettings.ScoringAssistantEnabled).Returns("true"); - var handler = BuildHandler(featureChecker, settingProvider); + var handler = BuildHandler(featureChecker); using var uow = _unitOfWorkManager.Begin(); var application = await _applicationRepository.GetAsync(GrantManagerTestData.Application2_Id); await handler.HandleEventAsync(new ApplicationAIScoringGeneratedEvent { ApplicationId = application.Id }); @@ -109,9 +83,7 @@ public async Task HandleEventAsync_Should_Be_Idempotent() { var featureChecker = Substitute.For(); featureChecker.IsEnabledAsync("Unity.AI.Scoring").Returns(true); - var settingProvider = Substitute.For(); - settingProvider.GetOrNullAsync(AISettings.ScoringAssistantEnabled).Returns("true"); - var handler = BuildHandler(featureChecker, settingProvider); + var handler = BuildHandler(featureChecker); using var uow = _unitOfWorkManager.Begin(); var application = await _applicationRepository.GetAsync(GrantManagerTestData.Application1_Id); var beforeCount = (await _assessmentRepository.GetQueryableAsync()) @@ -122,4 +94,4 @@ public async Task HandleEventAsync_Should_Be_Idempotent() .Count(a => a.ApplicationId == GrantManagerTestData.Application1_Id && a.IsAiAssessment); afterCount.ShouldBe(beforeCount); } -} \ No newline at end of file +} diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/GrantApplications/Automation/RunApplicationAIPipelineJobTests.cs b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/GrantApplications/Automation/RunApplicationAIPipelineJobTests.cs index 3b2cedff42..b40e9c0e54 100644 --- a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/GrantApplications/Automation/RunApplicationAIPipelineJobTests.cs +++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/GrantApplications/Automation/RunApplicationAIPipelineJobTests.cs @@ -4,12 +4,10 @@ using System.Threading.Tasks; using Unity.AI; using Unity.AI.Operations; -using Unity.AI.Settings; using Unity.GrantManager.GrantApplications.Automation.BackgroundJobs; using Volo.Abp.EventBus.Local; using Volo.Abp.Features; using Volo.Abp.MultiTenancy; -using Volo.Abp.Settings; using Xunit; using Xunit.Abstractions; namespace Unity.GrantManager.GrantApplications.Automation; @@ -17,7 +15,6 @@ public class RunApplicationAIPipelineJobTests(ITestOutputHelper outputHelper) : { private static RunApplicationAIPipelineJob BuildJob( IFeatureChecker featureChecker, - ISettingProvider settingProvider, IApplicationScoringService? scoringService = null, IAIService? aiService = null) { @@ -29,7 +26,6 @@ private static RunApplicationAIPipelineJob BuildJob( scoringService ?? Substitute.For(), ai, featureChecker, - settingProvider, Substitute.For(), Substitute.For(), NullLogger.Instance); @@ -42,45 +38,11 @@ public async Task ExecuteAsync_Should_Skip_Scoring_When_Feature_Disabled() featureChecker.IsEnabledAsync("Unity.AI.Scoring").Returns(false); featureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries").Returns(false); featureChecker.IsEnabledAsync("Unity.AI.ApplicationAnalysis").Returns(false); - var settingProvider = Substitute.For(); var scoringService = Substitute.For(); - var job = BuildJob(featureChecker, settingProvider, scoringService); - // Act - await job.ExecuteAsync(new RunApplicationAIPipelineJobArgs { ApplicationId = Guid.NewGuid() }); - // Assert - setting never checked, scoring service never called - await settingProvider.DidNotReceive().GetOrNullAsync(AISettings.ScoringAssistantEnabled); - await scoringService.DidNotReceive().RegenerateAndSaveAsync(Arg.Any(), Arg.Any()); - } - [Fact] - public async Task ExecuteAsync_Should_Skip_Scoring_When_Feature_Enabled_But_Setting_Disabled() - { - // Arrange - scoring feature ON, tenant setting OFF - var featureChecker = Substitute.For(); - featureChecker.IsEnabledAsync("Unity.AI.Scoring").Returns(true); - featureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries").Returns(false); - featureChecker.IsEnabledAsync("Unity.AI.ApplicationAnalysis").Returns(false); - var settingProvider = Substitute.For(); - settingProvider.GetOrNullAsync(AISettings.ScoringAssistantEnabled).Returns("false"); - var scoringService = Substitute.For(); - var job = BuildJob(featureChecker, settingProvider, scoringService); + var job = BuildJob(featureChecker, scoringService); // Act await job.ExecuteAsync(new RunApplicationAIPipelineJobArgs { ApplicationId = Guid.NewGuid() }); // Assert - scoring service never called await scoringService.DidNotReceive().RegenerateAndSaveAsync(Arg.Any(), Arg.Any()); } - [Fact] - public async Task ExecuteAsync_Should_Not_Check_Setting_When_Feature_Disabled() - { - // Arrange - scoring feature OFF - var featureChecker = Substitute.For(); - featureChecker.IsEnabledAsync("Unity.AI.Scoring").Returns(false); - featureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries").Returns(false); - featureChecker.IsEnabledAsync("Unity.AI.ApplicationAnalysis").Returns(false); - var settingProvider = Substitute.For(); - var job = BuildJob(featureChecker, settingProvider); - // Act - await job.ExecuteAsync(new RunApplicationAIPipelineJobArgs { ApplicationId = Guid.NewGuid() }); - // Assert - setting provider never consulted when all features are OFF - await settingProvider.DidNotReceive().GetOrNullAsync(Arg.Any()); - } } diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Web.Tests/Components/AssessmentScoresWidgetTests.cs b/applications/Unity.GrantManager/test/Unity.GrantManager.Web.Tests/Components/AssessmentScoresWidgetTests.cs index 00cb0f5659..983fc6dcd5 100644 --- a/applications/Unity.GrantManager/test/Unity.GrantManager.Web.Tests/Components/AssessmentScoresWidgetTests.cs +++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Web.Tests/Components/AssessmentScoresWidgetTests.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewComponents; using NSubstitute; @@ -12,7 +12,6 @@ using Unity.GrantManager.Web.Views.Shared.Components.AssessmentScoresWidget; using Volo.Abp.Authorization.Permissions; using Volo.Abp.Features; -using Volo.Abp.Settings; using Xunit; namespace Unity.GrantManager.Components @@ -55,8 +54,6 @@ public async Task AssessmentScoresWidgetReturnsStatus() instanceRepository.GetByCorrelationAsync(assessmentId).Returns(Task.FromResult(null)); featureChecker.IsEnabledAsync("Unity.AI.Scoring").Returns(Task.FromResult(true)); permissionChecker.IsGrantedAsync(Arg.Any()).Returns(Task.FromResult(true)); - var settingProvider = Substitute.For(); - settingProvider.GetOrNullAsync(Unity.AI.Settings.AISettings.ScoringAssistantEnabled).Returns(Task.FromResult("true")); var viewContext = new ViewContext { @@ -73,8 +70,7 @@ public async Task AssessmentScoresWidgetReturnsStatus() instanceRepository, applicationRepository, featureChecker, - permissionChecker, - settingProvider) + permissionChecker) { ViewComponentContext = viewComponentContext }; From b2921c0f36e51f09c3a746ba7d361c7fd0853b8b Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Mon, 13 Apr 2026 15:28:52 -0700 Subject: [PATCH 08/58] AB#32597 - Removed DTOs and update dettings UI --- .../Settings/AIScoringSettingsDto.cs | 6 --- .../Settings/UpdateAIScoringSettingsDto.cs | 6 --- .../AISettingGroup/AISettingViewModel.cs | 4 +- .../Settings/AISettingGroup/Default.cshtml | 38 +----------------- .../Views/Settings/AISettingGroup/Default.js | 40 +------------------ 5 files changed, 5 insertions(+), 89 deletions(-) delete mode 100644 applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/AIScoringSettingsDto.cs delete mode 100644 applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/UpdateAIScoringSettingsDto.cs diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/AIScoringSettingsDto.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/AIScoringSettingsDto.cs deleted file mode 100644 index 14837f27b4..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/AIScoringSettingsDto.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Unity.AI.Settings; - -public class AIScoringSettingsDto -{ - public bool ScoringAssistantEnabled { get; set; } -} diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/UpdateAIScoringSettingsDto.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/UpdateAIScoringSettingsDto.cs deleted file mode 100644 index a6323e4a34..0000000000 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/UpdateAIScoringSettingsDto.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Unity.AI.Settings; - -public class UpdateAIScoringSettingsDto -{ - public bool ScoringAssistantEnabled { get; set; } -} diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewModel.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewModel.cs index 3ae4713935..5a99654920 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewModel.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewModel.cs @@ -1,6 +1,8 @@ namespace Unity.AI.Web.Views.Settings.AISettingGroup; +#pragma warning disable S2094 public class AISettingViewModel +#pragma warning restore S2094 { - public bool ScoringAssistantEnabled { get; set; } + // AI configuration properties added in AB#32291 } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.cshtml index 809bd07b6d..7e981abf61 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.cshtml @@ -1,48 +1,12 @@ @model Unity.AI.Web.Views.Settings.AISettingGroup.AISettingViewModel - -

AI Configuration

-
-
- - -
- - -
-
-
- - -
-
- - - -
-
-
+

AI configuration settings will appear here.

diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.js b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.js index 8ed936e630..2cdcb8739e 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.js @@ -1,41 +1,3 @@ $(function () { - const uiElements = { - settingForm: $('#AISettingsForm'), - saveButton: $('#AISettingsSaveButton'), - discardButton: $('#AISettingsDiscardButton') - }; - - let initialFormState = uiElements.settingForm.serialize(); - - function checkFormChanges() { - let isFormChanged = uiElements.settingForm.serialize() !== initialFormState; - uiElements.saveButton.prop('disabled', !isFormChanged); - uiElements.discardButton.prop('disabled', !isFormChanged); - } - - uiElements.settingForm.on('change', function () { - checkFormChanges(); - }); - - uiElements.settingForm.on('submit', function (event) { - event.preventDefault(); - - const scoringEnabled = $('#ScoringAssistantEnabled').is(':checked'); - - unity.aI.settings.aIConfiguration.updateScoringSettings({ - scoringAssistantEnabled: scoringEnabled - }).then(function () { - $(document).trigger('AbpSettingSaved'); - initialFormState = uiElements.settingForm.serialize(); - checkFormChanges(); - }); - }); - - uiElements.discardButton.on('click', function () { - uiElements.settingForm[0].reset(); - initialFormState = uiElements.settingForm.serialize(); - checkFormChanges(); - }); - - checkFormChanges(); + // AI configuration UI logic added in AB#32291 }); From 0b7069c20af3715cae95963d7abdf8e0a81e8627 Mon Sep 17 00:00:00 2001 From: "Todosichuk, Daryl" Date: Mon, 13 Apr 2026 15:49:43 -0700 Subject: [PATCH 09/58] AB#32613 enable SonarCloud PR code scanning in GitHub actions --- .github/workflows/sonarsource-scan.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sonarsource-scan.yml b/.github/workflows/sonarsource-scan.yml index 856ba7d348..9e5ba89c6d 100644 --- a/.github/workflows/sonarsource-scan.yml +++ b/.github/workflows/sonarsource-scan.yml @@ -6,9 +6,9 @@ on: - dev2 - dev - test -# - main -# pull_request: -# types: [opened, synchronize, reopened] + - main + pull_request: + types: [opened, synchronize, reopened] workflow_dispatch: permissions: From 433ffcda65b02d96b8ffa79d5d13b4c1f3fc738d Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Mon, 13 Apr 2026 15:53:06 -0700 Subject: [PATCH 10/58] AB#32398 upadte the safetodate functions --- .../Scripts/safe_to_date.sql | 69 +++++++--- .../Scripts/safe_to_timestamp.sql | 120 ++++++++++++++---- 2 files changed, 143 insertions(+), 46 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_date.sql b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_date.sql index c822daeede..e143639416 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_date.sql +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_date.sql @@ -1,10 +1,15 @@ CREATE OR REPLACE FUNCTION "Reporting".safe_to_date(val text) RETURNS date LANGUAGE plpgsql - IMMUTABLE + STABLE AS $function$ DECLARE normalized text; + result date; + parts text[]; + p1 int; + p2 int; + p3 int; BEGIN IF val IS NULL OR trim(val) = '' THEN RETURN NULL; @@ -12,33 +17,57 @@ BEGIN normalized := trim(val); - -- Try direct cast first (handles ISO 8601 YYYY-MM-DD, etc.) + -- Direct cast handles ISO 8601 (YYYY-MM-DD) and is strict about invalid dates BEGIN RETURN normalized::date; EXCEPTION WHEN OTHERS THEN NULL; END; - -- Try MM/DD/YYYY - BEGIN - RETURN to_date(normalized, 'MM/DD/YYYY'); - EXCEPTION WHEN OTHERS THEN - NULL; - END; + -- Branch by detected layout; validate parsed results to reject + -- silently-wrapped invalid dates (e.g., month 13 rolling into next year) + IF normalized ~ '^\d{4}/' THEN + -- Year-first: YYYY/MM/DD + BEGIN + parts := string_to_array(normalized, '/'); + p1 := parts[1]::int; p2 := parts[2]::int; p3 := parts[3]::int; + result := to_date(normalized, 'YYYY/MM/DD'); + EXCEPTION WHEN OTHERS THEN + RETURN NULL; + END; + IF EXTRACT(year FROM result) = p1 + AND EXTRACT(month FROM result) = p2 + AND EXTRACT(day FROM result) = p3 THEN + RETURN result; + END IF; - -- Try DD/MM/YYYY - BEGIN - RETURN to_date(normalized, 'DD/MM/YYYY'); - EXCEPTION WHEN OTHERS THEN - NULL; - END; + ELSIF normalized ~ '^\d{1,2}/\d{1,2}/\d{4}' THEN + -- Extract date parts once (regex guarantees slash-separated integers) + BEGIN + parts := string_to_array(normalized, '/'); + p1 := parts[1]::int; p2 := parts[2]::int; p3 := parts[3]::int; + EXCEPTION WHEN OTHERS THEN + RETURN NULL; + END; - -- Try YYYY/MM/DD - BEGIN - RETURN to_date(normalized, 'YYYY/MM/DD'); - EXCEPTION WHEN OTHERS THEN - NULL; - END; + -- Try MM/DD/YYYY (North American locale used in BC) + BEGIN result := to_date(normalized, 'MM/DD/YYYY'); EXCEPTION WHEN OTHERS THEN result := NULL; END; + IF result IS NOT NULL + AND EXTRACT(month FROM result) = p1 + AND EXTRACT(day FROM result) = p2 + AND EXTRACT(year FROM result) = p3 THEN + RETURN result; + END IF; + + -- Try DD/MM/YYYY + BEGIN result := to_date(normalized, 'DD/MM/YYYY'); EXCEPTION WHEN OTHERS THEN result := NULL; END; + IF result IS NOT NULL + AND EXTRACT(day FROM result) = p1 + AND EXTRACT(month FROM result) = p2 + AND EXTRACT(year FROM result) = p3 THEN + RETURN result; + END IF; + END IF; -- All attempts failed, return NULL gracefully RETURN NULL; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_timestamp.sql b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_timestamp.sql index 900c26d5d9..f7733f3c6d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_timestamp.sql +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/safe_to_timestamp.sql @@ -1,10 +1,22 @@ CREATE OR REPLACE FUNCTION "Reporting".safe_to_timestamp(val text) RETURNS timestamp without time zone LANGUAGE plpgsql - IMMUTABLE + STABLE AS $function$ DECLARE normalized text; + has_meridian boolean; + has_seconds boolean; + has_time boolean; + result timestamp; + date_part text; + parts text[]; + p1 int; + p2 int; + p3 int; + fmt text; + fmt_mdy text; + fmt_dmy text; BEGIN IF val IS NULL OR trim(val) = '' THEN RETURN NULL; @@ -15,40 +27,96 @@ BEGIN normalized := regexp_replace(normalized, '\s*[aA]\.[mM]\.', ' AM', 'g'); normalized := regexp_replace(normalized, '\s*[pP]\.[mM]\.', ' PM', 'g'); - -- Try direct cast first (handles ISO 8601, standard YYYY-MM-DD HH24:MI:SS, etc.) + -- Try direct cast first (handles ISO 8601, YYYY-MM-DD HH24:MI:SS, etc.) BEGIN RETURN normalized::timestamp; EXCEPTION WHEN OTHERS THEN NULL; END; - -- Try MM/DD/YYYY with 12-hour clock - BEGIN - RETURN to_timestamp(normalized, 'MM/DD/YYYY HH12:MI:SS AM'); - EXCEPTION WHEN OTHERS THEN - NULL; - END; + -- Pre-detect format characteristics via regex to select the right parse + -- branch and avoid entering unnecessary BEGIN...EXCEPTION blocks + has_meridian := normalized ~* '\s+(AM|PM)\s*$'; + has_time := normalized ~ '\s+\d{1,2}:\d{2}'; + has_seconds := normalized ~ '\d{1,2}:\d{2}:\d{2}'; - -- Try MM/DD/YYYY with 24-hour clock - BEGIN - RETURN to_timestamp(normalized, 'MM/DD/YYYY HH24:MI:SS'); - EXCEPTION WHEN OTHERS THEN - NULL; - END; + -- Year-first with slash: YYYY/MM/DD variants + IF normalized ~ '^\d{4}/' THEN + date_part := split_part(normalized, ' ', 1); + BEGIN + parts := string_to_array(date_part, '/'); + p1 := parts[1]::int; p2 := parts[2]::int; p3 := parts[3]::int; + EXCEPTION WHEN OTHERS THEN + RETURN NULL; + END; - -- Try DD/MM/YYYY with 12-hour clock - BEGIN - RETURN to_timestamp(normalized, 'DD/MM/YYYY HH12:MI:SS AM'); - EXCEPTION WHEN OTHERS THEN - NULL; - END; + IF has_time AND has_seconds THEN + fmt := 'YYYY/MM/DD HH24:MI:SS'; + ELSIF has_time THEN + fmt := 'YYYY/MM/DD HH24:MI'; + ELSE + fmt := 'YYYY/MM/DD'; + END IF; - -- Try YYYY/MM/DD with 24-hour clock - BEGIN - RETURN to_timestamp(normalized, 'YYYY/MM/DD HH24:MI:SS'); - EXCEPTION WHEN OTHERS THEN - NULL; - END; + BEGIN result := to_timestamp(normalized, fmt); EXCEPTION WHEN OTHERS THEN result := NULL; END; + IF result IS NOT NULL + AND EXTRACT(year FROM result) = p1 + AND EXTRACT(month FROM result) = p2 + AND EXTRACT(day FROM result) = p3 THEN + RETURN result; + END IF; + RETURN NULL; + END IF; + + -- Slash-separated with year last: MM/DD/YYYY or DD/MM/YYYY + -- MM/DD is tried first (North American locale used in BC) + IF normalized ~ '^\d{1,2}/\d{1,2}/\d{4}' THEN + date_part := split_part(normalized, ' ', 1); + BEGIN + parts := string_to_array(date_part, '/'); + p1 := parts[1]::int; p2 := parts[2]::int; p3 := parts[3]::int; + EXCEPTION WHEN OTHERS THEN + RETURN NULL; + END; + + -- Select format strings based on detected time characteristics + IF has_time AND has_seconds AND has_meridian THEN + fmt_mdy := 'MM/DD/YYYY HH12:MI:SS AM'; + fmt_dmy := 'DD/MM/YYYY HH12:MI:SS AM'; + ELSIF has_time AND has_seconds THEN + fmt_mdy := 'MM/DD/YYYY HH24:MI:SS'; + fmt_dmy := 'DD/MM/YYYY HH24:MI:SS'; + ELSIF has_time AND has_meridian THEN + fmt_mdy := 'MM/DD/YYYY HH12:MI AM'; + fmt_dmy := 'DD/MM/YYYY HH12:MI AM'; + ELSIF has_time THEN + fmt_mdy := 'MM/DD/YYYY HH24:MI'; + fmt_dmy := 'DD/MM/YYYY HH24:MI'; + ELSE + fmt_mdy := 'MM/DD/YYYY'; + fmt_dmy := 'DD/MM/YYYY'; + END IF; + + -- Try MM/DD/YYYY, validate parsed date parts match input + BEGIN result := to_timestamp(normalized, fmt_mdy); EXCEPTION WHEN OTHERS THEN result := NULL; END; + IF result IS NOT NULL + AND EXTRACT(month FROM result) = p1 + AND EXTRACT(day FROM result) = p2 + AND EXTRACT(year FROM result) = p3 THEN + RETURN result; + END IF; + + -- Try DD/MM/YYYY, validate parsed date parts match input + BEGIN result := to_timestamp(normalized, fmt_dmy); EXCEPTION WHEN OTHERS THEN result := NULL; END; + IF result IS NOT NULL + AND EXTRACT(day FROM result) = p1 + AND EXTRACT(month FROM result) = p2 + AND EXTRACT(year FROM result) = p3 THEN + RETURN result; + END IF; + + RETURN NULL; + END IF; -- All attempts failed, return NULL gracefully RETURN NULL; From 45eaac748a0797b3d6c5637021b5e8067ac3a20d Mon Sep 17 00:00:00 2001 From: Andre Goncalves <98196495+AndreGAot@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:56:14 -0700 Subject: [PATCH 11/58] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/Unity.GrantManager.DbMigrator/appsettings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json index cfb8e65c8e..452a5ac1d3 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json @@ -1,7 +1,7 @@ { "ConnectionStrings": { - "Default": "Host=localhost;port=5432;Database=UnityGrantManager;Username=postgres;Password=admin", - "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres;Password=admin" + "Default": "Host=localhost;port=5432;Database=UnityGrantManager;Username=postgres", + "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres" }, "Redis": { "Configuration": "127.0.0.1" From 6ff9fb7aefca13f7041e1c1a63cd13dbc0617e02 Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Mon, 13 Apr 2026 16:13:45 -0700 Subject: [PATCH 12/58] AB#32398 remove the test local passwords from migrator --- .../src/Unity.GrantManager.DbMigrator/appsettings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json index cfb8e65c8e..626191b07d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json @@ -1,7 +1,7 @@ { "ConnectionStrings": { - "Default": "Host=localhost;port=5432;Database=UnityGrantManager;Username=postgres;Password=admin", - "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres;Password=admin" + "Default": "Host=localhost;port=5432;Database=UnityGrantManager;Username=postgres;", + "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres;" }, "Redis": { "Configuration": "127.0.0.1" From 9d7406a80f53df65c43645182577733bf401f626 Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Mon, 13 Apr 2026 16:20:07 -0700 Subject: [PATCH 13/58] AB#32398 undo config change --- .../src/Unity.GrantManager.DbMigrator/appsettings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json index 626191b07d..452a5ac1d3 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json @@ -1,7 +1,7 @@ { "ConnectionStrings": { - "Default": "Host=localhost;port=5432;Database=UnityGrantManager;Username=postgres;", - "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres;" + "Default": "Host=localhost;port=5432;Database=UnityGrantManager;Username=postgres", + "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres" }, "Redis": { "Configuration": "127.0.0.1" From e59a52fee32ddbe1873484c36f991260d23bd11e Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Mon, 13 Apr 2026 16:24:26 -0700 Subject: [PATCH 14/58] AB#31476 - Restructured the permissions --- .../AIPermissionDefinitionProvider.cs | 43 +++++++++++++------ .../Permissions/AIPermissions.cs | 18 ++++---- .../AttachmentSummaryAppService.cs | 2 +- .../ApplicationAnalysisAppService.cs | 2 +- .../ApplicationContentAppService.cs | 6 +-- .../ApplicationScoringAppService.cs | 2 +- .../Localization/AI/en.json | 10 +++-- .../Assessments/AssessmentAppService.cs | 4 +- .../Pages/GrantApplications/Details.cshtml | 6 +-- .../AssessmentScoresWidgetViewComponent.cs | 2 +- .../ChefsAttachments/ChefsAttachments.cs | 2 +- .../Components/ReviewList/ReviewList.cs | 2 +- 12 files changed, 60 insertions(+), 39 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Permissions/AIPermissionDefinitionProvider.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Permissions/AIPermissionDefinitionProvider.cs index fb7980f037..6f292a2779 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Permissions/AIPermissionDefinitionProvider.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Permissions/AIPermissionDefinitionProvider.cs @@ -21,20 +21,39 @@ public override void Define(IPermissionDefinitionContext context) L("Permission:AI.Reporting")) .RequireFeatures("Unity.AIReporting"); - aiPermissionsGroup.AddPermission( - AIPermissions.ApplicationAnalysis.ApplicationAnalysisDefault, - L("Permission:AI.ApplicationAnalysis")) - .RequireFeatures("Unity.AI.ApplicationAnalysis"); + var analysisParent = aiPermissionsGroup.AddPermission( + AIPermissions.Analysis.AnalysisDefault, + L("Permission:AI.Analysis")); - aiPermissionsGroup.AddPermission( - AIPermissions.AttachmentSummary.AttachmentSummaryDefault , - L("Permission:AI.AttachmentSummary")) - .RequireFeatures("Unity.AI.AttachmentSummaries"); + analysisParent.AddChild( + AIPermissions.Analysis.ViewApplicationAnalysis, + L("Permission:AI.Analysis.ViewApplicationAnalysis")) + .RequireFeatures("Unity.AI.ApplicationAnalysis"); - aiPermissionsGroup.AddPermission( - AIPermissions.ScoringAssistant.ScoringAssistantDefault, - L("Permission:AI.ScoringAssistant")) - .RequireFeatures("Unity.AI.Scoring"); + analysisParent.AddChild( + AIPermissions.Analysis.ViewAttachmentSummary, + L("Permission:AI.Analysis.ViewAttachmentSummary")) + .RequireFeatures("Unity.AI.AttachmentSummaries"); + + analysisParent.AddChild( + AIPermissions.Analysis.ViewScoringResult, + L("Permission:AI.Analysis.ViewScoringResult")) + .RequireFeatures("Unity.AI.Scoring"); + + analysisParent.AddChild( + AIPermissions.Analysis.GenerateApplicationAnalysis, + L("Permission:AI.Analysis.GenerateApplicationAnalysis")) + .RequireFeatures("Unity.AI.ApplicationAnalysis"); + + analysisParent.AddChild( + AIPermissions.Analysis.GenerateAttachmentSummaries, + L("Permission:AI.Analysis.GenerateAttachmentSummaries")) + .RequireFeatures("Unity.AI.AttachmentSummaries"); + + analysisParent.AddChild( + AIPermissions.Analysis.GenerateScoring, + L("Permission:AI.Analysis.GenerateScoring")) + .RequireFeatures("Unity.AI.Scoring"); var settingManagement = context.GetGroup(SettingManagementPermissions.GroupName); settingManagement.AddPermission( diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Permissions/AIPermissions.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Permissions/AIPermissions.cs index 8caef403c4..b119e140f9 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Permissions/AIPermissions.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Permissions/AIPermissions.cs @@ -13,19 +13,17 @@ public static class Reporting public const string ReportingDefault = GroupName + ".Reporting"; } - public static class ApplicationAnalysis + public static class Analysis { - public const string ApplicationAnalysisDefault = GroupName + ".ApplicationAnalysis"; - } + public const string AnalysisDefault = GroupName + ".Analysis"; - public static class AttachmentSummary - { - public const string AttachmentSummaryDefault = GroupName + ".AttachmentSummary"; - } + public const string ViewApplicationAnalysis = GroupName + ".Analysis.ViewApplicationAnalysis"; + public const string ViewAttachmentSummary = GroupName + ".Analysis.ViewAttachmentSummary"; + public const string ViewScoringResult = GroupName + ".Analysis.ViewScoringResult"; - public static class ScoringAssistant - { - public const string ScoringAssistantDefault = GroupName + ".ScoringAssistant"; + public const string GenerateApplicationAnalysis = GroupName + ".Analysis.GenerateApplicationAnalysis"; + public const string GenerateAttachmentSummaries = GroupName + ".Analysis.GenerateAttachmentSummaries"; + public const string GenerateScoring = GroupName + ".Analysis.GenerateScoring"; } public static class Configuration diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Attachments/AttachmentSummaryAppService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Attachments/AttachmentSummaryAppService.cs index 4849e454b7..681d7e55da 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Attachments/AttachmentSummaryAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Attachments/AttachmentSummaryAppService.cs @@ -12,7 +12,7 @@ namespace Unity.GrantManager.Attachments; -[Authorize(AIPermissions.AttachmentSummary.AttachmentSummaryDefault)] +[Authorize(AIPermissions.Analysis.ViewAttachmentSummary)] [Dependency(ReplaceServices = true)] [ExposeServices(typeof(AttachmentSummaryAppService), typeof(IAttachmentSummaryAppService))] public class AttachmentSummaryAppService( diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationAnalysisAppService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationAnalysisAppService.cs index e5cf789014..483a30c8df 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationAnalysisAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationAnalysisAppService.cs @@ -10,7 +10,7 @@ namespace Unity.GrantManager.GrantApplications; -[Authorize(AIPermissions.ApplicationAnalysis.ApplicationAnalysisDefault)] +[Authorize(AIPermissions.Analysis.ViewApplicationAnalysis)] public class ApplicationAnalysisAppService( IApplicationAIGenerationQueue aiGenerationQueue, IFeatureChecker featureChecker) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationContentAppService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationContentAppService.cs index 66f1799ce4..d19f82767d 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationContentAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationContentAppService.cs @@ -10,9 +10,9 @@ namespace Unity.GrantManager.GrantApplications; -[Authorize(AIPermissions.AttachmentSummary.AttachmentSummaryDefault)] -[Authorize(AIPermissions.ApplicationAnalysis.ApplicationAnalysisDefault)] -[Authorize(AIPermissions.ScoringAssistant.ScoringAssistantDefault)] +[Authorize(AIPermissions.Analysis.ViewAttachmentSummary)] +[Authorize(AIPermissions.Analysis.ViewApplicationAnalysis)] +[Authorize(AIPermissions.Analysis.ViewScoringResult)] public class ApplicationContentAppService( IApplicationAIGenerationQueue aiGenerationQueue, IFeatureChecker featureChecker) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationScoringAppService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationScoringAppService.cs index 6d3719f5d2..d42a95f206 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationScoringAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationScoringAppService.cs @@ -10,7 +10,7 @@ namespace Unity.GrantManager.GrantApplications; -[Authorize(AIPermissions.ScoringAssistant.ScoringAssistantDefault)] +[Authorize(AIPermissions.Analysis.ViewScoringResult)] public class ApplicationScoringAppService( IApplicationAIGenerationQueue aiGenerationQueue, IFeatureChecker featureChecker) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json index adbb81b821..0018963793 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json @@ -3,9 +3,13 @@ "texts": { "Permission:AI": "AI", "Permission:AI.Reporting": "AI Reporting", - "Permission:AI.ApplicationAnalysis": "AI Application Analysis", - "Permission:AI.AttachmentSummary": "AI Attachment Summary", - "Permission:AI.ScoringAssistant": "AI Scoring Assistant", + "Permission:AI.Analysis": "AI Analysis", + "Permission:AI.Analysis.ViewApplicationAnalysis": "View AI Application Analysis", + "Permission:AI.Analysis.ViewAttachmentSummary": "View AI Attachment Summary", + "Permission:AI.Analysis.ViewScoringResult": "View AI Scoring Result", + "Permission:AI.Analysis.GenerateApplicationAnalysis": "Generate AI Application Analysis", + "Permission:AI.Analysis.GenerateAttachmentSummaries": "Generate AI Attachment Summaries", + "Permission:AI.Analysis.GenerateScoring": "Generate AI Scoring", "Permission:AI.ConfigureAI": "AI Configuration", "Permission:AI.Prompts": "AI Prompt Management", "Permission:AI.Prompts.Create": "Create Prompts", diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Assessments/AssessmentAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Assessments/AssessmentAppService.cs index 1fda6f4f62..09d14f365a 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Assessments/AssessmentAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Assessments/AssessmentAppService.cs @@ -95,7 +95,7 @@ public async Task GetDisplayList(Guid applicationId) // If AI Scoring feature is disabled or user lacks permission, filter out AI assessments var aiScoringEnabled = await _featureChecker.IsEnabledAsync("Unity.AI.Scoring"); - var canViewAI = await AuthorizationService.IsGrantedAsync(AIPermissions.ScoringAssistant.ScoringAssistantDefault); + var canViewAI = await AuthorizationService.IsGrantedAsync(AIPermissions.Analysis.ViewScoringResult); assessmentList = assessmentList .Where(a => !a.IsAiAssessment || (aiScoringEnabled && canViewAI)) .OrderByDescending(a => a.IsAiAssessment) @@ -396,7 +396,7 @@ public async Task UpdateAssessmentScore(AssessmentScoresDto dto) /// /// Thrown when the specified assessment is not an AI assessment. /// - [Authorize(AIPermissions.ScoringAssistant.ScoringAssistantDefault)] + [Authorize(AIPermissions.Analysis.ViewScoringResult)] public async Task CloneFromAiAsync(Guid aiAssessmentId) { if (!await _featureChecker.IsEnabledAsync("Unity.AI.Scoring")) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml index 514249ed7f..30ea386fc6 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml @@ -29,11 +29,11 @@ var notificationsFeatureEnabled = await FeatureChecker.IsEnabledAsync("Unity.Notifications"); var readEmailGranted = await PermissionChecker.IsGrantedAsync("Notifications.Email"); var aiAttachmentSummariesEnabled = await FeatureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries") - && await PermissionChecker.IsGrantedAsync(AIPermissions.AttachmentSummary.AttachmentSummaryDefault); + && await PermissionChecker.IsGrantedAsync(AIPermissions.Analysis.ViewAttachmentSummary); var aiApplicationAnalysisEnabled = await FeatureChecker.IsEnabledAsync("Unity.AI.ApplicationAnalysis") - && await PermissionChecker.IsGrantedAsync(AIPermissions.ApplicationAnalysis.ApplicationAnalysisDefault); + && await PermissionChecker.IsGrantedAsync(AIPermissions.Analysis.ViewApplicationAnalysis); var aiScoringEnabled = await FeatureChecker.IsEnabledAsync("Unity.AI.Scoring") - && await PermissionChecker.IsGrantedAsync(AIPermissions.ScoringAssistant.ScoringAssistantDefault); + && await PermissionChecker.IsGrantedAsync(AIPermissions.Analysis.ViewScoringResult); var flexFeatureEnabled = await FeatureChecker.IsEnabledAsync("Unity.Flex"); } @section styles diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/AssessmentScoresWidgetViewComponent.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/AssessmentScoresWidgetViewComponent.cs index 107aa253bc..e8a2268724 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/AssessmentScoresWidgetViewComponent.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/AssessmentScoresWidgetViewComponent.cs @@ -100,7 +100,7 @@ public async Task InvokeAsync(Guid assessmentId, Guid curr CurrentUserId = currentUserId, AssessorId = assessment.AssessorId, IsAIScoringEnabled = await featureChecker.IsEnabledAsync("Unity.AI.Scoring") && - await permissionChecker.IsGrantedAsync(AIPermissions.ScoringAssistant.ScoringAssistantDefault), + await permissionChecker.IsGrantedAsync(AIPermissions.Analysis.ViewScoringResult), IsAiAssessment = assessment.IsAiAssessment, }; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.cs index 530a0338fd..2dc33039c7 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.cs @@ -29,7 +29,7 @@ public async Task InvokeAsync() { var isAIAttachmentSummariesEnabled = await _featureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries") && - await _permissionChecker.IsGrantedAsync(AIPermissions.AttachmentSummary.AttachmentSummaryDefault); + await _permissionChecker.IsGrantedAsync(AIPermissions.Analysis.ViewAttachmentSummary); ViewBag.IsAIAttachmentSummariesEnabled = isAIAttachmentSummariesEnabled; return View(); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ReviewList/ReviewList.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ReviewList/ReviewList.cs index 429617e305..1b61e74d83 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ReviewList/ReviewList.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ReviewList/ReviewList.cs @@ -26,7 +26,7 @@ public async Task InvokeAsync() var scoringFeatureEnabled = await featureChecker.IsEnabledAsync("Unity.AI.Scoring"); ViewBag.IsAIScoringEnabled = scoringFeatureEnabled && - await permissionChecker.IsGrantedAsync(AIPermissions.ScoringAssistant.ScoringAssistantDefault); + await permissionChecker.IsGrantedAsync(AIPermissions.Analysis.ViewScoringResult); return View(); } From 45cd47d0cfe29286cda1012091303a0f07e8be88 Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Mon, 13 Apr 2026 16:35:36 -0700 Subject: [PATCH 15/58] AB#31476 - Added data migration for permission rename --- ...0413232525_RenameAIPermissions.Designer.cs | 3030 +++++++++++++++++ .../20260413232525_RenameAIPermissions.cs | 26 + 2 files changed, 3056 insertions(+) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260413232525_RenameAIPermissions.Designer.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260413232525_RenameAIPermissions.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260413232525_RenameAIPermissions.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260413232525_RenameAIPermissions.Designer.cs new file mode 100644 index 0000000000..8b320cd0b7 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260413232525_RenameAIPermissions.Designer.cs @@ -0,0 +1,3030 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Unity.GrantManager.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace Unity.GrantManager.Migrations.HostMigrations +{ + [DbContext(typeof(GrantManagerDbContext))] + [Migration("20260413232525_RenameAIPermissions")] + partial class RenameAIPermissions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BlobData") + .HasColumnType("bytea") + .HasColumnName("blob_data"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_blob_triggers", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCalendar", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("calendar"); + + b.HasKey("SchedulerName", "CalendarName"); + + b.ToTable("qrtz_calendars", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CronExpression") + .IsRequired() + .HasColumnType("text") + .HasColumnName("cron_expression"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_cron_triggers", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzFiredTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("EntryId") + .HasColumnType("text") + .HasColumnName("entry_id"); + + b.Property("FiredTime") + .HasColumnType("bigint") + .HasColumnName("fired_time"); + + b.Property("InstanceName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.Property("ScheduledTime") + .HasColumnType("bigint") + .HasColumnName("sched_time"); + + b.Property("State") + .IsRequired() + .HasColumnType("text") + .HasColumnName("state"); + + b.Property("TriggerGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("TriggerName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.HasKey("SchedulerName", "EntryId"); + + b.HasIndex("InstanceName") + .HasDatabaseName("idx_qrtz_ft_trig_inst_name"); + + b.HasIndex("JobGroup") + .HasDatabaseName("idx_qrtz_ft_job_group"); + + b.HasIndex("JobName") + .HasDatabaseName("idx_qrtz_ft_job_name"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_ft_job_req_recovery"); + + b.HasIndex("TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_group"); + + b.HasIndex("TriggerName") + .HasDatabaseName("idx_qrtz_ft_trig_name"); + + b.HasIndex("SchedulerName", "TriggerName", "TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_nm_gp"); + + b.ToTable("qrtz_fired_triggers", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("IsDurable") + .HasColumnType("bool") + .HasColumnName("is_durable"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("IsUpdateData") + .HasColumnType("bool") + .HasColumnName("is_update_data"); + + b.Property("JobClassName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_class_name"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.HasKey("SchedulerName", "JobName", "JobGroup"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_j_req_recovery"); + + b.ToTable("qrtz_job_details", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzLock", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("LockName") + .HasColumnType("text") + .HasColumnName("lock_name"); + + b.HasKey("SchedulerName", "LockName"); + + b.ToTable("qrtz_locks", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzPausedTriggerGroup", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.HasKey("SchedulerName", "TriggerGroup"); + + b.ToTable("qrtz_paused_trigger_grps", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSchedulerState", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("InstanceName") + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("CheckInInterval") + .HasColumnType("bigint") + .HasColumnName("checkin_interval"); + + b.Property("LastCheckInTime") + .HasColumnType("bigint") + .HasColumnName("last_checkin_time"); + + b.HasKey("SchedulerName", "InstanceName"); + + b.ToTable("qrtz_scheduler_state", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BooleanProperty1") + .HasColumnType("bool") + .HasColumnName("bool_prop_1"); + + b.Property("BooleanProperty2") + .HasColumnType("bool") + .HasColumnName("bool_prop_2"); + + b.Property("DecimalProperty1") + .HasColumnType("numeric") + .HasColumnName("dec_prop_1"); + + b.Property("DecimalProperty2") + .HasColumnType("numeric") + .HasColumnName("dec_prop_2"); + + b.Property("IntegerProperty1") + .HasColumnType("integer") + .HasColumnName("int_prop_1"); + + b.Property("IntegerProperty2") + .HasColumnType("integer") + .HasColumnName("int_prop_2"); + + b.Property("LongProperty1") + .HasColumnType("bigint") + .HasColumnName("long_prop_1"); + + b.Property("LongProperty2") + .HasColumnType("bigint") + .HasColumnName("long_prop_2"); + + b.Property("StringProperty1") + .HasColumnType("text") + .HasColumnName("str_prop_1"); + + b.Property("StringProperty2") + .HasColumnType("text") + .HasColumnName("str_prop_2"); + + b.Property("StringProperty3") + .HasColumnType("text") + .HasColumnName("str_prop_3"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simprop_triggers", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("RepeatCount") + .HasColumnType("bigint") + .HasColumnName("repeat_count"); + + b.Property("RepeatInterval") + .HasColumnType("bigint") + .HasColumnName("repeat_interval"); + + b.Property("TimesTriggered") + .HasColumnType("bigint") + .HasColumnName("times_triggered"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simple_triggers", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("EndTime") + .HasColumnType("bigint") + .HasColumnName("end_time"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("JobGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("MisfireInstruction") + .HasColumnType("smallint") + .HasColumnName("misfire_instr"); + + b.Property("NextFireTime") + .HasColumnType("bigint") + .HasColumnName("next_fire_time"); + + b.Property("PreviousFireTime") + .HasColumnType("bigint") + .HasColumnName("prev_fire_time"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("StartTime") + .HasColumnType("bigint") + .HasColumnName("start_time"); + + b.Property("TriggerState") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_state"); + + b.Property("TriggerType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_type"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.HasIndex("NextFireTime") + .HasDatabaseName("idx_qrtz_t_next_fire_time"); + + b.HasIndex("TriggerState") + .HasDatabaseName("idx_qrtz_t_state"); + + b.HasIndex("NextFireTime", "TriggerState") + .HasDatabaseName("idx_qrtz_t_nft_st"); + + b.HasIndex("SchedulerName", "JobName", "JobGroup"); + + b.ToTable("qrtz_triggers", (string)null); + }); + + modelBuilder.Entity("Unity.AI.Domain.AIPrompt", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("AIPrompts", "AI"); + }); + + modelBuilder.Entity("Unity.AI.Domain.AIPromptVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeveloperNotes") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeprecated") + .HasColumnType("boolean"); + + b.Property("IsPublished") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MaxTokens") + .HasColumnType("integer"); + + b.Property("MetadataJson") + .HasColumnType("jsonb"); + + b.Property("PromptId") + .HasColumnType("uuid"); + + b.Property("SystemPrompt") + .IsRequired() + .HasColumnType("text"); + + b.Property("TargetModel") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TargetProvider") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Temperature") + .HasColumnType("double precision"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserPromptTemplate") + .IsRequired() + .HasColumnType("text"); + + b.Property("VersionNumber") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PromptId", "VersionNumber") + .IsUnique(); + + b.ToTable("AIPromptVersions", "AI"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applicants.ApplicantTenantMap", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastUpdated") + .HasColumnType("timestamp without time zone"); + + b.Property("OidcSubUsername") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TenantName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OidcSubUsername"); + + b.HasIndex("OidcSubUsername", "TenantId") + .IsUnique(); + + b.ToTable("ApplicantTenantMaps", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Integrations.CasClientCode", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientCode") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.Property("ClientId") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinancialMinistry") + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("MinistryPrefix") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.HasKey("Id"); + + b.ToTable("CasClientCodes", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Integrations.DynamicUrl", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("KeyName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DynamicUrls", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.Community", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RegionalDistrictCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Communities", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.EconomicRegion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EconomicRegionCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("EconomicRegionName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.HasKey("Id"); + + b.ToTable("EconomicRegions", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.ElectoralDistrict", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ElectoralDistrictCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("ElectoralDistrictName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.HasKey("Id"); + + b.ToTable("ElectoralDistricts", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.RegionalDistrict", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EconomicRegionCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("RegionalDistrictCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("RegionalDistrictName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("RegionalDistricts", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.Sector", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SectorCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("SectorName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Sectors", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.SubSector", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SectorId") + .HasColumnType("uuid"); + + b.Property("SubSectorCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubSectorName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("SectorId"); + + b.ToTable("SubSectors", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Messaging.InboxMessage", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DataType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Details") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MessageId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Payload") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ProcessedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ReceivedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RetryCount") + .HasColumnType("integer"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MessageId") + .IsUnique(); + + b.HasIndex("Source", "Status"); + + b.ToTable("InboxMessages", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Messaging.OutboxMessage", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AckStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DataType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Details") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MessageId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("OriginalMessageId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PublishedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RetryCount") + .HasColumnType("integer"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Source", "Status"); + + b.ToTable("OutboxMessages", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Tokens.TenantToken", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TenantTokens", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationName") + .HasMaxLength(96) + .HasColumnType("character varying(96)") + .HasColumnName("ApplicationName"); + + b.Property("BrowserInfo") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("BrowserInfo"); + + b.Property("ClientId") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("ClientId"); + + b.Property("ClientIpAddress") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("ClientIpAddress"); + + b.Property("ClientName") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("ClientName"); + + b.Property("Comments") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("Comments"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("CorrelationId"); + + b.Property("Exceptions") + .HasColumnType("text"); + + b.Property("ExecutionDuration") + .HasColumnType("integer") + .HasColumnName("ExecutionDuration"); + + b.Property("ExecutionTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("HttpMethod") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("HttpMethod"); + + b.Property("HttpStatusCode") + .HasColumnType("integer") + .HasColumnName("HttpStatusCode"); + + b.Property("ImpersonatorTenantId") + .HasColumnType("uuid") + .HasColumnName("ImpersonatorTenantId"); + + b.Property("ImpersonatorTenantName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("ImpersonatorTenantName"); + + b.Property("ImpersonatorUserId") + .HasColumnType("uuid") + .HasColumnName("ImpersonatorUserId"); + + b.Property("ImpersonatorUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("ImpersonatorUserName"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TenantName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("TenantName"); + + b.Property("Url") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("Url"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("UserName"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ExecutionTime"); + + b.HasIndex("TenantId", "UserId", "ExecutionTime"); + + b.ToTable("AuditLogs", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuditLogId") + .HasColumnType("uuid") + .HasColumnName("AuditLogId"); + + b.Property("ExecutionDuration") + .HasColumnType("integer") + .HasColumnName("ExecutionDuration"); + + b.Property("ExecutionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("ExecutionTime"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("MethodName") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("MethodName"); + + b.Property("Parameters") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("Parameters"); + + b.Property("ServiceName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("ServiceName"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AuditLogId"); + + b.HasIndex("TenantId", "ServiceName", "MethodName", "ExecutionTime"); + + b.ToTable("AuditLogActions", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuditLogId") + .HasColumnType("uuid") + .HasColumnName("AuditLogId"); + + b.Property("ChangeTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("ChangeTime"); + + b.Property("ChangeType") + .HasColumnType("smallint") + .HasColumnName("ChangeType"); + + b.Property("EntityId") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("EntityId"); + + b.Property("EntityTenantId") + .HasColumnType("uuid"); + + b.Property("EntityTypeFullName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("EntityTypeFullName"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AuditLogId"); + + b.HasIndex("TenantId", "EntityTypeFullName", "EntityId"); + + b.ToTable("EntityChanges", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("EntityChangeId") + .HasColumnType("uuid"); + + b.Property("NewValue") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("NewValue"); + + b.Property("OriginalValue") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("OriginalValue"); + + b.Property("PropertyName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("PropertyName"); + + b.Property("PropertyTypeFullName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("PropertyTypeFullName"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("EntityChangeId"); + + b.ToTable("EntityPropertyChanges", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.BackgroundJobs.BackgroundJobRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsAbandoned") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JobArgs") + .IsRequired() + .HasMaxLength(1048576) + .HasColumnType("character varying(1048576)"); + + b.Property("JobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("LastTryTime") + .HasColumnType("timestamp without time zone"); + + b.Property("NextTryTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Priority") + .ValueGeneratedOnAdd() + .HasColumnType("smallint") + .HasDefaultValue((byte)15); + + b.Property("TryCount") + .ValueGeneratedOnAdd() + .HasColumnType("smallint") + .HasDefaultValue((short)0); + + b.HasKey("Id"); + + b.HasIndex("IsAbandoned", "NextTryTime"); + + b.ToTable("BackgroundJobs", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("boolean"); + + b.Property("IsVisibleToClients") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ValueType") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Features", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("FeatureGroups", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ProviderKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ProviderName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey") + .IsUnique(); + + b.ToTable("FeatureValues", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsStatic") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Regex") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("RegexDescription") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.Property("ValueType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ClaimTypes", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityLinkUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("SourceTenantId") + .HasColumnType("uuid"); + + b.Property("SourceUserId") + .HasColumnType("uuid"); + + b.Property("TargetTenantId") + .HasColumnType("uuid"); + + b.Property("TargetUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SourceUserId", "SourceTenantId", "TargetUserId", "TargetTenantId") + .IsUnique(); + + b.ToTable("LinkUsers", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("EntityVersion") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDefault") + .HasColumnType("boolean") + .HasColumnName("IsDefault"); + + b.Property("IsPublic") + .HasColumnType("boolean") + .HasColumnName("IsPublic"); + + b.Property("IsStatic") + .HasColumnType("boolean") + .HasColumnName("IsStatic"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.ToTable("Roles", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClaimType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ClaimValue") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleClaims", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Action") + .HasMaxLength(96) + .HasColumnType("character varying(96)"); + + b.Property("ApplicationName") + .HasMaxLength(96) + .HasColumnType("character varying(96)"); + + b.Property("BrowserInfo") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ClientId") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ClientIpAddress") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("Identity") + .HasMaxLength(96) + .HasColumnType("character varying(96)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TenantName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("SecurityLogs", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentitySession", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Device") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("DeviceInfo") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IpAddresses") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("LastAccessed") + .HasColumnType("timestamp without time zone"); + + b.Property("SessionId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("SignedIn") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Device"); + + b.HasIndex("SessionId"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("Sessions", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("Email"); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("EmailConfirmed"); + + b.Property("EntityVersion") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasColumnName("IsActive"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsExternal"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastPasswordChangeTime") + .HasColumnType("timestamp with time zone"); + + b.Property("LockoutEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("LockoutEnabled"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("Name"); + + b.Property("NormalizedEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("NormalizedEmail"); + + b.Property("NormalizedUserName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("NormalizedUserName"); + + b.Property("OidcSub") + .HasColumnType("text"); + + b.Property("PasswordHash") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("PasswordHash"); + + b.Property("PhoneNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("PhoneNumber"); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("PhoneNumberConfirmed"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("SecurityStamp"); + + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("boolean"); + + b.Property("Surname") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("Surname"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TwoFactorEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("TwoFactorEnabled"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("UserName"); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("NormalizedEmail"); + + b.HasIndex("NormalizedUserName"); + + b.HasIndex("UserName"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClaimType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ClaimValue") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserClaims", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("EndTime") + .HasColumnType("timestamp without time zone"); + + b.Property("SourceUserId") + .HasColumnType("uuid"); + + b.Property("StartTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TargetUserId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("UserDelegations", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ProviderDisplayName") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ProviderKey") + .IsRequired() + .HasMaxLength(196) + .HasColumnType("character varying(196)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("UserId", "LoginProvider"); + + b.HasIndex("LoginProvider", "ProviderKey"); + + b.ToTable("UserLogins", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("OrganizationUnitId", "UserId"); + + b.HasIndex("UserId", "OrganizationUnitId"); + + b.ToTable("UserOrganizationUnits", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId", "UserId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(95) + .HasColumnType("character varying(95)") + .HasColumnName("Code"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("DisplayName"); + + b.Property("EntityVersion") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ParentId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("ParentId"); + + b.ToTable("OrganizationUnits", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("OrganizationUnitId", "RoleId"); + + b.HasIndex("RoleId", "OrganizationUnitId"); + + b.ToTable("OrganizationUnitRoles", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("MultiTenancySide") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Providers") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("StateCheckers") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Permissions", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ProviderKey") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ProviderName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name", "ProviderName", "ProviderKey") + .IsUnique(); + + b.ToTable("PermissionGrants", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("PermissionGroups", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ProviderKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ProviderName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey") + .IsUnique(); + + b.ToTable("Settings", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.SettingManagement.SettingDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DefaultValue") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Description") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsEncrypted") + .HasColumnType("boolean"); + + b.Property("IsInherited") + .HasColumnType("boolean"); + + b.Property("IsVisibleToClients") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Providers") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("SettingDefinitions", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.Tenant", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EntityVersion") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NormalizedName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("NormalizedName"); + + b.ToTable("Tenants", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b => + { + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Name") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("TenantId", "Name"); + + b.ToTable("TenantConnectionStrings", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("BlobTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("CronTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimplePropertyTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimpleTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", "JobDetail") + .WithMany("Triggers") + .HasForeignKey("SchedulerName", "JobName", "JobGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobDetail"); + }); + + modelBuilder.Entity("Unity.AI.Domain.AIPromptVersion", b => + { + b.HasOne("Unity.AI.Domain.AIPrompt", "Prompt") + .WithMany("Versions") + .HasForeignKey("PromptId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Prompt"); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.SubSector", b => + { + b.HasOne("Unity.GrantManager.Locality.Sector", "Sector") + .WithMany("SubSectors") + .HasForeignKey("SectorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Sector"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => + { + b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) + .WithMany("Actions") + .HasForeignKey("AuditLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => + { + b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) + .WithMany("EntityChanges") + .HasForeignKey("AuditLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b => + { + b.HasOne("Volo.Abp.AuditLogging.EntityChange", null) + .WithMany("PropertyChanges") + .HasForeignKey("EntityChangeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany("Claims") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Claims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("OrganizationUnits") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Tokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("ParentId"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany("Roles") + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b => + { + b.HasOne("Volo.Abp.TenantManagement.Tenant", null) + .WithMany("ConnectionStrings") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Navigation("Triggers"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Navigation("BlobTriggers"); + + b.Navigation("CronTriggers"); + + b.Navigation("SimplePropertyTriggers"); + + b.Navigation("SimpleTriggers"); + }); + + modelBuilder.Entity("Unity.AI.Domain.AIPrompt", b => + { + b.Navigation("Versions"); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.Sector", b => + { + b.Navigation("SubSectors"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b => + { + b.Navigation("Actions"); + + b.Navigation("EntityChanges"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => + { + b.Navigation("PropertyChanges"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => + { + b.Navigation("Claims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => + { + b.Navigation("Claims"); + + b.Navigation("Logins"); + + b.Navigation("OrganizationUnits"); + + b.Navigation("Roles"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.Navigation("Roles"); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.Tenant", b => + { + b.Navigation("ConnectionStrings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260413232525_RenameAIPermissions.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260413232525_RenameAIPermissions.cs new file mode 100644 index 0000000000..4b24b08a98 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260413232525_RenameAIPermissions.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Unity.GrantManager.Migrations.HostMigrations +{ + /// + public partial class RenameAIPermissions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"UPDATE public.\"PermissionGrants\" SET \"Name\" = 'AI.Analysis.ViewApplicationAnalysis' WHERE \"Name\" = 'AI.ApplicationAnalysis';"); + migrationBuilder.Sql($"UPDATE public.\"PermissionGrants\" SET \"Name\" = 'AI.Analysis.ViewAttachmentSummary' WHERE \"Name\" = 'AI.AttachmentSummary';"); + migrationBuilder.Sql($"UPDATE public.\"PermissionGrants\" SET \"Name\" = 'AI.Analysis.ViewScoringResult' WHERE \"Name\" = 'AI.ScoringAssistant';"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"UPDATE public.\"PermissionGrants\" SET \"Name\" = 'AI.ApplicationAnalysis' WHERE \"Name\" = 'AI.Analysis.ViewApplicationAnalysis';"); + migrationBuilder.Sql($"UPDATE public.\"PermissionGrants\" SET \"Name\" = 'AI.AttachmentSummary' WHERE \"Name\" = 'AI.Analysis.ViewAttachmentSummary';"); + migrationBuilder.Sql($"UPDATE public.\"PermissionGrants\" SET \"Name\" = 'AI.ScoringAssistant' WHERE \"Name\" = 'AI.Analysis.ViewScoringResult';"); + } + } +} From 4afeeccc913ad5579385bbef620fedbb66805ca4 Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Tue, 14 Apr 2026 14:12:12 -0700 Subject: [PATCH 16/58] AB#32134 add json editor for reporting configuration --- .../ReportingConfiguration/Default.cshtml | 29 +- .../ReportingConfiguration/Default.css | 25 +- .../ReportingConfiguration/Default.js | 381 +++++++---- .../UnityThemeUX2GlobalScriptContributor.cs | 1 + .../UnityThemeUX2GlobalStyleContributor.cs | 1 + .../wwwroot/themes/ux2/json-editor.css | 48 ++ .../wwwroot/themes/ux2/json-editor.js | 600 ++++++++++++++++++ 7 files changed, 946 insertions(+), 139 deletions(-) create mode 100644 applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/json-editor.css create mode 100644 applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/json-editor.js diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.cshtml index 871fa26a82..caae1e1560 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.cshtml @@ -13,9 +13,10 @@ + -
+
@@ -174,6 +175,32 @@ data-bs-placement="top" data-bs-original-title="Generate unique column names for all fields with duplicate names" class="btn unt-btn-outline-primary btn-outline-primary"> + +
+ + +
}
diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.css b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.css index 3ad76c57a9..d130b52d4c 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.css @@ -1,13 +1,11 @@ .report-config-content { - height: calc(97vh - 400px); - overflow: scroll; - overflow-x: hidden; + overflow: visible; } .provider-toggle-section { border-bottom: 1px solid #dee2e6; - padding-bottom: 1rem; - margin-bottom: 1rem; + padding-bottom: 0.5rem; + margin-bottom: 0.5rem; } .provider-toggle-section .btn-group { @@ -17,8 +15,8 @@ .provider-toggle-section .btn-group .btn { flex: none; - min-width: 120px; - padding: 0.5rem 1rem; + min-width: 100px; + padding: 0.375rem 0.75rem; font-weight: 500; transition: all 0.15s ease-in-out; } @@ -70,7 +68,7 @@ .report-config-content .action-bar { flex-shrink: 0; - margin-bottom: 1rem; + margin-bottom: 0.5rem; } .report-config-footer { @@ -160,6 +158,17 @@ .report-config-controls { display: flex !important; justify-content: space-between; + align-items: flex-end; +} + +/* Compact the version selector form-group within controls */ +.report-config-controls .form-group { + margin-bottom: 0; +} + +/* Reduce tab pane padding when reporting config is active */ +#nav-reporting-configuration.tab-pane { + padding: 0.5rem 0; } .report-config-btn-height-fix { diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.js b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.js index 3a1d3400e1..ff7c714683 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.js @@ -682,10 +682,7 @@ $(function () { dynamicButtonContainerId: 'reportConfigDynamicButtons', useNullPlaceholder: true, externalSearchId: 'search-report-config', - // Enable scrolling - let CSS handle the height - scrollY: true, - scrollCollapse: true, - responsive: true + fixedHeaders: true }); // Add event handler for when DataTable completes drawing @@ -700,34 +697,8 @@ $(function () { // Check for dynamic columns placeholder and update warning const hasDynamicColumns = checkForDynamicColumnsInTable(); updateDynamicColumnsWarning(hasDynamicColumns); - - // Force column adjustment after draw if tab is visible - setTimeout(function () { - if (isReportingTabVisible()) { - forceTableColumnAdjustment(); - } - }, 50); - }); - - // Add event handler for when DataTable data is loaded - dataTable.on('xhr.dt', function () { - // Force layout adjustment after data is loaded - setTimeout(function () { - if (isReportingTabVisible()) { - adjustTableLayout(); - forceTableColumnAdjustment(); - } - }, 100); }); - // Initial adjustment with longer delay to ensure DOM is ready - setTimeout(function () { - if (isReportingTabVisible()) { - adjustTableLayout(); - forceTableColumnAdjustment(); - } - }, 500); - // Track changes on column name inputs with validation $('#ReportConfigurationTable').on('input', '.column-name-input', function () { const $input = $(this); @@ -863,33 +834,11 @@ $(function () { return reportingPanel?.classList.contains('show', 'active'); } - // Enhanced function to force table column adjustment + // Force table column width recalculation function forceTableColumnAdjustment() { if (!dataTable) return; - try { - // Multiple approaches to ensure columns are properly sized - // 1. Force recalc of column widths dataTable.columns.adjust(); - - // 2. Trigger responsive recalc if responsive is enabled - if (dataTable.responsive) { - dataTable.responsive.recalc(); - } - - // 3. Force a layout recalculation by temporarily changing display - const wrapper = $('#ReportConfigurationTable_wrapper'); - if (wrapper.length) { - // Force browser reflow - wrapper.hide(); - wrapper.show(); - - // Final column adjustment after showing - setTimeout(function () { - dataTable.columns.adjust(); - }, 10); - } - } catch (error) { console.warn('Error during force column adjustment:', error); } @@ -1730,6 +1679,9 @@ $(function () { dataTable.ajax.reload(); } + // Clear the cached view status so import/edit are no longer blocked + $('#reportingViewStatus').val(''); + // Refresh view status widget refreshViewStatusWidget(correlationId, getCorrelationProvider()); @@ -1832,79 +1784,252 @@ $(function () { // Initialize the delete button functionality updateCheckConfigurationExistsCallbacks(); - // Initialize version selector visibility based on provider - updateVersionSelectorVisibility(); + // ====================================================================== + // JSON Export / Import / Edit + // ====================================================================== - // Function to adjust table layout dynamically - function adjustTableLayout() { - if (dataTable && dataTable.settings().length > 0) { - try { - const container = $('.report-config-table-container'); - const containerHeight = container.height(); - - if (containerHeight > 100) { // Only adjust if container has reasonable height - // Calculate available height for the table body - // Updated for DataTables v2 class names - const wrapper = $('#ReportConfigurationTable_wrapper'); - const header = wrapper.find('.dt-scroll-head'); - const info = wrapper.find('.dt-info'); - const paginate = wrapper.find('.dt-paging'); - - const headerHeight = header.length ? header.outerHeight(true) : 0; - const infoHeight = info.length ? info.outerHeight(true) : 0; - const paginateHeight = paginate.length ? paginate.outerHeight(true) : 0; - const padding = 20; // Extra padding - - const availableHeight = containerHeight - headerHeight - infoHeight - paginateHeight - padding; - const minHeight = 200; // Minimum table body height - - const scrollBodyHeight = Math.max(minHeight, availableHeight); - - // Apply the calculated height - wrapper.find('.dt-scroll-body').css({ - 'max-height': scrollBodyHeight + 'px', - 'height': scrollBodyHeight + 'px' - }); + /** + * Builds the simple [{propertyName, columnName}] array from the current + * DataTable state. This is what gets exported / shown in the editor. + */ + function buildJsonMapping() { + return getCurrentTableData().map(function (row) { + return { + propertyName: row.propertyName, + columnName: row.columnName + }; + }); + } + + /** + * Checks whether the view has been successfully generated. + * When true, import and edit are blocked because the mapping is locked. + */ + function isViewGenerated() { + // Check hidden field first (server-rendered initial state) + var status = ($('#reportingViewStatus').val() || '').toUpperCase(); + if (status === 'SUCCESS') return true; + + // Fallback: inspect the view-status widget DOM + var $widget = $('.view-status-compact'); + if ($widget.find('.fl-checkmark').length > 0) return true; + + return false; + } + + /** + * Applies an imported / edited [{propertyName, columnName}] array back + * into the DataTable inputs. Matching is done by propertyName; unmatched + * rows are silently skipped so partial imports work. + */ + function applyJsonMappingToTable(mappingArray) { + if (!dataTable) return; + + // Build a lookup: propertyName → columnName + var lookup = {}; + mappingArray.forEach(function (item) { + lookup[item.propertyName] = item.columnName; + }); + + var appliedCount = 0; + + dataTable.rows().every(function () { + var rowData = this.data(); + var node = this.node(); + var $input = $(node).find('.column-name-input'); + if (!$input.length) return; + + var propertyName = rowData.key; + if (propertyName in lookup) { + var newValue = sanitizeColumnName(lookup[propertyName] || ''); + if ($input.val() !== newValue) { + $input.val(newValue); + validateColumnNameInput($input, newValue, $input.data('path')); + appliedCount++; } - } catch (error) { - console.warn('Error adjusting table layout:', error); } + }); + + if (appliedCount > 0) { + markAsChanged(); } + + return appliedCount; } - // Function to handle tab visibility changes with improved column adjustment - function handleTabVisibilityChange() { - // Check if the reporting configuration tab is now visible - if (isReportingTabVisible()) { - // Progressive approach with multiple attempts - const adjustmentSequence = [ - { delay: 50, action: 'initial' }, - { delay: 150, action: 'layout' }, - { delay: 300, action: 'columns' }, - { delay: 500, action: 'final' } - ]; - - adjustmentSequence.forEach(step => { - setTimeout(() => { - if (dataTable && isReportingTabVisible()) { - switch (step.action) { - case 'initial': - dataTable.columns.adjust(); - break; - case 'layout': - adjustTableLayout(); - break; - case 'columns': - forceTableColumnAdjustment(); - break; - case 'final': - dataTable.columns.adjust(); - dataTable.draw(false); - break; - } + // Shared validators for the JSON editor and file import + var mappingValidators = [ + { + name: 'isArray', + message: 'JSON must be an array of objects.', + validate: function (data) { return Array.isArray(data); } + }, + { + name: 'uniquePropertyNames', + message: 'Duplicate propertyName values detected. Rows sharing the same propertyName will all receive the last columnName value when applied — use inline table editing for those rows instead.', + severity: 'warning', + validate: function (data) { + var names = data.map(function (r) { return r.propertyName; }); + return new Set(names).size === names.length; + } + }, + { + name: 'uniqueColumnNames', + message: 'Duplicate columnName values found. Each columnName must be unique.', + validate: function (data) { + var cols = data.filter(function (r) { return r.columnName; }) + .map(function (r) { return r.columnName.toLowerCase(); }); + return new Set(cols).size === cols.length; + } + }, + { + name: 'columnNameFormat', + message: 'One or more column names contain invalid characters. Use only lowercase letters, numbers, and underscores.', + validate: function (data) { + return data.every(function (r) { + if (!r.columnName) return true; // empty is allowed + return /^[a-z_][a-z0-9_]*$/i.test(r.columnName); + }); + } + }, + { + name: 'columnNameLength', + message: 'One or more column names exceed the maximum length of ' + COLUMN_VALIDATION.MAX_LENGTH + ' characters.', + validate: function (data) { + return data.every(function (r) { + if (!r.columnName) return true; + return r.columnName.length <= COLUMN_VALIDATION.MAX_LENGTH; + }); + } + }, + { + name: 'reservedWords', + message: 'One or more column names use a PostgreSQL reserved word.', + validate: function (data) { + return data.every(function (r) { + if (!r.columnName) return true; + return !COLUMN_VALIDATION.RESERVED_WORDS.includes(r.columnName.toLowerCase()); + }); + } + } + ]; + + // Create the editor instance (lazy – modal built on first use) + var mappingEditor = new UnityJsonEditor({ + title: 'Edit Column Mapping', + requiredFields: ['propertyName', 'columnName'], + validators: mappingValidators, + onSave: function (data, warnings) { + var count = applyJsonMappingToTable(data); + var msg = count + ' column mapping(s) updated. Remember to Save to persist changes.'; + if (warnings && warnings.length > 0) { + abp.message.warn(msg + '\n\n\u26A0 ' + warnings.map(function (w) { return w.message; }).join('\n')); + } else { + abp.message.success(msg); + } + } + }); + + // --- Export --- + $('#btn-export-json-mapping').on('click', function (e) { + e.preventDefault(); + if (!dataTable) { + abp.message.warn('No mapping data loaded.'); + return; + } + + var mapping = buildJsonMapping(); + if (mapping.length === 0) { + abp.message.warn('No mapping data to export.'); + return; + } + + var provider = getCorrelationProvider(); + var correlationId = getCurrentCorrelationId(); + var filename = 'column-mapping-' + provider + '-' + (correlationId || 'new') + '.json'; + UnityJsonEditor.exportToFile(mapping, filename); + }); + + // --- Import --- + $('#btn-import-json-mapping').on('click', function (e) { + e.preventDefault(); + if (isViewGenerated()) { + abp.message.error('Cannot import mapping: a database view has already been generated. Delete the view first to modify the mapping.'); + return; + } + + if (!dataTable) { + abp.message.warn('No mapping data loaded. Please load a configuration first.'); + return; + } + + var importWarnings = []; + UnityJsonEditor.importFromFile({ + validators: mappingValidators.concat([ + { + name: 'hasRequiredFields', + message: 'Each item must have "propertyName" and "columnName" fields.', + validate: function (data) { + if (!Array.isArray(data)) return false; + return data.every(function (r) { + return r !== null && typeof r === 'object' && + 'propertyName' in r && 'columnName' in r; + }); } - }, step.delay); - }); + } + ]), + onWarning: function (warnings) { + importWarnings = warnings; + } + }).then(function (data) { + var count = applyJsonMappingToTable(data); + var msg = count + ' column mapping(s) imported. Remember to Save to persist changes.'; + if (importWarnings.length > 0) { + abp.message.warn(msg + '\n\n\u26A0 ' + importWarnings.map(function (w) { return w.message; }).join('\n')); + } else { + abp.message.success(msg); + } + }).catch(function (err) { + abp.message.error(err.message || 'Failed to import file.'); + }); + }); + + // --- Edit JSON --- + $('#btn-edit-json-mapping').on('click', function (e) { + e.preventDefault(); + if (isViewGenerated()) { + abp.message.error('Cannot edit mapping: a database view has already been generated. Delete the view first to modify the mapping.'); + return; + } + + if (!dataTable) { + abp.message.warn('No mapping data loaded. Please load a configuration first.'); + return; + } + + var mapping = buildJsonMapping(); + mappingEditor.open(mapping); + }); + + // Initialize version selector visibility based on provider + updateVersionSelectorVisibility(); + + // Trigger ScrollResize recalculation by dispatching a window resize event. + // The ScrollResize plugin (enabled via fixedHeaders) handles all scroll body + // height calculations dynamically. + function adjustTableLayout() { + window.dispatchEvent(new Event('resize')); + } + + // Function to handle tab visibility changes + // When the tab becomes visible, recalculate column widths and trigger + // ScrollResize to set the correct scroll body height. + function handleTabVisibilityChange() { + if (isReportingTabVisible() && dataTable) { + setTimeout(function () { + dataTable.columns.adjust(); + dataTable.draw(false); + }, 50); } } @@ -1930,17 +2055,6 @@ $(function () { }, 150); }); - // Also handle window resize events - $(window).on('resize', function () { - if (isReportingTabVisible()) { - adjustTableLayout(); - // Add column adjustment on resize - setTimeout(function () { - forceTableColumnAdjustment(); - }, 100); - } - }); - // Add intersection observer for visibility detection (fallback) if (window.IntersectionObserver) { const observer = new IntersectionObserver((entries) => { @@ -1983,4 +2097,11 @@ $(function () { }); } } + + // Keep the hidden view-status field in sync when async generation completes + PubSub.subscribe('view_generation_completed', function (msg, data) { + if (data && data.finalStatus) { + $('#reportingViewStatus').val(data.finalStatus); + } + }); }); \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Bundling/UnityThemeUX2GlobalScriptContributor.cs b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Bundling/UnityThemeUX2GlobalScriptContributor.cs index f2472b27c6..4dea6cfe20 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Bundling/UnityThemeUX2GlobalScriptContributor.cs +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Bundling/UnityThemeUX2GlobalScriptContributor.cs @@ -50,6 +50,7 @@ public override void ConfigureBundle(BundleConfigurationContext context) context.Files.Add("/themes/ux2/plugins/scrollResize.js"); context.Files.Add("/themes/ux2/plugins/colvisAlpha.js"); context.Files.Add("/themes/ux2/table-utils.js"); + context.Files.Add("/themes/ux2/json-editor.js"); context.Files.Add("/js/DateUtils.js"); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Bundling/UnityThemeUX2GlobalStyleContributor.cs b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Bundling/UnityThemeUX2GlobalStyleContributor.cs index a4fbbd6a49..a1beefb47f 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Bundling/UnityThemeUX2GlobalStyleContributor.cs +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Bundling/UnityThemeUX2GlobalStyleContributor.cs @@ -12,6 +12,7 @@ public override void ConfigureBundle(BundleConfigurationContext context) context.Files.Add("/themes/ux2/fluenticons.min.css"); context.Files.Add("/themes/ux2/layout.css"); context.Files.Add("/themes/ux2/unity-styles.css"); + context.Files.Add("/themes/ux2/json-editor.css"); context.Files.AddIfNotContains("/libs/datatables.net-bs5/css/dataTables.bootstrap5.min.css"); context.Files.AddIfNotContains("/libs/datatables.net-buttons-bs5/css/buttons.bootstrap5.min.css"); diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/json-editor.css b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/json-editor.css new file mode 100644 index 0000000000..1d073f5fb8 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/json-editor.css @@ -0,0 +1,48 @@ +/* ========================================================================== + UnityJsonEditor - Reusable JSON editor component styles + ========================================================================== */ + +.uje-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; + gap: 0.75rem; +} + +.uje-status-text { + font-size: 0.8125rem; + color: #6c757d; +} + +.uje-textarea { + font-family: 'Cascadia Code', 'Fira Code', 'SF Mono', 'Consolas', 'Liberation Mono', 'Menlo', monospace; + font-size: 0.8125rem; + line-height: 1.5; + resize: vertical; + min-height: 120px; + tab-size: 2; + white-space: pre; + overflow-wrap: normal; + overflow-x: auto; +} + + .uje-textarea:focus { + border-color: #86b7fe; + box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.15); + } + + .uje-textarea[readonly] { + background-color: #f8f9fa; + cursor: default; + } + +.uje-status-bar { + margin-top: 0.5rem; + min-height: 1.5em; + font-size: 0.8125rem; +} + +.uje-validation-msg i { + font-size: 0.875rem; +} diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/json-editor.js b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/json-editor.js new file mode 100644 index 0000000000..875e862073 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/json-editor.js @@ -0,0 +1,600 @@ +/** + * UnityJsonEditor - A reusable, extensible JSON editor component. + * + * Provides a Bootstrap modal with a monospace textarea for editing JSON data, + * real-time validation, format/beautify, and file export/import capabilities. + * Supports custom validators for domain-specific rules. + * + * @requires jQuery, Bootstrap 5 + * + * @example + * // Basic usage + * const editor = new UnityJsonEditor({ + * title: 'Edit Mapping', + * onSave: function(data) { + * console.log('Saved:', data); + * } + * }); + * editor.open([{ key: 'value' }]); + * + * @example + * // With custom validators + * const editor = new UnityJsonEditor({ + * title: 'Edit Column Mapping', + * requiredFields: ['propertyName', 'columnName'], + * validators: [ + * { + * name: 'uniqueColumns', + * message: 'Column names must be unique', + * validate: function(data) { + * const cols = data.map(r => r.columnName); + * return new Set(cols).size === cols.length; + * } + * } + * ], + * onSave: function(data) { applyChanges(data); } + * }); + * + * @example + * // File operations (standalone, no modal needed) + * UnityJsonEditor.exportToFile(myData, 'config.json'); + * UnityJsonEditor.importFromFile({ accept: '.json' }).then(function(data) { + * console.log('Imported:', data); + * }); + */ +var UnityJsonEditor = (function ($) { + 'use strict'; + + let _instanceCount = 0; + + /** + * Default configuration options. + * @type {object} + */ + const DEFAULTS = { + /** Modal dialog title */ + title: 'Edit JSON', + + /** Bootstrap modal size class: 'modal-sm', 'modal-lg', 'modal-xl' */ + size: 'modal-lg', + + /** Number of visible rows in the textarea */ + rows: 18, + + /** Text for the save/apply button */ + saveButtonText: 'Apply Changes', + + /** Text for the cancel button */ + cancelButtonText: 'Cancel', + + /** Text for the format button */ + formatButtonText: 'Format JSON', + + /** Whether the modal should be read-only (disables editing and save) */ + readOnly: false, + + /** + * Array of field names required on each item when the data is an array of objects. + * Set to null or [] to skip required-field validation. + * @type {string[]|null} + */ + requiredFields: null, + + /** + * Custom validators array. Each validator is an object with: + * - name {string}: Identifier for the validator + * - message {string}: Message shown on failure + * - severity {string}: 'error' (default) blocks save; 'warning' allows save + * - validate {function(data): boolean}: Returns true if valid + * + * Validators run after JSON syntax and required-field checks pass. + * @type {Array<{name: string, message: string, severity?: string, validate: function}>} + */ + validators: [], + + /** + * Callback invoked when the user clicks Save/Apply and all validation passes. + * Receives the parsed JSON data and an array of active warnings. + * @type {function(data, warnings): void|null} + */ + onSave: null, + + /** + * Callback invoked when the user cancels or closes the modal. + * @type {function(): void|null} + */ + onCancel: null, + + /** + * Callback invoked when validation fails with errors. + * Receives an array of error objects: [{ validator: string, message: string }]. + * @type {function(errors): void|null} + */ + onValidationError: null + }; + + // ======================================================================== + // Constructor + // ======================================================================== + + /** + * Creates a new UnityJsonEditor instance. + * @param {object} opts - Configuration options (merged with DEFAULTS). + */ + function UnityJsonEditor(opts) { + this._id = ++_instanceCount; + this._opts = $.extend({}, DEFAULTS, opts); + this._modalId = 'unityJsonEditorModal_' + this._id; + this._modal = null; + this._bsModal = null; + this._textarea = null; + this._statusBar = null; + this._saveBtn = null; + this._lastWarnings = []; + this._built = false; + } + + // ======================================================================== + // Static helpers (usable without an instance) + // ======================================================================== + + /** + * Exports data as a downloadable JSON file. + * @param {*} data - Data to serialize. + * @param {string} [filename='export.json'] - Download filename. + */ + UnityJsonEditor.exportToFile = function (data, filename) { + filename = filename || 'export.json'; + var json = typeof data === 'string' ? data : JSON.stringify(data, null, 2); + var blob = new Blob([json], { type: 'application/json' }); + var url = URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + /** + * Opens a file picker and reads a JSON file. + * @param {object} [opts] - Options. + * @param {string} [opts.accept='.json'] - File input accept attribute. + * @param {Array<{name:string,message:string,severity?:string,validate:function}>} [opts.validators] - Optional validators to run on imported data. + * @param {function} [opts.onWarning] - Callback receiving an array of warning objects when warnings are present but no errors. + * @returns {Promise<*>} Resolves with parsed JSON data, rejects on error. + */ + UnityJsonEditor.importFromFile = function (opts) { + opts = opts || {}; + var accept = opts.accept || '.json'; + var validators = opts.validators || []; + var onWarning = opts.onWarning || null; + + return new Promise(function (resolve, reject) { + var input = document.createElement('input'); + input.type = 'file'; + input.accept = accept; + input.style.display = 'none'; + + input.addEventListener('change', function () { + var file = input.files && input.files[0]; + if (!file) { + reject(new Error('No file selected.')); + return; + } + var reader = new FileReader(); + reader.onload = function (e) { + try { + var data = JSON.parse(e.target.result); + + // Run validators if provided + var result = _runValidators(data, validators, null); + if (result.errors.length > 0) { + reject(new Error(result.errors.map(function (err) { return err.message; }).join('\n'))); + return; + } + + // Report warnings but allow import + if (result.warnings.length > 0 && typeof onWarning === 'function') { + onWarning(result.warnings); + } + + resolve(data); + } catch (ex) { + reject(new Error('Invalid JSON file: ' + ex.message)); + } + }; + reader.onerror = function () { + reject(new Error('Failed to read file.')); + }; + reader.readAsText(file); + }); + + document.body.appendChild(input); + input.click(); + document.body.removeChild(input); + }); + }; + + // ======================================================================== + // Prototype (instance methods) + // ======================================================================== + + UnityJsonEditor.prototype = { + constructor: UnityJsonEditor, + + /** + * Opens the editor modal with the given data. + * @param {*} data - Data to edit (will be serialized to JSON). + */ + open: function (data) { + if (!this._built) { + this._build(); + } + + var json = typeof data === 'string' ? data : JSON.stringify(data, null, 2); + this._textarea.val(json); + this._clearStatus(); + this._validate(); + this._bsModal.show(); + }, + + /** + * Closes the editor modal. + */ + close: function () { + if (this._bsModal) { + this._bsModal.hide(); + } + }, + + /** + * Returns the current parsed data from the editor, or null if invalid. + * @returns {*|null} + */ + getData: function () { + try { + return JSON.parse(this._textarea.val()); + } catch (_) { + return null; + } + }, + + /** + * Updates configuration options on the fly. + * @param {object} opts - Options to merge. + */ + setOptions: function (opts) { + $.extend(this._opts, opts); + if (this._built) { + this._updateUI(); + } + }, + + /** + * Destroys the editor instance and removes the modal from the DOM. + */ + destroy: function () { + if (this._bsModal) { + this._bsModal.dispose(); + } + if (this._modal) { + this._modal.remove(); + } + this._built = false; + }, + + // ==================================================================== + // Private methods + // ==================================================================== + + /** + * Builds the Bootstrap modal and appends it to the document body. + * @private + */ + _build: function () { + var self = this; + var opts = this._opts; + var id = this._modalId; + + var html = + ''; + + this._modal = $(html); + $('body').append(this._modal); + + this._textarea = this._modal.find('.uje-textarea'); + this._statusBar = this._modal.find('.uje-validation-msg'); + this._statusText = this._modal.find('.uje-status-text'); + this._saveBtn = this._modal.find('.uje-save-btn'); + this._bsModal = new bootstrap.Modal(document.getElementById(id)); + + // Event: real-time validation on input + this._textarea.on('input', function () { + self._validate(); + }); + + // Event: format button + this._modal.find('.uje-format-btn').on('click', function () { + self._format(); + }); + + // Event: save button + if (!opts.readOnly) { + this._saveBtn.on('click', function () { + self._onSave(); + }); + } + + // Event: modal hidden (cancel) + this._modal.on('hidden.bs.modal', function () { + if (typeof opts.onCancel === 'function') { + opts.onCancel(); + } + }); + + // Tab key inserts spaces instead of changing focus + this._textarea.on('keydown', function (e) { + if (e.key === 'Tab') { + e.preventDefault(); + var start = this.selectionStart; + var end = this.selectionEnd; + var value = $(this).val(); + $(this).val(value.substring(0, start) + ' ' + value.substring(end)); + this.selectionStart = this.selectionEnd = start + 2; + $(self._textarea).trigger('input'); + } + }); + + this._built = true; + }, + + /** + * Updates UI elements to reflect current options. + * @private + */ + _updateUI: function () { + var opts = this._opts; + this._modal.find('.modal-title').text(opts.title); + this._modal.find('.uje-format-btn').html('' + _escapeHtml(opts.formatButtonText)); + if (this._saveBtn.length) { + this._saveBtn.text(opts.saveButtonText); + } + this._textarea.prop('readonly', opts.readOnly); + }, + + /** + * Formats/beautifies the JSON in the textarea. + * @private + */ + _format: function () { + try { + var raw = this._textarea.val(); + var parsed = JSON.parse(raw); + this._textarea.val(JSON.stringify(parsed, null, 2)); + this._validate(); + } catch (_) { + // Validation will show the error + this._validate(); + } + }, + + /** + * Runs all validation checks and updates the UI. + * @private + * @returns {boolean} True if no errors (warnings are acceptable). + */ + _validate: function () { + var raw = this._textarea.val().trim(); + this._lastWarnings = []; + + // Empty check + if (!raw) { + this._showStatus('warning', 'Editor is empty'); + this._setSaveEnabled(false); + return false; + } + + // JSON syntax check + var data; + try { + data = JSON.parse(raw); + } catch (e) { + this._showStatus('error', 'Invalid JSON: ' + e.message); + this._setSaveEnabled(false); + return false; + } + + // Required fields check (only for arrays of objects) + var requiredFields = this._opts.requiredFields; + if (requiredFields && requiredFields.length > 0 && Array.isArray(data)) { + for (var i = 0; i < data.length; i++) { + if (typeof data[i] !== 'object' || data[i] === null) { + this._showStatus('error', 'Item at index ' + i + ' is not an object.'); + this._setSaveEnabled(false); + return false; + } + for (var f = 0; f < requiredFields.length; f++) { + var field = requiredFields[f]; + if (!(field in data[i]) || data[i][field] === null || data[i][field] === undefined) { + this._showStatus('error', 'Item at index ' + i + ' is missing required field "' + field + '".'); + this._setSaveEnabled(false); + return false; + } + } + } + } + + // Custom validators (errors + warnings) + var result = _runValidators(data, this._opts.validators, this._opts.requiredFields); + + // Errors block save + if (result.errors.length > 0) { + this._showStatus('error', result.errors[0].message); + this._setSaveEnabled(false); + if (typeof this._opts.onValidationError === 'function') { + this._opts.onValidationError(result.errors); + } + return false; + } + + // Warnings allow save but show amber status + var itemCount = Array.isArray(data) ? data.length + ' items' : 'Object'; + if (result.warnings.length > 0) { + this._lastWarnings = result.warnings; + var warningText = result.warnings[0].message; + this._showStatus('warning', 'Valid JSON \u00B7 ' + itemCount, '\u26A0 ' + warningText); + this._textarea.removeClass('is-invalid'); + this._setSaveEnabled(true); + return true; + } + + // All passed — no errors, no warnings + this._showStatus('success', 'Valid JSON \u00B7 ' + itemCount); + this._setSaveEnabled(true); + return true; + }, + + /** + * Handles the save action. + * @private + */ + _onSave: function () { + if (!this._validate()) return; + + var data = JSON.parse(this._textarea.val()); + var warnings = this._lastWarnings || []; + if (typeof this._opts.onSave === 'function') { + this._opts.onSave(data, warnings); + } + this.close(); + }, + + /** + * Displays a status message. + * @private + * @param {'success'|'error'|'warning'} type + * @param {string} message + */ + _showStatus: function (type, message, secondLine) { + var icon = type === 'success' ? 'fa-check-circle' : + type === 'error' ? 'fa-times-circle' : + 'fa-exclamation-circle'; + var colorClass = type === 'success' ? 'text-success' : + type === 'error' ? 'text-danger' : + 'text-warning'; + + var html = '' + _escapeHtml(message); + if (secondLine) { + html += '
' + _escapeHtml(secondLine); + } + + this._statusBar + .html(html) + .removeClass('text-success text-danger text-warning') + .addClass(colorClass); + + this._textarea + .toggleClass('is-invalid', type === 'error') + .toggleClass('is-valid', type === 'success'); + }, + + /** + * Clears the status bar. + * @private + */ + _clearStatus: function () { + this._statusBar.html('').removeClass('text-success text-danger text-warning'); + this._textarea.removeClass('is-invalid is-valid'); + }, + + /** + * Enables or disables the save button. + * @private + * @param {boolean} enabled + */ + _setSaveEnabled: function (enabled) { + if (this._saveBtn.length) { + this._saveBtn.prop('disabled', !enabled); + } + } + }; + + // ======================================================================== + // Private utility functions + // ======================================================================== + + /** + * Runs an array of custom validators against parsed data. + * Validators with severity:'warning' produce warnings (non-blocking); + * all others produce errors (blocking). + * @param {*} data - Parsed JSON data. + * @param {Array} validators - Validator definitions. + * @param {string[]|null} requiredFields - Required field names (for context). + * @returns {{errors: Array<{validator: string, message: string}>, warnings: Array<{validator: string, message: string}>}} + */ + function _runValidators(data, validators, requiredFields) { + var errors = []; + var warnings = []; + if (!validators || validators.length === 0) return { errors: errors, warnings: warnings }; + + for (var i = 0; i < validators.length; i++) { + var v = validators[i]; + var isWarning = v.severity === 'warning'; + var target = isWarning ? warnings : errors; + try { + var result = v.validate(data, requiredFields); + if (result === false) { + target.push({ validator: v.name, message: v.message || 'Validation failed: ' + v.name }); + } else if (typeof result === 'object' && result !== null && result.valid === false) { + target.push({ validator: v.name, message: result.message || v.message || 'Validation failed: ' + v.name }); + } + } catch (ex) { + errors.push({ validator: v.name, message: 'Validator "' + v.name + '" error: ' + ex.message }); + } + } + return { errors: errors, warnings: warnings }; + } + + /** + * Escapes HTML special characters. + * @param {string} str + * @returns {string} + */ + function _escapeHtml(str) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; + } + + return UnityJsonEditor; + +})(jQuery); From d74f47963bbb95207d52a78a1dd2a440335b766c Mon Sep 17 00:00:00 2001 From: aurelio-aot Date: Tue, 14 Apr 2026 14:26:20 -0700 Subject: [PATCH 17/58] AB#9205: Attachment Preview with Mammoth.JS and SheetJS --- .../Attachments/IS3PresignedUrlService.cs | 6 + .../Attachments/S3PresignedUrlService.cs | 57 +++++ .../Controllers/AttachmentController.cs | 71 +++++- .../Attachments/PreviewAttachmentModal.cshtml | 219 ++++++++++++++++++ .../PreviewAttachmentModal.cshtml.cs | 39 ++++ .../ChefsAttachments/ChefsAttachments.js | 34 ++- .../Shared/Components/_Shared/Attachments.css | 2 +- .../Shared/Components/_Shared/Attachments.js | 20 ++ .../abp.resourcemapping.js | 4 +- .../src/Unity.GrantManager.Web/package.json | 2 + .../src/Unity.GrantManager.Web/yarn.lock | 219 ++++++++++++++---- .../Components/AttachmentControllerTests.cs | 6 +- 12 files changed, 627 insertions(+), 52 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Attachments/IS3PresignedUrlService.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application/Attachments/S3PresignedUrlService.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Attachments/PreviewAttachmentModal.cshtml create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Attachments/PreviewAttachmentModal.cshtml.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Attachments/IS3PresignedUrlService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Attachments/IS3PresignedUrlService.cs new file mode 100644 index 0000000000..fc29ecf798 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Attachments/IS3PresignedUrlService.cs @@ -0,0 +1,6 @@ +namespace Unity.GrantManager.Attachments; + +public interface IS3PresignedUrlService +{ + string GetPresignedUrl(string s3ObjectKey, int expiryMinutes = 10); +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Attachments/S3PresignedUrlService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Attachments/S3PresignedUrlService.cs new file mode 100644 index 0000000000..023da1222c --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Attachments/S3PresignedUrlService.cs @@ -0,0 +1,57 @@ +using Amazon.S3; +using Amazon.S3.Model; +using Microsoft.Extensions.Configuration; +using System; +using System.Text.RegularExpressions; +using Volo.Abp.DependencyInjection; + +namespace Unity.GrantManager.Attachments; + +public partial class S3PresignedUrlService : IS3PresignedUrlService, ITransientDependency +{ + private readonly AmazonS3Client _amazonS3Client; + private readonly string _bucket; + + public S3PresignedUrlService(IConfiguration configuration) + { + _bucket = configuration["S3:Bucket"] ?? throw new InvalidOperationException("Missing configuration: S3:Bucket"); + + AmazonS3Config s3config = new() + { + RegionEndpoint = null, + ServiceURL = configuration["S3:Endpoint"], + AllowAutoRedirect = true, + ForcePathStyle = true + }; + + _amazonS3Client = new AmazonS3Client( + configuration["S3:AccessKeyId"], + configuration["S3:SecretAccessKey"], + s3config); + } + + public string GetPresignedUrl(string s3ObjectKey, int expiryMinutes = 10) + { + var request = new GetPreSignedUrlRequest + { + BucketName = _bucket, + Key = EscapeKeyFileName(s3ObjectKey), + Expires = DateTime.UtcNow.AddMinutes(expiryMinutes), + Verb = HttpVerb.GET + }; + + return _amazonS3Client.GetPreSignedURL(request); + } + + private static string EscapeKeyFileName(string s3ObjectKey) + { + Regex regex = S3KeysRegex(); + string[] keys = regex.Split(s3ObjectKey); + string escapedName = Uri.EscapeDataString(keys[^1]); + keys[^1] = escapedName; + return string.Join("", keys); + } + + [GeneratedRegex("(/)")] + private static partial Regex S3KeysRegex(); +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Controllers/AttachmentController.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Controllers/AttachmentController.cs index 2170ef2c1a..efc6f5329d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Controllers/AttachmentController.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Controllers/AttachmentController.cs @@ -28,6 +28,7 @@ public class AttachmentController : AbpController private readonly ISubmissionAppService _submissionAppService; private readonly IEmailLogAttachmentUploadService _emailLogAttachmentUploadService; private readonly ICurrentTenant _currentTenant; + private readonly IS3PresignedUrlService _s3PresignedUrlService; private ILogger logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); private const string badRequestFileMsg = "File name must be provided."; private const string NotFoundFileMsg = "File not found."; @@ -40,13 +41,15 @@ public AttachmentController( IConfiguration configuration, ISubmissionAppService submissionAppService, IEmailLogAttachmentUploadService emailLogAttachmentUploadService, - ICurrentTenant currentTenant) + ICurrentTenant currentTenant, + IS3PresignedUrlService s3PresignedUrlService) { _fileAppService = fileAppService; _configuration = configuration; _submissionAppService = submissionAppService; _emailLogAttachmentUploadService = emailLogAttachmentUploadService; _currentTenant = currentTenant; + _s3PresignedUrlService = s3PresignedUrlService; } [HttpGet("applicant/{applicantId}/download/{fileName}")] @@ -273,6 +276,72 @@ public async Task DownloadAllChefsAttachment([FromBody] ListGetApplicationPresignedUrl: {Message}", ex.Message); + return StatusCode(500, errorFileMsg); + } + } + + [HttpGet("assessment/{assessmentId}/presigned-url/{fileName}")] + public IActionResult GetAssessmentPresignedUrl(string assessmentId, string fileName) + { + if (string.IsNullOrWhiteSpace(assessmentId)) return BadRequest("Assessment ID must be provided."); + if (string.IsNullOrWhiteSpace(fileName)) return BadRequest(badRequestFileMsg); + + var folder = _configuration["S3:AssessmentS3Folder"] ?? throw new AbpValidationException("Missing server configuration: S3:AssessmentS3Folder"); + if (!folder.EndsWith('/')) folder += "/"; + var key = folder + assessmentId + "/" + fileName; + + try + { + var url = _s3PresignedUrlService.GetPresignedUrl(key); + return Ok(new { url }); + } + catch (Exception ex) + { + logger.LogError(ex, "AttachmentController->GetAssessmentPresignedUrl: {Message}", ex.Message); + return StatusCode(500, errorFileMsg); + } + } + + [HttpGet("applicant/{applicantId}/presigned-url/{fileName}")] + public IActionResult GetApplicantPresignedUrl(string applicantId, string fileName) + { + if (string.IsNullOrWhiteSpace(applicantId)) return BadRequest("Applicant ID must be provided."); + if (string.IsNullOrWhiteSpace(fileName)) return BadRequest(badRequestFileMsg); + + var folder = _configuration["S3:ApplicantS3Folder"] ?? throw new AbpValidationException("Missing server configuration: S3:ApplicantS3Folder"); + if (!folder.EndsWith('/')) folder += "/"; + var key = folder + applicantId + "/" + fileName; + + try + { + var url = _s3PresignedUrlService.GetPresignedUrl(key); + return Ok(new { url }); + } + catch (Exception ex) + { + logger.LogError(ex, "AttachmentController->GetApplicantPresignedUrl: {Message}", ex.Message); + return StatusCode(500, errorFileMsg); + } + } + [HttpPost("applicant/{applicantId}/upload")] #pragma warning disable IDE0060 // Remove unused parameter public async Task UploadApplicantAttachments(Guid applicantId, IList files, string userId, string userName) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Attachments/PreviewAttachmentModal.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Attachments/PreviewAttachmentModal.cshtml new file mode 100644 index 0000000000..23f97036db --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Attachments/PreviewAttachmentModal.cshtml @@ -0,0 +1,219 @@ +@page +@model Unity.GrantManager.Web.Pages.Attachments.PreviewAttachmentModalModel +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@{ + Layout = null; +} + + + + +
+
+ +
+
+ Loading... +
+
+ + + + + + + + + + + + + + + + +
+ + + Download + + + +
+ + diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Attachments/PreviewAttachmentModal.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Attachments/PreviewAttachmentModal.cshtml.cs new file mode 100644 index 0000000000..41f5af1ce4 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Attachments/PreviewAttachmentModal.cshtml.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Unity.GrantManager.Web.Pages.Attachments; + +public class PreviewAttachmentModalModel : AbpPageModel +{ + public string FileName { get; private set; } = ""; + public string DisplayName { get; private set; } = ""; + public string DownloadUrl { get; private set; } = ""; + public string AttachmentType { get; private set; } = ""; + public string PresignedUrlEndpoint { get; private set; } = ""; + +#pragma warning disable CS1998 + public async Task OnGetAsync( + string attachmentType, + string ownerId, + string fileName, + string? displayName = null, + string? chefsFileId = null) +#pragma warning restore CS1998 + { + FileName = fileName; + DisplayName = string.IsNullOrWhiteSpace(displayName) ? fileName : displayName; + AttachmentType = attachmentType.ToLower(); + + if (string.Equals(attachmentType, "chefs", StringComparison.OrdinalIgnoreCase)) + { + DownloadUrl = $"/api/app/attachment/chefs/{Uri.EscapeDataString(ownerId)}/download/{Uri.EscapeDataString(chefsFileId ?? string.Empty)}/{Uri.EscapeDataString(fileName)}"; + PresignedUrlEndpoint = ""; + } + else + { + DownloadUrl = $"/api/app/attachment/{AttachmentType}/{Uri.EscapeDataString(ownerId)}/download/{Uri.EscapeDataString(fileName)}"; + PresignedUrlEndpoint = $"/api/app/attachment/{AttachmentType}/{Uri.EscapeDataString(ownerId)}/presigned-url/{Uri.EscapeDataString(fileName)}"; + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.js index 0a71ea1f31..927149587e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.js @@ -116,10 +116,23 @@ $(function () { title: '', name: 'chefsFileDownload', data: 'chefsFileId', - width: '150px', + width: '220px', className: 'text-nowrap', render: function (data, type, full, meta) { let html = + '' + ' +
}
diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.js b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.js index 03dab4e59b..d719a36bf3 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Views/Shared/Components/ReportingConfiguration/Default.js @@ -440,7 +440,7 @@ $(function () { // Helper function to process detected changes alert (module-level to reduce nesting) function processDetectedChanges(detectedChanges) { - if (detectedChanges && detectedChanges.trim() !== '') { + if (detectedChanges?.trim() !== '') { setTimeout(function () { displayDetectedChangesAlert(detectedChanges); }, 100); @@ -834,16 +834,6 @@ $(function () { return reportingPanel?.classList.contains('show', 'active'); } - // Force table column width recalculation - function forceTableColumnAdjustment() { - if (!dataTable) return; - try { - dataTable.columns.adjust(); - } catch (error) { - console.warn('Error during force column adjustment:', error); - } - } - // Column name sanitization function function sanitizeColumnName(name) { let sanitized = sanitizeSqlNames(name); @@ -1534,7 +1524,7 @@ $(function () { try { const errorResponse = JSON.parse(xhr.responseText); if (errorResponse?.error?.message) { - errorMessage = errorResponse.error.message; + errorMessage = errorResponse?.error?.message; } else if (typeof errorResponse === 'string') { errorMessage = errorResponse; } else { @@ -1556,9 +1546,9 @@ $(function () { try { const parsedError = JSON.parse(xhr.responseText); if (parsedError?.error?.message) { - errorMessage = parsedError.error.message; - } else if (parsedError.message) { - errorMessage = parsedError.message; + errorMessage = parsedError?.error?.message; + } else if (parsedError?.message) { + errorMessage = parsedError?.message; } else { errorMessage = xhr.responseText; } @@ -1597,7 +1587,7 @@ $(function () { const viewName = result.viewName; const $deleteViewListItem = $('#deleteViewListItem'); - if (viewName && viewName.trim() !== '') { + if (viewName?.trim() !== '') { // Update the view name in the list item and show it $deleteViewListItem.html(`The associated database view (${viewName})`); $deleteViewListItem.show(); @@ -1698,7 +1688,7 @@ $(function () { } else if (xhr.status === 400) { const errorResponse = JSON.parse(xhr.responseText); if (errorResponse?.error?.message) { - errorMessage = errorResponse.error.message; + errorMessage = errorResponse?.error?.message; } else if (typeof errorResponse === 'string') { errorMessage = errorResponse; } else { @@ -1715,9 +1705,9 @@ $(function () { } else if (xhr.responseText) { const parsedError = JSON.parse(xhr.responseText); if (parsedError?.error?.message) { - errorMessage = parsedError.error.message; - } else if (parsedError.message) { - errorMessage = parsedError.message; + errorMessage = parsedError?.error?.message; + } else if (parsedError?.message) { + errorMessage = parsedError?.message; } else { errorMessage = xhr.responseText; } @@ -1807,11 +1797,11 @@ $(function () { */ function isViewGenerated() { // Check hidden field first (server-rendered initial state) - var status = ($('#reportingViewStatus').val() || '').toUpperCase(); + const status = ($('#reportingViewStatus').val() || '').toUpperCase(); if (status === 'SUCCESS') return true; // Fallback: inspect the view-status widget DOM - var $widget = $('.view-status-compact'); + const $widget = $('.view-status-compact'); if ($widget.find('.fl-checkmark').length > 0) return true; return false; @@ -1826,22 +1816,22 @@ $(function () { if (!dataTable) return; // Build a lookup: propertyName → columnName - var lookup = {}; + const lookup = {}; mappingArray.forEach(function (item) { lookup[item.propertyName] = item.columnName; }); - var appliedCount = 0; + let appliedCount = 0; dataTable.rows().every(function () { - var rowData = this.data(); - var node = this.node(); - var $input = $(node).find('.column-name-input'); + const rowData = this.data(); + const node = this.node(); + const $input = $(node).find('.column-name-input'); if (!$input.length) return; - var propertyName = rowData.key; + const propertyName = rowData.key; if (propertyName in lookup) { - var newValue = sanitizeColumnName(lookup[propertyName] || ''); + const newValue = sanitizeColumnName(lookup[propertyName] || ''); if ($input.val() !== newValue) { $input.val(newValue); validateColumnNameInput($input, newValue, $input.data('path')); @@ -1858,7 +1848,7 @@ $(function () { } // Shared validators for the JSON editor and file import - var mappingValidators = [ + const mappingValidators = [ { name: 'isArray', message: 'JSON must be an array of objects.', @@ -1870,7 +1860,7 @@ $(function () { severity: 'warning', validate: function (data) { if (!Array.isArray(data)) return true; - var names = data.map(function (r) { return r.propertyName; }); + const names = data.map(function (r) { return r.propertyName; }); return new Set(names).size === names.length; } }, @@ -1879,7 +1869,7 @@ $(function () { message: 'Duplicate columnName values found. Each columnName must be unique.', validate: function (data) { if (!Array.isArray(data)) return true; - var cols = data.filter(function (r) { return r.columnName; }) + const cols = data.filter(function (r) { return r.columnName; }) .map(function (r) { return r.columnName.toLowerCase(); }); return new Set(cols).size === cols.length; } @@ -1920,14 +1910,14 @@ $(function () { ]; // Create the editor instance (lazy – modal built on first use) - var mappingEditor = new UnityJsonEditor({ + const mappingEditor = new UnityJsonEditor({ title: 'Edit Column Mapping', requiredFields: ['propertyName', 'columnName'], validators: mappingValidators, onSave: function (data, warnings) { - var count = applyJsonMappingToTable(data); - var msg = count + ' column mapping(s) updated. Remember to Save to persist changes.'; - if (warnings && warnings.length > 0) { + const count = applyJsonMappingToTable(data); + const msg = count + ' column mapping(s) updated. Remember to Save to persist changes.'; + if (warnings?.length > 0) { abp.message.warn(msg + '\n\n\u26A0 ' + warnings.map(function (w) { return w.message; }).join('\n')); } else { abp.message.success(msg); @@ -1943,15 +1933,15 @@ $(function () { return; } - var mapping = buildJsonMapping(); + const mapping = buildJsonMapping(); if (mapping.length === 0) { abp.message.warn('No mapping data to export.'); return; } - var provider = getCorrelationProvider(); - var correlationId = getCurrentCorrelationId(); - var filename = 'column-mapping-' + provider + '-' + (correlationId || 'new') + '.json'; + const provider = getCorrelationProvider(); + const correlationId = getCurrentCorrelationId(); + const filename = 'column-mapping-' + provider + '-' + (correlationId || 'new') + '.json'; UnityJsonEditor.exportToFile(mapping, filename); }); @@ -1968,7 +1958,7 @@ $(function () { return; } - var importWarnings = []; + let importWarnings = []; UnityJsonEditor.importFromFile({ validators: mappingValidators.concat([ { @@ -1987,8 +1977,8 @@ $(function () { importWarnings = warnings; } }).then(function (data) { - var count = applyJsonMappingToTable(data); - var msg = count + ' column mapping(s) imported. Remember to Save to persist changes.'; + const count = applyJsonMappingToTable(data); + const msg = count + ' column mapping(s) imported. Remember to Save to persist changes.'; if (importWarnings.length > 0) { abp.message.warn(msg + '\n\n\u26A0 ' + importWarnings.map(function (w) { return w.message; }).join('\n')); } else { @@ -2012,7 +2002,7 @@ $(function () { return; } - var mapping = buildJsonMapping(); + const mapping = buildJsonMapping(); mappingEditor.open(mapping); }); @@ -2098,7 +2088,7 @@ $(function () { // Keep the hidden view-status field in sync when async generation completes PubSub.subscribe('view_generation_completed', function (msg, data) { - if (data && data.finalStatus) { + if (data?.finalStatus) { $('#reportingViewStatus').val(data.finalStatus); } }); diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/json-editor.js b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/json-editor.js index 0e6e97bffa..396e7cd6b0 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/json-editor.js +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/json-editor.js @@ -147,15 +147,15 @@ var UnityJsonEditor = (function ($) { */ UnityJsonEditor.exportToFile = function (data, filename) { filename = filename || 'export.json'; - var json = typeof data === 'string' ? data : JSON.stringify(data, null, 2); - var blob = new Blob([json], { type: 'application/json' }); - var url = URL.createObjectURL(blob); - var a = document.createElement('a'); + const json = typeof data === 'string' ? data : JSON.stringify(data, null, 2); + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); - document.body.removeChild(a); + a.remove(); setTimeout(function () { URL.revokeObjectURL(url); }, 100); }; @@ -169,53 +169,44 @@ var UnityJsonEditor = (function ($) { */ UnityJsonEditor.importFromFile = function (opts) { opts = opts || {}; - var accept = opts.accept || '.json'; - var validators = opts.validators || []; - var onWarning = opts.onWarning || null; + const accept = opts.accept || '.json'; + const validators = opts.validators || []; + const onWarning = opts.onWarning || null; return new Promise(function (resolve, reject) { - var input = document.createElement('input'); + const input = document.createElement('input'); input.type = 'file'; input.accept = accept; input.style.display = 'none'; input.addEventListener('change', function () { // Remove from DOM now that the browser has fired the change event - if (input.parentNode) { - input.parentNode.removeChild(input); - } + input.remove(); - var file = input.files && input.files[0]; + const file = input.files && input.files[0]; if (!file) { reject(new Error('No file selected.')); return; } - var reader = new FileReader(); - reader.onload = function (e) { - try { - var data = JSON.parse(e.target.result); - - // Run validators if provided - var result = _runValidators(data, validators, null); - if (result.errors.length > 0) { - reject(new Error(result.errors.map(function (err) { return err.message; }).join('\n'))); - return; - } - - // Report warnings but allow import - if (result.warnings.length > 0 && typeof onWarning === 'function') { - onWarning(result.warnings); - } + file.text().then(function (text) { + const data = JSON.parse(text); + + // Run validators if provided + const result = _runValidators(data, validators, null); + if (result.errors.length > 0) { + reject(new Error(result.errors.map(function (err) { return err.message; }).join('\n'))); + return; + } - resolve(data); - } catch (ex) { - reject(new Error('Invalid JSON file: ' + ex.message)); + // Report warnings but allow import + if (result.warnings.length > 0 && typeof onWarning === 'function') { + onWarning(result.warnings); } - }; - reader.onerror = function () { - reject(new Error('Failed to read file.')); - }; - reader.readAsText(file); + + resolve(data); + }).catch(function (ex) { + reject(new Error('Invalid JSON file: ' + ex.message)); + }); }); document.body.appendChild(input); @@ -240,7 +231,7 @@ var UnityJsonEditor = (function ($) { } this._savedFlag = false; - var json = typeof data === 'string' ? data : JSON.stringify(data, null, 2); + const json = typeof data === 'string' ? data : JSON.stringify(data, null, 2); this._textarea.val(json); this._clearStatus(); this._validate(); @@ -301,11 +292,11 @@ var UnityJsonEditor = (function ($) { * @private */ _build: function () { - var self = this; - var opts = this._opts; - var id = this._modalId; + const self = this; + const opts = this._opts; + const id = this._modalId; - var html = + const html = '