diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/ProfileData/ExternalLinkDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/ProfileData/ExternalLinkDto.cs
new file mode 100644
index 0000000000..b9e17dffc4
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/ProfileData/ExternalLinkDto.cs
@@ -0,0 +1,14 @@
+namespace Unity.GrantManager.ApplicantProfile.ProfileData;
+
+///
+/// Represents a link to be used within the Applicant Portal, including the URL, title, and description.
+///
+public class ExternalLinkDto
+{
+ public required string Uri { get; set; }
+ public string Title { get; set; } = string.Empty;
+ public string Description { get; set; } = string.Empty;
+ public ExternalLinkType ExternalLinkType { get; set; }
+ public bool Published { get; set; } = false;
+ public int Order { get; set; } = -1;
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/ProfileData/SubmissionInfoItemDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/ProfileData/SubmissionInfoItemDto.cs
index 38b7855fdd..2334c37133 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/ProfileData/SubmissionInfoItemDto.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/ProfileData/SubmissionInfoItemDto.cs
@@ -1,15 +1,17 @@
using System;
+using System.Collections.Generic;
-namespace Unity.GrantManager.ApplicantProfile.ProfileData
+namespace Unity.GrantManager.ApplicantProfile.ProfileData;
+
+public class SubmissionInfoItemDto
{
- public class SubmissionInfoItemDto
- {
- public Guid Id { get; set; }
- public string LinkId { get; set; } = string.Empty;
- public DateTime ReceivedTime { get; set; }
- public DateTime SubmissionTime { get; set; }
- public string ReferenceNo { get; set; } = string.Empty;
- public string Type { get; set; } = string.Empty;
- public string Status { get; set; } = string.Empty;
- }
+ public Guid Id { get; set; }
+ public string LinkId { get; set; } = string.Empty;
+ public DateTime ReceivedTime { get; set; }
+ public DateTime SubmissionTime { get; set; }
+ public string ReferenceNo { get; set; } = string.Empty;
+ public string Type { get; set; } = string.Empty;
+ public string Status { get; set; } = string.Empty;
+ public ExternalLinkDto? RenewalLink { get; set; }
+ public List RelatedLinks { get; set; } = [];
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ApplicationFormDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ApplicationFormDto.cs
index 02d5f484e5..20a7bcf93a 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ApplicationFormDto.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ApplicationFormDto.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using Unity.GrantManager.GrantApplications;
using Volo.Abp.Application.Dtos;
@@ -32,5 +33,6 @@ public class ApplicationFormDto : EntityDto
public string? Prefix { get; set; }
public SuffixConfigType? SuffixType { get; set; }
public int? DefaultPaymentGroup { get; set; }
+ public List ExternalLinks { get; set; } = [];
}
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ExternalLinkConfigDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ExternalLinkConfigDto.cs
new file mode 100644
index 0000000000..0eb4abd51c
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ExternalLinkConfigDto.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using Unity.GrantManager.ApplicantProfile;
+
+namespace Unity.GrantManager.ApplicationForms;
+
+public class ExternalLinkConfigDto : IValidatableObject
+{
+ [Required]
+ [MaxLength(2048)]
+ public string Uri { get; set; } = string.Empty;
+
+ [MaxLength(255)]
+ public string Title { get; set; } = string.Empty;
+
+ [MaxLength(512)]
+ public string Description { get; set; } = string.Empty;
+
+ public bool Published { get; set; }
+
+ public ExternalLinkType ExternalLinkType { get; set; } = ExternalLinkType.Related;
+
+ public int Order { get; set; } = -1;
+
+ public IEnumerable Validate(ValidationContext validationContext)
+ {
+ if (!ExternalLinkUriValidator.IsValidHttpUri(Uri))
+ {
+ yield return new ValidationResult(
+ "Uri must be an absolute, well-formed http or https URL.",
+ [nameof(Uri)]);
+ }
+ }
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ExternalLinkUriValidator.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ExternalLinkUriValidator.cs
new file mode 100644
index 0000000000..1db4baae5e
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ExternalLinkUriValidator.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Unity.GrantManager.ApplicationForms;
+
+///
+/// Shared http/https-only URI validation to block script-scheme injection (e.g. javascript:, data:).
+///
+public static class ExternalLinkUriValidator
+{
+ public static bool IsValidHttpUri(string? uri)
+ {
+ if (string.IsNullOrWhiteSpace(uri))
+ {
+ return false;
+ }
+
+ return Uri.TryCreate(uri, UriKind.Absolute, out var parsed)
+ && (parsed.Scheme == Uri.UriSchemeHttp || parsed.Scheme == Uri.UriSchemeHttps);
+ }
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ExternalLinksConfigDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ExternalLinksConfigDto.cs
new file mode 100644
index 0000000000..054edff08d
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ExternalLinksConfigDto.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using Unity.GrantManager.ApplicantProfile;
+
+namespace Unity.GrantManager.ApplicationForms;
+
+public class ExternalLinksConfigDto : IValidatableObject
+{
+ public const int MaxRelatedLinks = 8;
+
+ public ExternalLinkConfigDto? RenewalLink { get; set; }
+
+ public List RelatedLinks { get; set; } = [];
+
+ public IEnumerable Validate(ValidationContext validationContext)
+ {
+ if (RenewalLink is { Published: true } && !ExternalLinkUriValidator.IsValidHttpUri(RenewalLink.Uri))
+ {
+ yield return new ValidationResult(
+ "Renewal link visibility cannot be enabled without a valid renewal link URL.",
+ [nameof(RenewalLink)]);
+ }
+
+ if (RelatedLinks.Count > MaxRelatedLinks)
+ {
+ yield return new ValidationResult(
+ $"A maximum of {MaxRelatedLinks} related links is allowed.",
+ [nameof(RelatedLinks)]);
+ }
+
+ if (RenewalLink is not null && RenewalLink.ExternalLinkType != ExternalLinkType.Renewal)
+ {
+ yield return new ValidationResult(
+ "Renewal link must be of type Renewal.",
+ [nameof(RenewalLink)]);
+ }
+
+ if (RelatedLinks.Exists(l => l.ExternalLinkType != ExternalLinkType.Related))
+ {
+ yield return new ValidationResult(
+ "Related links must all be of type Related.",
+ [nameof(RelatedLinks)]);
+ }
+
+ for (var i = 0; i < RelatedLinks.Count; i++)
+ {
+ var link = RelatedLinks[i];
+ if (link.Published && !ExternalLinkUriValidator.IsValidHttpUri(link.Uri))
+ {
+ yield return new ValidationResult(
+ $"Related link visibility cannot be enabled without a valid URL (item {i + 1}).",
+ [nameof(RelatedLinks)]);
+ }
+ }
+ }
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/IApplicationFormAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/IApplicationFormAppService.cs
index 5de7519628..d452abddec 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/IApplicationFormAppService.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/IApplicationFormAppService.cs
@@ -19,6 +19,7 @@ public interface IApplicationFormAppService : ICrudAppService<
Task> GetPublishedVersionsAsync(Guid id);
Task PatchOtherConfig(Guid id, OtherConfigDto config);
Task PatchAiConfig(Guid id, AIConfigDto config);
+ Task PatchExternalLinksConfigAsync(Guid id, ExternalLinksConfigDto config);
Task GetFormPaymentApprovalThresholdByApplicationIdAsync(Guid applicationId);
Task GetFormPreventPaymentStatusByApplicationId(Guid applicationId);
Task GetFormDetailsByApplicationIdAsync(Guid applicationId);
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationDto.cs
index 1e634c4df5..4861a89cb1 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationDto.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationDto.cs
@@ -90,4 +90,5 @@ public class GrantApplicationDto : AuditedEntityDto
public Guid? DefaultSiteId { get; set; }
public ApplicationAnalysisResponse? AIAnalysisData { get; set; }
public string? AIScoresheetAnswers { get; set; }
+ public bool EligibleForRenewal { get; set; }
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/SubmissionInfoDataProvider.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/SubmissionInfoDataProvider.cs
index d56d98fa28..47d6cf812b 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/SubmissionInfoDataProvider.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/SubmissionInfoDataProvider.cs
@@ -69,7 +69,30 @@ join status in statusesQuery on application.ApplicationStatusId equals status.Id
FormName = form.ApplicationFormName ?? string.Empty,
Status = application.ExternalStatusVisibility
? status.NotifiedStatus ?? status.ExternalStatus
- : status.ExternalStatus
+ : status.ExternalStatus,
+ RenewalLink = application.EligibleForRenewal ? form.ExternalLinks
+ .Where(x => x.Published && x.ExternalLinkType == ExternalLinkType.Renewal)
+ .Select(x => new ExternalLinkDto
+ {
+ Uri = x.Uri,
+ ExternalLinkType = x.ExternalLinkType,
+ Order = x.Order,
+ Title = x.Title,
+ Description = x.Description
+ })
+ .FirstOrDefault() : null,
+ RelatedLinks = form.ExternalLinks
+ .Where(x => x.Published && x.ExternalLinkType == ExternalLinkType.Related)
+ .OrderBy(x => x.Order == -1 ? int.MaxValue : x.Order) // Links without an order default to -1
+ .ThenBy(x => x.Title)
+ .Select(x => new ExternalLinkDto
+ {
+ Uri = x.Uri,
+ ExternalLinkType = x.ExternalLinkType,
+ Order = x.Order,
+ Title = x.Title,
+ Description = x.Description
+ })
}).ToListAsync();
dto.Submissions.AddRange(results.Select(s => new SubmissionInfoItemDto
@@ -80,7 +103,9 @@ join status in statusesQuery on application.ApplicationStatusId equals status.Id
SubmissionTime = ResolveSubmissionTime(s.Submission, s.CreationTime),
ReferenceNo = s.ReferenceNo,
Type = s.FormName,
- Status = s.Status
+ Status = s.Status,
+ RenewalLink = s.RenewalLink,
+ RelatedLinks = [.. s.RelatedLinks]
}));
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormAppService.cs
index 01642a6fcb..a7ee7409ed 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormAppService.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormAppService.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using Unity.GrantManager.ApplicantProfile;
using Unity.GrantManager.Applications;
using Unity.GrantManager.Forms;
using Unity.GrantManager.GrantApplications;
@@ -187,6 +188,31 @@ public async Task PatchAiConfig(Guid id, AIConfigDto config)
await Repository.UpdateAsync(form);
}
+ [Authorize(GrantManagerPermissions.ApplicationForms.Default)]
+ public async Task PatchExternalLinksConfigAsync(Guid id, ExternalLinksConfigDto config)
+ {
+ ArgumentNullException.ThrowIfNull(config);
+
+ var form = await Repository.GetAsync(id);
+
+ var renewalLink = config.RenewalLink is null ? null : MapToExternalLink(config.RenewalLink);
+ var relatedLinks = (config.RelatedLinks ?? []).Select(MapToExternalLink).ToList();
+
+ form.SetExternalLinks(renewalLink, relatedLinks);
+
+ await Repository.UpdateAsync(form);
+ }
+
+ private static ExternalLink MapToExternalLink(ExternalLinkConfigDto dto) => new()
+ {
+ Uri = dto.Uri,
+ Title = dto.Title,
+ Description = dto.Description,
+ Published = dto.Published,
+ ExternalLinkType = dto.ExternalLinkType,
+ Order = dto.Order
+ };
+
[Authorize(PaymentsPermissions.Payments.EditFormPaymentConfiguration)]
public async Task GetFormPreventPaymentStatusByApplicationId(Guid applicationId)
{
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationMapperlyProfile.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationMapperlyProfile.cs
index 01bd3c112c..50aa3dc156 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationMapperlyProfile.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationMapperlyProfile.cs
@@ -366,6 +366,7 @@ public partial class ApplicationFormDtoToEntityMapper : MapperBase { public override partial ApplicationFormVersionDto Map(ApplicationFormVersion source); public override partial void Map(ApplicationFormVersion source, ApplicationFormVersionDto destination); }
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs
new file mode 100644
index 0000000000..1f8f7c36fa
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Unity.GrantManager.ApplicantProfile;
+
+///
+/// Represents a link to be used within the Applicant Portal, including the URI, title, and description.
+///
+[ComplexType]
+public class ExternalLink
+{
+ [MaxLength(2048)]
+ public required string Uri { get; set; }
+
+ [MaxLength(255)]
+ public string Title { get; set; } = string.Empty;
+
+ [MaxLength(512)]
+ public string Description { get; set; } = string.Empty;
+
+ public ExternalLinkType ExternalLinkType { get; set; } = ExternalLinkType.Related;
+ public bool Published { get; set; } = false;
+ public int Order { get; set; } = -1;
+}
\ No newline at end of file
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLinkType.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLinkType.cs
new file mode 100644
index 0000000000..4136ccd68f
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLinkType.cs
@@ -0,0 +1,7 @@
+namespace Unity.GrantManager.ApplicantProfile;
+
+public enum ExternalLinkType
+{
+ Related = 1,
+ Renewal = 2
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/GrantManagerDomainErrorCodes.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/GrantManagerDomainErrorCodes.cs
index c189918a53..bdab44ff44 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/GrantManagerDomainErrorCodes.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/GrantManagerDomainErrorCodes.cs
@@ -26,4 +26,10 @@ public static class GrantManagerDomainErrorCodes
public const string PayableFormRequiresHierarchy = "GrantManager:PayableFormRequiresHierarchy";
public const string ChildFormRequiresParentForm = "GrantManager:ChildFormRequiresParentForm";
public const string ChildFormCannotReferenceSelf = "GrantManager:ChildFormCannotReferenceSelf";
+
+ /* APPLICANT PORTAL EXTERNAL LINKS */
+ public const string RenewalLinkRequiredForVisibility = "GrantManager:RenewalLinkRequiredForVisibility";
+ public const string RenewalLinkInvalidUri = "GrantManager:RenewalLinkInvalidUri";
+ public const string RelatedLinkInvalidUri = "GrantManager:RelatedLinkInvalidUri";
+ public const string TooManyRelatedLinks = "GrantManager:TooManyRelatedLinks";
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json
index 10945ccd1a..1e2b2eb5b5 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json
@@ -192,6 +192,24 @@
"ApplicationForms.Configuration.Notes:BypassAssessmentWorkflow": "Enabling this feature will bypass the Assessment workflow when you approve or deny submissions associated with this form.",
"ApplicationForms.Configuration.Notes:SelectedApplicantElectoralAddress": "The selected address type will determine which submitted applicant address is used to extract the electoral district.",
"ApplicationForms.Configuration.Warnings:ApplicantElectoralAddressTypeChange": "Changing the address type will only affect new submissions. Existing applications will retain the electoral district extracted from their original address type.",
+ "ApplicationForms.Configuration:ApplicantPortalLinks": "Applicant Portal Related Links & Visibility",
+ "ApplicationForms.Configuration:RenewalLink": "Renewal Link",
+ "ApplicationForms.Configuration:LinkUrl": "Link URL",
+ "ApplicationForms.Configuration:RenewalLinkDisplayName": "Renewal Link Display Name",
+ "ApplicationForms.Configuration:ShowRenewalLinksInPortal": "Show Renewal Links in Portal",
+ "ApplicationForms.Configuration:ApplicantMessage": "Applicant Message",
+ "ApplicationForms.Configuration:OtherLinks": "Other Links",
+ "ApplicationForms.Configuration:ShowOtherLinksInPortal": "Show in Portal",
+ "ApplicationForms.Configuration:LinkDisplayName": "Display Name",
+ "ApplicationForms.Configuration:LinkDescription": "Description",
+ "ApplicationForms.Configuration:AddLink": "Add Link",
+ "ApplicationForms.Configuration:RemoveLink": "Remove Link",
+ "ApplicationForms.Configuration.Notes:LinkVisibilityRequiresUrl": "If there is no link provided when Visibility is enabled, nothing will be displayed.",
+ "ApplicationForms.Configuration.Notes:MaxRelatedLinks": "A maximum of 8 other links can be added.",
+ "ApplicationForms.Configuration.Errors:InvalidUrl": "Please enter a valid, absolute http or https URL.",
+ "ApplicationForms.Configuration.Errors:RenewalLinkRequiredForVisibility": "A valid renewal link URL is required before enabling visibility.",
+ "ApplicationForms.Configuration.Errors:OtherLinkRequiredForVisibility": "A valid URL is required before enabling visibility for this link.",
+ "ApplicationForms.Configuration.Errors:MaxRelatedLinksReached": "A maximum of 8 other links is allowed.",
"Intakes": "Intakes",
@@ -271,6 +289,10 @@
"GrantManager:PayableFormRequiresHierarchy": "Please select a form hierarchy before saving a payable form.",
"GrantManager:ChildFormRequiresParentForm": "Please select a parent form when the form hierarchy is set to Child.",
"GrantManager:ChildFormCannotReferenceSelf": "A form cannot reference itself as the parent.",
+ "GrantManager:RenewalLinkRequiredForVisibility": "A valid renewal link URL is required before enabling renewal link visibility.",
+ "GrantManager:RenewalLinkInvalidUri": "The renewal link URL must be a valid, absolute http or https URL.",
+ "GrantManager:RelatedLinkInvalidUri": "Related link URLs must be valid, absolute http or https URLs.",
+ "GrantManager:TooManyRelatedLinks": "A maximum of 8 related links is allowed.",
"GrantManager:CannotModifyAiAssessment": "AI assessments are read-only.",
"GrantManager:CannotCloneNonAiAssessment": "Only AI assessments can be cloned.",
"GrantManager:NotCommentOwner": "You can only delete your own comments.",
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/Application.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/Application.cs
index ff5f68410b..16d7a932c5 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/Application.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/Application.cs
@@ -144,6 +144,8 @@ public virtual ApplicationStatus ApplicationStatus
public string? AIAnalysis { get; set; }
+ public bool EligibleForRenewal { get; set; }
+
[Column(TypeName = "jsonb")]
public string? AIScoresheetAnswers { get; set; }
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs
index a2644e9363..c525c1c6d7 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
+using Unity.GrantManager.ApplicantProfile;
using Unity.GrantManager.ApplicationForms;
using Unity.GrantManager.GrantApplications;
+using Volo.Abp;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
@@ -32,6 +34,8 @@ public class ApplicationForm : FullAuditedAggregateRoot, IMultiTenant
public FormHierarchyType? FormHierarchy { get; set; }
public Guid? ParentFormId { get; set; }
public bool IsDirectApproval { get; set; } = false;
+ public List ExternalLinks { get; set; } = [];
+
public bool AutomaticallyGenerateAIAnalysis { get; set; } = false;
public bool ManuallyInitiateAIAnalysis { get; set; } = false;
[MaxLength(100)]
@@ -79,4 +83,53 @@ public static AddressType GetDefaultElectoralDistrictAddressType()
{
return AddressType.PhysicalAddress;
}
+
+ public const int MaxRelatedExternalLinks = 8;
+
+ ///
+ /// Replaces the Renewal and Related external links as a set, enforcing that a link
+ /// cannot be marked visible in the Applicant Portal without a valid URI.
+ ///
+ public ApplicationForm SetExternalLinks(ExternalLink? renewalLink, List relatedLinks)
+ {
+ ArgumentNullException.ThrowIfNull(relatedLinks);
+
+ // Cap the number of related links to the maximum allowed
+ if (relatedLinks.Count > MaxRelatedExternalLinks)
+ {
+ throw new BusinessException(GrantManagerDomainErrorCodes.TooManyRelatedLinks);
+ }
+
+ // Validate that if a renewal link is published, it must have a valid URI
+ if (renewalLink is { Published: true } && string.IsNullOrWhiteSpace(renewalLink.Uri))
+ {
+ throw new BusinessException(GrantManagerDomainErrorCodes.RenewalLinkRequiredForVisibility);
+ }
+
+ // Validate that if any related link is published, it must have a valid URI
+ if (relatedLinks.Exists(l => l.Published && string.IsNullOrWhiteSpace(l.Uri)))
+ {
+ throw new BusinessException(GrantManagerDomainErrorCodes.RelatedLinkInvalidUri);
+ }
+
+ var links = new List();
+
+ if (renewalLink is not null)
+ {
+ renewalLink.ExternalLinkType = ExternalLinkType.Renewal;
+ renewalLink.Order = 0;
+ links.Add(renewalLink);
+ }
+
+ for (var i = 0; i < relatedLinks.Count; i++)
+ {
+ relatedLinks[i].ExternalLinkType = ExternalLinkType.Related;
+ relatedLinks[i].Order = i;
+ links.Add(relatedLinks[i]);
+ }
+
+ ExternalLinks = links;
+
+ return this;
+ }
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs
index f26b4a7abb..dcdb082cac 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs
@@ -2,22 +2,22 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System.Linq;
+using Unity.Flex.EntityFrameworkCore;
using Unity.GrantManager.Applications;
-using Unity.GrantManager.Intakes;
using Unity.GrantManager.Assessments;
using Unity.GrantManager.Comments;
+using Unity.GrantManager.Contacts;
+using Unity.GrantManager.GlobalTag;
using Unity.GrantManager.GrantApplications;
+using Unity.GrantManager.Identity;
+using Unity.GrantManager.Intakes;
using Unity.GrantManager.Notifications;
+using Unity.Notifications.EntityFrameworkCore;
+using Unity.Payments.EntityFrameworkCore;
+using Unity.Reporting.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Modeling;
-using Unity.GrantManager.Identity;
-using Unity.Payments.EntityFrameworkCore;
-using Unity.Flex.EntityFrameworkCore;
-using Unity.Notifications.EntityFrameworkCore;
-using Unity.Reporting.EntityFrameworkCore;
-using Unity.GrantManager.GlobalTag;
-using Unity.GrantManager.Contacts;
namespace Unity.GrantManager.EntityFrameworkCore
{
@@ -121,6 +121,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
b.ConfigureByConvention(); //auto configure for the base class props
b.Property(x => x.ApplicationFormName).IsRequired().HasMaxLength(255);
+ b.ComplexCollection(x => x.ExternalLinks, e => e.ToJson());
b.HasOne().WithMany().HasForeignKey(x => x.IntakeId).IsRequired();
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813164219_AB33234_ApplicantPortalExternalLinks.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813164219_AB33234_ApplicantPortalExternalLinks.Designer.cs
new file mode 100644
index 0000000000..ca97882d1f
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813164219_AB33234_ApplicantPortalExternalLinks.Designer.cs
@@ -0,0 +1,5309 @@
+//
+using System;
+using System.Collections.Generic;
+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("20260813164219_AB33234_ApplicantPortalExternalLinks")]
+ partial class AB33234_ApplicantPortalExternalLinks
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql)
+ .HasAnnotation("ProductVersion", "10.0.10")
+ .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("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("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("DeleterId")
+ .HasColumnType("uuid");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("ExtraProperties")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("FiscalDay")
+ .HasColumnType("integer");
+
+ b.Property("FiscalMonth")
+ .HasColumnType("text");
+
+ b.Property("FiscalYearEnd")
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("date");
+
+ b.Property("FundingHistoryComments")
+ .HasColumnType("text");
+
+ b.Property("IndigenousOrgInd")
+ .HasColumnType("text");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean");
+
+ 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("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("OrgName");
+
+ b.HasIndex("OrgNumber");
+
+ b.HasIndex("Status");
+
+ b.HasIndex("SupplierId");
+
+ b.HasIndex("TenantId");
+
+ b.HasIndex("UnityApplicantId");
+
+ b.HasIndex("TenantId", "IsDeleted", "CreationTime")
+ .HasFilter("\"IsDeleted\" = false");
+
+ 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