From 9080c295111b4b00a6f7c4b7d5caa0fe4e5f7911 Mon Sep 17 00:00:00 2001 From: Jacob Smith Date: Fri, 15 May 2026 09:42:44 -0700 Subject: [PATCH 01/63] AB#33053 fix AI cooldown wait state --- .../IAttachmentSummaryAppService.cs | 2 - .../IApplicationAIGenerationQueue.cs | 3 +- .../AI/Operations/AttachmentSummaryService.cs | 23 +++++- .../Operations/IAttachmentSummaryService.cs | 2 +- .../AttachmentSummaryAppService.cs | 36 +-------- ...erateAttachmentSummaryBackgroundJobArgs.cs | 4 + .../IGrantApplicationAppService.cs | 2 +- .../QueueAttachmentSummaryRequestDto.cs | 10 +++ .../ApplicationAIGenerationQueue.cs | 4 +- .../GenerateApplicationAnalysisJob.cs | 2 +- .../GenerateApplicationScoringJob.cs | 2 +- .../GenerateAttachmentSummaryJob.cs | 4 +- .../RunApplicationAIPipelineJob.cs | 4 +- .../GrantApplicationAppService.cs | 78 ++++++++++++++----- .../ChefsAttachments/ChefsAttachments.js | 60 +++++++++----- .../Components/ReviewList/ReviewList.js | 26 ++++--- 16 files changed, 161 insertions(+), 101 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/QueueAttachmentSummaryRequestDto.cs diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Attachments/IAttachmentSummaryAppService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Attachments/IAttachmentSummaryAppService.cs index 7b572a070..e82a6f766 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Attachments/IAttachmentSummaryAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Attachments/IAttachmentSummaryAppService.cs @@ -7,7 +7,5 @@ namespace Unity.GrantManager.Attachments; public interface IAttachmentSummaryAppService : IApplicationService { - Task GenerateAttachmentSummaryAsync(Guid attachmentId, string? promptVersion = null); - Task> GenerateAttachmentSummariesAsync(List attachmentIds, string? promptVersion = null); Task> GenerateAttachmentSummariesForPipelineAsync(List attachmentIds, string? promptVersion = null); } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Automation/IApplicationAIGenerationQueue.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Automation/IApplicationAIGenerationQueue.cs index f7837b6c5..e7c0dbbb6 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Automation/IApplicationAIGenerationQueue.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Automation/IApplicationAIGenerationQueue.cs @@ -1,11 +1,12 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace Unity.AI.Automation; public interface IApplicationAIGenerationQueue { - Task QueueAttachmentSummaryAsync(Guid applicationId, Guid? tenantId, string? promptVersion = null); + Task QueueAttachmentSummaryAsync(Guid applicationId, Guid? tenantId, string? promptVersion = null, List? attachmentIds = null); Task QueueApplicationAnalysisAsync(Guid applicationId, Guid? tenantId, string? promptVersion = null); Task QueueApplicationScoringAsync(Guid applicationId, Guid? tenantId, string? promptVersion = null); Task QueueAllAIStagesAsync(Guid applicationId, Guid? tenantId, string? promptVersion = null); diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/AI/Operations/AttachmentSummaryService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/AI/Operations/AttachmentSummaryService.cs index 156ccfda9..78762cea4 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/AI/Operations/AttachmentSummaryService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/AI/Operations/AttachmentSummaryService.cs @@ -97,13 +97,30 @@ private async Task GenerateOrFallbackAsync( } } - public async Task> GenerateForApplicationAsync(Guid applicationId, string? promptVersion = null, CancellationToken cancellationToken = default) + public async Task> GenerateForApplicationAsync( + Guid applicationId, + string? promptVersion = null, + IReadOnlyCollection? attachmentIds = null, + CancellationToken cancellationToken = default) { - var attachmentIds = (await applicationChefsFileAttachmentRepository.GetListAsync(a => a.ApplicationId == applicationId)) + var applicationAttachmentIds = (await applicationChefsFileAttachmentRepository.GetListAsync(a => a.ApplicationId == applicationId)) .Select(a => a.Id) .ToList(); - return await GenerateAndSaveAsync(attachmentIds, promptVersion, cancellationToken); + if (attachmentIds is not { Count: > 0 }) + { + return await GenerateAndSaveAsync(applicationAttachmentIds, promptVersion, cancellationToken); + } + + var applicationAttachmentIdSet = applicationAttachmentIds.ToHashSet(); + var selectedIds = attachmentIds.Distinct().ToList(); + + if (selectedIds.Any(id => !applicationAttachmentIdSet.Contains(id))) + { + throw new InvalidOperationException("One or more selected attachments do not belong to the application."); + } + + return await GenerateAndSaveAsync(selectedIds, promptVersion, cancellationToken); } private async Task OpenAttachmentStreamAsync( diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/AI/Operations/IAttachmentSummaryService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/AI/Operations/IAttachmentSummaryService.cs index feed62805..af2e30144 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/AI/Operations/IAttachmentSummaryService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/AI/Operations/IAttachmentSummaryService.cs @@ -9,5 +9,5 @@ public interface IAttachmentSummaryService { Task GenerateAndSaveAsync(Guid attachmentId, string? promptVersion = null, CancellationToken cancellationToken = default); Task> GenerateAndSaveAsync(IEnumerable attachmentIds, string? promptVersion = null, CancellationToken cancellationToken = default); - Task> GenerateForApplicationAsync(Guid applicationId, string? promptVersion = null, CancellationToken cancellationToken = default); + Task> GenerateForApplicationAsync(Guid applicationId, string? promptVersion = null, IReadOnlyCollection? attachmentIds = null, CancellationToken cancellationToken = default); } 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 6e1cedc43..1a5cdbfe4 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 @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using Unity.AI; -using Unity.AI.Features; -using Unity.AI.Localization; using Unity.AI.Operations; using Unity.AI.Permissions; using Unity.AI.Settings; @@ -15,40 +13,8 @@ namespace Unity.GrantManager.Attachments; [Authorize(AIPermissions.Analysis.GenerateAttachmentSummaries)] [ExposeServices(typeof(AttachmentSummaryAppService), typeof(IAttachmentSummaryAppService))] public class AttachmentSummaryAppService( - IAttachmentSummaryService attachmentSummaryService, - AIFeatureGuard featureGuard) : AIAppService, IAttachmentSummaryAppService + IAttachmentSummaryService attachmentSummaryService) : AIAppService, IAttachmentSummaryAppService { - public virtual async Task GenerateAttachmentSummaryAsync(System.Guid attachmentId, string? promptVersion = null) - { - await featureGuard.EnsureEnabledAsync( - AIFeatures.AttachmentSummaries, - AILocalizationKeys.AttachmentSummariesDisabled); - - await attachmentSummaryService.GenerateAndSaveAsync(attachmentId, promptVersion); - return new AttachmentSummaryResultDto { Completed = true }; - } - - public virtual async Task> GenerateAttachmentSummariesAsync(List attachmentIds, string? promptVersion = null) - { - await featureGuard.EnsureEnabledAsync( - AIFeatures.AttachmentSummaries, - AILocalizationKeys.AttachmentSummariesDisabled); - - if (attachmentIds.Count == 0) - { - return []; - } - - var results = new List(); - foreach (var attachmentId in attachmentIds) - { - await attachmentSummaryService.GenerateAndSaveAsync(attachmentId, promptVersion); - results.Add(new AttachmentSummaryResultDto { Completed = true }); - } - - return results; - } - // Internal-only: no HTTP endpoint, no auth check — safe for background job callers [AllowAnonymous] [RemoteService(IsEnabled = false)] diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/GenerateAttachmentSummaryBackgroundJobArgs.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/GenerateAttachmentSummaryBackgroundJobArgs.cs index af4a185c6..7b2b4f6d6 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/GenerateAttachmentSummaryBackgroundJobArgs.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/GenerateAttachmentSummaryBackgroundJobArgs.cs @@ -1,10 +1,14 @@ using System; +using System.Collections.Generic; + namespace Unity.GrantManager.GrantApplications.Automation.BackgroundJobs; + public class GenerateAttachmentSummaryBackgroundJobArgs { public Guid ApplicationId { get; set; } public Guid? TenantId { get; set; } public Guid? RequestedByUserId { get; set; } + public List? AttachmentIds { get; set; } public string? PromptVersion { get; set; } public string RequestKey { get; set; } = string.Empty; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IGrantApplicationAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IGrantApplicationAppService.cs index c6a3db376..1c876c142 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IGrantApplicationAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IGrantApplicationAppService.cs @@ -20,7 +20,7 @@ public interface IGrantApplicationAppService Task TriggerAction(Guid applicationId, GrantApplicationAction triggerAction); Task QueueAIGenerationAsync(Guid applicationId, string? promptVersion = null); Task QueueApplicationAnalysisAsync(Guid applicationId, string? promptVersion = null); - Task QueueAttachmentSummaryAsync(Guid applicationId, string? promptVersion = null); + Task QueueAttachmentSummaryAsync(QueueAttachmentSummaryRequestDto input, string? promptVersion = null); Task QueueApplicationScoringAsync(Guid applicationId, string? promptVersion = null); Task GetAIGenerationStatusAsync(Guid applicationId, string operationType, string? promptVersion = null); Task QueueAllAIStagesAsync(Guid applicationId, string? promptVersion = null); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/QueueAttachmentSummaryRequestDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/QueueAttachmentSummaryRequestDto.cs new file mode 100644 index 000000000..dd89554bf --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/QueueAttachmentSummaryRequestDto.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace Unity.GrantManager.GrantApplications; + +public class QueueAttachmentSummaryRequestDto +{ + public Guid ApplicationId { get; set; } + public List? AttachmentIds { get; set; } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/ApplicationAIGenerationQueue.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/ApplicationAIGenerationQueue.cs index 3c42325d0..6eec9bc4a 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/ApplicationAIGenerationQueue.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/ApplicationAIGenerationQueue.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Unity.AI.Automation; @@ -23,7 +24,7 @@ public class ApplicationAIGenerationQueue( ILogger logger) : IApplicationAIGenerationQueue, ITransientDependency { - public async Task QueueAttachmentSummaryAsync(Guid applicationId, Guid? tenantId, string? promptVersion = null) + public async Task QueueAttachmentSummaryAsync(Guid applicationId, Guid? tenantId, string? promptVersion = null, List? attachmentIds = null) { var requestKey = AIGenerationRequestKeyHelper.BuildRequestKey(tenantId, applicationId, AIGenerationRequestKeyHelper.AttachmentSummaryOperationType); await EnsureRequestAndEnqueueAsync( @@ -36,6 +37,7 @@ await EnsureRequestAndEnqueueAsync( return backgroundJobManager.EnqueueAsync(new GenerateAttachmentSummaryBackgroundJobArgs { ApplicationId = applicationId, + AttachmentIds = attachmentIds, PromptVersion = promptVersion, RequestedByUserId = currentUser.Id, TenantId = tenantId, diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateApplicationAnalysisJob.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateApplicationAnalysisJob.cs index 35d3f554c..2094abad6 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateApplicationAnalysisJob.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateApplicationAnalysisJob.cs @@ -31,8 +31,8 @@ public override async Task ExecuteAsync(GenerateApplicationAnalysisBackgroundJob await applicationAnalysisService.RegenerateAndSaveAsync(args.ApplicationId, args.PromptVersion); logger.LogInformation("Completed AI application analysis job for application {ApplicationId}.", args.ApplicationId); - await AIGenerationRequestJobHelper.MarkCompletedInNewUowAsync(unitOfWorkManager, generationRequestRepository, args.RequestKey); await AIGenerationRequestJobHelper.StampRateLimitBestEffortAsync(aiRateLimiter, logger, args.RequestedByUserId, args.ApplicationId, args.RequestKey); + await AIGenerationRequestJobHelper.MarkCompletedInNewUowAsync(unitOfWorkManager, generationRequestRepository, args.RequestKey); } catch (Exception ex) { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateApplicationScoringJob.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateApplicationScoringJob.cs index 2292324a5..cd4d2513a 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateApplicationScoringJob.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateApplicationScoringJob.cs @@ -40,8 +40,8 @@ await localEventBus.PublishAsync(new ApplicationAIScoringGeneratedEvent } logger.LogInformation("Completed AI application scoring job for application {ApplicationId}.", args.ApplicationId); - await AIGenerationRequestJobHelper.MarkCompletedInNewUowAsync(unitOfWorkManager, generationRequestRepository, args.RequestKey); await AIGenerationRequestJobHelper.StampRateLimitBestEffortAsync(aiRateLimiter, logger, args.RequestedByUserId, args.ApplicationId, args.RequestKey); + await AIGenerationRequestJobHelper.MarkCompletedInNewUowAsync(unitOfWorkManager, generationRequestRepository, args.RequestKey); } catch (Exception ex) { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateAttachmentSummaryJob.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateAttachmentSummaryJob.cs index 63507cd58..036c9b582 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateAttachmentSummaryJob.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateAttachmentSummaryJob.cs @@ -30,10 +30,10 @@ public override async Task ExecuteAsync(GenerateAttachmentSummaryBackgroundJobAr logger.LogInformation( "Executing AI attachment summary job for application {ApplicationId}.", args.ApplicationId); - await attachmentSummaryService.GenerateForApplicationAsync(args.ApplicationId, args.PromptVersion); + await attachmentSummaryService.GenerateForApplicationAsync(args.ApplicationId, args.PromptVersion, args.AttachmentIds); - await AIGenerationRequestJobHelper.MarkCompletedInNewUowAsync(unitOfWorkManager, generationRequestRepository, args.RequestKey); await AIGenerationRequestJobHelper.StampRateLimitBestEffortAsync(aiRateLimiter, logger, args.RequestedByUserId, args.ApplicationId, args.RequestKey); + await AIGenerationRequestJobHelper.MarkCompletedInNewUowAsync(unitOfWorkManager, generationRequestRepository, args.RequestKey); } catch (Exception ex) { 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 2a46fc353..269d08aae 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 @@ -51,8 +51,8 @@ public override async Task ExecuteAsync(RunApplicationAIPipelineJobArgs args) if (!attachmentSummariesEnabled && !applicationAnalysisEnabled && !scoringEnabled) { logger.LogDebug("All AI features are disabled, skipping queued AI generation for application {ApplicationId}.", args.ApplicationId); - await AIGenerationRequestJobHelper.MarkCompletedInNewUowAsync(unitOfWorkManager, generationRequestRepository, args.RequestKey); await AIGenerationRequestJobHelper.StampRateLimitBestEffortAsync(aiRateLimiter, logger, args.RequestedByUserId, args.ApplicationId, args.RequestKey); + await AIGenerationRequestJobHelper.MarkCompletedInNewUowAsync(unitOfWorkManager, generationRequestRepository, args.RequestKey); return; } @@ -115,8 +115,8 @@ await localEventBus.PublishAsync(new ApplicationAIScoringGeneratedEvent throw analysisException; } - await AIGenerationRequestJobHelper.MarkCompletedInNewUowAsync(unitOfWorkManager, generationRequestRepository, args.RequestKey); await AIGenerationRequestJobHelper.StampRateLimitBestEffortAsync(aiRateLimiter, logger, args.RequestedByUserId, args.ApplicationId, args.RequestKey); + await AIGenerationRequestJobHelper.MarkCompletedInNewUowAsync(unitOfWorkManager, generationRequestRepository, args.RequestKey); } catch (Exception ex) { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs index 462bb0d17..6178fea11 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs @@ -45,9 +45,10 @@ namespace Unity.GrantManager.GrantApplications; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(GrantApplicationAppService), typeof(IGrantApplicationAppService))] public class GrantApplicationAppService( - IApplicationManager applicationManager, - IApplicationRepository applicationRepository, - IApplicationStatusRepository applicationStatusRepository, + IApplicationManager applicationManager, + IApplicationRepository applicationRepository, + IApplicationChefsFileAttachmentRepository applicationChefsFileAttachmentRepository, + IApplicationStatusRepository applicationStatusRepository, IApplicationFormSubmissionRepository applicationFormSubmissionRepository, IApplicantRepository applicantRepository, IApplicationFormRepository applicationFormRepository, @@ -1183,15 +1184,16 @@ public async Task QueueApplicationAnalysisAsync(Guid app } [Authorize(AIPermissions.Analysis.GenerateAttachmentSummaries)] - public async Task QueueAttachmentSummaryAsync(Guid applicationId, string? promptVersion = null) - { - await EnsureAttachmentSummariesEnabledAsync(); - await aiGenerationQueue.QueueAttachmentSummaryAsync(applicationId, CurrentTenant.Id, promptVersion); - - var request = await aiGenerationStatusAppService.GetLatestAsync( - applicationId, - AIGenerationRequestKeyHelper.AttachmentSummaryOperationType, - CurrentTenant.Id); + public async Task QueueAttachmentSummaryAsync(QueueAttachmentSummaryRequestDto input, string? promptVersion = null) + { + await EnsureAttachmentSummariesEnabledAsync(); + var attachmentIds = await ResolveAttachmentSummaryIdsAsync(input); + await aiGenerationQueue.QueueAttachmentSummaryAsync(input.ApplicationId, CurrentTenant.Id, promptVersion, attachmentIds); + + var request = await aiGenerationStatusAppService.GetLatestAsync( + input.ApplicationId, + AIGenerationRequestKeyHelper.AttachmentSummaryOperationType, + CurrentTenant.Id); return request ?? throw new UserFriendlyException("Unable to queue AI attachment summary request."); } @@ -1257,13 +1259,49 @@ private async Task EnsureAIGenerationStatusAccessAsync(string operationType) } } - private async Task EnsureAttachmentSummariesEnabledAsync() - { - if (!await featureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries")) - { - throw new UserFriendlyException("AI attachment summaries are not enabled."); - } - } + private async Task EnsureAttachmentSummariesEnabledAsync() + { + if (!await featureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries")) + { + throw new UserFriendlyException("AI attachment summaries are not enabled."); + } + } + + private async Task> ResolveAttachmentSummaryIdsAsync(QueueAttachmentSummaryRequestDto input) + { + if (input == null) + { + throw new UserFriendlyException("Attachment summary request is required."); + } + + if (input.ApplicationId == Guid.Empty) + { + throw new UserFriendlyException("Application id is required."); + } + + var applicationAttachmentIds = (await applicationChefsFileAttachmentRepository.GetListAsync(a => a.ApplicationId == input.ApplicationId)) + .Select(a => a.Id) + .ToList(); + + if (applicationAttachmentIds.Count == 0) + { + throw new UserFriendlyException("No attachments were found to generate summaries."); + } + + if (input.AttachmentIds is not { Count: > 0 }) + { + return applicationAttachmentIds; + } + + var applicationAttachmentIdSet = applicationAttachmentIds.ToHashSet(); + var selectedAttachmentIds = input.AttachmentIds.Distinct().ToList(); + if (selectedAttachmentIds.Any(id => !applicationAttachmentIdSet.Contains(id))) + { + throw new UserFriendlyException("One or more selected attachments do not belong to the application."); + } + + return selectedAttachmentIds; + } private async Task EnsureAIAnalysisEnabledAsync() { @@ -1424,4 +1462,4 @@ private static void UpdateFindingDismissedState(IEnumerableGenerating...' - ) - .prop('disabled', true); globalThis.AIGenerationButtonState?.setGenerating($activeButton); - $.ajax({ - url: '/api/app/attachment-summary/generate-attachment-summaries', - data: JSON.stringify(summaryAttachmentIds), - contentType: 'application/json', - type: 'POST', - success: function () { + const monitorAttachmentSummaries = () => globalThis.AIGenerationButtonState.monitor({ + $button: $activeButton, + originalHtml: existingHTML, + getStatus: () => unity.grantManager.grantApplications.grantApplication + .getAIGenerationStatus(getApplicationId(), 'attachment-summary'), + onComplete: () => { resetAttachmentSelection(); chefsDataTable.ajax.reload(); abp.notify.success('AI summaries generated successfully.'); - globalThis.AIGenerationButtonState?.restore($activeButton); - $activeButton.html(existingHTML).prop('disabled', false); }, - error: function (error) { - console.error('Error generating AI summaries:', error); - abp.message.error('An error occurred while generating AI summaries. Please try again.'); + onFailed: (request) => abp.message.error(request?.failureReason || 'AI summary generation failed.') + }); + + unity.grantManager.grantApplications.grantApplication + .queueAttachmentSummary({ + applicationId: getApplicationId(), + attachmentIds: summaryAttachmentIds + }) + .done(function (request) { + const status = globalThis.AIGenerationButtonState?.resolveStatus(request?.status) ?? ''; + + if (status === 'Completed') { + resetAttachmentSelection(); + chefsDataTable.ajax.reload(); + abp.notify.success('AI summaries generated successfully.'); + globalThis.AIGenerationButtonState?.restore($activeButton); + $activeButton.html(existingHTML).prop('disabled', false); + globalThis.refreshAIRateLimitState?.(); + return; + } + + monitorAttachmentSummaries(); + }) + .fail(function (error) { + console.error('Error queueing AI summaries:', error); + abp.message.error('An error occurred while queueing AI summaries. Please try again.'); globalThis.AIGenerationButtonState?.restore($activeButton); $activeButton.html(existingHTML).prop('disabled', false); setGenerateSummariesEnabled(); - }, - }); + }); }); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ReviewList/ReviewList.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ReviewList/ReviewList.js index a75af55ef..92df6b622 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ReviewList/ReviewList.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ReviewList/ReviewList.js @@ -424,16 +424,22 @@ function updateAiActionButtonsVisibility(dataTableContext, rowsData) { $('#CloneButton').toggle(hasAiAssessment); } } -function renderUnityWorkflowButton(actionValue) { - let buttonConfig = actionButtonConfigMap[actionValue] ?? actionButtonConfigMap['_Fallback'] - - return { - extend: buttonConfig.buttonType, - name: actionValue, - sortOrder: buttonConfig.order ?? 100, - attr: { id: `${actionValue}Button` } - }; -} +function renderUnityWorkflowButton(actionValue) { + let buttonConfig = actionButtonConfigMap[actionValue] ?? actionButtonConfigMap['_Fallback'] + + const button = { + extend: buttonConfig.buttonType, + name: actionValue, + sortOrder: buttonConfig.order ?? 100, + attr: { id: `${actionValue}Button` } + }; + + if (actionValue === 'Generate') { + button.className = 'ai-generate-btn'; + } + + return button; +} /* Cutom Unity Workflow Buttons */ function unityWorkflowButtonText(dt, button, config) { From 235e0314089c76edd13050d1d4bfa1409a814e73 Mon Sep 17 00:00:00 2001 From: Jacob Smith Date: Fri, 15 May 2026 10:51:04 -0700 Subject: [PATCH 02/63] AB#33047 collapse dismissed AI analysis sections --- .../Pages/GrantApplications/ai-analysis.js | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ai-analysis.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ai-analysis.js index 322acadab..c708ff907 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ai-analysis.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ai-analysis.js @@ -164,21 +164,37 @@ function configureSectionStatus($status, text, statusClass) { .show(); } -function configureCollapseToggle($section, $collapseToggle) { +function setSectionCollapsed($section, $collapseToggle, isCollapsed) { const labels = getAnalysisLabels(); + const $icon = $collapseToggle.find('i'); + + $section.toggleClass('collapsed', isCollapsed); + $collapseToggle + .attr('aria-expanded', (!isCollapsed).toString()) + .attr('title', isCollapsed ? labels.expandTitle : labels.collapseTitle); + + $icon + .toggleClass('fa-chevron-down', !isCollapsed) + .toggleClass('fa-chevron-up', isCollapsed); +} + +function syncSectionCollapseWithVisibleItems($section, $items, $collapseToggle) { + const hasVisibleItems = $items + .children('.ai-analysis-detail-item') + .filter(function() { + return this.style.display !== 'none'; + }) + .length > 0; + + setSectionCollapsed($section, $collapseToggle, !hasVisibleItems); +} + +function configureCollapseToggle($section, $collapseToggle) { $collapseToggle .off('click') .on('click', function() { const isCollapsed = $section.toggleClass('collapsed').hasClass('collapsed'); - const $icon = $(this).find('i'); - - $(this) - .attr('aria-expanded', (!isCollapsed).toString()) - .attr('title', isCollapsed ? labels.expandTitle : labels.collapseTitle); - - $icon - .toggleClass('fa-chevron-down', !isCollapsed) - .toggleClass('fa-chevron-up', isCollapsed); + setSectionCollapsed($section, $(this), isCollapsed); }); } @@ -214,7 +230,7 @@ function appendSectionItems($items, section, isDismissedVisible) { updateVisibleItemLayout($items); } -function configureDismissedItemsToggle($items, $toggle, section, isDismissedVisible) { +function configureDismissedItemsToggle($section, $items, $toggle, $collapseToggle, section, isDismissedVisible) { const labels = getAnalysisLabels(); const hiddenCount = section.hiddenItems.length; @@ -239,6 +255,7 @@ function configureDismissedItemsToggle($items, $toggle, section, isDismissedVisi dismissedSectionVisibility[section.itemType] = shouldShow; $items.find('.dismissed-item').toggle(shouldShow); updateVisibleItemLayout($items); + syncSectionCollapseWithVisibleItems($section, $items, $collapseToggle); $toggle.text(shouldShow ? labels.hideDismissed : labels.showDismissed); }); } @@ -250,7 +267,6 @@ function renderSection(section) { $section .addClass(section.sectionClass) - .toggleClass('compact', section.activeItems.length === 0) .toggleClass('header-only', !section.hasItems); const $items = $section.find('[data-element="items"]'); @@ -264,7 +280,8 @@ function renderSection(section) { $collapseToggle.toggle(section.hasItems); appendSectionItems($items, section, isDismissedVisible); - configureDismissedItemsToggle($items, $toggle, section, isDismissedVisible); + configureDismissedItemsToggle($section, $items, $toggle, $collapseToggle, section, isDismissedVisible); + syncSectionCollapseWithVisibleItems($section, $items, $collapseToggle); return $section; } From 113cd56c12450d0a1f30b45ab01dc7790523f985 Mon Sep 17 00:00:00 2001 From: Jacob Smith Date: Fri, 15 May 2026 11:30:15 -0700 Subject: [PATCH 03/63] AB#33047 collapse dismissed AI analysis sections --- .../Pages/GrantApplications/Details.css | 11 +++++---- .../Pages/GrantApplications/ai-analysis.js | 24 ++++++++++++------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.css index 790e48491..9adb01aaf 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.css +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.css @@ -538,11 +538,6 @@ form label.error { line-height: 1.6; } -.ai-analysis-section.compact .ai-analysis-section-body { - padding-top: 10px; - padding-bottom: 10px; -} - .ai-analysis-section.header-only .ai-analysis-section-body { display: none; } @@ -645,6 +640,12 @@ form label.error { background-color: #eef2f6; } +.ai-analysis-collapse-toggle:disabled { + color: #9ca3af; + cursor: default; + pointer-events: none; +} + .ai-analysis-collapse-toggle:focus, .ai-analysis-collapse-toggle:focus-visible { outline: none; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ai-analysis.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ai-analysis.js index c708ff907..cdadaff88 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ai-analysis.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ai-analysis.js @@ -134,14 +134,20 @@ function createFindingItem(item, type, hidden) { function updateVisibleItemLayout($items) { const $allItems = $items.children('.ai-analysis-detail-item'); - const $visibleItems = $allItems.filter(function() { - return this.style.display !== 'none'; - }); + const $visibleItems = getVisibleAnalysisItems($items); $allItems.removeClass('last-visible'); $visibleItems.last().addClass('last-visible'); } +function getVisibleAnalysisItems($items) { + return $items + .children('.ai-analysis-detail-item') + .filter(function() { + return this.style.display !== 'none'; + }); +} + function formatSectionTitle(title, count) { return `${title} (${count})`; } @@ -179,13 +185,9 @@ function setSectionCollapsed($section, $collapseToggle, isCollapsed) { } function syncSectionCollapseWithVisibleItems($section, $items, $collapseToggle) { - const hasVisibleItems = $items - .children('.ai-analysis-detail-item') - .filter(function() { - return this.style.display !== 'none'; - }) - .length > 0; + const hasVisibleItems = getVisibleAnalysisItems($items).length > 0; + $collapseToggle.prop('disabled', !hasVisibleItems); setSectionCollapsed($section, $collapseToggle, !hasVisibleItems); } @@ -193,6 +195,10 @@ function configureCollapseToggle($section, $collapseToggle) { $collapseToggle .off('click') .on('click', function() { + if ($(this).prop('disabled')) { + return; + } + const isCollapsed = $section.toggleClass('collapsed').hasClass('collapsed'); setSectionCollapsed($section, $(this), isCollapsed); }); From ee139ad17f0260d32ea7dbc11ca4806cb4f34495 Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Fri, 15 May 2026 12:42:22 -0700 Subject: [PATCH 04/63] AB#31228 - Added today date to email template. --- .../NotificationsDataSeedContributor.cs | 11 ++++---- .../Shared/Components/EmailsWidget/Default.js | 26 ++++++++++++++++--- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/NotificationsDataSeedContributor.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/NotificationsDataSeedContributor.cs index c47d8a0b0..caa34d854 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/NotificationsDataSeedContributor.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/NotificationsDataSeedContributor.cs @@ -44,7 +44,8 @@ public async Task SeedAsync(DataSeedContext context) new() { Name = "Applicant ID", Token = "applicant_id", MapTo = "applicant.unityApplicantId" }, new() { Name = "Requested Amount", Token = "requested_amount", MapTo = "requestedAmount" }, new() { Name = "Recommended Amount", Token = "recommended_amount", MapTo = "recommendedAmount" }, - new() { Name = "Unity Application ID", Token = "unity_application_id", MapTo = "unityApplicationId" } + new() { Name = "Unity Application ID", Token = "unity_application_id", MapTo = "unityApplicationId" }, + new() { Name = "Today's Date", Token = "today_date", MapTo = "" } }; try @@ -74,9 +75,9 @@ await templateVariablesRepository.InsertAsync( var emailGroups = new List { - new() {Name = "FSB-AP", Description = "This group manages the recipients for PO-related payments, which will be sent to FSB-AP to update contracts and initiate payment creation.",Type = "static"}, - new() {Name = "Payments", Description = "This group manages the recipients for payment notifications, such as failures or errors",Type = "static"} - }; + new() {Name = "FSB-AP", Description = "This group manages the recipients for PO-related payments, which will be sent to FSB-AP to update contracts and initiate payment creation.",Type = "static"}, + new() {Name = "Payments", Description = "This group manages the recipients for payment notifications, such as failures or errors",Type = "static"} + }; try { var allGroups = await emailGroupsRepository.GetListAsync(); @@ -111,4 +112,4 @@ internal class EmailTempateVariableDto public string Token { get; set; } = string.Empty; public string MapTo { get; set; } = string.Empty; } -} +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js index 7a5188757..b66618f3e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js @@ -613,6 +613,26 @@ return inputString; } + const buildTodayDateSpan = () => new Handlebars.SafeString(buildTodayDateHtml()); + + const buildTodayDateHtml = () => { + const formatted = new Intl.DateTimeFormat('en-CA', { + timeZone: 'America/Vancouver', + year: 'numeric', + month: 'long', + day: 'numeric' + }).format(new Date()); + return `${formatted}`; + }; + + const refreshTodayDateSpans = (html) => { + if (!html) return html; + return html.replaceAll( + /]*data-token="today_date"[^>]*>.*?<\/span>/gi, + buildTodayDateHtml() + ); + }; + function extractTemplateData(apiResponse, mappingConfig) { const templateData = {}; @@ -620,7 +640,7 @@ const { token, mapTo } = mapping; if (!mapTo) { - templateData[token] = ""; // handle empty MapTo + templateData[token] = token === 'today_date' ? buildTodayDateSpan() : ""; return; } @@ -690,7 +710,7 @@ handleDraftChange(); }); editorInstance = editor; - editorInstance.setContent(data.body); + editorInstance.setContent(refreshTodayDateSpans(data.body)); } }); UIElements.inputEmailTo.val(data.toAddress); @@ -698,7 +718,7 @@ UIElements.inputEmailBCC.val(data.bcc ? data.bcc.replace(/,/g, '; ') : ''); UIElements.inputEmailFrom.val(data.fromAddress); UIElements.inputEmailSubject.val(data.subject); - UIElements.inputEmailBody.val(data.body); + UIElements.inputEmailBody.val(refreshTodayDateSpans(data.body)); const isDraft = data?.status === 'Draft'; if (isDraft) { From a3661fd6c0370ada7ed46032e539d079b6bd6f7c Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Fri, 15 May 2026 12:48:36 -0700 Subject: [PATCH 05/63] AB#32619 update datagrid editor to open modal with fields in table order --- .../Worksheets/CustomFieldValueAppService.cs | 7 +- .../DataGrid/EditDataRowModal.cshtml.cs | 22 +++++-- .../Components/DataGridWidget/Default.js | 65 ++++++++++--------- 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Worksheets/CustomFieldValueAppService.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Worksheets/CustomFieldValueAppService.cs index c4bb4261d..5f0accc30 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Worksheets/CustomFieldValueAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Worksheets/CustomFieldValueAppService.cs @@ -27,7 +27,12 @@ public async Task ExplicitSetAsync(Guid valueId, string value) [RemoteService(false)] public async Task ExplicitAddAsync(CustomFieldValueDto value) { - await customFieldValueRepository.InsertAsync(ObjectMapper.Map(value)); + var entity = new CustomFieldValue( + value.Id, + value.WorksheetInstanceId, + value.CustomFieldId, + value.CurrentValue ?? "{}"); + await customFieldValueRepository.InsertAsync(entity); } [RemoteService(false)] diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/Components/DataGrid/EditDataRowModal.cshtml.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/Components/DataGrid/EditDataRowModal.cshtml.cs index 50fc621fa..ebe5fe096 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/Components/DataGrid/EditDataRowModal.cshtml.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Pages/Components/DataGrid/EditDataRowModal.cshtml.cs @@ -68,7 +68,8 @@ public async Task OnGetAsync(Guid valueId, Guid worksheetInstanceId, Guid formVersionId, Guid applicationId, - string uiAnchor) + string uiAnchor, + string columnOrder = "") { Row = row; ValueId = valueId; @@ -114,7 +115,7 @@ public async Task OnGetAsync(Guid valueId, DynamicKeyMap = JsonSerializer.Serialize(keyMap); - AllFields = MergeAndSortFields(DynamicFields ?? [], Properties ?? []); + AllFields = MergeAndSortFields(DynamicFields ?? [], Properties ?? [], columnOrder); } private static DynamicFieldMap[] PrefixDynamicFields(DynamicFieldMap[] dynamicFieldMaps) @@ -246,15 +247,26 @@ private static void ApplyDynamicFieldPresentationFormat( private sealed record DynamicKeyMapEntry(string Name, string Type, bool IsDynamic = true); - private static List MergeAndSortFields(DynamicFieldMap[] dynamicFields, List customFields) + private static List MergeAndSortFields(DynamicFieldMap[] dynamicFields, List customFields, string columnOrder) { + var columnKeys = columnOrder.Split(',', StringSplitOptions.RemoveEmptyEntries); + var orderMap = columnKeys + .Select((key, idx) => (key, idx)) + .ToDictionary(t => t.key, t => t.idx, StringComparer.OrdinalIgnoreCase); + + int GetOrder(string key) => orderMap.TryGetValue(key, out var idx) ? idx : int.MaxValue; + var fields = new List(); foreach (var df in dynamicFields) { + var rawKey = df.Key.StartsWith(DynamicFieldPrefix, StringComparison.Ordinal) + ? df.Key[DynamicFieldPrefix.Length..] + : df.Key; fields.Add(new EditRowField { SortKey = df.Name, + SortOrder = GetOrder(rawKey), DynamicField = df }); } @@ -264,16 +276,18 @@ private static List MergeAndSortFields(DynamicFieldMap[] dynamicFi fields.Add(new EditRowField { SortKey = cf.Label, + SortOrder = GetOrder(cf.Name), CustomField = cf }); } - return [.. fields.OrderBy(f => f.SortKey, StringComparer.OrdinalIgnoreCase)]; + return [.. fields.OrderBy(f => f.SortOrder).ThenBy(f => f.SortKey, StringComparer.OrdinalIgnoreCase)]; } public class EditRowField { public string SortKey { get; set; } = string.Empty; + public int SortOrder { get; set; } = int.MaxValue; public WorksheetFieldViewModel? CustomField { get; set; } public DynamicFieldMap? DynamicField { get; set; } public bool IsDynamic => DynamicField != null; diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/Default.js b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/Default.js index 6e1c2b2ed..7fd001ce6 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/DataGridWidget/Default.js @@ -162,31 +162,32 @@ $(function () { } return -1; // Return -1 if the column is not found } - function openEditDatagridRowModal(valueId, - fieldId, - worksheetId, - worksheetInstanceId, - row, - isNew, - uiAnchor) { - + function openEditDatagridRowModal(options) { let formVersionId = $('#ApplicationFormVersionId').val(); let applicationId = $('#DetailsViewApplicationId').val(); editDatagridRowModal.open({ - valueId: valueId, - fieldId: fieldId, - row: row, - isNew: isNew, - worksheetId: worksheetId, - worksheetInstanceId: worksheetInstanceId, + valueId: options.valueId, + fieldId: options.fieldId, + row: options.row, + isNew: options.isNew, + worksheetId: options.worksheetId, + worksheetInstanceId: options.worksheetInstanceId, // There is dependency here on the core module and details page ! formVersionId: formVersionId, applicationId: applicationId, - uiAnchor: uiAnchor + uiAnchor: options.uiAnchor, + columnOrder: options.columnOrder || '' }); } + function getColumnOrder(dt) { + return dt.columns().header().toArray() + .map(th => $(th).data('key')) + .filter(key => key !== undefined && key !== null) + .join(','); + } + let actionButtons = [ { id: 'AddRecord', @@ -201,13 +202,16 @@ $(function () { let tableElement = $('#' + tableId); let tableDataSet = tableElement[0].dataset; - openEditDatagridRowModal(tableDataSet.valueId, - tableDataSet.fieldId, - tableDataSet.wsId, - tableDataSet.wsiId, - 0, - true, - tableDataSet.wsAnchor); + openEditDatagridRowModal({ + valueId: tableDataSet.valueId, + fieldId: tableDataSet.fieldId, + worksheetId: tableDataSet.wsId, + worksheetInstanceId: tableDataSet.wsiId, + row: 0, + isNew: true, + uiAnchor: tableDataSet.wsAnchor, + columnOrder: getColumnOrder(dt) + }); } }, { @@ -320,13 +324,16 @@ $(function () { let table = $(button).closest('table'); let tableDataSet = table[0].dataset; - openEditDatagridRowModal(tableDataSet.valueId, - tableDataSet.fieldId, - tableDataSet.wsId, - tableDataSet.wsiId, - rowDataSet.rowNo, - false, - tableDataSet.uiAnchor); + openEditDatagridRowModal({ + valueId: tableDataSet.valueId, + fieldId: tableDataSet.fieldId, + worksheetId: tableDataSet.wsId, + worksheetInstanceId: tableDataSet.wsiId, + row: rowDataSet.rowNo, + isNew: false, + uiAnchor: tableDataSet.uiAnchor, + columnOrder: getColumnOrder(table.DataTable()) + }); } PubSub.subscribe( From acc8db957c9beda2091eeebfd17bd8c8dad7c629 Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Fri, 15 May 2026 12:51:58 -0700 Subject: [PATCH 06/63] Using newline safe pattern for the span Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Views/Shared/Components/EmailsWidget/Default.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js index b66618f3e..ba75a8b60 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js @@ -628,7 +628,7 @@ const refreshTodayDateSpans = (html) => { if (!html) return html; return html.replaceAll( - /]*data-token="today_date"[^>]*>.*?<\/span>/gi, + /]*data-token="today_date"[^>]*>[\s\S]*?<\/span>/gi, buildTodayDateHtml() ); }; From e92a4917a85259a24f6bd6c061484126c4389579 Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Fri, 15 May 2026 15:10:30 -0700 Subject: [PATCH 07/63] AB#31883 improve timezone offset --- .../UnityThemeUX2GlobalScriptContributor.cs | 1 + .../Themes/UX2/Layouts/Application.cshtml | 2 + .../wwwroot/js/TimezoneBootstrap.js | 2 + .../wwwroot/js/TimezoneUtils.js | 34 +++++++++++++++ .../GrantManagerWebModule.cs | 2 + .../Middleware/TimezoneMiddleware.cs | 41 +++++++++++++++++++ .../src/Unity.GrantManager.Web/Pages/Index.js | 16 -------- 7 files changed, 82 insertions(+), 16 deletions(-) create mode 100644 applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneBootstrap.js create mode 100644 applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneUtils.js create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/TimezoneMiddleware.cs 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 2540f8c39..ac4c3a644 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 @@ -53,5 +53,6 @@ public override void ConfigureBundle(BundleConfigurationContext context) context.Files.Add("/themes/ux2/json-editor.js"); context.Files.Add("/js/DateUtils.js"); context.Files.Add("/js/AnalyticsUtils.js"); + context.Files.Add("/js/TimezoneUtils.js"); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Layouts/Application.cshtml b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Layouts/Application.cshtml index dd5831ac8..66260ee2e 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Layouts/Application.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Layouts/Application.cshtml @@ -130,6 +130,8 @@ AnalyticsUtils.initMatomo(window.MATOMO_URL, 1); } })(); + + TimezoneUtils.setTimezoneCookie(); @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Application) diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneBootstrap.js b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneBootstrap.js new file mode 100644 index 000000000..e8c0ed556 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneBootstrap.js @@ -0,0 +1,2 @@ +TimezoneUtils.setTimezoneCookie(); +location.reload(); diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneUtils.js b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneUtils.js new file mode 100644 index 000000000..4346a56cf --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneUtils.js @@ -0,0 +1,34 @@ +/** + * TimezoneUtils.js + * + * Utility functions for handling timezone-related operations, such as setting a cookie with the user's timezone offset. + */ +const TimezoneUtils = (function () { + 'use strict'; + + function setCookie(cname, cvalue, exdays) { + const d = new Date(); + d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); + let expires = "expires=" + d.toUTCString(); + document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/;domain=" + + globalThis.location.hostname + ";SameSite=Lax;secure;"; + } + + function setTimezoneCookie() { + const cookieName = "timezoneoffset"; + const currentOffset = new Date().getTimezoneOffset(); + + const existing = document.cookie + .split('; ') + .find(row => row.startsWith(cookieName + '=')) + ?.split('=')[1]; + + if (existing === undefined || Number.parseInt(existing, 10) !== currentOffset) { + setCookie(cookieName, currentOffset, 1); + } + } + + return { + setTimezoneCookie: setTimezoneCookie + }; +})(); \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/GrantManagerWebModule.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/GrantManagerWebModule.cs index 081fa8dd7..21f6b2627 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/GrantManagerWebModule.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/GrantManagerWebModule.cs @@ -35,6 +35,7 @@ using Unity.GrantManager.Web.Exceptions; using Unity.GrantManager.Web.Filters; using Unity.GrantManager.Web.Identity; +using Unity.GrantManager.Web.Middleware; using Unity.GrantManager.Web.Identity.Policy; using Unity.GrantManager.Web.Menus; using Unity.GrantManager.Web.Settings; @@ -591,6 +592,7 @@ public override void OnApplicationInitialization(ApplicationInitializationContex app.UseCorrelationId(); app.UseStaticFiles(); + app.UseMiddleware(); app.UseRouting(); app.UseAuthentication(); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/TimezoneMiddleware.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/TimezoneMiddleware.cs new file mode 100644 index 000000000..be1f2e3fd --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/TimezoneMiddleware.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Unity.GrantManager.Web.Middleware; + +public class TimezoneMiddleware(RequestDelegate next) +{ + private const string CookieName = "timezoneoffset"; + + public async Task InvokeAsync(HttpContext context) + { + if (ShouldIntercept(context)) + { + context.Response.ContentType = "text/html; charset=utf-8"; + await context.Response.WriteAsync(""" + + + + + + + + + """); + return; + } + + await next(context); + } + + private static bool ShouldIntercept(HttpContext context) + { + var request = context.Request; + + return request.Method == HttpMethods.Get + && !request.Cookies.ContainsKey(CookieName) + && !request.Path.StartsWithSegments("/api") + && !request.Headers.ContainsKey("X-Requested-With") + && request.Headers.Accept.ToString().Contains("text/html"); + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Index.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Index.js index e59af14a2..ed6b9961f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Index.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Index.js @@ -11,23 +11,7 @@ $(function () { }); }); - $(function () { - setTimezoneCookie(); - - function setTimezoneCookie() { - let timezone_cookie = "timezoneoffset"; - setCookie(timezone_cookie, new Date().getTimezoneOffset(), 5); - } - - function setCookie(cname, cvalue, exdays) { - const d = new Date(); - d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); - let expires = "expires=" + d.toUTCString(); - document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/;domain=" + - window.location.hostname + ";SameSite=Lax;secure;"; - } - $('.btn-forms-templte').on("click", function (e) { document.getElementById("formsTemplateLibrary").style.display = "block"; document.getElementById("welcomeContent").style.display = "none"; From 069bdbe9e6a8722a718328802c6266a34946a0b8 Mon Sep 17 00:00:00 2001 From: aurelio-aot Date: Fri, 15 May 2026 15:10:45 -0700 Subject: [PATCH 08/63] AB#27996: Refactor CreatePaymentRequests to Create Reusable Service Class --- .../CreatePaymentRequests.cshtml.cs | 402 +----------------- .../Pages/PaymentRequests/IPaymentFormItem.cs | 16 + .../PaymentRequestPageHelperService.cs | 348 +++++++++++++++ .../Pages/PaymentRequests/PaymentsModel.cs | 2 +- .../Unity.Payments.Web/PaymentsWebModule.cs | 2 + 5 files changed, 382 insertions(+), 388 deletions(-) create mode 100644 applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/IPaymentFormItem.cs create mode 100644 applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentRequestPageHelperService.cs diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreatePaymentRequests.cshtml.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreatePaymentRequests.cshtml.cs index 53e372265..0197e3c61 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreatePaymentRequests.cshtml.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreatePaymentRequests.cshtml.cs @@ -2,23 +2,20 @@ using System.Collections.Generic; using System; using System.Diagnostics.CodeAnalysis; -using Unity.Payments.Suppliers; using Unity.Payments.PaymentRequests; using System.Threading.Tasks; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; using Unity.Payments.PaymentConfigurations; using Unity.GrantManager.GrantApplications; -using Unity.Payments.Domain.Suppliers; -using System.Linq; -using Unity.GrantManager.Payments; using Unity.GrantManager.Applications; -using Unity.GrantManager.ApplicationForms; using Volo.Abp; -using Unity.Payments.Enums; +using System.Linq; using Microsoft.Extensions.Logging; using Unity.Payments.Domain.AccountCodings; using Microsoft.AspNetCore.Authorization; using Unity.Payments.Permissions; +using Unity.Payments.Domain.Suppliers; +using Unity.GrantManager.Payments; namespace Unity.Payments.Web.Pages.Payments @@ -29,13 +26,11 @@ public class CreatePaymentRequestsModel( IGrantApplicationAppService applicationService, IPaymentRequestAppService paymentRequestAppService, IPaymentConfigurationAppService paymentConfigurationAppService, - ISupplierAppService iSupplierAppService, ISiteRepository siteRepository, IPaymentSettingsAppService paymentSettingsAppService, - IApplicationLinksService applicationLinksService, IApplicationFormRepository applicationFormRepository, - IApplicationFormAppService applicationFormAppService, - ApplicationIdsCacheService cacheService + ApplicationIdsCacheService cacheService, + PaymentRequestPageHelperService helperService ) : AbpPageModel { @@ -58,7 +53,7 @@ ApplicationIdsCacheService cacheService public string? DefaultAccountCodingId { get; set; } [BindProperty] - public string? AccountCodingOverride { get; set; } + public string? AccountCodingOverride { get; set; } [BindProperty] public string BatchNumberDisplay { get; set; } = string.Empty; @@ -119,7 +114,7 @@ public async Task OnGetAsync(string cacheKey) foreach (var application in applications) { - decimal remainingAmount = await GetRemainingAmountAllowedByApplicationAsync(application); + decimal remainingAmount = await helperService.GetRemainingAmountAsync(application); // Grabs the Account Coding ID from the Application Form and if there is none then the Payment Configuration // If neither exist then an error on the payment request will be shown @@ -141,12 +136,13 @@ public async Task OnGetAsync(string cacheKey) AccountCodingId = accountCodingId }; - var supplier = await GetSupplierByApplicationAync(application); - string supplierNumber = supplier?.Number?? string.Empty; + var supplier = await helperService.GetSupplierAsync(application); + string supplierNumber = supplier?.Number ?? string.Empty; Guid siteId = application.DefaultSiteId ?? Guid.Empty; Site? site = null; - if(siteId != Guid.Empty) { + if (siteId != Guid.Empty) + { site = await siteRepository.GetAsync(siteId); var siteName = $"{site.Number} ({supplierNumber}, {site.City})"; request.SiteName = siteName; @@ -156,7 +152,7 @@ public async Task OnGetAsync(string cacheKey) request.SupplierName = supplier?.Name; request.SupplierNumber = supplierNumber; - var (errorList, parentReferenceNo) = await GetErrorlist(supplier, site, application, applicationForm, remainingAmount, accountCodingId); + var (errorList, parentReferenceNo) = await helperService.GetErrorListAsync(supplier, site, application, applicationForm, remainingAmount, accountCodingId); request.ErrorList = errorList; request.ParentReferenceNo = parentReferenceNo; @@ -168,183 +164,22 @@ public async Task OnGetAsync(string cacheKey) ApplicationPaymentRequestForm!.Add(request); } - ApplicationPaymentRequestForm = SortPaymentRequestsByHierarchy(ApplicationPaymentRequestForm); + ApplicationPaymentRequestForm = helperService.SortByHierarchy(ApplicationPaymentRequestForm); // Populate parent-child validation data for frontend - await PopulateParentChildValidationData(); + await helperService.PopulateParentChildValidationDataAsync(ApplicationPaymentRequestForm); var batchName = await paymentRequestAppService.GetNextBatchInfoAsync(); BatchNumberDisplay = batchName; TotalAmount = ApplicationPaymentRequestForm?.Sum(x => x.Amount) ?? 0m; } - private async Task<(List ErrorList, string? ParentReferenceNo)> GetErrorlist(SupplierDto? supplier, Site? site, GrantApplicationDto application, ApplicationForm applicationForm, decimal remainingAmount, Guid? accountCodingId) - { - bool missingFields = false; - - List errorList = []; - if (supplier == null || site == null || string.IsNullOrWhiteSpace(supplier.Number)) - { - missingFields = true; - } - - // If the site paygroup is eft but there is no bank account - if(site != null && site.PaymentGroup == PaymentGroup.EFT && string.IsNullOrWhiteSpace(site.BankAccount)) - { - errorList.Add("Payment cannot be submitted because the default site’s pay group is set to EFT, but no bank account is configured. Please update the bank account before proceeding."); - } - - if (remainingAmount <= 0) - { - errorList.Add("There is no remaining amount for this application."); - } - - if (missingFields) - { - errorList.Add("Some payment information is missing for this applicant. Please make sure supplier information is provided and default site is selected."); - } - - if (application.StatusCode != GrantApplicationState.GRANT_APPROVED) - { - errorList.Add("The selected Application is not Approved. To continue please remove the item from the list."); - } - - var allLinks = await applicationLinksService.GetListByApplicationAsync(application.Id); - var parentLink = allLinks.Find(link => link.LinkType == ApplicationLinkType.Parent && link.ApplicationId != application.Id); - if (parentLink != null) - { - var parentApplication = await applicationService.GetAsync(parentLink.ApplicationId); - if (parentApplication.Id == Guid.Empty || parentApplication.StatusCode != GrantApplicationState.GRANT_APPROVED) - { - errorList.Add("Payment cannot be processed because the linked parent submission is not approved. Please ensure the parent submission is approved before creating a payment."); - } - } - - if (!application.ApplicationForm.Payable) - { - errorList.Add("The selected application is not Payable. To continue please remove the item from the list."); - } - - if(accountCodingId == null || accountCodingId == Guid.Empty) - { - errorList.Add("The selected application form does not have an Account Coding or no default Account Coding is set."); - } - - // Add form hierarchy and parent link validation - var (hierarchyErrors, parentReferenceNo) = await ValidateFormHierarchyAndParentLink(application, applicationForm); - errorList.AddRange(hierarchyErrors); - - return (errorList, parentReferenceNo); - } - - private async Task GetRemainingAmountAllowedByApplicationAsync(GrantApplicationDto application) - { - decimal remainingAmount = 0; - // Calculate the "Future paid amount" and if it is more than Approved Amount, the system shall: - // Highlight the record - // Show error message: This payment exceeds the Approved Amount. - // Future paid amount: Total Pending Amount + Total Paid amount + Amount that is in the current payment request - if (application.ApprovedAmount > 0) - { - decimal approvedAmmount = application.ApprovedAmount; - decimal totalFutureRequested = await paymentRequestAppService.GetTotalPaymentRequestAmountByCorrelationIdAsync(application.Id); - - // If this application has children, include their paid/pending amounts too - decimal childrenTotalPaidPending = 0; - var applicationLinks = await applicationLinksService.GetListByApplicationAsync(application.Id); - var childLinks = applicationLinks.Where(link => link.LinkType == ApplicationLinkType.Child && link.ApplicationId != application.Id).ToList(); - - if (childLinks.Count > 0) - { - // This is a parent application, sum up all children's paid/pending payments - foreach (var childLink in childLinks) - { - decimal childTotal = await paymentRequestAppService - .GetTotalPaymentRequestAmountByCorrelationIdAsync(childLink.ApplicationId); - childrenTotalPaidPending += childTotal; - } - } - - // Calculate remaining: Approved - (Parent Paid/Pending + Children Paid/Pending) - decimal totalConsumed = totalFutureRequested + childrenTotalPaidPending; - if (approvedAmmount > totalConsumed) - { - remainingAmount = approvedAmmount - totalConsumed; - } - } - - return remainingAmount; - } - - private async Task GetSupplierByApplicationAync(GrantApplicationDto application) - { - if (application.Applicant.SupplierId != Guid.Empty) - { - return await iSupplierAppService.GetAsync(application.Applicant.SupplierId); - } - - return null; - } - - private async Task<(List Errors, string? ParentReferenceNo)> ValidateFormHierarchyAndParentLink( - GrantApplicationDto application, - ApplicationForm applicationForm) - { - List errors = []; - string? parentReferenceNo = null; - - // Only validate if form is payable and has Child hierarchy - if (!applicationForm.Payable || - !applicationForm.FormHierarchy.HasValue || - applicationForm.FormHierarchy.Value != FormHierarchyType.Child) - { - return (errors, parentReferenceNo); // No validation needed - } - - // Check if ParentFormId is set - if (!applicationForm.ParentFormId.HasValue) - { - // Configuration issue - should not happen if validation works - return (errors, parentReferenceNo); - } - - // Get parent links for this application - var allLinks = await applicationLinksService.GetListByApplicationAsync(application.Id); - var parentLink = allLinks.Find(link => link.LinkType == ApplicationLinkType.Parent && link.ApplicationId != application.Id); - - // Rule 2: No parent link exists - if (parentLink == null) - { - errors.Add("Payment Configuration for this form requires a valid parent application link before payments can be processed."); - return (errors, parentReferenceNo); - } - - // Rule 1: Parent link exists but doesn't match Payment Configuration - // Get the parent application's form version details - var parentFormDetails = await applicationFormAppService.GetFormDetailsByApplicationIdAsync(parentLink.ApplicationId); - - // If validation passed, get the parent application's reference number - var parentApplication = await applicationService.GetAsync(parentLink.ApplicationId); - parentReferenceNo = parentApplication.ReferenceNo; - - // Validate ParentFormId matches - bool formIdMatches = parentFormDetails.ApplicationFormId == applicationForm.ParentFormId.Value; - - if (!formIdMatches) - { - errors.Add("The selected parent form in Payment Configuration does not match the application's linked parent. Please verify and try again."); - return (errors, parentReferenceNo); - } - - return (errors, parentReferenceNo); - } - public async Task OnPostAsync() { if (ApplicationPaymentRequestForm == null) return NoContent(); // Validate parent-child payment amounts - var validationErrors = await ValidateParentChildPaymentAmounts(); + var validationErrors = await helperService.ValidateParentChildPaymentAmountsAsync(ApplicationPaymentRequestForm); if (validationErrors.Count != 0) { throw new UserFriendlyException(string.Join(" ", validationErrors)); @@ -402,212 +237,5 @@ private List MapPaymentRequests(bool hasOverridePermiss return payments; } - - private static List SortPaymentRequestsByHierarchy(List paymentRequests) - { - var sortedList = new List(); - var processed = new HashSet(); - - // Step 1: Find all parent-child groups and process them - var parentGroups = paymentRequests - .Where(x => !string.IsNullOrEmpty(x.ParentReferenceNo)) - .GroupBy(x => x.ParentReferenceNo) - .ToList(); - - foreach (var group in parentGroups.OrderBy(g => g.Key)) - { - string parentRefNo = group.Key!; - - // Add children first (sorted by InvoiceNumber) - var children = group.OrderBy(x => x.InvoiceNumber).ToList(); - sortedList.AddRange(children); - foreach (var child in children) - { - processed.Add(child.InvoiceNumber); - } - - // Add parent after children (if it exists in the list) - var parent = paymentRequests - .Find(x => x.SubmissionConfirmationCode == parentRefNo && string.IsNullOrEmpty(x.ParentReferenceNo)); - - if (parent != null) - { - sortedList.Add(parent); - processed.Add(parent.InvoiceNumber); - } - } - - // Step 2: Add standalone items at the end - var standaloneItems = paymentRequests - .Where(x => !processed.Contains(x.InvoiceNumber)) - .OrderBy(x => x.InvoiceNumber) - .ToList(); - sortedList.AddRange(standaloneItems); - - return sortedList; - } - - private async Task PopulateParentChildValidationData() - { - // Find all child groups in current submission - var childGroups = ApplicationPaymentRequestForm - .Where(x => !string.IsNullOrEmpty(x.ParentReferenceNo)) - .GroupBy(x => x.ParentReferenceNo); - - foreach (var childGroup in childGroups) - { - string parentRefNo = childGroup.Key!; - var children = childGroup.ToList(); - - // Find parent in current submission - var parentInSubmission = ApplicationPaymentRequestForm - .Find(x => x.SubmissionConfirmationCode == parentRefNo && - string.IsNullOrEmpty(x.ParentReferenceNo)); - - // Get parent application details - Guid parentApplicationId; - - if (parentInSubmission != null) - { - parentApplicationId = parentInSubmission.CorrelationId; - } - else - { - // Parent not in submission, get from first child's link - var firstChild = await applicationService.GetAsync(children[0].CorrelationId); - var allLinks = await applicationLinksService.GetListByApplicationAsync(firstChild.Id); - var parentLink = allLinks.Find(link => link.LinkType == ApplicationLinkType.Parent && link.ApplicationId != firstChild.Id); - - if (parentLink == null) - { - // Skip this group if parent link not found - continue; - } - parentApplicationId = parentLink.ApplicationId; - } - - // Get parent application - var parentApplication = await applicationService.GetAsync(parentApplicationId); - decimal approvedAmount = parentApplication.ApprovedAmount; - - // Get parent's total paid + pending - decimal parentTotalPaidPending = await paymentRequestAppService - .GetTotalPaymentRequestAmountByCorrelationIdAsync(parentApplicationId); - - // Get ALL children of this parent and their total paid + pending - var parentLinks = await applicationLinksService.GetListByApplicationAsync(parentApplicationId); - var allChildLinks = parentLinks.Where(link => link.LinkType == ApplicationLinkType.Child).ToList(); - - decimal childrenTotalPaidPending = 0; - foreach (var childLink in allChildLinks) - { - decimal childTotal = await paymentRequestAppService - .GetTotalPaymentRequestAmountByCorrelationIdAsync(childLink.ApplicationId); - childrenTotalPaidPending += childTotal; - } - - // Calculate maximum allowed - decimal maximumPaymentAmount = approvedAmount - (parentTotalPaidPending + childrenTotalPaidPending); - - // Apply validation data to all children in this group - foreach (var child in children) - { - child.MaximumAllowedAmount = maximumPaymentAmount; - child.IsPartOfParentChildGroup = true; - child.ParentApprovedAmount = approvedAmount; - } - - // Apply validation data to parent if in submission - if (parentInSubmission != null) - { - parentInSubmission.MaximumAllowedAmount = maximumPaymentAmount; - parentInSubmission.IsPartOfParentChildGroup = true; - parentInSubmission.ParentApprovedAmount = approvedAmount; - } - } - } - - private async Task> ValidateParentChildPaymentAmounts() - { - List errors = []; - - // Find all child groups in current submission - var childGroups = ApplicationPaymentRequestForm - .Where(x => !string.IsNullOrEmpty(x.ParentReferenceNo)) - .GroupBy(x => x.ParentReferenceNo); - - foreach (var childGroup in childGroups) - { - string parentRefNo = childGroup.Key!; - var children = childGroup.ToList(); - - // Find parent in current submission - var parentInSubmission = ApplicationPaymentRequestForm - .Find(x => x.SubmissionConfirmationCode == parentRefNo && - string.IsNullOrEmpty(x.ParentReferenceNo)); - - // Get parent application details - Guid parentApplicationId; - decimal currentParentAmount = 0; - - if (parentInSubmission != null) - { - parentApplicationId = parentInSubmission.CorrelationId; - currentParentAmount = parentInSubmission.Amount; - } - else - { - // Parent not in submission, get from first child's link - var firstChild = await applicationService.GetAsync(children[0].CorrelationId); - var allLinks = await applicationLinksService.GetListByApplicationAsync(firstChild.Id); - var parentLink = allLinks.Find(link => link.LinkType == ApplicationLinkType.Parent && link.ApplicationId != firstChild.Id); - - if (parentLink == null) - { - errors.Add($"Parent application link not found for reference {parentRefNo}."); - continue; - } - parentApplicationId = parentLink.ApplicationId; - } - - // Get parent application - var parentApplication = await applicationService.GetAsync(parentApplicationId); - decimal approvedAmount = parentApplication.ApprovedAmount; - - // Get parent's total paid + pending - decimal parentTotalPaidPending = await paymentRequestAppService - .GetTotalPaymentRequestAmountByCorrelationIdAsync(parentApplicationId); - - // Get ALL children of this parent and their total paid + pending - var parentLinks = await applicationLinksService.GetListByApplicationAsync(parentApplicationId); - var allChildLinks = parentLinks.Where(link => link.LinkType == ApplicationLinkType.Child).ToList(); - - decimal childrenTotalPaidPending = 0; - foreach (var childLink in allChildLinks) - { - decimal childTotal = await paymentRequestAppService - .GetTotalPaymentRequestAmountByCorrelationIdAsync(childLink.ApplicationId); - childrenTotalPaidPending += childTotal; - } - - // Calculate maximum allowed - decimal maximumPaymentAmount = approvedAmount - (parentTotalPaidPending + childrenTotalPaidPending); - - // Calculate current submission total - decimal currentChildrenAmount = children.Sum(x => x.Amount); - decimal currentSubmissionTotal = currentParentAmount + currentChildrenAmount; - - // Validate - if (currentSubmissionTotal > maximumPaymentAmount) - { - errors.Add($"Payment request for parent application {parentRefNo} and its children exceeds the maximum allowed amount. " + - $"Maximum: ${maximumPaymentAmount:N2}, Requested: ${currentSubmissionTotal:N2}. " + - $"(Parent Approved Amount: ${approvedAmount:N2}, Already Paid/Pending for Parent: ${parentTotalPaidPending:N2}, " + - $"Already Paid/Pending for All Children: ${childrenTotalPaidPending:N2})"); - } - } - - return errors; - } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/IPaymentFormItem.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/IPaymentFormItem.cs new file mode 100644 index 000000000..75977b4f8 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/IPaymentFormItem.cs @@ -0,0 +1,16 @@ +using System; + +namespace Unity.Payments.Web.Pages.Payments +{ + public interface IPaymentFormItem + { + Guid CorrelationId { get; set; } + string InvoiceNumber { get; set; } + decimal Amount { get; set; } + string? ParentReferenceNo { get; set; } + string? SubmissionConfirmationCode { get; set; } + decimal? MaximumAllowedAmount { get; set; } + bool IsPartOfParentChildGroup { get; set; } + decimal? ParentApprovedAmount { get; set; } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentRequestPageHelperService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentRequestPageHelperService.cs new file mode 100644 index 000000000..d3c3dc9d4 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentRequestPageHelperService.cs @@ -0,0 +1,348 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Unity.GrantManager.ApplicationForms; +using Unity.GrantManager.Applications; +using Unity.GrantManager.GrantApplications; +using Unity.Payments.Domain.Suppliers; +using Unity.Payments.Enums; +using Unity.Payments.PaymentRequests; +using Unity.Payments.Suppliers; + +namespace Unity.Payments.Web.Pages.Payments +{ + public class PaymentRequestPageHelperService( + IGrantApplicationAppService applicationService, + IPaymentRequestAppService paymentRequestAppService, + IApplicationLinksService applicationLinksService, + ISupplierAppService supplierAppService, + IApplicationFormAppService applicationFormAppService + ) + { + public async Task GetRemainingAmountAsync(GrantApplicationDto application) + { + decimal remainingAmount = 0; + if (application.ApprovedAmount > 0) + { + decimal approvedAmount = application.ApprovedAmount; + decimal totalFutureRequested = await paymentRequestAppService.GetTotalPaymentRequestAmountByCorrelationIdAsync(application.Id); + + decimal childrenTotalPaidPending = 0; + var applicationLinks = await applicationLinksService.GetListByApplicationAsync(application.Id); + var childLinks = applicationLinks + .Where(link => link.LinkType == ApplicationLinkType.Child && link.ApplicationId != application.Id) + .ToList(); + + foreach (var childLink in childLinks) + { + decimal childTotal = await paymentRequestAppService + .GetTotalPaymentRequestAmountByCorrelationIdAsync(childLink.ApplicationId); + childrenTotalPaidPending += childTotal; + } + + decimal totalConsumed = totalFutureRequested + childrenTotalPaidPending; + if (approvedAmount > totalConsumed) + { + remainingAmount = approvedAmount - totalConsumed; + } + } + + return remainingAmount; + } + + public async Task GetSupplierAsync(GrantApplicationDto application) + { + if (application.Applicant.SupplierId != Guid.Empty) + { + return await supplierAppService.GetAsync(application.Applicant.SupplierId); + } + + return null; + } + + public async Task<(List Errors, string? ParentReferenceNo)> ValidateFormHierarchyAndParentLink( + GrantApplicationDto application, + ApplicationForm applicationForm) + { + List errors = []; + string? parentReferenceNo = null; + + if (!applicationForm.Payable || + !applicationForm.FormHierarchy.HasValue || + applicationForm.FormHierarchy.Value != FormHierarchyType.Child) + { + return (errors, parentReferenceNo); + } + + if (!applicationForm.ParentFormId.HasValue) + { + return (errors, parentReferenceNo); + } + + var allLinks = await applicationLinksService.GetListByApplicationAsync(application.Id); + var parentLink = allLinks.Find(link => link.LinkType == ApplicationLinkType.Parent && link.ApplicationId != application.Id); + + if (parentLink == null) + { + errors.Add("Payment Configuration for this form requires a valid parent application link before payments can be processed."); + return (errors, parentReferenceNo); + } + + var parentFormDetails = await applicationFormAppService.GetFormDetailsByApplicationIdAsync(parentLink.ApplicationId); + var parentApplication = await applicationService.GetAsync(parentLink.ApplicationId); + parentReferenceNo = parentApplication.ReferenceNo; + + bool formIdMatches = parentFormDetails.ApplicationFormId == applicationForm.ParentFormId.Value; + if (!formIdMatches) + { + errors.Add("The selected parent form in Payment Configuration does not match the application's linked parent. Please verify and try again."); + return (errors, parentReferenceNo); + } + + return (errors, parentReferenceNo); + } + + public async Task<(List ErrorList, string? ParentReferenceNo)> GetErrorListAsync( + SupplierDto? supplier, + Site? site, + GrantApplicationDto application, + ApplicationForm applicationForm, + decimal remainingAmount, + Guid? accountCodingId, + bool isHistorical = false) + { + bool missingFields = false; + List errorList = []; + + if (!isHistorical && (supplier == null || site == null || string.IsNullOrWhiteSpace(supplier.Number))) + { + missingFields = true; + } + + if (!isHistorical && site != null && site.PaymentGroup == PaymentGroup.EFT && string.IsNullOrWhiteSpace(site.BankAccount)) + { + errorList.Add("Payment cannot be submitted because the default site's pay group is set to EFT, but no bank account is configured. Please update the bank account before proceeding."); + } + + if (remainingAmount <= 0) + { + errorList.Add("There is no remaining amount for this application."); + } + + if (missingFields) + { + errorList.Add("Some payment information is missing for this applicant. Please make sure supplier information is provided and default site is selected."); + } + + if (application.StatusCode != GrantApplicationState.GRANT_APPROVED) + { + errorList.Add("The selected Application is not Approved. To continue please remove the item from the list."); + } + + var allLinks = await applicationLinksService.GetListByApplicationAsync(application.Id); + var parentLink = allLinks.Find(link => link.LinkType == ApplicationLinkType.Parent && link.ApplicationId != application.Id); + if (parentLink != null) + { + var parentApplication = await applicationService.GetAsync(parentLink.ApplicationId); + if (parentApplication.Id == Guid.Empty || parentApplication.StatusCode != GrantApplicationState.GRANT_APPROVED) + { + errorList.Add("Payment cannot be processed because the linked parent submission is not approved. Please ensure the parent submission is approved before creating a payment."); + } + } + + if (!application.ApplicationForm.Payable) + { + errorList.Add("The selected application is not Payable. To continue please remove the item from the list."); + } + + if (!isHistorical && (accountCodingId == null || accountCodingId == Guid.Empty)) + { + errorList.Add("The selected application form does not have an Account Coding or no default Account Coding is set."); + } + + var (hierarchyErrors, parentReferenceNo) = await ValidateFormHierarchyAndParentLink(application, applicationForm); + errorList.AddRange(hierarchyErrors); + + return (errorList, parentReferenceNo); + } + + public List SortByHierarchy(List paymentRequests) where T : IPaymentFormItem + { + var sortedList = new List(); + var processed = new HashSet(); + + var parentGroups = paymentRequests + .Where(x => !string.IsNullOrEmpty(x.ParentReferenceNo)) + .GroupBy(x => x.ParentReferenceNo) + .ToList(); + + foreach (var group in parentGroups.OrderBy(g => g.Key)) + { + string parentRefNo = group.Key!; + + var children = group.OrderBy(x => x.InvoiceNumber).ToList(); + sortedList.AddRange(children); + foreach (var child in children) + { + processed.Add(child.InvoiceNumber); + } + + var parent = paymentRequests + .Find(x => x.SubmissionConfirmationCode == parentRefNo && string.IsNullOrEmpty(x.ParentReferenceNo)); + + if (parent != null) + { + sortedList.Add(parent); + processed.Add(parent.InvoiceNumber); + } + } + + var standaloneItems = paymentRequests + .Where(x => !processed.Contains(x.InvoiceNumber)) + .OrderBy(x => x.InvoiceNumber) + .ToList(); + sortedList.AddRange(standaloneItems); + + return sortedList; + } + + public async Task PopulateParentChildValidationDataAsync(List form) where T : IPaymentFormItem + { + var childGroups = form + .Where(x => !string.IsNullOrEmpty(x.ParentReferenceNo)) + .GroupBy(x => x.ParentReferenceNo); + + foreach (var childGroup in childGroups) + { + string parentRefNo = childGroup.Key!; + var children = childGroup.ToList(); + + var parentInSubmission = form + .Find(x => x.SubmissionConfirmationCode == parentRefNo && string.IsNullOrEmpty(x.ParentReferenceNo)); + + Guid parentApplicationId; + + if (parentInSubmission != null) + { + parentApplicationId = parentInSubmission.CorrelationId; + } + else + { + var firstChild = await applicationService.GetAsync(children[0].CorrelationId); + var allLinks = await applicationLinksService.GetListByApplicationAsync(firstChild.Id); + var parentLink = allLinks.Find(link => link.LinkType == ApplicationLinkType.Parent && link.ApplicationId != firstChild.Id); + + if (parentLink == null) continue; + + parentApplicationId = parentLink.ApplicationId; + } + + var parentApplication = await applicationService.GetAsync(parentApplicationId); + decimal approvedAmount = parentApplication.ApprovedAmount; + + decimal parentTotalPaidPending = await paymentRequestAppService + .GetTotalPaymentRequestAmountByCorrelationIdAsync(parentApplicationId); + + var parentLinks = await applicationLinksService.GetListByApplicationAsync(parentApplicationId); + var allChildLinks = parentLinks.Where(link => link.LinkType == ApplicationLinkType.Child).ToList(); + + decimal childrenTotalPaidPending = 0; + foreach (var childLink in allChildLinks) + { + decimal childTotal = await paymentRequestAppService + .GetTotalPaymentRequestAmountByCorrelationIdAsync(childLink.ApplicationId); + childrenTotalPaidPending += childTotal; + } + + decimal maximumPaymentAmount = approvedAmount - (parentTotalPaidPending + childrenTotalPaidPending); + + foreach (var child in children) + { + child.MaximumAllowedAmount = maximumPaymentAmount; + child.IsPartOfParentChildGroup = true; + child.ParentApprovedAmount = approvedAmount; + } + + if (parentInSubmission != null) + { + parentInSubmission.MaximumAllowedAmount = maximumPaymentAmount; + parentInSubmission.IsPartOfParentChildGroup = true; + parentInSubmission.ParentApprovedAmount = approvedAmount; + } + } + } + + public async Task> ValidateParentChildPaymentAmountsAsync(List form) where T : IPaymentFormItem + { + List errors = []; + + var childGroups = form + .Where(x => !string.IsNullOrEmpty(x.ParentReferenceNo)) + .GroupBy(x => x.ParentReferenceNo); + + foreach (var childGroup in childGroups) + { + string parentRefNo = childGroup.Key!; + var children = childGroup.ToList(); + + var parentInSubmission = form + .Find(x => x.SubmissionConfirmationCode == parentRefNo && string.IsNullOrEmpty(x.ParentReferenceNo)); + + Guid parentApplicationId; + decimal currentParentAmount = 0; + + if (parentInSubmission != null) + { + parentApplicationId = parentInSubmission.CorrelationId; + currentParentAmount = parentInSubmission.Amount; + } + else + { + var firstChild = await applicationService.GetAsync(children[0].CorrelationId); + var allLinks = await applicationLinksService.GetListByApplicationAsync(firstChild.Id); + var parentLink = allLinks.Find(link => link.LinkType == ApplicationLinkType.Parent && link.ApplicationId != firstChild.Id); + + if (parentLink == null) + { + errors.Add($"Parent application link not found for reference {parentRefNo}."); + continue; + } + parentApplicationId = parentLink.ApplicationId; + } + + var parentApplication = await applicationService.GetAsync(parentApplicationId); + decimal approvedAmount = parentApplication.ApprovedAmount; + + decimal parentTotalPaidPending = await paymentRequestAppService + .GetTotalPaymentRequestAmountByCorrelationIdAsync(parentApplicationId); + + var parentLinks = await applicationLinksService.GetListByApplicationAsync(parentApplicationId); + var allChildLinks = parentLinks.Where(link => link.LinkType == ApplicationLinkType.Child).ToList(); + + decimal childrenTotalPaidPending = 0; + foreach (var childLink in allChildLinks) + { + decimal childTotal = await paymentRequestAppService + .GetTotalPaymentRequestAmountByCorrelationIdAsync(childLink.ApplicationId); + childrenTotalPaidPending += childTotal; + } + + decimal maximumPaymentAmount = approvedAmount - (parentTotalPaidPending + childrenTotalPaidPending); + + decimal currentChildrenAmount = children.Sum(x => x.Amount); + decimal currentSubmissionTotal = currentParentAmount + currentChildrenAmount; + + if (currentSubmissionTotal > maximumPaymentAmount) + { + errors.Add($"Payment request for parent application {parentRefNo} and its children exceeds the maximum allowed amount. " + + $"Maximum: ${maximumPaymentAmount:N2}, Requested: ${currentSubmissionTotal:N2}. " + + $"(Parent Approved Amount: ${approvedAmount:N2}, Already Paid/Pending for Parent: ${parentTotalPaidPending:N2}, " + + $"Already Paid/Pending for All Children: ${childrenTotalPaidPending:N2})"); + } + } + + return errors; + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentsModel.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentsModel.cs index 30fcfd50a..93987602b 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentsModel.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentsModel.cs @@ -5,7 +5,7 @@ namespace Unity.Payments.Web.Pages.Payments { - public class PaymentsModel + public class PaymentsModel : IPaymentFormItem { [DisplayName("ApplicationPaymentRequest:Amount")] [Required] diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/PaymentsWebModule.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/PaymentsWebModule.cs index 0aeb0fb4a..330d8be82 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/PaymentsWebModule.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/PaymentsWebModule.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.DependencyInjection; +using Unity.Payments.Web.Pages.Payments; using Unity.Payments.Localization; using Unity.Payments.Web.Menus; using Volo.Abp.AspNetCore.Mvc.Localization; @@ -44,6 +45,7 @@ public override void ConfigureServices(ServiceConfigurationContext context) }); context.Services.AddMapperlyObjectMapper(); + context.Services.AddScoped(); Configure(options => { From 2d29595664d1d46626b338a9ab9087d6e8f56b57 Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Fri, 15 May 2026 15:27:42 -0700 Subject: [PATCH 09/63] AB#31883 copilot feedback --- .../Unity.Theme.UX2/Themes/UX2/Layouts/Application.cshtml | 4 +++- .../src/Unity.Theme.UX2/wwwroot/js/TimezoneBootstrap.js | 8 +++++++- .../src/Unity.Theme.UX2/wwwroot/js/TimezoneUtils.js | 5 +++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Layouts/Application.cshtml b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Layouts/Application.cshtml index 66260ee2e..ce55c784e 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Layouts/Application.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Layouts/Application.cshtml @@ -131,7 +131,9 @@ } })(); - TimezoneUtils.setTimezoneCookie(); + if (TimezoneUtils.setTimezoneCookie()) { + location.reload(); + } @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Application) diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneBootstrap.js b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneBootstrap.js index e8c0ed556..27a2f8bb5 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneBootstrap.js +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneBootstrap.js @@ -1,2 +1,8 @@ TimezoneUtils.setTimezoneCookie(); -location.reload(); + +// Only reload if the cookie was actually persisted — prevents an infinite reload +// loop if the browser silently rejects the cookie (e.g. strict privacy settings). +const cookieSet = document.cookie.split('; ').some(row => row.startsWith('timezoneoffset=')); +if (cookieSet) { + location.reload(); +} diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneUtils.js b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneUtils.js index 4346a56cf..f20f89394 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneUtils.js +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneUtils.js @@ -10,8 +10,7 @@ const TimezoneUtils = (function () { const d = new Date(); d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); let expires = "expires=" + d.toUTCString(); - document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/;domain=" + - globalThis.location.hostname + ";SameSite=Lax;secure;"; + document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/;SameSite=Lax;secure;"; } function setTimezoneCookie() { @@ -25,7 +24,9 @@ const TimezoneUtils = (function () { if (existing === undefined || Number.parseInt(existing, 10) !== currentOffset) { setCookie(cookieName, currentOffset, 1); + return true; } + return false; } return { From c9badb702eb7ce29babafb89c8bab81f007d615f Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Fri, 15 May 2026 16:02:40 -0700 Subject: [PATCH 10/63] AB#31883 more copilot suggestions --- .../Themes/UX2/Layouts/Application.cshtml | 22 +++++++++++++++++-- .../wwwroot/js/TimezoneBootstrap.js | 8 +------ .../Middleware/TimezoneMiddleware.cs | 13 +++++++++++ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Layouts/Application.cshtml b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Layouts/Application.cshtml index ce55c784e..2374b9863 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Layouts/Application.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Layouts/Application.cshtml @@ -131,9 +131,27 @@ } })(); - if (TimezoneUtils.setTimezoneCookie()) { + (function () { + var timezoneReloadGuardKey = 'unity.theme.ux2.timezone-reload-attempted'; + var shouldReload = TimezoneUtils.setTimezoneCookie(); + if (!shouldReload) { + try { + sessionStorage.removeItem(timezoneReloadGuardKey); + } catch (e) { + // Ignore storage access failures and continue without a guard reset. + } + return; + } + try { + if (sessionStorage.getItem(timezoneReloadGuardKey) === 'true') { + return; + } + sessionStorage.setItem(timezoneReloadGuardKey, 'true'); + } catch (e) { + // Ignore storage access failures; reloading once is still preferable to a hard failure. + } location.reload(); - } + })(); @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Application) diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneBootstrap.js b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneBootstrap.js index 27a2f8bb5..e8c0ed556 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneBootstrap.js +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/js/TimezoneBootstrap.js @@ -1,8 +1,2 @@ TimezoneUtils.setTimezoneCookie(); - -// Only reload if the cookie was actually persisted — prevents an infinite reload -// loop if the browser silently rejects the cookie (e.g. strict privacy settings). -const cookieSet = document.cookie.split('; ').some(row => row.startsWith('timezoneoffset=')); -if (cookieSet) { - location.reload(); -} +location.reload(); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/TimezoneMiddleware.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/TimezoneMiddleware.cs index be1f2e3fd..e8e06dc1d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/TimezoneMiddleware.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/TimezoneMiddleware.cs @@ -6,11 +6,23 @@ namespace Unity.GrantManager.Web.Middleware; public class TimezoneMiddleware(RequestDelegate next) { private const string CookieName = "timezoneoffset"; + private const string AttemptCookieName = "tzoffset_attempted"; public async Task InvokeAsync(HttpContext context) { if (ShouldIntercept(context)) { + // Set a server-side session flag before serving the bootstrap page. + // If the browser cannot persist the timezone cookie, this flag ensures + // the next request passes through to the app with the default offset + // instead of looping on the bootstrap page indefinitely. + context.Response.Cookies.Append(AttemptCookieName, "1", new CookieOptions + { + Path = "/", + IsEssential = true, + HttpOnly = true + }); + context.Response.ContentType = "text/html; charset=utf-8"; await context.Response.WriteAsync(""" @@ -34,6 +46,7 @@ private static bool ShouldIntercept(HttpContext context) return request.Method == HttpMethods.Get && !request.Cookies.ContainsKey(CookieName) + && !request.Cookies.ContainsKey(AttemptCookieName) && !request.Path.StartsWithSegments("/api") && !request.Headers.ContainsKey("X-Requested-With") && request.Headers.Accept.ToString().Contains("text/html"); From e65494c8960516534886edd1f47a8c3bd521ffe0 Mon Sep 17 00:00:00 2001 From: Andre Goncalves <98196495+AndreGAot@users.noreply.github.com> Date: Fri, 15 May 2026 16:09:03 -0700 Subject: [PATCH 11/63] Potential fix for pull request finding 'CodeQL / Cookie 'Secure' attribute is not set to true' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .../Unity.GrantManager.Web/Middleware/TimezoneMiddleware.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/TimezoneMiddleware.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/TimezoneMiddleware.cs index e8e06dc1d..b657f9f82 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/TimezoneMiddleware.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/TimezoneMiddleware.cs @@ -20,7 +20,8 @@ public async Task InvokeAsync(HttpContext context) { Path = "/", IsEssential = true, - HttpOnly = true + HttpOnly = true, + Secure = true }); context.Response.ContentType = "text/html; charset=utf-8"; From c911592ba4afdbaccd289508c0c85fedd698b6e4 Mon Sep 17 00:00:00 2001 From: aurelio-aot Date: Sat, 16 May 2026 00:36:59 -0700 Subject: [PATCH 12/63] AB#27996: Create Historical Payments - Initial Draft --- .../Enums/PaymentRequestStatus.cs | 1 + .../CreateHistoricalPaymentRequestDto.cs | 28 + .../IPaymentRequestAppService.cs | 1 + .../Domain/Exceptions/ErrorConsts.cs | 2 + .../Domain/PaymentRequests/PaymentRequest.cs | 50 +- .../Domain/Services/InvoiceManager.cs | 3 +- .../Services/PaymentRequestQueryManager.cs | 7 +- ...aymentsDbContextModelCreatingExtensions.cs | 1 + .../Repositories/PaymentRequestRepository.cs | 5 +- .../CasPaymentRequestCoordinator.cs | 4 +- .../PaymentRequestAppService.cs | 109 +- .../PaymentsPermissionDefinitionProvider.cs | 1 + .../Localization/Payments/en.json | 8 + .../Permissions/PaymentsPermissions.cs | 3 +- .../CreateHistoricalPayments.cshtml | 212 + .../CreateHistoricalPayments.cshtml.cs | 171 + .../CreateHistoricalPaymentsModal.js | 86 + .../CreatePaymentRequests.cshtml.cs | 46 +- .../HistoricalPaymentsModel.cs | 39 + .../Pages/PaymentRequests/Index.js | 1 + .../PaymentRequestPageHelperService.cs | 38 + .../Shared/Components/PaymentInfo/Default.js | 4 + .../PaymentRequestAppService_Tests.cs | 7 +- ...ntRequestRepository_PaymentRollup_Tests.cs | 67 +- .../PaymentRequestRepository_Tests.cs | 3 +- .../PaymentRequest_Constructor_Tests.cs | 180 + .../DataProviders/PaymentInfoDataProvider.cs | 8 +- ...kePaymentRequestSiteIdNullable.Designer.cs | 5031 +++++++++++++++++ ...025648_MakePaymentRequestSiteIdNullable.cs | 39 + .../GrantTenantDbContextModelSnapshot.cs | 5 +- .../Shared/Components/ActionBar/ActionBar.cs | 3 + .../Components/ActionBar/Default.cshtml | 9 + .../Shared/Components/ActionBar/Default.js | 33 +- .../PaymentInfoDataProviderTests.cs | 60 +- 34 files changed, 6190 insertions(+), 75 deletions(-) create mode 100644 applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/CreateHistoricalPaymentRequestDto.cs create mode 100644 applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml create mode 100644 applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml.cs create mode 100644 applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js create mode 100644 applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/HistoricalPaymentsModel.cs create mode 100644 applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequest_Constructor_Tests.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260516025648_MakePaymentRequestSiteIdNullable.Designer.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260516025648_MakePaymentRequestSiteIdNullable.cs diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/Enums/PaymentRequestStatus.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/Enums/PaymentRequestStatus.cs index 16d557583..9d0bd1546 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/Enums/PaymentRequestStatus.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/Enums/PaymentRequestStatus.cs @@ -18,5 +18,6 @@ public enum PaymentRequestStatus Paid = 10, Failed = 11, FSB = 12, // Financial Services Branch - Prevent CAS Payment + HistoricalPayment = 13, } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/CreateHistoricalPaymentRequestDto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/CreateHistoricalPaymentRequestDto.cs new file mode 100644 index 000000000..1e7a83b66 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/CreateHistoricalPaymentRequestDto.cs @@ -0,0 +1,28 @@ +using System; + +namespace Unity.Payments.PaymentRequests +{ +#pragma warning disable CS8618 + [Serializable] + public class CreateHistoricalPaymentRequestDto + { + public string InvoiceNumber { get; set; } + public decimal Amount { get; set; } + public string? Description { get; set; } + public Guid CorrelationId { get; set; } + public Guid? SiteId { get; set; } + public string PayeeName { get; set; } + public string ContractNumber { get; set; } + public string? SupplierNumber { get; set; } + public string? SupplierName { get; set; } + public string CorrelationProvider { get; set; } = string.Empty; + public string BatchName { get; set; } + public decimal BatchNumber { get; set; } = 0; + public string ReferenceNumber { get; set; } = string.Empty; + public string SubmissionConfirmationCode { get; set; } = string.Empty; + public Guid? AccountCodingId { get; set; } + public string? Note { get; set; } + public string PaidDate { get; set; } + } +#pragma warning restore CS8618 +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs index 611002c02..bd604dbc3 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs @@ -9,6 +9,7 @@ namespace Unity.Payments.PaymentRequests public interface IPaymentRequestAppService : IApplicationService { Task> CreateAsync(List paymentRequests); + Task> CreateHistoricalAsync(List paymentRequests); Task> GetListAsync(PagedAndSortedResultRequestDto input); Task GetTotalPaymentRequestAmountByCorrelationIdAsync(Guid correlationId); Task> GetListByApplicationIdAsync(Guid applicationId); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Exceptions/ErrorConsts.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Exceptions/ErrorConsts.cs index be14623c4..99f86d945 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Exceptions/ErrorConsts.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Exceptions/ErrorConsts.cs @@ -9,5 +9,7 @@ public static class ErrorConsts public const string InvalidAccountCodingField = "Unity.Payments:Errors:InvalidAccountCodingFiled"; public const string L2ApproverRestriction = "Unity.Payments:Errors:L2ApproverRestriction"; public const string MissingSupplierNumber = "Unity.Payments:Errors:MissingSupplierNumber"; + public const string MissingSite = "Unity.Payments:Errors:MissingSite"; + public const string MissingAccountCoding = "Unity.Payments:Errors:MissingAccountCoding"; } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/PaymentRequest.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/PaymentRequest.cs index 629eab4a6..8a2f4d431 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/PaymentRequest.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/PaymentRequest.cs @@ -9,6 +9,7 @@ using Volo.Abp; using Unity.Payments.Domain.Exceptions; using Unity.Payments.PaymentRequests; +using Unity.Payments.Codes; using Unity.Payments.Domain.PaymentTags; using Unity.Payments.Domain.AccountCodings; @@ -17,14 +18,8 @@ namespace Unity.Payments.Domain.PaymentRequests public class PaymentRequest : FullAuditedAggregateRoot, IMultiTenant, ICorrelationEntity { public Guid? TenantId { get; set; } - public virtual Guid SiteId { get; set; } - public virtual Site Site - { - set => _site = value; - get => _site - ?? throw new InvalidOperationException("Uninitialized property: " + nameof(Site)); - } - private Site? _site; + public virtual Guid? SiteId { get; set; } + public virtual Site? Site { get; set; } public virtual string InvoiceNumber { get; private set; } = string.Empty; public virtual decimal Amount { get; private set; } @@ -110,6 +105,35 @@ public PaymentRequest(Guid id, CreatePaymentRequestDto createPaymentRequestDto) ValidatePaymentRequest(); } + public PaymentRequest(Guid id, CreateHistoricalPaymentRequestDto dto) : base(id) + { + InvoiceNumber = dto.InvoiceNumber; + Amount = dto.Amount; + PayeeName = dto.PayeeName; + ContractNumber = dto.ContractNumber; + SupplierNumber = dto.SupplierNumber ?? string.Empty; + SupplierName = dto.SupplierName; + SiteId = dto.SiteId; + Description = dto.Description; + CorrelationId = dto.CorrelationId; + CorrelationProvider = dto.CorrelationProvider; + ReferenceNumber = dto.ReferenceNumber; + SubmissionConfirmationCode = dto.SubmissionConfirmationCode; + BatchName = dto.BatchName; + BatchNumber = dto.BatchNumber; + AccountCodingId = dto.AccountCodingId; + Note = dto.Note; + Status = PaymentRequestStatus.HistoricalPayment; + PaymentStatus = CasPaymentRequestStatus.Paid; + InvoiceStatus = CasPaymentRequestStatus.Paid; + PaymentDate = dto.PaidDate; + ExpenseApprovals = []; + PaymentTags = null; + + if (Amount <= 0) + throw new BusinessException(ErrorConsts.ZeroPayment); + } + public PaymentRequest SetNote(string note) { Note = note; @@ -210,6 +234,16 @@ public PaymentRequest ValidatePaymentRequest() throw new BusinessException(ErrorConsts.MissingSupplierNumber); } + if (!SiteId.HasValue || SiteId.Value == Guid.Empty) + { + throw new BusinessException(ErrorConsts.MissingSite); + } + + if (!AccountCodingId.HasValue || AccountCodingId.Value == Guid.Empty) + { + throw new BusinessException(ErrorConsts.MissingAccountCoding); + } + return this; } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/InvoiceManager.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/InvoiceManager.cs index 565b400a5..599a96d37 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/InvoiceManager.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/InvoiceManager.cs @@ -25,7 +25,8 @@ public class InvoiceManager( { public async Task GetSiteByPaymentRequestAsync(PaymentRequest paymentRequest) { - Site? site = await siteRepository.GetAsync(paymentRequest.SiteId, true); + if (!paymentRequest.SiteId.HasValue) return null; + Site? site = await siteRepository.GetAsync(paymentRequest.SiteId.Value, true); if (site?.SupplierId != null) { Supplier supplier = await supplierRepository.GetAsync(site.SupplierId); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/PaymentRequestQueryManager.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/PaymentRequestQueryManager.cs index 9edd0cb42..c9b7f6e81 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/PaymentRequestQueryManager.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/PaymentRequestQueryManager.cs @@ -209,8 +209,11 @@ public async Task ManuallyAddPaymentRequestsToReconciliationQueueAsync(List(paymentRequest); - Site site = await siteRepository.GetAsync(paymentRequest.SiteId); - paymentRequestDto.Site = objectMapper.Map(site); + if (paymentRequest.SiteId.HasValue) + { + Site site = await siteRepository.GetAsync(paymentRequest.SiteId.Value); + paymentRequestDto.Site = objectMapper.Map(site); + } paymentRequestDtos.Add(paymentRequestDto); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContextModelCreatingExtensions.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContextModelCreatingExtensions.cs index 6f1a35251..f476876bd 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContextModelCreatingExtensions.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContextModelCreatingExtensions.cs @@ -33,6 +33,7 @@ public static void ConfigurePayments( b.HasOne(e => e.Site) .WithMany() .HasForeignKey(x => x.SiteId) + .IsRequired(false) .OnDelete(DeleteBehavior.NoAction); b.HasOne(e => e.AccountCoding) diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/PaymentRequestRepository.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/PaymentRequestRepository.cs index f26ca656c..2b2e2c831 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/PaymentRequestRepository.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/PaymentRequestRepository.cs @@ -140,8 +140,9 @@ public async Task> GetBatchPaymentRollupsByCor { ApplicationId = g.Key, TotalPaid = g - .Where(p => p.PaymentStatus != null - && p.PaymentStatus.Trim().ToUpper() == CasPaymentRequestStatus.FullyPaid.ToUpper()) + .Where(p => (p.PaymentStatus != null + && p.PaymentStatus.Trim().ToUpper() == CasPaymentRequestStatus.FullyPaid.ToUpper()) + || p.Status == PaymentRequestStatus.HistoricalPayment) .Sum(p => p.Amount), TotalPending = g .Where(p => p.Status == PaymentRequestStatus.L1Pending diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/CasPaymentRequestCoordinator.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/CasPaymentRequestCoordinator.cs index a4698477a..e97d71ab6 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/CasPaymentRequestCoordinator.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/CasPaymentRequestCoordinator.cs @@ -55,7 +55,7 @@ public async Task AddPaymentRequestsToInvoiceQueue(PaymentRequest paymentRequest PaymentRequestId = paymentRequest.Id, InvoiceNumber = paymentRequest.InvoiceNumber, SupplierNumber = paymentRequest.SupplierNumber, - SiteNumber = paymentRequest.Site.Number, + SiteNumber = paymentRequest.Site?.Number ?? string.Empty, TenantId = (Guid)currentTenant.Id }; @@ -103,7 +103,7 @@ public async Task AddPaymentRequestsToReconciliationQueue() PaymentRequestId = paymentRequest.Id, InvoiceNumber = paymentRequest.InvoiceNumber, SupplierNumber = paymentRequest.SupplierNumber, - SiteNumber = paymentRequest.Site.Number, + SiteNumber = paymentRequest.Site?.Number ?? string.Empty, TenantId = tenantId }; diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/PaymentRequestAppService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/PaymentRequestAppService.cs index a387f4099..fd4606703 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/PaymentRequestAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/PaymentRequestAppService.cs @@ -43,29 +43,46 @@ public class PaymentRequestAppService( public virtual async Task> CreateAsync(List paymentRequests) { List createdPayments = []; - var paymentConfig = await paymentRequestConfigurationManager.GetPaymentConfigurationAsync(); - var paymentIdPrefix = string.Empty; + var (batchName, batchNumber, nextSequenceNumber, paymentIdPrefix) = await GetBatchSetupAsync(); - if (paymentConfig != null && !paymentConfig.PaymentIdPrefix.IsNullOrEmpty()) + foreach (var paymentRequestItem in paymentRequests.Select((value, i) => new { i, value })) { - paymentIdPrefix = paymentConfig.PaymentIdPrefix; + try + { + // referenceNumber + Chefs Confirmation ID + 6 digit sequence based on sequence number and index + CreatePaymentRequestDto paymentRequestDto = paymentRequestItem.value; + var (invoiceNumber, referenceNumber) = GeneratePaymentNumbers(paymentIdPrefix, paymentRequestDto.InvoiceNumber, nextSequenceNumber, paymentRequestItem.i); + + paymentRequestDto.InvoiceNumber = invoiceNumber; + paymentRequestDto.ReferenceNumber = referenceNumber; + paymentRequestDto.BatchName = batchName; + paymentRequestDto.BatchNumber = batchNumber; + + var payment = new PaymentRequest(Guid.NewGuid(), paymentRequestDto); + var result = await paymentRequestQueryManager.InsertPaymentRequestAsync(payment); + createdPayments.Add(MapToPaymentRequestDto(result)); + } + catch (Exception ex) + { + Logger.LogException(ex); + throw; + } } + return createdPayments; + } - var batchNumber = await paymentRequestConfigurationManager.GetMaxBatchNumberAsync(); - var batchName = $"{paymentIdPrefix}_UNITY_BATCH_{batchNumber}"; - var currentYear = DateTime.UtcNow.Year; - var nextSequenceNumber = await paymentRequestConfigurationManager.GetNextSequenceNumberAsync(currentYear); + [Authorize(PaymentsPermissions.Payments.AddHistoricalPayment)] + public virtual async Task> CreateHistoricalAsync(List paymentRequests) + { + List createdPayments = []; + var (batchName, batchNumber, nextSequenceNumber, paymentIdPrefix) = await GetBatchSetupAsync(); foreach (var paymentRequestItem in paymentRequests.Select((value, i) => new { i, value })) { try { - // referenceNumber + Chefs Confirmation ID + 6 digit sequence based on sequence number and index - CreatePaymentRequestDto paymentRequestDto = paymentRequestItem.value; - string referenceNumberPrefix = paymentRequestConfigurationManager.GenerateReferenceNumberPrefix(paymentIdPrefix); - string sequenceNumber = paymentRequestConfigurationManager.GenerateSequenceNumber(nextSequenceNumber, paymentRequestItem.i); - string referenceNumber = paymentRequestConfigurationManager.GenerateReferenceNumber(referenceNumberPrefix, sequenceNumber); - string invoiceNumber = paymentRequestConfigurationManager.GenerateInvoiceNumber(referenceNumberPrefix, paymentRequestDto.InvoiceNumber, sequenceNumber); + CreateHistoricalPaymentRequestDto paymentRequestDto = paymentRequestItem.value; + var (invoiceNumber, referenceNumber) = GeneratePaymentNumbers(paymentIdPrefix, paymentRequestDto.InvoiceNumber, nextSequenceNumber, paymentRequestItem.i); paymentRequestDto.InvoiceNumber = invoiceNumber; paymentRequestDto.ReferenceNumber = referenceNumber; @@ -74,32 +91,62 @@ public virtual async Task> CreateAsync(List GetBatchSetupAsync() + { + var paymentConfig = await paymentRequestConfigurationManager.GetPaymentConfigurationAsync(); + var paymentIdPrefix = string.Empty; + if (paymentConfig != null && !paymentConfig.PaymentIdPrefix.IsNullOrEmpty()) + { + paymentIdPrefix = paymentConfig.PaymentIdPrefix; + } + var batchNumber = await paymentRequestConfigurationManager.GetMaxBatchNumberAsync(); + var batchName = $"{paymentIdPrefix}_UNITY_BATCH_{batchNumber}"; + var nextSequenceNumber = await paymentRequestConfigurationManager.GetNextSequenceNumberAsync(DateTime.UtcNow.Year); + return (batchName, batchNumber, nextSequenceNumber, paymentIdPrefix); + } + + private (string invoiceNumber, string referenceNumber) GeneratePaymentNumbers( + string paymentIdPrefix, string originalInvoiceNumber, int nextSequenceNumber, int index) + { + string referenceNumberPrefix = paymentRequestConfigurationManager.GenerateReferenceNumberPrefix(paymentIdPrefix); + string sequenceNumber = paymentRequestConfigurationManager.GenerateSequenceNumber(nextSequenceNumber, index); + string referenceNumber = paymentRequestConfigurationManager.GenerateReferenceNumber(referenceNumberPrefix, sequenceNumber); + string invoiceNumber = paymentRequestConfigurationManager.GenerateInvoiceNumber(referenceNumberPrefix, originalInvoiceNumber, sequenceNumber); + return (invoiceNumber, referenceNumber); + } + + private static PaymentRequestDto MapToPaymentRequestDto(PaymentRequest result) + { + return new PaymentRequestDto() + { + Id = result.Id, + InvoiceNumber = result.InvoiceNumber, + InvoiceStatus = result.InvoiceStatus, + Amount = result.Amount, + PayeeName = result.PayeeName, + SupplierNumber = result.SupplierNumber, + ContractNumber = result.ContractNumber, + CorrelationId = result.CorrelationId, + CorrelationProvider = result.CorrelationProvider, + Description = result.Description, + CreationTime = result.CreationTime, + Status = result.Status, + ReferenceNumber = result.ReferenceNumber, + SubmissionConfirmationCode = result.SubmissionConfirmationCode + }; + } + public async Task GetNextBatchInfoAsync() { return await paymentRequestConfigurationManager.GetNextBatchInfoAsync(); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Permissions/PaymentsPermissionDefinitionProvider.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Permissions/PaymentsPermissionDefinitionProvider.cs index d29c48009..e4186e3b7 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Permissions/PaymentsPermissionDefinitionProvider.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Permissions/PaymentsPermissionDefinitionProvider.cs @@ -19,6 +19,7 @@ public override void Define(IPermissionDefinitionContext context) paymentsPermissions.AddChild(PaymentsPermissions.Payments.L3ApproveOrDecline, L("Permission:Payments.L3ApproveOrDecline")); paymentsPermissions.AddChild(PaymentsPermissions.Payments.RequestPayment, L("Permission:Payments.RequestPayment")); paymentsPermissions.AddChild(PaymentsPermissions.Payments.AccountCodingOverride, L("Permission:Payments.AccountCodingOverride")); + paymentsPermissions.AddChild(PaymentsPermissions.Payments.AddHistoricalPayment, L("Permission:Payments.AddHistoricalPayment")); //-- PAYMENT INFO PERMISSIONS grantApplicationPermissionsGroup.Add_PaymentInfo_Permissions(); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Localization/Payments/en.json b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Localization/Payments/en.json index 79e908367..bd8d494ef 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Localization/Payments/en.json +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Localization/Payments/en.json @@ -7,6 +7,8 @@ "Unity.Payments:Errors:ThresholdExceeded": "There are payments in this batch that require a third level of approval. Please remove them from this batch and add to another for the appropriate level of approval", "Unity.Payments:Errors:ZeroPayment": "Cannot submit a payment request for $0.00", "Unity.Payments:Errors:MissingSupplierNumber": "Cannot submit a payment request without a supplier number", + "Unity.Payments:Errors:MissingSite": "Cannot submit a payment request without a site", + "Unity.Payments:Errors:MissingAccountCoding": "Cannot submit a payment request without an account coding", "Unity.Payments:Errors:ConfigurationExists": "Configuration already exitst", "Unity.Payments:Errors:ConfigurationDoesNotExist": "Configuration does not exits", "Unity.Payments:Errors:InvalidAccountCodingFiled": "Invalid account coding field {field} : {length}", @@ -124,6 +126,7 @@ "Permission:Payments.RequestPayment": "Request Payment", "Permission:Payments.AccountCodingOverride": "Override Account Coding", "Permission:Payments.EditFormPaymentConfiguration": "Edit Form Payment Configuration", + "Permission:Payments.AddHistoricalPayment": "Add Historical Payment", "Enum:PaymentRequestStatus.L1Pending": "L1 Pending", "Enum:PaymentRequestStatus.L1Approved": "L1 Approved", "Enum:PaymentRequestStatus.L1Declined": "L1 Declined", @@ -140,6 +143,11 @@ "Enum:PaymentRequestStatus.Paid": "Paid", "Enum:PaymentRequestStatus.Failed": "Payment Failed", "Enum:PaymentRequestStatus.PaymentFailed": "Payment Failed", + "Enum:PaymentRequestStatus.HistoricalPayment": "Historical Payment", + "ApplicationHistoricalPaymentRequest:Title": "Historical Payment", + "ApplicationHistoricalPaymentRequest:PaidDate": "Paid Date", + "ApplicationHistoricalPaymentRequest:SubmitButtonText": "Add Historical Payment", + "ApplicationHistoricalPaymentRequest:CancelButtonText": "Cancel", "Unity.GrantManager.ApplicationManagement.Payment": "Payment Info", "Unity.GrantManager.ApplicationManagement.Payment.Summary": "Payment Summary", diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Permissions/PaymentsPermissions.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Permissions/PaymentsPermissions.cs index 0cda791d7..a0b470a79 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Permissions/PaymentsPermissions.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Permissions/PaymentsPermissions.cs @@ -17,7 +17,8 @@ public static class Payments public const string AccountCodingOverride = Default + ".AccountCodingOverride"; public const string EditSupplierInfo = Default + ".EditSupplierInfo"; public const string EditFormPaymentConfiguration = Default + ".EditFormPaymentConfiguration"; - } + public const string AddHistoricalPayment = Default + ".AddHistoricalPayment"; + } public static string[] GetAll() { diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml new file mode 100644 index 000000000..15b6ffbc1 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml @@ -0,0 +1,212 @@ +@page +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Unity.Payments.Localization +@using Microsoft.Extensions.Localization +@model Unity.Payments.Web.Pages.Payments.CreateHistoricalPaymentsModel + +@inject IStringLocalizer L +@{ + Layout = null; +} + + + +
+ + + + @if (ViewData["Error"] != null) + { + + } + else + { + + + @{ + string? currentGroupKey = null; + bool groupOpen = false; + } + @for (var i = 0; i < Model.ApplicationPaymentRequestForm?.Count; i++) + { + var item = Model.ApplicationPaymentRequestForm[i]; + var itemParentReference = !string.IsNullOrEmpty(item.ParentReferenceNo) + ? item.ParentReferenceNo + : item.SubmissionConfirmationCode; + string? itemGroupKey = item.IsPartOfParentChildGroup + ? itemParentReference + : null; + + string? nextGroupKey = null; + if (i + 1 < Model.ApplicationPaymentRequestForm.Count) + { + var next = Model.ApplicationPaymentRequestForm[i + 1]; + var nextParentReference = !string.IsNullOrEmpty(next.ParentReferenceNo) + ? next.ParentReferenceNo + : next.SubmissionConfirmationCode; + nextGroupKey = next.IsPartOfParentChildGroup + ? nextParentReference + : null; + } + + if (itemGroupKey != null && itemGroupKey != currentGroupKey) + { + @:
+ groupOpen = true; + } + +
+ + +
+ @item.ApplicantName/@item.InvoiceNumber + @if (!string.IsNullOrEmpty(item.ParentReferenceNo)) + { +   (Parent Id: @item.ParentReferenceNo) + } +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
+ $ + +
+
+ + + + + + + + + + @for (var j = 0; j < item.ErrorList?.Count; j++) + { + + Error  @item.ErrorList[j] + + } +
+
+ + if (groupOpen && nextGroupKey != itemGroupKey) + { + @: + @:
+ groupOpen = false; + } + + currentGroupKey = itemGroupKey; + } + +
+
+ } +
+ + + + +
+
+ + diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml.cs new file mode 100644 index 000000000..d26612341 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml.cs @@ -0,0 +1,171 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Unity.GrantManager.GrantApplications; +using Unity.Payments.PaymentRequests; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Unity.GrantManager.Applications; +using Unity.GrantManager.Payments; +using Unity.Payments.Domain.Suppliers; +using Unity.Payments.PaymentConfigurations; + +namespace Unity.Payments.Web.Pages.Payments +{ + [SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "Primary constructor for dependency injection")] + public class CreateHistoricalPaymentsModel( + IGrantApplicationAppService applicationService, + IPaymentRequestAppService paymentRequestAppService, + IApplicationFormRepository applicationFormRepository, + ISiteRepository siteRepository, + IPaymentSettingsAppService paymentSettingsAppService, + ApplicationIdsCacheService cacheService, + PaymentRequestPageHelperService helperService + ) : AbpPageModel + { + public List SelectedApplicationIds { get; set; } = []; + + [BindProperty] + public List ApplicationPaymentRequestForm { get; set; } = []; + + [BindProperty] + public bool DisableSubmit { get; set; } + + public async Task OnGetAsync(string cacheKey) + { + try + { + var applicationIds = await cacheService.GetApplicationIdsAsync(cacheKey); + + if (applicationIds == null || applicationIds.Count == 0) + { + Logger.LogWarning("Cache key expired or invalid: {CacheKey}", cacheKey); + ViewData["Error"] = "The session has expired. Please select applications and try again."; + DisableSubmit = true; + return; + } + + SelectedApplicationIds = applicationIds; + Logger.LogInformation("Successfully loaded historical payment modal for {Count} applications", applicationIds.Count); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error loading historical payment modal"); + ViewData["Error"] = "An error occurred while loading the historical payment form. Please try again."; + DisableSubmit = true; + return; + } + + var applications = await applicationService.GetApplicationDetailsListAsync(SelectedApplicationIds); + + foreach (var application in applications) + { + decimal remainingAmount = await helperService.GetRemainingAmountAsync(application); + var applicationForm = await applicationFormRepository.GetAsync(application.ApplicationForm.Id); + Guid? accountCodingId = await paymentSettingsAppService.GetAccountCodingIdByApplicationIdAsync(application.Id); + + HistoricalPaymentsModel request = new() + { + CorrelationId = application.Id, + ApplicantName = application.Applicant.ApplicantName == "" ? "Applicant Name" : application.Applicant.ApplicantName, + SubmissionConfirmationCode = application.ReferenceNo, + Amount = remainingAmount, + Description = "", + InvoiceNumber = application.ReferenceNo, + ContractNumber = application.ContractNumber, + RemainingAmount = remainingAmount, + AccountCodingId = accountCodingId + }; + + var supplier = await helperService.GetSupplierAsync(application); + string supplierNumber = supplier?.Number ?? string.Empty; + + Guid? siteId = application.DefaultSiteId; + Site? site = null; + if (siteId.HasValue && siteId != Guid.Empty) + { + site = await siteRepository.GetAsync(siteId.Value); + request.SiteName = $"{site.Number} ({supplierNumber}, {site.City})"; + request.SiteId = siteId; + } + + request.SupplierName = supplier?.Name; + request.SupplierNumber = supplierNumber; + + var (errorList, parentReferenceNo) = await helperService.GetErrorListAsync( + supplier, site, application, applicationForm, remainingAmount, + accountCodingId, isHistorical: true); + + request.ErrorList = errorList; + request.ParentReferenceNo = parentReferenceNo; + + if (request.ErrorList.Count > 0) + { + request.DisableFields = true; + } + + ApplicationPaymentRequestForm!.Add(request); + } + + ApplicationPaymentRequestForm = helperService.SortByHierarchy(ApplicationPaymentRequestForm); + await helperService.PopulateParentChildValidationDataAsync(ApplicationPaymentRequestForm); + } + + public async Task OnPostAsync() + { + if (ApplicationPaymentRequestForm == null) return NoContent(); + + foreach (var payment in ApplicationPaymentRequestForm) + { + if (string.IsNullOrWhiteSpace(payment.PaidDate)) + throw new UserFriendlyException("Paid Date is required for all historical payments."); + + if (!DateTime.TryParseExact(payment.PaidDate, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var paidDate)) + throw new UserFriendlyException($"Paid Date '{payment.PaidDate}' is not a valid date."); + + if (paidDate.Date > DateTime.Today) + throw new UserFriendlyException("Paid Date cannot be in the future."); + + payment.PaidDate = paidDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + } + + var standaloneErrors = await helperService.ValidateStandalonePaymentAmountsAsync(ApplicationPaymentRequestForm); + if (standaloneErrors.Count != 0) + { + throw new UserFriendlyException(string.Join(" ", standaloneErrors)); + } + + var validationErrors = await helperService.ValidateParentChildPaymentAmountsAsync(ApplicationPaymentRequestForm); + if (validationErrors.Count != 0) + { + throw new UserFriendlyException(string.Join(" ", validationErrors)); + } + + var payments = ApplicationPaymentRequestForm.Select(payment => new CreateHistoricalPaymentRequestDto() + { + Amount = payment.Amount, + CorrelationId = payment.CorrelationId, + SiteId = payment.SiteId, + AccountCodingId = payment.AccountCodingId, + Description = payment.Description, + InvoiceNumber = payment.InvoiceNumber, + ContractNumber = payment.ContractNumber ?? string.Empty, + SupplierName = payment.SupplierName, + SupplierNumber = payment.SupplierNumber, + PayeeName = payment.ApplicantName ?? string.Empty, + SubmissionConfirmationCode = payment.SubmissionConfirmationCode ?? string.Empty, + CorrelationProvider = PaymentConsts.ApplicationCorrelationProvider, + PaidDate = payment.PaidDate, + }).ToList(); + + await paymentRequestAppService.CreateHistoricalAsync(payments); + + return NoContent(); + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js new file mode 100644 index 000000000..82d3df228 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js @@ -0,0 +1,86 @@ +function removeHistoricalPaymentRequest(applicationId) { + let $container = $('#' + applicationId); + let $parentGroup = $container.closest('.parent-child-group'); + $container.remove(); + + if (!$('div.single-payment').length) { + $('#no-payment-msg').css('display', 'block'); + $('#historical-payment-modal').find('#btnSubmitHistoricalPayment').prop('disabled', true); + } else { + $('#no-payment-msg').css('display', 'none'); + } + + if ($parentGroup.length && $parentGroup.find('.single-payment').length === 0) { + $parentGroup.remove(); + } + + validateAllHistoricalPaymentAmounts(); +} + +function closeHistoricalPaymentModal() { + $('#historical-payment-modal').modal('hide'); +} + +function checkHistoricalMaxValueRequest(applicationId, input, amountRemaining) { + if (isPartOfParentChildGroup(applicationId)) { + validateParentChildAmounts(applicationId); + } else { + let enteredValue = Number.parseFloat(input.value.replace(/,/g, '')); + let remainingErrorId = '#error_column_' + applicationId; + if (amountRemaining < enteredValue) { + $(remainingErrorId).css('display', 'block'); + } else { + $(remainingErrorId).css('display', 'none'); + } + } +} + +function validateAllHistoricalPaymentAmounts() { + $('input[name*=".CorrelationId"]').each(function () { + let correlationId = $(this).val(); + let index = getIndexByCorrelationId(correlationId); + let isPartOfGroup = + $(`input[name="ApplicationPaymentRequestForm[${index}].IsPartOfParentChildGroup"]`).val() === 'True'; + + if (isPartOfGroup) { + validateParentChildAmounts(correlationId); + } else { + let amountInput = $(`input[name="ApplicationPaymentRequestForm[${index}].Amount"]`); + let remainingAmount = parseFloat( + $(`input[name="ApplicationPaymentRequestForm[${index}].RemainingAmount"]`).val() + ); + let enteredValue = parseFloat(amountInput.val().replace(/,/g, '')) || 0; + let remainingErrorId = `#error_column_${correlationId}`; + + if (enteredValue > remainingAmount) { + $(remainingErrorId).css('display', 'block'); + } else { + $(remainingErrorId).css('display', 'none'); + } + } + }); +} + +function submitHistoricalPayments() { + validateAllHistoricalPaymentAmounts(); + + let validationFailed = $('.payment-error-column:visible').length > 0; + + if (validationFailed) { + abp.notify.error( + '', + 'There are payment requests that are in error. Please remove or fix them before submitting.' + ); + return false; + } else { + let form = document.getElementById('historicalpaymentform'); + if (!form.reportValidity()) { + return false; + } + $('#historicalpaymentform').submit(); + } +} + +$(function () { + validateAllHistoricalPaymentAmounts(); +}); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreatePaymentRequests.cshtml.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreatePaymentRequests.cshtml.cs index 0197e3c61..ccebb2e77 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreatePaymentRequests.cshtml.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreatePaymentRequests.cshtml.cs @@ -178,7 +178,13 @@ public async Task OnPostAsync() { if (ApplicationPaymentRequestForm == null) return NoContent(); - // Validate parent-child payment amounts + // Validate standalone and parent-child payment amounts against current DB state + var standaloneErrors = await helperService.ValidateStandalonePaymentAmountsAsync(ApplicationPaymentRequestForm); + if (standaloneErrors.Count != 0) + { + throw new UserFriendlyException(string.Join(" ", standaloneErrors)); + } + var validationErrors = await helperService.ValidateParentChildPaymentAmountsAsync(ApplicationPaymentRequestForm); if (validationErrors.Count != 0) { @@ -191,31 +197,43 @@ public async Task OnPostAsync() "Cannot submit payment request: Supplier number is missing for one or more applications."); } + if (ApplicationPaymentRequestForm.Exists(payment => payment.SiteId == Guid.Empty)) + { + throw new UserFriendlyException( + "Cannot submit payment request: Site is missing for one or more applications."); + } + + // Resolve override once — used for both validation and mapping below bool hasOverridePermission = await AuthorizationService.IsGrantedAsync(PaymentsPermissions.Payments.AccountCodingOverride); - var payments = MapPaymentRequests(hasOverridePermission); + Guid? accountCodingOverrideId = null; + if (hasOverridePermission + && !string.IsNullOrWhiteSpace(AccountCodingOverride) + && Guid.TryParse(AccountCodingOverride, out var overrideGuid) + && overrideGuid != Guid.Empty) + { + accountCodingOverrideId = overrideGuid; + } + + if (accountCodingOverrideId == null + && ApplicationPaymentRequestForm.Exists(payment => payment.AccountCodingId == null || payment.AccountCodingId == Guid.Empty)) + { + throw new UserFriendlyException( + "Cannot submit payment request: Account Coding is missing for one or more applications."); + } + + var payments = MapPaymentRequests(accountCodingOverrideId); await paymentRequestAppService.CreateAsync(payments); return NoContent(); } - private List MapPaymentRequests(bool hasOverridePermission) + private List MapPaymentRequests(Guid? accountCodingOverrideId) { var payments = new List(); if (ApplicationPaymentRequestForm == null) return payments; - // Only apply the override if the user has the permission, - // a value was provided, and it parses to a valid non-empty GUID - Guid? accountCodingOverrideId = null; - if (hasOverridePermission - && !string.IsNullOrWhiteSpace(AccountCodingOverride) - && Guid.TryParse(AccountCodingOverride, out var overrideGuid) - && overrideGuid != Guid.Empty) - { - accountCodingOverrideId = overrideGuid; - } - foreach (var payment in ApplicationPaymentRequestForm) { payments.Add(new CreatePaymentRequestDto() diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/HistoricalPaymentsModel.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/HistoricalPaymentsModel.cs new file mode 100644 index 000000000..657bb6fc9 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/HistoricalPaymentsModel.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace Unity.Payments.Web.Pages.Payments +{ + public class HistoricalPaymentsModel : IPaymentFormItem + { + // IPaymentFormItem + public Guid CorrelationId { get; set; } + public string InvoiceNumber { get; set; } = string.Empty; + public decimal Amount { get; set; } + public string? ParentReferenceNo { get; set; } + public string? SubmissionConfirmationCode { get; set; } + public decimal? MaximumAllowedAmount { get; set; } + public bool IsPartOfParentChildGroup { get; set; } + public decimal? ParentApprovedAmount { get; set; } + + [Required(ErrorMessage = "Paid Date is required.")] + [DisplayName("ApplicationHistoricalPaymentRequest:PaidDate")] + public string PaidDate { get; set; } = string.Empty; + + [DisplayName("ApplicationPaymentRequest:Description")] + [MaxLength(40)] + public string? Description { get; set; } + + public string? ApplicantName { get; set; } + public string? ContractNumber { get; set; } + public decimal RemainingAmount { get; set; } + public List ErrorList { get; set; } = []; + public bool DisableFields { get; set; } = false; + public string? SupplierName { get; set; } + public string? SupplierNumber { get; set; } + public Guid? SiteId { get; set; } + public string? SiteName { get; set; } + public Guid? AccountCodingId { get; set; } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js index ba06ae76b..beb72f0da 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js @@ -484,6 +484,7 @@ $(function () { className: 'data-table-header', index: columnIndex, render: function (data) { + if (!data) return ''; switch (data.paymentGroup) { case 1: return 'EFT'; diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentRequestPageHelperService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentRequestPageHelperService.cs index d3c3dc9d4..eaf5559cb 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentRequestPageHelperService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentRequestPageHelperService.cs @@ -273,10 +273,48 @@ public async Task PopulateParentChildValidationDataAsync(List form) where } } + public async Task> ValidateStandalonePaymentAmountsAsync(List form) where T : IPaymentFormItem + { + List errors = []; + + var standaloneItems = form.Where(x => !x.IsPartOfParentChildGroup).ToList(); + + foreach (var item in standaloneItems) + { + if (item.Amount <= 0) + { + errors.Add($"Payment amount for application must be greater than zero."); + continue; + } + + var application = await applicationService.GetAsync(item.CorrelationId); + decimal currentRemainingAmount = await GetRemainingAmountAsync(application); + + if (item.Amount > currentRemainingAmount) + { + errors.Add($"Payment amount (${item.Amount:N2}) for application {application.ReferenceNo} exceeds the current remaining amount (${currentRemainingAmount:N2}). " + + $"The remaining amount may have changed since the form was loaded. Please refresh and try again."); + } + } + + return errors; + } + public async Task> ValidateParentChildPaymentAmountsAsync(List form) where T : IPaymentFormItem { List errors = []; + var zeroAmountItems = form + .Where(x => !string.IsNullOrEmpty(x.ParentReferenceNo) && x.Amount <= 0) + .ToList(); + + foreach (var item in zeroAmountItems) + { + errors.Add($"Payment amount for application in parent-child group '{item.ParentReferenceNo}' must be greater than zero."); + } + + if (errors.Count > 0) return errors; + var childGroups = form .Where(x => !string.IsNullOrEmpty(x.ParentReferenceNo)) .GroupBy(x => x.ParentReferenceNo); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/PaymentInfo/Default.js b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/PaymentInfo/Default.js index 6db3794f7..e36b24ac2 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/PaymentInfo/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/PaymentInfo/Default.js @@ -409,6 +409,7 @@ data: 'site.addressLine1', className: 'data-table-header', render: function (data, type, full, meta) { + if (!full.site) return ''; return ( nullToEmpty(full.site.addressLine1) + ' ' + @@ -576,6 +577,9 @@ function getPaymentStatusTextColor(status) { case 'Paid': return '#42814A'; + case 'HistoricalPayment': + return '#42814A'; + case 'Failed': return '#CE3E39'; diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/BatchPaymentRequests/PaymentRequestAppService_Tests.cs b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/BatchPaymentRequests/PaymentRequestAppService_Tests.cs index 3d6f2bcda..a4525bbda 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/BatchPaymentRequests/PaymentRequestAppService_Tests.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/BatchPaymentRequests/PaymentRequestAppService_Tests.cs @@ -68,6 +68,7 @@ public async Task CreateAsync_CreatesPaymentRequest() PayeeName= "", SiteId= siteId, SupplierNumber = "SUP-TEST", + AccountCodingId = Guid.NewGuid(), } ]; // Act @@ -100,7 +101,8 @@ public async Task GetListAsync_ReturnsPaymentsList() CorrelationProvider = "", ReferenceNumber = "UP-XXXX-000000", BatchName = "UNITY_BATCH_1", - BatchNumber = 1 + BatchNumber = 1, + AccountCodingId = Guid.NewGuid() }; _ = await _paymentRequestRepository.InsertAsync(new PaymentRequest(Guid.NewGuid(), paymentRequestDto), true); @@ -135,7 +137,8 @@ public async Task GetListAsync_ReturnsPagedPaymentsList() CorrelationProvider = "TestProvider", ReferenceNumber = "UP-XXXX-000001", BatchName = "UNITY_BATCH_1", - BatchNumber = 1 + BatchNumber = 1, + AccountCodingId = Guid.NewGuid() }; _ = await _paymentRequestRepository.InsertAsync(new PaymentRequest(Guid.NewGuid(), paymentRequestDto), true); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_PaymentRollup_Tests.cs b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_PaymentRollup_Tests.cs index 8a97321dc..150e1751f 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_PaymentRollup_Tests.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_PaymentRollup_Tests.cs @@ -376,6 +376,47 @@ public async Task Should_Return_Empty_For_Unknown_CorrelationIds() results.ShouldBeEmpty(); } + [Fact] + [Trait("Category", "Integration")] + public async Task Should_Count_HistoricalPayment_In_TotalPaid() + { + // Arrange + var correlationId = Guid.NewGuid(); + + using var uow = _unitOfWorkManager.Begin(); + await InsertHistoricalPaymentRequestAsync(correlationId, 750m); + + // Act + var results = await _paymentRequestRepository + .GetBatchPaymentRollupsByCorrelationIdsAsync([correlationId]); + + // Assert + results.Count.ShouldBe(1); + results[0].TotalPaid.ShouldBe(750m); + } + + [Fact] + [Trait("Category", "Integration")] + public async Task Should_Count_HistoricalPayment_Alongside_FullyPaid_In_TotalPaid() + { + // Arrange + var correlationId = Guid.NewGuid(); + var siteId = await CreateSupplierAndSiteAsync(); + + using var uow = _unitOfWorkManager.Begin(); + await InsertPaymentRequestAsync(siteId, correlationId, 1000m, + PaymentRequestStatus.Submitted, paymentStatus: "Fully Paid"); + await InsertHistoricalPaymentRequestAsync(correlationId, 500m); + + // Act + var results = await _paymentRequestRepository + .GetBatchPaymentRollupsByCorrelationIdsAsync([correlationId]); + + // Assert + results.Count.ShouldBe(1); + results[0].TotalPaid.ShouldBe(1500m); + } + #endregion #region Helpers @@ -411,7 +452,8 @@ private async Task InsertPaymentRequestAsync( CorrelationProvider = "Test", ReferenceNumber = $"REF-{Guid.NewGuid():N}", BatchName = "TEST_BATCH", - BatchNumber = 1 + BatchNumber = 1, + AccountCodingId = Guid.NewGuid() }; var paymentRequest = new PaymentRequest(Guid.NewGuid(), dto); @@ -430,5 +472,28 @@ private async Task InsertPaymentRequestAsync( await _paymentRequestRepository.InsertAsync(paymentRequest, true); } + private async Task InsertHistoricalPaymentRequestAsync( + Guid correlationId, + decimal amount) + { + var dto = new CreateHistoricalPaymentRequestDto + { + InvoiceNumber = $"HIST-{Guid.NewGuid():N}", + Amount = amount, + PayeeName = "Test Payee", + ContractNumber = "0000000000", + CorrelationId = correlationId, + CorrelationProvider = "Test", + ReferenceNumber = $"REF-{Guid.NewGuid():N}", + BatchName = "HIST_BATCH", + BatchNumber = 1, + PaidDate = "2025-01-15" + // SiteId, SupplierNumber, AccountCodingId intentionally omitted — optional for historical + }; + + var paymentRequest = new PaymentRequest(Guid.NewGuid(), dto); + await _paymentRequestRepository.InsertAsync(paymentRequest, true); + } + #endregion } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_Tests.cs b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_Tests.cs index 6da52c79a..7623ebf30 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_Tests.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_Tests.cs @@ -820,7 +820,8 @@ private async Task InsertAndGetPaymentRequestAsync( CorrelationProvider = "Test", ReferenceNumber = $"REF-{Guid.NewGuid():N}", BatchName = "TEST_BATCH", - BatchNumber = 1 + BatchNumber = 1, + AccountCodingId = Guid.NewGuid() }; var paymentRequest = new PaymentRequest(Guid.NewGuid(), dto); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequest_Constructor_Tests.cs b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequest_Constructor_Tests.cs new file mode 100644 index 000000000..658e44e11 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequest_Constructor_Tests.cs @@ -0,0 +1,180 @@ +using Shouldly; +using System; +using System.ComponentModel; +using Unity.Payments.Codes; +using Unity.Payments.Domain.Exceptions; +using Unity.Payments.Domain.PaymentRequests; +using Unity.Payments.Enums; +using Unity.Payments.PaymentRequests; +using Volo.Abp; +using Xunit; + +namespace Unity.Payments.Domain.PaymentRequests; + +[Category("Domain")] +public class PaymentRequest_Constructor_Tests : PaymentsApplicationTestBase +{ + #region Normal payment constructor — new validations + + [Fact] + public void NormalConstructor_WithEmptySiteId_ThrowsMissingSite() + { + var dto = BuildNormalDto(siteId: Guid.Empty); + Should.Throw(() => new PaymentRequest(Guid.NewGuid(), dto)) + .Code.ShouldBe(ErrorConsts.MissingSite); + } + + [Fact] + public void NormalConstructor_WithNullAccountCodingId_ThrowsMissingAccountCoding() + { + var dto = BuildNormalDto(accountCodingId: null); + Should.Throw(() => new PaymentRequest(Guid.NewGuid(), dto)) + .Code.ShouldBe(ErrorConsts.MissingAccountCoding); + } + + [Fact] + public void NormalConstructor_WithEmptyAccountCodingId_ThrowsMissingAccountCoding() + { + var dto = BuildNormalDto(accountCodingId: Guid.Empty); + Should.Throw(() => new PaymentRequest(Guid.NewGuid(), dto)) + .Code.ShouldBe(ErrorConsts.MissingAccountCoding); + } + + [Fact] + public void NormalConstructor_WithValidData_Succeeds() + { + var dto = BuildNormalDto(); + var payment = new PaymentRequest(Guid.NewGuid(), dto); + payment.ShouldNotBeNull(); + payment.Status.ShouldBe(PaymentRequestStatus.L1Pending); + payment.ExpenseApprovals.Count.ShouldBe(2); + } + + #endregion + + #region Historical payment constructor + + [Fact] + public void HistoricalConstructor_WithZeroAmount_ThrowsZeroPayment() + { + var dto = BuildHistoricalDto(amount: 0m); + Should.Throw(() => new PaymentRequest(Guid.NewGuid(), dto)) + .Code.ShouldBe(ErrorConsts.ZeroPayment); + } + + [Fact] + public void HistoricalConstructor_WithNegativeAmount_ThrowsZeroPayment() + { + var dto = BuildHistoricalDto(amount: -1m); + Should.Throw(() => new PaymentRequest(Guid.NewGuid(), dto)) + .Code.ShouldBe(ErrorConsts.ZeroPayment); + } + + [Fact] + public void HistoricalConstructor_WithNullSiteSupplierAndAccountCoding_DoesNotThrow() + { + var dto = BuildHistoricalDto(); + dto.SiteId.ShouldBeNull(); + dto.SupplierNumber.ShouldBeNull(); + dto.AccountCodingId.ShouldBeNull(); + + Should.NotThrow(() => new PaymentRequest(Guid.NewGuid(), dto)); + } + + [Fact] + public void HistoricalConstructor_SetsStatus_ToHistoricalPayment() + { + var payment = new PaymentRequest(Guid.NewGuid(), BuildHistoricalDto()); + payment.Status.ShouldBe(PaymentRequestStatus.HistoricalPayment); + } + + [Fact] + public void HistoricalConstructor_SetsPaymentStatus_ToPaid() + { + var payment = new PaymentRequest(Guid.NewGuid(), BuildHistoricalDto()); + payment.PaymentStatus.ShouldBe(CasPaymentRequestStatus.Paid); + } + + [Fact] + public void HistoricalConstructor_SetsInvoiceStatus_ToPaid() + { + var payment = new PaymentRequest(Guid.NewGuid(), BuildHistoricalDto()); + payment.InvoiceStatus.ShouldBe(CasPaymentRequestStatus.Paid); + } + + [Fact] + public void HistoricalConstructor_SetsPaymentDate_FromPaidDate() + { + var dto = BuildHistoricalDto(); + dto.PaidDate = "2025-06-15"; + + var payment = new PaymentRequest(Guid.NewGuid(), dto); + + payment.PaymentDate.ShouldBe("2025-06-15"); + } + + [Fact] + public void HistoricalConstructor_CreatesNoExpenseApprovals() + { + var payment = new PaymentRequest(Guid.NewGuid(), BuildHistoricalDto()); + payment.ExpenseApprovals.ShouldBeEmpty(); + } + + [Fact] + public void HistoricalConstructor_WithOptionalFieldsProvided_StoresThem() + { + var siteId = Guid.NewGuid(); + var accountCodingId = Guid.NewGuid(); + var dto = BuildHistoricalDto(); + dto.SiteId = siteId; + dto.SupplierNumber = "SUP-001"; + dto.SupplierName = "Test Supplier"; + dto.AccountCodingId = accountCodingId; + + var payment = new PaymentRequest(Guid.NewGuid(), dto); + + payment.SiteId.ShouldBe(siteId); + payment.SupplierNumber.ShouldBe("SUP-001"); + payment.SupplierName.ShouldBe("Test Supplier"); + payment.AccountCodingId.ShouldBe(accountCodingId); + } + + #endregion + + #region Helpers + + private static CreatePaymentRequestDto BuildNormalDto( + Guid? siteId = null, + Guid? accountCodingId = null) => new() + { + InvoiceNumber = "INV-001", + Amount = 500m, + PayeeName = "Test Payee", + ContractNumber = "C-001", + SupplierNumber = "SUP-001", + SiteId = siteId ?? Guid.NewGuid(), + CorrelationId = Guid.NewGuid(), + CorrelationProvider = "Application", + ReferenceNumber = $"REF-{Guid.NewGuid():N}", + BatchName = "TEST_BATCH", + BatchNumber = 1, + AccountCodingId = accountCodingId ?? Guid.NewGuid() + }; + + private static CreateHistoricalPaymentRequestDto BuildHistoricalDto(decimal amount = 500m) => new() + { + InvoiceNumber = "HIST-INV-001", + Amount = amount, + PayeeName = "Test Payee", + ContractNumber = "C-001", + PaidDate = "2025-01-15", + CorrelationId = Guid.NewGuid(), + CorrelationProvider = "Application", + ReferenceNumber = $"REF-{Guid.NewGuid():N}", + BatchName = "HIST_BATCH", + BatchNumber = 1 + // SiteId, SupplierNumber, SupplierName, AccountCodingId intentionally omitted + }; + + #endregion +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/PaymentInfoDataProvider.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/PaymentInfoDataProvider.cs index 973cc86a3..c602ddecf 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/PaymentInfoDataProvider.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/PaymentInfoDataProvider.cs @@ -6,6 +6,7 @@ using Unity.GrantManager.Applications; using Unity.Payments.Domain.PaymentRequests; using Unity.Payments.Codes; +using Unity.Payments.Enums; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; using Volo.Abp.MultiTenancy; @@ -60,8 +61,9 @@ join application in applicationsQuery on submission.ApplicationId equals applica #pragma warning disable CA1862 // EF Core does not support StringComparison overloads - https://github.com/dotnet/efcore/issues/1222 var paymentDetails = await paymentsQueryable .Where(pr => applicationLookup.Keys.Contains(pr.CorrelationId) - && pr.PaymentStatus != null - && pr.PaymentStatus.Trim().ToUpper() == CasPaymentRequestStatus.FullyPaid.ToUpper()) + && ((pr.PaymentStatus != null + && pr.PaymentStatus.Trim().ToUpper() == CasPaymentRequestStatus.FullyPaid.ToUpper()) + || pr.Status == PaymentRequestStatus.HistoricalPayment)) .ToListAsync(); #pragma warning restore CA1862 @@ -72,7 +74,7 @@ join application in applicationsQuery on submission.ApplicationId equals applica ReferenceNo = applicationLookup.TryGetValue(p.CorrelationId, out var refNo) ? refNo : string.Empty, Amount = p.Amount, PaymentDate = p.PaymentDate, - PaymentStatus = CasPaymentRequestStatus.FullyPaid + PaymentStatus = p.PaymentStatus ?? CasPaymentRequestStatus.FullyPaid })); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260516025648_MakePaymentRequestSiteIdNullable.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260516025648_MakePaymentRequestSiteIdNullable.Designer.cs new file mode 100644 index 000000000..f04feac77 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260516025648_MakePaymentRequestSiteIdNullable.Designer.cs @@ -0,0 +1,5031 @@ +// +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("20260516025648_MakePaymentRequestSiteIdNullable")] + partial class MakePaymentRequestSiteIdNullable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "10.0.3") + .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("IsArchived") + .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("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("Definition") + .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("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("ReportsComments") + .HasColumnType("text"); + + b.Property("Sector") + .HasColumnType("text"); + + b.Property("SectorSubSectorIndustryDesc") + .HasColumnType("text"); + + 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.HasIndex("TenantId"); + + 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.HasIndex("TenantId", "ApplicationId"); + + 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("DefaultSiteId") + .HasColumnType("uuid"); + + 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.HasIndex("ReferenceNo"); + + b.HasIndex("TenantId", "SubmissionDate") + .HasFilter("\"IsDeleted\" = false"); + + 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.HasIndex("TenantId", "ApplicationId"); + + 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("AutomaticallyGenerateAIAnalysis") + .HasColumnType("boolean"); + + 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("ManuallyInitiateAIAnalysis") + .HasColumnType("boolean"); + + 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("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.HasIndex("TenantId", "IsDeleted") + .HasFilter("\"IsDeleted\" = false"); + + 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("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.HasIndex("TenantId", "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.HasIndex("TenantId", "ApplicationId"); + + 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.Applications.ReportsHistory", 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("FiscalYear") + .HasColumnType("text"); + + b.Property("IncompleteReport") + .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("Outstanding") + .HasColumnType("boolean"); + + b.Property("ReportDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ReportsHistories", (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.HasIndex("TenantId"); + + 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("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.Applications.ReportsHistory", 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); + + 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/20260516025648_MakePaymentRequestSiteIdNullable.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260516025648_MakePaymentRequestSiteIdNullable.cs new file mode 100644 index 000000000..a2e17c162 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260516025648_MakePaymentRequestSiteIdNullable.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + /// + public partial class MakePaymentRequestSiteIdNullable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "SiteId", + schema: "Payments", + table: "PaymentRequests", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "SiteId", + schema: "Payments", + table: "PaymentRequests", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs index c97b21dda..3d12304dc 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs @@ -4037,7 +4037,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); - b.Property("SiteId") + b.Property("SiteId") .HasColumnType("uuid"); b.Property("Status") @@ -4912,8 +4912,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasOne("Unity.Payments.Domain.Suppliers.Site", "Site") .WithMany() .HasForeignKey("SiteId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); + .OnDelete(DeleteBehavior.NoAction); b.Navigation("AccountCoding"); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/ActionBar.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/ActionBar.cs index fc08087d5..97183d98f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/ActionBar.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/ActionBar.cs @@ -41,6 +41,9 @@ public override void ConfigureBundle(BundleConfigurationContext context) .AddIfNotContains("/Pages/AssigneeSelection/AssigneeSelection.js"); context.Files .AddIfNotContains("/Pages/PaymentRequests/CreatePaymentRequestsModal.js"); + // NOTE: CreateHistoricalPaymentsModal.js depends on helpers defined in CreatePaymentRequestsModal.js — must remain after it. + context.Files + .AddIfNotContains("/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js"); context.Files .AddIfNotContains("/libs/jquery-maskmoney/dist/jquery.maskMoney.min.js"); context.Files diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.cshtml index 1134892b1..7aaf21086 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.cshtml @@ -96,7 +96,16 @@ text="Payment" class="custom-table-btn flex-none btn btn-secondary action-bar-btn-unavailable" button-type="Secondary" /> + } + @if (await FeatureChecker.IsEnabledAsync(PaymentConsts.UnityPaymentsFeature) + && await PermissionChecker.IsGrantedAsync(PaymentsPermissions.Payments.AddHistoricalPayment)) + { + } (); + dto.Payments.Count.ShouldBe(1); + dto.Payments[0].Amount.ShouldBe(800m); + } + + [Fact] + public async Task GetDataAsync_HistoricalPayment_ShouldHavePaidStatus() + { + var request = CreateRequest(); + var applicationId = Guid.NewGuid(); + + var historical = CreateHistoricalPaymentRequest(applicationId); + + SetupQueryables( + [CreateSubmission(applicationId, "TESTUSER")], + [CreateApplication(applicationId, "REF-001")], + [historical]); + + var result = await _provider.GetDataAsync(request); + + var dto = result.ShouldBeOfType(); + dto.Payments.Count.ShouldBe(1); + dto.Payments[0].PaymentStatus.ShouldBe(CasPaymentRequestStatus.Paid); + } + [Fact] public void Key_ShouldMatchExpected() { From c2ec744fd931e6d18a51b2ded14bf2aed51fbe17 Mon Sep 17 00:00:00 2001 From: aurelio-aot Date: Tue, 19 May 2026 14:15:28 -0700 Subject: [PATCH 13/63] AB#27996: Fix bug and address sonarqube issues --- .../PaymentRequests/CreateHistoricalPayments.cshtml | 6 +++--- .../PaymentRequests/CreateHistoricalPaymentsModal.js | 12 ++++++------ .../Pages/PaymentRequests/Index.js | 4 +++- .../PaymentRequestPageHelperService.cs | 7 +++++-- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml index 15b6ffbc1..0d54b965b 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml @@ -142,7 +142,7 @@ - + - - + + diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js index 82d3df228..c24998af6 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js @@ -3,11 +3,11 @@ function removeHistoricalPaymentRequest(applicationId) { let $parentGroup = $container.closest('.parent-child-group'); $container.remove(); - if (!$('div.single-payment').length) { + if ($('div.single-payment').length) { + $('#no-payment-msg').css('display', 'none'); + } else { $('#no-payment-msg').css('display', 'block'); $('#historical-payment-modal').find('#btnSubmitHistoricalPayment').prop('disabled', true); - } else { - $('#no-payment-msg').css('display', 'none'); } if ($parentGroup.length && $parentGroup.find('.single-payment').length === 0) { @@ -25,7 +25,7 @@ function checkHistoricalMaxValueRequest(applicationId, input, amountRemaining) { if (isPartOfParentChildGroup(applicationId)) { validateParentChildAmounts(applicationId); } else { - let enteredValue = Number.parseFloat(input.value.replace(/,/g, '')); + let enteredValue = Number.parseFloat(input.value.replaceAll(',', '')); let remainingErrorId = '#error_column_' + applicationId; if (amountRemaining < enteredValue) { $(remainingErrorId).css('display', 'block'); @@ -46,10 +46,10 @@ function validateAllHistoricalPaymentAmounts() { validateParentChildAmounts(correlationId); } else { let amountInput = $(`input[name="ApplicationPaymentRequestForm[${index}].Amount"]`); - let remainingAmount = parseFloat( + let remainingAmount = Number.parseFloat( $(`input[name="ApplicationPaymentRequestForm[${index}].RemainingAmount"]`).val() ); - let enteredValue = parseFloat(amountInput.val().replace(/,/g, '')) || 0; + let enteredValue = Number.parseFloat(amountInput.val().replaceAll(',', '')) || 0; let remainingErrorId = `#error_column_${correlationId}`; if (enteredValue > remainingAmount) { diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js index beb72f0da..b09e87e7a 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js @@ -286,7 +286,8 @@ $(function () { } else { payment_check_status_buttons.disable(); } - if (dataTable.rows({ selected: true }).indexes().length > 0 && !isInSentState) { + let hasHistoricalPayment = dataTable.rows('.selected').data().toArray().some(row => row.status === 'HistoricalPayment'); + if (dataTable.rows({ selected: true }).indexes().length > 0 && !isInSentState && !hasHistoricalPayment) { if (abp.auth.isGranted('PaymentsPermissions.Payments.L1ApproveOrDecline') || abp.auth.isGranted('PaymentsPermissions.Payments.L2ApproveOrDecline') || abp.auth.isGranted('PaymentsPermissions.Payments.L3ApproveOrDecline')) { @@ -802,6 +803,7 @@ $(function () { case "Submitted": return "#5595D9"; + case "HistoricalPayment": case "Paid": return "#42814A"; diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentRequestPageHelperService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentRequestPageHelperService.cs index eaf5559cb..7686fd1e4 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentRequestPageHelperService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/PaymentRequestPageHelperService.cs @@ -305,12 +305,15 @@ public async Task> ValidateParentChildPaymentAmountsAsync(List errors = []; var zeroAmountItems = form - .Where(x => !string.IsNullOrEmpty(x.ParentReferenceNo) && x.Amount <= 0) + .Where(x => x.IsPartOfParentChildGroup && x.Amount <= 0) .ToList(); foreach (var item in zeroAmountItems) { - errors.Add($"Payment amount for application in parent-child group '{item.ParentReferenceNo}' must be greater than zero."); + var groupRef = !string.IsNullOrEmpty(item.ParentReferenceNo) + ? item.ParentReferenceNo + : item.SubmissionConfirmationCode; + errors.Add($"Payment amount for application in parent-child group '{groupRef}' must be greater than zero."); } if (errors.Count > 0) return errors; From 66f9d01657dd8f42cdbaed36e9c435293150bb10 Mon Sep 17 00:00:00 2001 From: aurelio-aot Date: Tue, 19 May 2026 15:37:41 -0700 Subject: [PATCH 14/63] AB#27996: Fix failing unit tests --- .../PaymentRequestAppService_Tests.cs | 23 +++++++++++++++---- ...ntRequestRepository_PaymentRollup_Tests.cs | 16 ++++++++++++- .../PaymentRequestRepository_Tests.cs | 15 +++++++++++- .../PaymentRequest_Constructor_Tests.cs | 3 ++- .../DataProviders/PaymentInfoDataProvider.cs | 4 +++- 5 files changed, 53 insertions(+), 8 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/BatchPaymentRequests/PaymentRequestAppService_Tests.cs b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/BatchPaymentRequests/PaymentRequestAppService_Tests.cs index a4525bbda..35c286753 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/BatchPaymentRequests/PaymentRequestAppService_Tests.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/BatchPaymentRequests/PaymentRequestAppService_Tests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Unity.Payments.Domain.AccountCodings; using Unity.Payments.Domain.PaymentRequests; using Unity.Payments.Domain.Suppliers; using Unity.Payments.Domain.Suppliers.ValueObjects; @@ -16,6 +17,7 @@ public class PaymentRequestAppService_Tests : PaymentsApplicationTestBase private readonly IPaymentRequestAppService _paymentRequestAppService; private readonly IPaymentRequestRepository _paymentRequestRepository; private readonly ISupplierRepository _supplierRepository; + private readonly IAccountCodingRepository _accountCodingRepository; private readonly IUnitOfWorkManager _unitOfWorkManager; public PaymentRequestAppService_Tests() @@ -23,14 +25,24 @@ public PaymentRequestAppService_Tests() _paymentRequestAppService = GetRequiredService(); _paymentRequestRepository = GetRequiredService(); _supplierRepository = GetRequiredService(); + _accountCodingRepository = GetRequiredService(); _unitOfWorkManager = GetRequiredService(); } + private async Task CreateAccountCodingAsync() + { + using var uow = _unitOfWorkManager.Begin(); + var accountCoding = AccountCoding.Create("ABC", "ABCDE", "AB001", "AB01", "AB00001"); + await _accountCodingRepository.InsertAsync(accountCoding, true); + await uow.CompleteAsync(); + return accountCoding.Id; + } + [Fact] [Trait("Category", "Integration")] public async Task CreateAsync_CreatesPaymentRequest() { - // Arrange + // Arrange using var uow = _unitOfWorkManager.Begin(); var siteId = Guid.NewGuid(); var newSupplier = new Supplier(Guid.NewGuid(), @@ -55,6 +67,7 @@ public async Task CreateAsync_CreatesPaymentRequest() "ABC123"))); _ = await _supplierRepository.InsertAsync(newSupplier, true); + var accountCodingId = await CreateAccountCodingAsync(); List paymentRequests = [ @@ -68,7 +81,7 @@ public async Task CreateAsync_CreatesPaymentRequest() PayeeName= "", SiteId= siteId, SupplierNumber = "SUP-TEST", - AccountCodingId = Guid.NewGuid(), + AccountCodingId = accountCodingId, } ]; // Act @@ -89,6 +102,7 @@ public async Task GetListAsync_ReturnsPaymentsList() var supplier = new Supplier(Guid.NewGuid(), "supp", "123"); supplier.AddSite(new Site(Guid.NewGuid(), "123", PaymentGroup.EFT)); var addedSupplier = await _supplierRepository.InsertAsync(supplier); + var accountCodingId = await CreateAccountCodingAsync(); CreatePaymentRequestDto paymentRequestDto = new() { InvoiceNumber = "", @@ -102,7 +116,7 @@ public async Task GetListAsync_ReturnsPaymentsList() ReferenceNumber = "UP-XXXX-000000", BatchName = "UNITY_BATCH_1", BatchNumber = 1, - AccountCodingId = Guid.NewGuid() + AccountCodingId = accountCodingId }; _ = await _paymentRequestRepository.InsertAsync(new PaymentRequest(Guid.NewGuid(), paymentRequestDto), true); @@ -125,6 +139,7 @@ public async Task GetListAsync_ReturnsPagedPaymentsList() var supplier = new Supplier(Guid.NewGuid(), "supp", "123"); supplier.AddSite(new Site(Guid.NewGuid(), "123", PaymentGroup.EFT)); var addedSupplier = await _supplierRepository.InsertAsync(supplier); + var accountCodingId = await CreateAccountCodingAsync(); CreatePaymentRequestDto paymentRequestDto = new() { InvoiceNumber = "INV-001", @@ -138,7 +153,7 @@ public async Task GetListAsync_ReturnsPagedPaymentsList() ReferenceNumber = "UP-XXXX-000001", BatchName = "UNITY_BATCH_1", BatchNumber = 1, - AccountCodingId = Guid.NewGuid() + AccountCodingId = accountCodingId }; _ = await _paymentRequestRepository.InsertAsync(new PaymentRequest(Guid.NewGuid(), paymentRequestDto), true); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_PaymentRollup_Tests.cs b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_PaymentRollup_Tests.cs index 150e1751f..054a52ce5 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_PaymentRollup_Tests.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_PaymentRollup_Tests.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel; using System.Threading.Tasks; +using Unity.Payments.Domain.AccountCodings; using Unity.Payments.Domain.Suppliers; using Unity.Payments.Enums; using Unity.Payments.PaymentRequests; @@ -15,12 +16,14 @@ public class PaymentRequestRepository_PaymentRollup_Tests : PaymentsApplicationT { private readonly IPaymentRequestRepository _paymentRequestRepository; private readonly ISupplierRepository _supplierRepository; + private readonly IAccountCodingRepository _accountCodingRepository; private readonly IUnitOfWorkManager _unitOfWorkManager; public PaymentRequestRepository_PaymentRollup_Tests() { _paymentRequestRepository = GetRequiredService(); _supplierRepository = GetRequiredService(); + _accountCodingRepository = GetRequiredService(); _unitOfWorkManager = GetRequiredService(); } @@ -432,6 +435,15 @@ private async Task CreateSupplierAndSiteAsync() return siteId; } + private async Task CreateAccountCodingAsync() + { + using var uow = _unitOfWorkManager.Begin(); + var accountCoding = AccountCoding.Create("ABC", "ABCDE", "AB001", "AB01", "AB00001"); + await _accountCodingRepository.InsertAsync(accountCoding, true); + await uow.CompleteAsync(); + return accountCoding.Id; + } + private async Task InsertPaymentRequestAsync( Guid siteId, Guid correlationId, @@ -440,6 +452,8 @@ private async Task InsertPaymentRequestAsync( string? paymentStatus = null, string? invoiceStatus = null) { + var accountCodingId = await CreateAccountCodingAsync(); + var dto = new CreatePaymentRequestDto { InvoiceNumber = $"INV-{Guid.NewGuid():N}", @@ -453,7 +467,7 @@ private async Task InsertPaymentRequestAsync( ReferenceNumber = $"REF-{Guid.NewGuid():N}", BatchName = "TEST_BATCH", BatchNumber = 1, - AccountCodingId = Guid.NewGuid() + AccountCodingId = accountCodingId }; var paymentRequest = new PaymentRequest(Guid.NewGuid(), dto); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_Tests.cs b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_Tests.cs index 7623ebf30..f9e72f09a 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_Tests.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequestRepository_Tests.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Unity.Payments.Codes; +using Unity.Payments.Domain.AccountCodings; using Unity.Payments.Domain.Suppliers; using Unity.Payments.Enums; using Unity.Payments.PaymentRequests; @@ -17,15 +18,26 @@ public class PaymentRequestRepository_Tests : PaymentsApplicationTestBase { private readonly IPaymentRequestRepository _paymentRequestRepository; private readonly ISupplierRepository _supplierRepository; + private readonly IAccountCodingRepository _accountCodingRepository; private readonly IUnitOfWorkManager _unitOfWorkManager; public PaymentRequestRepository_Tests() { _paymentRequestRepository = GetRequiredService(); _supplierRepository = GetRequiredService(); + _accountCodingRepository = GetRequiredService(); _unitOfWorkManager = GetRequiredService(); } + private async Task CreateAccountCodingAsync() + { + using var uow = _unitOfWorkManager.Begin(); + var accountCoding = AccountCoding.Create("ABC", "ABCDE", "AB001", "AB01", "AB00001"); + await _accountCodingRepository.InsertAsync(accountCoding, true); + await uow.CompleteAsync(); + return accountCoding.Id; + } + #region GetCountByCorrelationId [Fact] @@ -808,6 +820,7 @@ private async Task InsertAndGetPaymentRequestAsync( string? invoiceStatus = null) { var invoiceNumber = customInvoiceNumber ?? $"INV-{Guid.NewGuid():N}"; + var accountCodingId = await CreateAccountCodingAsync(); var dto = new CreatePaymentRequestDto { InvoiceNumber = invoiceNumber, @@ -821,7 +834,7 @@ private async Task InsertAndGetPaymentRequestAsync( ReferenceNumber = $"REF-{Guid.NewGuid():N}", BatchName = "TEST_BATCH", BatchNumber = 1, - AccountCodingId = Guid.NewGuid() + AccountCodingId = accountCodingId }; var paymentRequest = new PaymentRequest(Guid.NewGuid(), dto); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequest_Constructor_Tests.cs b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequest_Constructor_Tests.cs index 658e44e11..7a646fc93 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequest_Constructor_Tests.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/test/Unity.Payments.Application.Tests/Domain/PaymentRequests/PaymentRequest_Constructor_Tests.cs @@ -27,7 +27,8 @@ public void NormalConstructor_WithEmptySiteId_ThrowsMissingSite() [Fact] public void NormalConstructor_WithNullAccountCodingId_ThrowsMissingAccountCoding() { - var dto = BuildNormalDto(accountCodingId: null); + var dto = BuildNormalDto(); + dto.AccountCodingId = null; Should.Throw(() => new PaymentRequest(Guid.NewGuid(), dto)) .Code.ShouldBe(ErrorConsts.MissingAccountCoding); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/PaymentInfoDataProvider.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/PaymentInfoDataProvider.cs index c602ddecf..1bf5dad29 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/PaymentInfoDataProvider.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/PaymentInfoDataProvider.cs @@ -74,7 +74,9 @@ join application in applicationsQuery on submission.ApplicationId equals applica ReferenceNo = applicationLookup.TryGetValue(p.CorrelationId, out var refNo) ? refNo : string.Empty, Amount = p.Amount, PaymentDate = p.PaymentDate, - PaymentStatus = p.PaymentStatus ?? CasPaymentRequestStatus.FullyPaid + PaymentStatus = p.Status == PaymentRequestStatus.HistoricalPayment + ? CasPaymentRequestStatus.Paid //Historical payments are considered paid as they represent completed transactions from the past, even if they were not processed through the current payment system. This allows us to acknowledge the payment without requiring it to have gone through the current validation and payment process. + : CasPaymentRequestStatus.FullyPaid })); } From d6553deab8b203e5f8e727984e10ea7219d26af5 Mon Sep 17 00:00:00 2001 From: aurelio-aot Date: Tue, 19 May 2026 18:01:12 -0700 Subject: [PATCH 15/63] AB#27996: Fix button alignment --- .../Pages/GrantApplications/Index.css | 6 +++--- .../Views/Shared/Components/ActionBar/Default.css | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.css index 40f6a4c98..74a44f8c5 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.css +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.css @@ -144,11 +144,11 @@ table.dataTable tbody tr { } .action-bar-date-controls #app_custom_buttons { - margin-top: 10px; + margin-top: 0; } .action-bar-date-controls #dynamicButtonContainerId { - margin-top: 10px; + margin-top: 0; } -/**end**/ \ No newline at end of file +/**end**/ diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.css index 5ccbfa7af..bd42bda46 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.css +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.css @@ -13,12 +13,25 @@ .search-action-bar { display: flex; + flex-wrap: wrap; justify-content: flex-end; align-items: center; + row-gap: 6px; } .search-action-bar_search-wrapper { flex: 1; + min-width: 220px; +} + +#app_custom_buttons { + flex-shrink: 0; +} + +#dynamicButtonContainerId { + flex-shrink: 0; + display: flex; + align-items: flex-end; } .action-bar-btn-unavailable { From 5832c696bc26302ecb94d6d6e70e54ca88244bc9 Mon Sep 17 00:00:00 2001 From: aurelio-aot Date: Wed, 20 May 2026 13:47:54 -0700 Subject: [PATCH 16/63] AB#27996: Move Warning Inside Historical payment dialog box. Add Site Name. --- .../CreateHistoricalPayments.cshtml | 3 +++ .../CreateHistoricalPaymentsModal.js | 9 ++++++- .../CreatePaymentRequests.cshtml | 2 +- .../Shared/Components/ActionBar/Default.js | 25 +++++++------------ 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml index 0d54b965b..213c5a393 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPayments.cshtml @@ -151,6 +151,9 @@ disabled="@(item.DisableFields ? "disabled" : null)" max="@DateTime.Today.ToString("yyyy-MM-dd")" /> + + + diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js index c24998af6..977c2e92e 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js @@ -77,7 +77,14 @@ function submitHistoricalPayments() { if (!form.reportValidity()) { return false; } - $('#historicalpaymentform').submit(); + abp.message.confirm( + 'You are about to create and associate a historical payment to the current application. This action bypasses the standard approval workflow and creates a payment with Paid status. Are you sure you want to proceed?', + 'Add Historical Payment', + function (confirmed) { + if (!confirmed) return; + $('#historicalpaymentform').submit(); + } + ); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreatePaymentRequests.cshtml b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreatePaymentRequests.cshtml index 7509ead8f..2c25168f1 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreatePaymentRequests.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreatePaymentRequests.cshtml @@ -191,7 +191,7 @@ - + diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.js index 624709eb9..4cbdd8650 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ActionBar/Default.js @@ -427,22 +427,15 @@ $(function () { }); $('#applicationHistoricalPaymentRequest').on('click', function () { - abp.message.confirm( - 'You are about to create and associate a historical payment to the current application. This action bypasses the standard approval workflow and creates a payment with Paid status. Are you sure you want to proceed?', - 'Add Historical Payment', - function (confirmed) { - if (!confirmed) return; - unity.grantManager.applications.applicationBulkActions - .storeApplicationIds({ applicationIds: selectedApplicationIds }) - .then(function (response) { - applicationHistoricalPaymentRequestModal.open({ cacheKey: response.cacheKey }); - }) - .catch(function (error) { - abp.notify.error('Failed to prepare historical payment. Please try again.'); - console.error('Error storing application IDs:', error); - }); - } - ); + unity.grantManager.applications.applicationBulkActions + .storeApplicationIds({ applicationIds: selectedApplicationIds }) + .then(function (response) { + applicationHistoricalPaymentRequestModal.open({ cacheKey: response.cacheKey }); + }) + .catch(function (error) { + abp.notify.error('Failed to prepare historical payment. Please try again.'); + console.error('Error storing application IDs:', error); + }); }); applicationHistoricalPaymentRequestModal.onResult(function () { From 1d5601596f5bd376e95611316d4520d6323af1d6 Mon Sep 17 00:00:00 2001 From: aurelio-aot Date: Wed, 20 May 2026 15:29:19 -0700 Subject: [PATCH 17/63] AB#27996: Fix Invoice and Amounts validation --- .../CreateHistoricalPaymentsModal.js | 26 +++++++++---------- .../HistoricalPaymentsModel.cs | 4 +++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js index 977c2e92e..b4f8787f7 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/CreateHistoricalPaymentsModal.js @@ -72,20 +72,20 @@ function submitHistoricalPayments() { 'There are payment requests that are in error. Please remove or fix them before submitting.' ); return false; - } else { - let form = document.getElementById('historicalpaymentform'); - if (!form.reportValidity()) { - return false; - } - abp.message.confirm( - 'You are about to create and associate a historical payment to the current application. This action bypasses the standard approval workflow and creates a payment with Paid status. Are you sure you want to proceed?', - 'Add Historical Payment', - function (confirmed) { - if (!confirmed) return; - $('#historicalpaymentform').submit(); - } - ); } + + if (!$('#historicalpaymentform').valid()) { + return false; + } + + abp.message.confirm( + 'You are about to create and associate a historical payment to the current application. This action bypasses the standard approval workflow and creates a payment with Paid status. Are you sure you want to proceed?', + 'Add Historical Payment', + function (confirmed) { + if (!confirmed) return; + $('#historicalpaymentform').submit(); + } + ); } $(function () { diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/HistoricalPaymentsModel.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/HistoricalPaymentsModel.cs index 657bb6fc9..562af0411 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/HistoricalPaymentsModel.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/HistoricalPaymentsModel.cs @@ -9,7 +9,11 @@ public class HistoricalPaymentsModel : IPaymentFormItem { // IPaymentFormItem public Guid CorrelationId { get; set; } + [Required(ErrorMessage = "This field is required.")] + [DisplayName("ApplicationPaymentRequest:InvoiceNumber")] public string InvoiceNumber { get; set; } = string.Empty; + [Required] + [DisplayName("ApplicationPaymentRequest:Amount")] public decimal Amount { get; set; } public string? ParentReferenceNo { get; set; } public string? SubmissionConfirmationCode { get; set; } From 9767a8cb9767ddee59e96109e2c59ef2357213be Mon Sep 17 00:00:00 2001 From: aurelio-aot Date: Wed, 20 May 2026 16:03:30 -0700 Subject: [PATCH 18/63] AB#27996: AccountCoding is not required for Historical Payments --- ...RequestAccountCodingIdNullable.Designer.cs | 5031 +++++++++++++++++ ...kePaymentRequestAccountCodingIdNullable.cs | 39 + 2 files changed, 5070 insertions(+) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260520225348_MakePaymentRequestAccountCodingIdNullable.Designer.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260520225348_MakePaymentRequestAccountCodingIdNullable.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260520225348_MakePaymentRequestAccountCodingIdNullable.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260520225348_MakePaymentRequestAccountCodingIdNullable.Designer.cs new file mode 100644 index 000000000..a2dcbd839 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260520225348_MakePaymentRequestAccountCodingIdNullable.Designer.cs @@ -0,0 +1,5031 @@ +// +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("20260520225348_MakePaymentRequestAccountCodingIdNullable")] + partial class MakePaymentRequestAccountCodingIdNullable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "10.0.3") + .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("IsArchived") + .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("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("Definition") + .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("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("ReportsComments") + .HasColumnType("text"); + + b.Property("Sector") + .HasColumnType("text"); + + b.Property("SectorSubSectorIndustryDesc") + .HasColumnType("text"); + + 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.HasIndex("TenantId"); + + 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.HasIndex("TenantId", "ApplicationId"); + + 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("DefaultSiteId") + .HasColumnType("uuid"); + + 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.HasIndex("ReferenceNo"); + + b.HasIndex("TenantId", "SubmissionDate") + .HasFilter("\"IsDeleted\" = false"); + + 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.HasIndex("TenantId", "ApplicationId"); + + 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("AutomaticallyGenerateAIAnalysis") + .HasColumnType("boolean"); + + 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("ManuallyInitiateAIAnalysis") + .HasColumnType("boolean"); + + 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("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.HasIndex("TenantId", "IsDeleted") + .HasFilter("\"IsDeleted\" = false"); + + 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("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.HasIndex("TenantId", "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.HasIndex("TenantId", "ApplicationId"); + + 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.Applications.ReportsHistory", 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("FiscalYear") + .HasColumnType("text"); + + b.Property("IncompleteReport") + .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("Outstanding") + .HasColumnType("boolean"); + + b.Property("ReportDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ReportsHistories", (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.HasIndex("TenantId"); + + 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("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.Applications.ReportsHistory", 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); + + 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/20260520225348_MakePaymentRequestAccountCodingIdNullable.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260520225348_MakePaymentRequestAccountCodingIdNullable.cs new file mode 100644 index 000000000..03644ed6d --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260520225348_MakePaymentRequestAccountCodingIdNullable.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + /// + public partial class MakePaymentRequestAccountCodingIdNullable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "AccountCodingId", + schema: "Payments", + table: "PaymentRequests", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "AccountCodingId", + schema: "Payments", + table: "PaymentRequests", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + } + } +} From 39d4a1f499a5efb3e561d859743a04f832be42dd Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Thu, 21 May 2026 09:13:24 -0700 Subject: [PATCH 19/63] AB#32881 submissions and description --- .../Configuration/FieldPathTypeDto.cs | 7 + .../Configuration/MapMetadataDto.cs | 5 + .../Configuration/ReportColumnsMapDto.cs | 12 + .../Configuration/UpsertColumnMappingDto.cs | 5 + .../ConsolidatedFormVersionFieldsProvider.cs | 253 + .../ConsolidatedWorksheetFieldsProvider.cs | 316 ++ .../Configuration/ReportMappingUtils.cs | 22 +- .../Domain/Configuration/ReportColumnsMap.cs | 13 + .../ReportColumnsMapRepository.cs | 2 + .../ReportingApplicationMapperlyProfile.cs | 8 + .../Configuration/Providers.cs | 14 + .../ReportingConfiguration/Default.cshtml | 88 +- .../ReportingConfiguration/Default.js | 203 +- .../ReportingConfigurationViewComponent.cs | 20 +- ...lidatedWorksheetViewGeneration.Designer.cs | 4947 ++++++++++++++++ ..._AddConsolidatedWorksheetViewGeneration.cs | 36 + ...datedFormVersionViewGeneration.Designer.cs | 4948 +++++++++++++++++ ...ddConsolidatedFormVersionViewGeneration.cs | 36 + ...generate_consolidated_formversion_view.sql | 51 + .../generate_consolidated_worksheet_view.sql | 51 + .../get_consolidated_formversion_data.sql | 336 ++ .../get_consolidated_worksheet_data.sql | 278 + ...ty.GrantManager.EntityFrameworkCore.csproj | 16 + 23 files changed, 11612 insertions(+), 55 deletions(-) create mode 100644 applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedFormVersionFieldsProvider.cs create mode 100644 applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedWorksheetFieldsProvider.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260519000001_AddConsolidatedWorksheetViewGeneration.Designer.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260519000001_AddConsolidatedWorksheetViewGeneration.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260519000002_AddConsolidatedFormVersionViewGeneration.Designer.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260519000002_AddConsolidatedFormVersionViewGeneration.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/generate_consolidated_formversion_view.sql create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/generate_consolidated_worksheet_view.sql create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/get_consolidated_formversion_data.sql create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Scripts/get_consolidated_worksheet_data.sql diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/FieldPathTypeDto.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/FieldPathTypeDto.cs index 6d7490503..8178d8866 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/FieldPathTypeDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/FieldPathTypeDto.cs @@ -39,5 +39,12 @@ public class FieldPathTypeDto /// The path to reach the data, this is a datacentric version of the Path, and could be the same /// public string DataPath { get; set; } = string.Empty; + + /// + /// Optional version label used only by the consolidated worksheet provider. + /// Null means the field is merged across all versions; non-null (e.g., "v1") means the field + /// is specific to that form version (conflict or version-exclusive field). + /// + public string? VersionLabel { get; set; } = null; } } diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/MapMetadataDto.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/MapMetadataDto.cs index c6b57d93a..2b948f659 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/MapMetadataDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/MapMetadataDto.cs @@ -16,5 +16,10 @@ public class MapMetadataDto /// used for display purposes, change detection analysis, and mapping management operations. /// public Dictionary Info { get; set; } = new Dictionary(); + + /// + /// Gets or sets the optional free-text description for this mapping configuration (max 500 characters). + /// + public string? Description { get; set; } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/ReportColumnsMapDto.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/ReportColumnsMapDto.cs index f7116f322..6583e3cf6 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/ReportColumnsMapDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/ReportColumnsMapDto.cs @@ -72,6 +72,11 @@ public class MappingDto /// Each row defines how a source field maps to a database column with type, path, and label information. /// public MapRowDto[] Rows { get; set; } = []; + + /// + /// Gets or sets optional metadata for this mapping configuration including description and info context. + /// + public MapMetadataDto? Metadata { get; set; } } /// @@ -128,5 +133,12 @@ public class MapRowDto /// Represents the component type path (e.g., "form->panel->textfield") in the source schema structure. /// public string TypePath { get; set; } = string.Empty; + + /// + /// Gets or sets an optional version label indicating which form version this column belongs to. + /// Used exclusively for consolidated worksheet views: null means the column is merged across all versions; + /// a non-null value (e.g., "v1", "v2") means the column is specific to that form version. + /// + public string? VersionLabel { get; set; } = null; } } diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/UpsertColumnMappingDto.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/UpsertColumnMappingDto.cs index 01042b9eb..9c3a227eb 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/UpsertColumnMappingDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application.Contracts/Configuration/UpsertColumnMappingDto.cs @@ -15,6 +15,11 @@ public class UpsertColumnMappingDto /// of the auto-generated mapping configuration while preserving automatic naming for unmapped fields. /// public UpsertMapRowDto[] Rows { get; set; } = []; + + /// + /// Gets or sets the optional free-text description for this mapping configuration (max 500 characters). + /// + public string? Description { get; set; } } /// diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedFormVersionFieldsProvider.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedFormVersionFieldsProvider.cs new file mode 100644 index 000000000..0f0d1e3bb --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedFormVersionFieldsProvider.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Unity.GrantManager.ApplicationForms; +using Unity.GrantManager.Reporting.Configuration; +using Unity.Reporting.Domain.Configuration; +using Volo.Abp.DependencyInjection; + +namespace Unity.Reporting.Configuration.FieldsProviders +{ + /// + /// Fields provider for consolidated form version submission views that span all form versions. + /// Reads live field metadata directly from the form metadata service across all form versions, + /// merges fields by (Label, Path, Type), and detects version changes for break notification. + /// The CorrelationId for this provider is the FormId (not a specific form version ID). + /// + public class ConsolidatedFormVersionFieldsProvider( + IApplicationFormAppService applicationFormAppService, + IFormMetadataService formMetadataService) + : IFieldsProvider, ITransientDependency + { + public string CorrelationProvider => Providers.FormVersionConsolidated; + + /// + /// Retrieves and merges submission field metadata across all form versions for consolidated view configuration. + /// Fields matching on (Label, Path, Type) are merged into a single column entry. + /// Fields with the same (Label, Path) but different Type produce per-version conflict entries. + /// Fields unique to one version are included with a VersionLabel marker. + /// + public async Task GetFieldsMetadataAsync(Guid formId) + { + var versions = await applicationFormAppService.GetVersionsAsync(formId); + var versionsWithFields = new List<(Guid VersionId, string VersionLabel, FieldPathTypeDto[] Fields)>(); + var metadataInfo = new Dictionary(); + + foreach (var version in versions.OrderBy(v => v.Version)) + { + var versionLabel = $"v{version.Version}"; + var fullMetadata = await formMetadataService.GetFormComponentMetaDataAsync(version.Id); + + var fields = fullMetadata.Components + .Select(ConvertToFieldPathType) + .Where(x => x != null) + .Select(x => x!) + .ToArray(); + + if (fields.Length == 0) + continue; + + versionsWithFields.Add((version.Id, versionLabel, fields)); + metadataInfo[$"formversion_{version.Id}"] = versionLabel; + } + + var mergedFields = MergeFields(versionsWithFields); + var mapMetadata = new MapMetadataDto { Info = metadataInfo }; + + return new FieldPathMetaMapDto { Fields = [.. mergedFields], Metadata = mapMetadata }; + } + + /// + /// Detects changes in form versions since the consolidated mapping was last saved. + /// Returns a semicolon-joined change description or null if nothing has changed. + /// Since form version fields are immutable, only added/removed versions are tracked. + /// + public async Task DetectChangesAsync(Guid formId, ReportColumnsMap reportColumnsMap) + { + var versions = await applicationFormAppService.GetVersionsAsync(formId); + var currentInfo = new Dictionary(); + + foreach (var version in versions) + { + var versionLabel = $"v{version.Version}"; + var fullMetadata = await formMetadataService.GetFormComponentMetaDataAsync(version.Id); + + if (fullMetadata.Components.Count == 0) + continue; + + currentInfo[$"formversion_{version.Id}"] = versionLabel; + } + + var storedInfo = GetStoredInfo(reportColumnsMap); + var changes = DetectInfoChanges(storedInfo, currentInfo); + + return changes.Count > 0 ? string.Join("; ", changes) : null; + } + + private static FieldPathTypeDto? ConvertToFieldPathType(FormComponentMetaDataItemDto? item) + { + if (item == null) + return null; + + return new FieldPathTypeDto + { + Id = item.Id, + Path = item.Path, + Type = item.Type, + Key = item.Key, + Label = item.Label, + TypePath = item.TypePath, + DataPath = item.DataPath + }; + } + + private static List MergeFields( + List<(Guid VersionId, string VersionLabel, FieldPathTypeDto[] Fields)> versionsWithFields) + { + var exactMatchGroups = new Dictionary>(StringComparer.OrdinalIgnoreCase); + var pathGroups = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + foreach (var (_, versionLabel, fields) in versionsWithFields) + { + foreach (var field in fields) + { + var exactKey = $"{field.Label?.ToLowerInvariant()}|{field.Path?.ToLowerInvariant()}|{field.Type?.ToLowerInvariant()}"; + var pathKey = $"{field.Label?.ToLowerInvariant()}|{field.Path?.ToLowerInvariant()}"; + + if (!exactMatchGroups.TryGetValue(exactKey, out var exactList)) + { + exactList = []; + exactMatchGroups[exactKey] = exactList; + } + if (!exactList.Any(e => e.VersionLabel == versionLabel)) + { + exactList.Add((versionLabel, field)); + } + + if (!pathGroups.TryGetValue(pathKey, out var typeSet)) + { + typeSet = new HashSet(StringComparer.OrdinalIgnoreCase); + pathGroups[pathKey] = typeSet; + } + typeSet.Add(field.Type?.ToLowerInvariant() ?? string.Empty); + } + } + + var result = new List(); + var processedExactKeys = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var (_, versionLabel, fields) in versionsWithFields) + { + foreach (var field in fields) + { + var exactKey = $"{field.Label?.ToLowerInvariant()}|{field.Path?.ToLowerInvariant()}|{field.Type?.ToLowerInvariant()}"; + var pathKey = $"{field.Label?.ToLowerInvariant()}|{field.Path?.ToLowerInvariant()}"; + + if (processedExactKeys.Contains(exactKey)) + continue; + + processedExactKeys.Add(exactKey); + + var typesForPath = pathGroups[pathKey]; + var exactGroup = exactMatchGroups[exactKey]; + var versionsHavingThisExact = exactGroup.Select(e => e.VersionLabel).ToHashSet(StringComparer.OrdinalIgnoreCase); + + if (typesForPath.Count > 1) + { + // Conflict: same (label, path) but different types — emit per-version entry + result.Add(new FieldPathTypeDto + { + Id = field.Id, + Key = field.Key, + Label = field.Label, + Path = field.Path, + Type = field.Type, + TypePath = field.TypePath, + DataPath = field.DataPath, + VersionLabel = versionLabel + }); + } + else if (versionsWithFields.Count > 1 && versionsHavingThisExact.Count == versionsWithFields.Count) + { + // Merged: exact match across all versions — no version label + result.Add(new FieldPathTypeDto + { + Id = field.Id, + Key = field.Key, + Label = field.Label, + Path = field.Path, + Type = field.Type, + TypePath = field.TypePath, + DataPath = field.DataPath, + VersionLabel = null + }); + } + else + { + // Version-exclusive field: present in some but not all versions + result.Add(new FieldPathTypeDto + { + Id = field.Id, + Key = field.Key, + Label = field.Label, + Path = field.Path, + Type = field.Type, + TypePath = field.TypePath, + DataPath = field.DataPath, + VersionLabel = versionLabel + }); + } + } + } + + return result; + } + + private static Dictionary GetStoredInfo(ReportColumnsMap reportColumnsMap) + { + if (string.IsNullOrEmpty(reportColumnsMap.Mapping)) + return []; + + try + { + var mapping = JsonSerializer.Deserialize(reportColumnsMap.Mapping); + return mapping?.Metadata?.Info ?? []; + } + catch + { + return []; + } + } + + private static List DetectInfoChanges( + Dictionary storedInfo, + Dictionary currentInfo) + { + var changes = new List(); + + var addedVersionKeys = currentInfo.Keys + .Where(k => k.StartsWith("formversion_", StringComparison.OrdinalIgnoreCase)) + .Except(storedInfo.Keys, StringComparer.OrdinalIgnoreCase); + + foreach (var key in addedVersionKeys) + { + var label = currentInfo[key]; + changes.Add($"Version added: {label}"); + } + + var removedVersionKeys = storedInfo.Keys + .Where(k => k.StartsWith("formversion_", StringComparison.OrdinalIgnoreCase)) + .Except(currentInfo.Keys, StringComparer.OrdinalIgnoreCase); + + foreach (var key in removedVersionKeys) + { + var label = storedInfo[key]; + changes.Add($"Version removed: {label}"); + } + + return changes; + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedWorksheetFieldsProvider.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedWorksheetFieldsProvider.cs new file mode 100644 index 000000000..89d78b923 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/FieldsProviders/ConsolidatedWorksheetFieldsProvider.cs @@ -0,0 +1,316 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Unity.Flex.Reporting.Configuration; +using Unity.Flex.WorksheetLinks; +using Unity.GrantManager.ApplicationForms; +using Unity.Reporting.Domain.Configuration; +using Volo.Abp.DependencyInjection; + +namespace Unity.Reporting.Configuration.FieldsProviders +{ + /// + /// Fields provider for consolidated worksheet views that span all form versions. + /// Reads live worksheet field metadata directly from the Flex module across all form versions, + /// merges fields by (Label, Path, Type), and detects version/worksheet changes for break notification. + /// The CorrelationId for this provider is the FormId (not a specific form version ID). + /// + public class ConsolidatedWorksheetFieldsProvider( + IApplicationFormAppService applicationFormAppService, + IWorksheetsMetadataService worksheetsMetadataService, + IWorksheetLinkAppService worksheetLinkAppService) + : IFieldsProvider, ITransientDependency + { + public string CorrelationProvider => Providers.WorksheetConsolidated; + + /// + /// Retrieves and merges worksheet field metadata across all form versions for consolidated view configuration. + /// Fields matching on (Label, Path, Type) are merged into a single column entry. + /// Fields with the same (Label, Path) but different Type produce per-version conflict entries. + /// Fields unique to one version are included with a VersionLabel marker. + /// + public async Task GetFieldsMetadataAsync(Guid formId) + { + var versions = await applicationFormAppService.GetVersionsAsync(formId); + var versionsWithFields = new List<(Guid VersionId, string VersionLabel, FieldPathTypeDto[] Fields)>(); + var metadataInfo = new Dictionary(); + + foreach (var version in versions.OrderBy(v => v.Version)) + { + var versionLabel = $"v{version.Version}"; + var links = await worksheetLinkAppService.GetListByCorrelationAsync(version.Id, "FormVersion"); + + if (links.Count == 0) + continue; + + var allComponents = new List(); + + foreach (var link in links) + { + var metadata = await worksheetsMetadataService.GetWorksheetSchemaMetaDataAsync(link.WorksheetId, version.Id); + var components = metadata.Components + .Select(ConvertToFieldPathType) + .Where(x => x != null) + .Select(x => x!); + allComponents.AddRange(components); + + var worksheetTitle = link.Worksheet?.Title ?? "Unknown Worksheet"; + var worksheetName = link.Worksheet?.Name ?? "Unknown"; + metadataInfo[$"ws_{version.Id}_{link.WorksheetId}"] = $"{worksheetTitle} ({worksheetName})"; + } + + versionsWithFields.Add((version.Id, versionLabel, [.. allComponents])); + metadataInfo[$"formversion_{version.Id}"] = versionLabel; + } + + var mergedFields = MergeFields(versionsWithFields); + var mapMetadata = new MapMetadataDto { Info = metadataInfo }; + + return new FieldPathMetaMapDto { Fields = [.. mergedFields], Metadata = mapMetadata }; + } + + /// + /// Detects changes in form versions and worksheet links since the consolidated mapping was last saved. + /// Returns a semicolon-joined change description or null if nothing has changed. + /// + public async Task DetectChangesAsync(Guid formId, ReportColumnsMap reportColumnsMap) + { + var versions = await applicationFormAppService.GetVersionsAsync(formId); + var currentInfo = new Dictionary(); + + foreach (var version in versions) + { + var versionLabel = $"v{version.Version}"; + var links = await worksheetLinkAppService.GetListByCorrelationAsync(version.Id, "FormVersion"); + + if (links.Count == 0) + continue; + + currentInfo[$"formversion_{version.Id}"] = versionLabel; + + foreach (var link in links) + { + var worksheetTitle = link.Worksheet?.Title ?? "Unknown Worksheet"; + var worksheetName = link.Worksheet?.Name ?? "Unknown"; + currentInfo[$"ws_{version.Id}_{link.WorksheetId}"] = $"{worksheetTitle} ({worksheetName})"; + } + } + + var storedInfo = GetStoredInfo(reportColumnsMap); + var changes = DetectInfoChanges(storedInfo, currentInfo); + + return changes.Count > 0 ? string.Join("; ", changes) : null; + } + + private static FieldPathTypeDto? ConvertToFieldPathType(WorksheetComponentMetaDataItemDto? item) + { + if (item == null) + return null; + + return new FieldPathTypeDto + { + Id = item.Id, + Path = item.Path, + Type = item.Type, + Key = item.Key, + Label = item.Label, + TypePath = item.TypePath, + DataPath = item.DataPath + }; + } + + private static List MergeFields( + List<(Guid VersionId, string VersionLabel, FieldPathTypeDto[] Fields)> versionsWithFields) + { + // Track: (label.lower, path.lower, type.lower) → list of (versionLabel, field) + var exactMatchGroups = new Dictionary>(StringComparer.OrdinalIgnoreCase); + // Track: (label.lower, path.lower) → set of types seen + var pathGroups = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + foreach (var (_, versionLabel, fields) in versionsWithFields) + { + foreach (var field in fields) + { + var exactKey = $"{field.Label?.ToLowerInvariant()}|{field.Path?.ToLowerInvariant()}|{field.Type?.ToLowerInvariant()}"; + var pathKey = $"{field.Label?.ToLowerInvariant()}|{field.Path?.ToLowerInvariant()}"; + + if (!exactMatchGroups.TryGetValue(exactKey, out var exactList)) + { + exactList = []; + exactMatchGroups[exactKey] = exactList; + } + // Only add first occurrence per version (avoid duplicates within same version) + if (!exactList.Any(e => e.VersionLabel == versionLabel)) + { + exactList.Add((versionLabel, field)); + } + + if (!pathGroups.TryGetValue(pathKey, out var typeSet)) + { + typeSet = new HashSet(StringComparer.OrdinalIgnoreCase); + pathGroups[pathKey] = typeSet; + } + typeSet.Add(field.Type?.ToLowerInvariant() ?? string.Empty); + } + } + + var result = new List(); + var processedExactKeys = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var (_, versionLabel, fields) in versionsWithFields) + { + foreach (var field in fields) + { + var exactKey = $"{field.Label?.ToLowerInvariant()}|{field.Path?.ToLowerInvariant()}|{field.Type?.ToLowerInvariant()}"; + var pathKey = $"{field.Label?.ToLowerInvariant()}|{field.Path?.ToLowerInvariant()}"; + + if (processedExactKeys.Contains(exactKey)) + continue; + + processedExactKeys.Add(exactKey); + + var typesForPath = pathGroups[pathKey]; + var exactGroup = exactMatchGroups[exactKey]; + var allVersionLabels = versionsWithFields.Select(v => v.VersionLabel).ToList(); + var versionsHavingThisExact = exactGroup.Select(e => e.VersionLabel).ToHashSet(StringComparer.OrdinalIgnoreCase); + + if (typesForPath.Count > 1) + { + // Conflict: same (label, path) but different types — emit per-version entry + result.Add(new FieldPathTypeDto + { + Id = field.Id, + Key = field.Key, + Label = field.Label, + Path = field.Path, + Type = field.Type, + TypePath = field.TypePath, + DataPath = field.DataPath, + VersionLabel = versionLabel + }); + } + else if (versionsWithFields.Count > 1 && versionsHavingThisExact.Count == versionsWithFields.Count) + { + // Merged: exact match across all versions — no version label + result.Add(new FieldPathTypeDto + { + Id = field.Id, + Key = field.Key, + Label = field.Label, + Path = field.Path, + Type = field.Type, + TypePath = field.TypePath, + DataPath = field.DataPath, + VersionLabel = null + }); + } + else + { + // Version-exclusive field: present in some but not all versions + result.Add(new FieldPathTypeDto + { + Id = field.Id, + Key = field.Key, + Label = field.Label, + Path = field.Path, + Type = field.Type, + TypePath = field.TypePath, + DataPath = field.DataPath, + VersionLabel = versionLabel + }); + } + } + } + + return result; + } + + private static Dictionary GetStoredInfo(ReportColumnsMap reportColumnsMap) + { + if (string.IsNullOrEmpty(reportColumnsMap.Mapping)) + return []; + + try + { + var mapping = JsonSerializer.Deserialize(reportColumnsMap.Mapping); + return mapping?.Metadata?.Info ?? []; + } + catch + { + return []; + } + } + + private static List DetectInfoChanges( + Dictionary storedInfo, + Dictionary currentInfo) + { + var changes = new List(); + + // Detect added/removed form versions + var addedVersionKeys = currentInfo.Keys + .Where(k => k.StartsWith("formversion_", StringComparison.OrdinalIgnoreCase)) + .Except(storedInfo.Keys, StringComparer.OrdinalIgnoreCase); + + foreach (var key in addedVersionKeys) + { + var label = currentInfo[key]; + changes.Add($"Version added: {label} (has worksheets)"); + } + + var removedVersionKeys = storedInfo.Keys + .Where(k => k.StartsWith("formversion_", StringComparison.OrdinalIgnoreCase)) + .Except(currentInfo.Keys, StringComparer.OrdinalIgnoreCase); + + foreach (var key in removedVersionKeys) + { + var label = storedInfo[key]; + changes.Add($"Version removed: {label}"); + } + + // Detect added/removed worksheets within versions + var addedWsKeys = currentInfo.Keys + .Where(k => k.StartsWith("ws_", StringComparison.OrdinalIgnoreCase)) + .Except(storedInfo.Keys, StringComparer.OrdinalIgnoreCase); + + foreach (var key in addedWsKeys) + { + var worksheetInfo = currentInfo[key]; + var versionLabel = GetVersionLabelFromWsKey(key, currentInfo); + changes.Add($"Worksheet added to {versionLabel}: {worksheetInfo}"); + } + + var removedWsKeys = storedInfo.Keys + .Where(k => k.StartsWith("ws_", StringComparison.OrdinalIgnoreCase)) + .Except(currentInfo.Keys, StringComparer.OrdinalIgnoreCase); + + foreach (var key in removedWsKeys) + { + var worksheetInfo = storedInfo[key]; + var versionLabel = GetVersionLabelFromWsKey(key, storedInfo); + changes.Add($"Worksheet removed from {versionLabel}: {worksheetInfo}"); + } + + return changes; + } + + // ws_{versionId}_{worksheetId} → look up formversion_{versionId} in info + // versionId is a GUID (36 chars) starting at position 3 (after "ws_") + private static string GetVersionLabelFromWsKey(string wsKey, Dictionary info) + { + const int guidLength = 36; + const int prefixLength = 3; // "ws_" + + if (wsKey.Length >= prefixLength + guidLength) + { + var versionIdStr = wsKey.Substring(prefixLength, guidLength); + var versionKey = $"formversion_{versionIdStr}"; + if (info.TryGetValue(versionKey, out var label)) + return label; + } + return "unknown version"; + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/ReportMappingUtils.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/ReportMappingUtils.cs index 6c3673670..956a7aff0 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/ReportMappingUtils.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Configuration/ReportMappingUtils.cs @@ -343,7 +343,8 @@ internal static ReportColumnsMap CreateNewMap(UpsertReportColumnsMapDto upsertRe Path = field.Path, DataPath = field.DataPath, TypePath = field.TypePath, - Id = field.Id + Id = field.Id, + VersionLabel = field.VersionLabel }; }).ToList(); @@ -351,7 +352,11 @@ internal static ReportColumnsMap CreateNewMap(UpsertReportColumnsMapDto upsertRe var mapping = new Mapping { Rows = [.. mapRows], - Metadata = new MapMetadata() { Info = fieldsMap.Metadata?.Info } + Metadata = new MapMetadata() + { + Info = fieldsMap.Metadata?.Info, + Description = upsertReportColmnsMapDto.Mapping?.Description + } }; // Create and return the map entity @@ -447,15 +452,20 @@ internal static ReportColumnsMap UpdateExistingMap(UpsertReportColumnsMapDto upd Path = field.Path, DataPath = field.DataPath, TypePath = field.TypePath, - Id = field.Id + Id = field.Id, + VersionLabel = field.VersionLabel }; }).ToList(); // Create new mapping object and serialize it - var updatedMapping = new Mapping - { + var updatedMapping = new Mapping + { Rows = [.. mapRows], - Metadata = new MapMetadata() { Info = fieldsMap.Metadata?.Info } + Metadata = new MapMetadata() + { + Info = fieldsMap.Metadata?.Info, + Description = updateReportColumnsMapDto.Mapping?.Description + } }; existing.Mapping = JsonSerializer.Serialize(updatedMapping); diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Domain/Configuration/ReportColumnsMap.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Domain/Configuration/ReportColumnsMap.cs index df20a2043..2a55f1483 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Domain/Configuration/ReportColumnsMap.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/Domain/Configuration/ReportColumnsMap.cs @@ -133,6 +133,13 @@ public class MapRow /// Represents the component type path (e.g., "form->panel->textfield") in the source schema. /// public string TypePath { get; set; } = string.Empty; + + /// + /// Gets or sets an optional version label indicating which form version this column belongs to. + /// Used exclusively for consolidated worksheet views: null means the column is merged across all versions; + /// a non-null value (e.g., "v1", "v2") means the column is specific to that form version (conflict or version-exclusive field). + /// + public string? VersionLabel { get; set; } = null; } /// @@ -148,5 +155,11 @@ public class MapMetadata /// used for display purposes and change detection analysis. /// public Dictionary? Info { get; set; } = null; + + /// + /// Gets or sets an optional free-text description for this mapping configuration. + /// Maximum 500 characters. Used to document the purpose or context of this reporting configuration. + /// + public string? Description { get; set; } = null; } } diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/EntityFrameworkCore/Repositories/ReportColumnsMapRepository.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/EntityFrameworkCore/Repositories/ReportColumnsMapRepository.cs index 2e559ef4c..782c0c338 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/EntityFrameworkCore/Repositories/ReportColumnsMapRepository.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/EntityFrameworkCore/Repositories/ReportColumnsMapRepository.cs @@ -91,6 +91,8 @@ public async Task GenerateViewAsync(Guid correlationId, string correlationProvid "formversion" => $@"CALL ""Reporting"".generate_formversion_view({correlationId});", "worksheet" => $@"CALL ""Reporting"".generate_worksheet_view({correlationId});", "scoresheet" => $@"CALL ""Reporting"".generate_scoresheet_view({correlationId});", + "worksheetconsolidated" => $@"CALL ""Reporting"".generate_consolidated_worksheet_view({correlationId});", + "formversionconsolidated" => $@"CALL ""Reporting"".generate_consolidated_formversion_view({correlationId});", _ => throw new ArgumentException($"Unsupported correlation provider: {correlationProvider}"), }; await dbContext.Database.ExecuteSqlAsync(sql); diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/ReportingApplicationMapperlyProfile.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/ReportingApplicationMapperlyProfile.cs index 134acf454..75ddf4527 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/ReportingApplicationMapperlyProfile.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Application/ReportingApplicationMapperlyProfile.cs @@ -49,3 +49,11 @@ public partial class MapRowToMapRowDtoMapper : MapperBase public override partial void Map(MapRow source, MapRowDto destination); } + +[Mapper] +public partial class MapMetadataToMapMetadataDtoMapper : MapperBase +{ + public override partial MapMetadataDto Map(MapMetadata source); + + public override partial void Map(MapMetadata source, MapMetadataDto destination); +} diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Domain.Shared/Configuration/Providers.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Domain.Shared/Configuration/Providers.cs index 8fe4e5cf0..9c58c4a50 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Domain.Shared/Configuration/Providers.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Domain.Shared/Configuration/Providers.cs @@ -28,5 +28,19 @@ public static class Providers /// Scoresheets contain structured evaluation criteria and scoring mechanisms for application review. /// public static string Scoresheet => "scoresheet"; + + /// + /// Gets the correlation provider identifier for consolidated worksheet views spanning all form versions. + /// Used when creating a single unified report view that merges worksheet data across all versions of a form. + /// The CorrelationId for this provider is the FormId (not a specific version ID). + /// + public static string WorksheetConsolidated => "worksheetconsolidated"; + + /// + /// Gets the correlation provider identifier for consolidated form version submission views spanning all form versions. + /// Used when creating a single unified report view that merges submission data across all versions of a form. + /// The CorrelationId for this provider is the FormId (not a specific version ID). + /// + public static string FormVersionConsolidated => "formversionconsolidated"; } } 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 c8ded8844..9ccec0937 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 @@ -19,12 +19,12 @@
- + @if (await FeatureChecker.IsEnabledAsync("Unity.Flex")) { - + @@ -43,34 +43,44 @@
- @if (Model.IsVersionSelectorVisible) - { -
- +
+
+
+ + + + +
- } - else - { - -
+ + @if (await FeatureChecker.IsEnabledAsync("Unity.Flex")) + { +
+
+ + + + +
+
+ } + +
- -
+ + - } +
@if (Model.CorrelationId.HasValue) @@ -90,6 +100,15 @@ data-bs-placement="top" data-bs-original-title="Generate view" class="btn unt-btn-primary btn-primary @(Model.HasSavedConfiguration ? "" : "generate-view-btn-hidden")"> + + }
+ + +