diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain.Shared/Comments/CommentType.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain.Shared/Comments/CommentType.cs
new file mode 100644
index 0000000000..3d003cfe8e
--- /dev/null
+++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain.Shared/Comments/CommentType.cs
@@ -0,0 +1,12 @@
+using System.Text.Json.Serialization;
+
+namespace Unity.Notifications.Comments;
+
+/// Edit Unity.GrantManager.Comments.CommentType if changing this enum as it is shared between the two projects and used in the API layer.
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum CommentType
+{
+ ApplicationComment,
+ AssessmentComment,
+ ApplicantComment,
+}
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 e0c4c9f100..849c713d5c 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
@@ -209,6 +209,17 @@ public async Task OnGetAsync(string cacheKey)
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.");
diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Menus/ReportingMenuContributor.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Menus/ReportingMenuContributor.cs
index 7df63808e4..d6e91808e7 100644
--- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Menus/ReportingMenuContributor.cs
+++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Menus/ReportingMenuContributor.cs
@@ -33,15 +33,6 @@ public async Task ConfigureMenuAsync(MenuConfigurationContext context)
/// A completed task representing the synchronous menu item addition operations.
private static Task ConfigureReportingMenuAsync(MenuConfigurationContext context)
{
- // Add Reconciliation menu item for IT Admin users
- context.Menu.AddItem(
- new ApplicationMenuItem(
- ReportingMenus.Prefix,
- displayName: "Reconciliation",
- "~/TenantManagement/Reconciliation",
- requiredPermissionName: IdentityConsts.ITAdminPermissionName
- ));
-
// Add Reporting Configuration menu item for IT Admin users
context.Menu.AddItem(
new ApplicationMenuItem(
diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml
index d31fa1bcb9..7d6d1c5e13 100644
--- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml
+++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml
@@ -51,6 +51,10 @@
Scoresheets Configuration
Custom Fields Configuration
}
+ @if (CurrentUser.IsInRole("ITOperations"))
+ {
+ Unity Admin
+ }
@if (CurrentUser.IsInRole("system_admin") && await FeatureChecker.IsEnabledAsync("SettingManagement.Enable"))
{
Settings
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/IApplicantProfileContactService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/IApplicantProfileContactService.cs
index 3db1d7dcd6..779b4f7148 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/IApplicantProfileContactService.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/IApplicantProfileContactService.cs
@@ -14,11 +14,14 @@ namespace Unity.GrantManager.ApplicantProfile;
public interface IApplicantProfileContactService
{
///
- /// Retrieves contacts linked to the specified applicant profile.
- ///
- /// The unique identifier of the applicant profile.
- /// A list of with IsEditable set to true.
- Task> GetProfileContactsAsync(Guid profileId);
+ /// Retrieves contacts linked to the applicant profile by resolving applicant IDs from
+ /// form submissions that match the given OIDC subject. When the subject resolves to a
+ /// single applicant ID the returned contacts are editable; when multiple applicant IDs
+ /// are found they are read-only.
+ ///
+ /// The pre-normalized OIDC subject identifier used to resolve applicant IDs from submissions.
+ /// A list of with IsEditable reflecting the applicant-count rule.
+ Task> GetApplicantContactsAsync(string subject);
///
/// Retrieves application contacts associated with submissions matching the given OIDC subject.
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/ProfileData/ContactInfoItemDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/ProfileData/ContactInfoItemDto.cs
index 112eed817b..ac0ec9b771 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/ProfileData/ContactInfoItemDto.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/ProfileData/ContactInfoItemDto.cs
@@ -18,5 +18,6 @@ public class ContactInfoItemDto
public bool IsEditable { get; set; }
public Guid? ApplicationId { get; set; }
public string? ReferenceNo { get; set; }
+ public DateTime? CreationTime { get; set; }
}
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/BackgroundJobs/GenerateApplicationAnalysisBackgroundJobArgs.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/GenerateApplicationAnalysisBackgroundJobArgs.cs
similarity index 73%
rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/BackgroundJobs/GenerateApplicationAnalysisBackgroundJobArgs.cs
rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/GenerateApplicationAnalysisBackgroundJobArgs.cs
index 7829f8028b..d1f71301fa 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/BackgroundJobs/GenerateApplicationAnalysisBackgroundJobArgs.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/GenerateApplicationAnalysisBackgroundJobArgs.cs
@@ -1,10 +1,8 @@
using System;
-
-namespace Unity.GrantManager.AI.BackgroundJobs;
-
+namespace Unity.GrantManager.GrantApplications.Automation.BackgroundJobs;
public class GenerateApplicationAnalysisBackgroundJobArgs
{
public Guid ApplicationId { get; set; }
- public string? PromptVersion { get; set; }
public Guid? TenantId { get; set; }
-}
+ public string? PromptVersion { get; set; }
+}
\ No newline at end of file
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/BackgroundJobs/GenerateApplicationScoringBackgroundJobArgs.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/GenerateApplicationScoringBackgroundJobArgs.cs
similarity index 73%
rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/BackgroundJobs/GenerateApplicationScoringBackgroundJobArgs.cs
rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/GenerateApplicationScoringBackgroundJobArgs.cs
index 234f8ec706..06b0d0cd97 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/BackgroundJobs/GenerateApplicationScoringBackgroundJobArgs.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/GenerateApplicationScoringBackgroundJobArgs.cs
@@ -1,10 +1,8 @@
using System;
-
-namespace Unity.GrantManager.AI.BackgroundJobs;
-
+namespace Unity.GrantManager.GrantApplications.Automation.BackgroundJobs;
public class GenerateApplicationScoringBackgroundJobArgs
{
public Guid ApplicationId { get; set; }
- public string? PromptVersion { get; set; }
public Guid? TenantId { get; set; }
-}
+ public string? PromptVersion { get; set; }
+}
\ No newline at end of file
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/BackgroundJobs/GenerateAttachmentSummaryBackgroundJobArgs.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/GenerateAttachmentSummaryBackgroundJobArgs.cs
similarity index 76%
rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/BackgroundJobs/GenerateAttachmentSummaryBackgroundJobArgs.cs
rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/GenerateAttachmentSummaryBackgroundJobArgs.cs
index 7836e5abe3..bf87e59783 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/BackgroundJobs/GenerateAttachmentSummaryBackgroundJobArgs.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/GenerateAttachmentSummaryBackgroundJobArgs.cs
@@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
-
-namespace Unity.GrantManager.AI.BackgroundJobs;
-
+namespace Unity.GrantManager.GrantApplications.Automation.BackgroundJobs;
public class GenerateAttachmentSummaryBackgroundJobArgs
{
public List AttachmentIds { get; set; } = [];
- public string? PromptVersion { get; set; }
public Guid? TenantId { get; set; }
-}
+ public string? PromptVersion { get; set; }
+}
\ No newline at end of file
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/BackgroundJobs/GenerateContentBackgroundJobArgs.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/RunApplicationAIPipelineJobArgs.cs
similarity index 55%
rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/BackgroundJobs/GenerateContentBackgroundJobArgs.cs
rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/RunApplicationAIPipelineJobArgs.cs
index d320bf8316..9cfc51304c 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/BackgroundJobs/GenerateContentBackgroundJobArgs.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/Automation/BackgroundJobs/RunApplicationAIPipelineJobArgs.cs
@@ -1,10 +1,8 @@
using System;
-
-namespace Unity.GrantManager.AI.BackgroundJobs;
-
-public class GenerateContentBackgroundJobArgs
+namespace Unity.GrantManager.GrantApplications.Automation.BackgroundJobs;
+public class RunApplicationAIPipelineJobArgs
{
public Guid ApplicationId { get; set; }
- public string? PromptVersion { get; set; }
public Guid? TenantId { get; set; }
+ public string? PromptVersion { get; set; }
}
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 1dda8c6696..e1883fbe35 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
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
-using Unity.GrantManager.AI.Responses;
+using Unity.AI.Responses;
using Unity.GrantManager.ApplicationForms;
using Volo.Abp.Application.Dtos;
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Identity/IUserTenantAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Identity/IUserTenantAppService.cs
index 668421df6b..5ac06a0aa1 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Identity/IUserTenantAppService.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Identity/IUserTenantAppService.cs
@@ -8,5 +8,6 @@ public interface IUserTenantAppService : IApplicationService
{
Task GetUserAdminAccountAsync(string oidcSub);
Task> GetUserTenantsAsync(string oidcSub);
+ Task> GetListAsync();
}
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Intakes/GetSubmissionsListInput.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Intakes/GetSubmissionsListInput.cs
new file mode 100644
index 0000000000..723c0cd41e
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Intakes/GetSubmissionsListInput.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Unity.GrantManager.Intakes;
+
+[Serializable]
+public class GetSubmissionsListInput
+{
+ public bool ReturnAllSubmissions { get; set; } = true;
+
+ public string? TenantName { get; set; }
+
+ public DateTime? DateFrom { get; set; }
+
+ public DateTime? DateTo { get; set; }
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Intakes/ISubmissionAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Intakes/ISubmissionAppService.cs
index 8962ab097e..618cf756ac 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Intakes/ISubmissionAppService.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Intakes/ISubmissionAppService.cs
@@ -14,10 +14,9 @@ public interface ISubmissionAppService : IApplicationService
///
/// List submissions for a form
///
- /// ID of the form
- /// A list of form fields to search on. Refer to the related `versions/{formVersionId}/fields` endpoint for a list of valid values to query for. The list should be comma separated.
+ /// Filter parameters including tenant, date range, and whether to include all submissions.
/// List<FormSubmissionSummary>
- Task> GetSubmissionsList(bool allSubmissions);
+ Task> GetSubmissionsListAsync(GetSubmissionsListInput input);
///
/// Get a form submission
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Unity.GrantManager.Application.Contracts.csproj b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Unity.GrantManager.Application.Contracts.csproj
index 4f76fb8d6a..7df55afd60 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Unity.GrantManager.Application.Contracts.csproj
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Unity.GrantManager.Application.Contracts.csproj
@@ -1,35 +1,32 @@
-
-
+
-
net9.0
enable
Unity.GrantManager
-
+
-
-
+
+
-
-
-
-
+
+
+
+
-
- **/Assessments/AssessmentListItemDto.cs, **/Assessments/AssessmentScoresDto.cs
-
+
+ **/Assessments/AssessmentListItemDto.cs, **/Assessments/AssessmentScoresDto.cs
+
-
-
+
\ No newline at end of file
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/ApplicantProfileContactService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/ApplicantProfileContactService.cs
index eba51fa136..06dd26366d 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/ApplicantProfileContactService.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/ApplicantProfileContactService.cs
@@ -15,8 +15,11 @@ namespace Unity.GrantManager.ApplicantProfile;
///
/// Applicant-profile-specific contact service. Retrieves contacts linked to applicant profiles,
/// application-level contacts matched by OIDC subject, and applicant agent contacts derived from
-/// the submission login token. This service operates independently from the generic
-/// and queries repositories directly.
+/// the submission login token. Profile contacts are resolved by looking up form submissions that
+/// match the OIDC subject to obtain applicant IDs, then querying
+/// records against those IDs. When a single applicant ID is resolved the contacts are editable;
+/// when multiple IDs are found the contacts are read-only. This service operates independently from the
+/// generic and queries repositories directly.
///
public class ApplicantProfileContactService(
IContactRepository contactRepository,
@@ -27,19 +30,28 @@ public class ApplicantProfileContactService(
IRepository applicationRepository)
: IApplicantProfileContactService, ITransientDependency
{
- private const string ApplicantProfileEntityType = "ApplicantProfile";
+ private const string ApplicantEntityType = "Applicant";
///
- public async Task> GetProfileContactsAsync(Guid profileId)
+ public async Task> GetApplicantContactsAsync(string subject)
{
var contactLinksQuery = await contactLinkRepository.GetQueryableAsync();
var contactsQuery = await contactRepository.GetQueryableAsync();
+ var submissionsQuery = await applicationFormSubmissionRepository.GetQueryableAsync();
+
+ var applicantIds = await submissionsQuery
+ .Where(s => s.OidcSub == subject)
+ .Select(s => s.ApplicantId)
+ .Distinct()
+ .ToListAsync();
+
+ var isEditable = applicantIds.Count <= 1;
return await (
from link in contactLinksQuery
join contact in contactsQuery on link.ContactId equals contact.Id
- where link.RelatedEntityType == ApplicantProfileEntityType
- && link.RelatedEntityId == profileId
+ where link.RelatedEntityType == ApplicantEntityType
+ && applicantIds.Contains(link.RelatedEntityId)
&& link.IsActive
select new ContactInfoItemDto
{
@@ -54,9 +66,9 @@ join contact in contactsQuery on link.ContactId equals contact.Id
ContactType = link.RelatedEntityType,
Role = link.Role,
IsPrimary = link.IsPrimary,
- IsEditable = true,
- ApplicationId = null,
- ReferenceNo = null
+ IsEditable = isEditable,
+ ReferenceNo = null,
+ CreationTime = contact.CreationTime
}).ToListAsync();
}
@@ -85,7 +97,8 @@ join application in applicationsQuery on submission.ApplicationId equals applica
IsPrimary = false,
IsEditable = false,
ApplicationId = appContact.ApplicationId,
- ReferenceNo = application.ReferenceNo
+ ReferenceNo = application.ReferenceNo,
+ CreationTime = appContact.CreationTime
}).ToListAsync();
return applicationContacts;
@@ -117,7 +130,8 @@ join application in applicationsQuery on submission.ApplicationId equals applica
IsPrimary = false,
IsEditable = false,
ApplicationId = agent.ApplicationId,
- ReferenceNo = application.ReferenceNo
+ ReferenceNo = application.ReferenceNo,
+ CreationTime = agent.CreationTime
}).ToListAsync();
return agentContacts;
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/ContactInfoDataProvider.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/ContactInfoDataProvider.cs
index 5062536063..8b7cbe547d 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/ContactInfoDataProvider.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/ContactInfoDataProvider.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using System.Threading.Tasks;
using Unity.GrantManager.ApplicantProfile.ProfileData;
using Volo.Abp.DependencyInjection;
@@ -7,7 +8,7 @@ namespace Unity.GrantManager.ApplicantProfile
{
///
/// Provides contact information for the applicant profile by aggregating
- /// profile-linked contacts, application-level contacts, and applicant agent contacts.
+ /// applicant linked contacts, application-level contacts, and applicant agent contacts.
///
[ExposeServices(typeof(IApplicantProfileDataProvider))]
public class ContactInfoDataProvider(
@@ -33,8 +34,8 @@ public async Task GetDataAsync(ApplicantProfileInfoRequ
using (currentTenant.Change(tenantId))
{
- var profileContacts = await applicantProfileContactService.GetProfileContactsAsync(request.ProfileId);
- dto.Contacts.AddRange(profileContacts);
+ var applicantContacts = await applicantProfileContactService.GetApplicantContactsAsync(normalizedSubject);
+ dto.Contacts.AddRange(applicantContacts);
var applicationContacts = await applicantProfileContactService.GetApplicationContactsBySubjectAsync(normalizedSubject);
dto.Contacts.AddRange(applicationContacts);
@@ -43,6 +44,14 @@ public async Task GetDataAsync(ApplicantProfileInfoRequ
dto.Contacts.AddRange(agentContacts);
}
+ if (dto.Contacts.Count > 0 && !dto.Contacts.Any(c => c.IsPrimary))
+ {
+ var latest = dto.Contacts
+ .OrderByDescending(c => c.CreationTime)
+ .First();
+ latest.IsPrimary = true;
+ }
+
return dto;
}
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/OrgInfoDataProvider.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/OrgInfoDataProvider.cs
index 0534e2f0a7..9e4db5b8a9 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/OrgInfoDataProvider.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/OrgInfoDataProvider.cs
@@ -58,6 +58,7 @@ join applicant in applicantsQuery on submission.ApplicantId equals applicant.Id
applicant.Sector,
applicant.SubSector
})
+ .Distinct()
.ToListAsync();
dto.Organizations.AddRange(results.Select(r => new OrgInfoItemDto
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
new file mode 100644
index 0000000000..e5f52cf296
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/ApplicationAIGenerationQueue.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Unity.AI.Automation;
+using Unity.GrantManager.GrantApplications.Automation.BackgroundJobs;
+using Volo.Abp.BackgroundJobs;
+using Volo.Abp.DependencyInjection;
+
+namespace Unity.GrantManager.GrantApplications.Automation;
+
+public class ApplicationAIGenerationQueue(IBackgroundJobManager backgroundJobManager)
+ : IApplicationAIGenerationQueue, ITransientDependency
+{
+ public async Task QueueAttachmentSummariesAsync(IReadOnlyList attachmentIds, Guid? tenantId, string? promptVersion = null)
+ {
+ await backgroundJobManager.EnqueueAsync(new GenerateAttachmentSummaryBackgroundJobArgs
+ {
+ AttachmentIds = [.. attachmentIds],
+ PromptVersion = promptVersion,
+ TenantId = tenantId
+ });
+ }
+
+ public async Task QueueApplicationAnalysisAsync(Guid applicationId, Guid? tenantId, string? promptVersion = null)
+ {
+ await backgroundJobManager.EnqueueAsync(new GenerateApplicationAnalysisBackgroundJobArgs
+ {
+ ApplicationId = applicationId,
+ PromptVersion = promptVersion,
+ TenantId = tenantId
+ });
+ }
+
+ public async Task QueueApplicationScoringAsync(Guid applicationId, Guid? tenantId, string? promptVersion = null)
+ {
+ await backgroundJobManager.EnqueueAsync(new GenerateApplicationScoringBackgroundJobArgs
+ {
+ ApplicationId = applicationId,
+ PromptVersion = promptVersion,
+ TenantId = tenantId
+ });
+ }
+
+ public async Task QueueApplicationPipelineAsync(Guid applicationId, Guid? tenantId, string? promptVersion = null)
+ {
+ await backgroundJobManager.EnqueueAsync(new RunApplicationAIPipelineJobArgs
+ {
+ ApplicationId = applicationId,
+ PromptVersion = promptVersion,
+ TenantId = tenantId
+ });
+ }
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/BackgroundJobs/GenerateApplicationAnalysisBackgroundJob.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateApplicationAnalysisJob.cs
similarity index 62%
rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/BackgroundJobs/GenerateApplicationAnalysisBackgroundJob.cs
rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateApplicationAnalysisJob.cs
index e9bd6ee84b..703093123a 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/BackgroundJobs/GenerateApplicationAnalysisBackgroundJob.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateApplicationAnalysisJob.cs
@@ -1,23 +1,21 @@
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
-using Unity.GrantManager.AI.Operations;
+using Unity.AI.Operations;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
-
-namespace Unity.GrantManager.AI.BackgroundJobs;
-
-public class GenerateApplicationAnalysisBackgroundJob(
+namespace Unity.GrantManager.GrantApplications.Automation.BackgroundJobs;
+public class GenerateApplicationAnalysisJob(
IApplicationAnalysisService applicationAnalysisService,
ICurrentTenant currentTenant,
- ILogger logger) : AsyncBackgroundJob, ITransientDependency
+ ILogger logger) : AsyncBackgroundJob, ITransientDependency
{
public override async Task ExecuteAsync(GenerateApplicationAnalysisBackgroundJobArgs args)
{
using (currentTenant.Change(args.TenantId))
{
- logger.LogInformation("Executing AI application analysis background job for application {ApplicationId}.", args.ApplicationId);
+ logger.LogInformation("Executing AI application analysis job for application {ApplicationId}.", args.ApplicationId);
await applicationAnalysisService.RegenerateAndSaveAsync(args.ApplicationId, args.PromptVersion);
}
}
-}
+}
\ No newline at end of file
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/BackgroundJobs/GenerateApplicationScoringBackgroundJob.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateApplicationScoringJob.cs
similarity index 64%
rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/BackgroundJobs/GenerateApplicationScoringBackgroundJob.cs
rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateApplicationScoringJob.cs
index a445993a68..e57e157ac0 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/BackgroundJobs/GenerateApplicationScoringBackgroundJob.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateApplicationScoringJob.cs
@@ -1,35 +1,32 @@
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
-using Unity.GrantManager.AI.Operations;
-using Unity.GrantManager.Intakes.Events;
+using Unity.AI.Operations;
+using Unity.GrantManager.GrantApplications.Automation.Events;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Local;
using Volo.Abp.MultiTenancy;
-
-namespace Unity.GrantManager.AI.BackgroundJobs;
-
-public class GenerateApplicationScoringBackgroundJob(
+namespace Unity.GrantManager.GrantApplications.Automation.BackgroundJobs;
+public class GenerateApplicationScoringJob(
IApplicationScoringService applicationScoringService,
ILocalEventBus localEventBus,
ICurrentTenant currentTenant,
- ILogger logger) : AsyncBackgroundJob, ITransientDependency
+ ILogger logger) : AsyncBackgroundJob, ITransientDependency
{
public override async Task ExecuteAsync(GenerateApplicationScoringBackgroundJobArgs args)
{
using (currentTenant.Change(args.TenantId))
{
- logger.LogInformation("Executing AI application scoring background job for application {ApplicationId}.", args.ApplicationId);
-
+ logger.LogInformation("Executing AI application scoring job for application {ApplicationId}.", args.ApplicationId);
var result = await applicationScoringService.RegenerateAndSaveAsync(args.ApplicationId, args.PromptVersion);
if (!string.Equals(result, "{}", StringComparison.Ordinal))
{
- await localEventBus.PublishAsync(new AIApplicationScoringGeneratedEvent
+ await localEventBus.PublishAsync(new ApplicationAIScoringGeneratedEvent
{
ApplicationId = args.ApplicationId
});
}
}
}
-}
+}
\ No newline at end of file
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/BackgroundJobs/GenerateAttachmentSummaryBackgroundJob.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateAttachmentSummaryJob.cs
similarity index 60%
rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/BackgroundJobs/GenerateAttachmentSummaryBackgroundJob.cs
rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateAttachmentSummaryJob.cs
index ed6a1ebd91..21a9a7450a 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/BackgroundJobs/GenerateAttachmentSummaryBackgroundJob.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/GenerateAttachmentSummaryJob.cs
@@ -1,26 +1,23 @@
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
-using Unity.GrantManager.AI.Operations;
+using Unity.AI.Operations;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
-
-namespace Unity.GrantManager.AI.BackgroundJobs;
-
-public class GenerateAttachmentSummaryBackgroundJob(
+namespace Unity.GrantManager.GrantApplications.Automation.BackgroundJobs;
+public class GenerateAttachmentSummaryJob(
IAttachmentSummaryService attachmentSummaryService,
ICurrentTenant currentTenant,
- ILogger logger) : AsyncBackgroundJob, ITransientDependency
+ ILogger logger) : AsyncBackgroundJob, ITransientDependency
{
public override async Task ExecuteAsync(GenerateAttachmentSummaryBackgroundJobArgs args)
{
using (currentTenant.Change(args.TenantId))
{
logger.LogInformation(
- "Executing AI attachment summary background job for {AttachmentCount} attachment(s).",
+ "Executing AI attachment summary job for {AttachmentCount} attachment(s).",
args.AttachmentIds.Count);
-
await attachmentSummaryService.GenerateAndSaveAsync(args.AttachmentIds, args.PromptVersion);
}
}
-}
+}
\ No newline at end of file
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/BackgroundJobs/GenerateContentBackgroundJob.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/RunApplicationAIPipelineJob.cs
similarity index 87%
rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/BackgroundJobs/GenerateContentBackgroundJob.cs
rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/RunApplicationAIPipelineJob.cs
index 2e55de9895..94459c6da8 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/BackgroundJobs/GenerateContentBackgroundJob.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/BackgroundJobs/RunApplicationAIPipelineJob.cs
@@ -1,19 +1,18 @@
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
+using Unity.AI;
+using Unity.AI.Operations;
using Unity.AI.Settings;
-using Unity.GrantManager.AI.Operations;
-using Unity.GrantManager.Intakes.Events;
+using Unity.GrantManager.GrantApplications.Automation.Events;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Local;
using Volo.Abp.Features;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Settings;
-
-namespace Unity.GrantManager.AI.BackgroundJobs;
-
-public class GenerateContentBackgroundJob(
+namespace Unity.GrantManager.GrantApplications.Automation.BackgroundJobs;
+public class RunApplicationAIPipelineJob(
IAttachmentSummaryService attachmentSummaryService,
IApplicationAnalysisService applicationAnalysisService,
IApplicationScoringService applicationScoringService,
@@ -22,43 +21,36 @@ public class GenerateContentBackgroundJob(
ISettingProvider settingProvider,
ILocalEventBus localEventBus,
ICurrentTenant currentTenant,
- ILogger logger) : AsyncBackgroundJob, ITransientDependency
+ ILogger logger) : AsyncBackgroundJob, ITransientDependency
{
- public override async Task ExecuteAsync(GenerateContentBackgroundJobArgs args)
+ public override async Task ExecuteAsync(RunApplicationAIPipelineJobArgs args)
{
using (currentTenant.Change(args.TenantId))
{
var attachmentSummariesEnabled = await featureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries");
var applicationAnalysisEnabled = await featureChecker.IsEnabledAsync("Unity.AI.ApplicationAnalysis");
var scoringEnabled = await featureChecker.IsEnabledAsync("Unity.AI.Scoring");
-
if (scoringEnabled)
{
scoringEnabled = await settingProvider.GetAsync(AISettings.ScoringAssistantEnabled, defaultValue: false);
}
-
if (!attachmentSummariesEnabled && !applicationAnalysisEnabled && !scoringEnabled)
{
logger.LogDebug("All AI features are disabled, skipping queued AI generation for application {ApplicationId}.", args.ApplicationId);
return;
}
-
if (!await aiService.IsAvailableAsync())
{
logger.LogWarning("AI service is not available, skipping queued AI generation for application {ApplicationId}.", args.ApplicationId);
return;
}
-
logger.LogInformation("Executing queued AI content pipeline for application {ApplicationId}.", args.ApplicationId);
-
if (attachmentSummariesEnabled)
{
await attachmentSummaryService.GenerateForApplicationAsync(args.ApplicationId, args.PromptVersion);
}
-
Exception? analysisException = null;
Exception? scoringException = null;
-
if (applicationAnalysisEnabled)
{
try
@@ -71,7 +63,6 @@ public override async Task ExecuteAsync(GenerateContentBackgroundJobArgs args)
logger.LogError(ex, "Error executing AI application analysis stage for application {ApplicationId}.", args.ApplicationId);
}
}
-
if (scoringEnabled)
{
try
@@ -79,7 +70,7 @@ public override async Task ExecuteAsync(GenerateContentBackgroundJobArgs args)
var result = await applicationScoringService.RegenerateAndSaveAsync(args.ApplicationId, args.PromptVersion);
if (!string.Equals(result, "{}", StringComparison.Ordinal))
{
- await localEventBus.PublishAsync(new AIApplicationScoringGeneratedEvent
+ await localEventBus.PublishAsync(new ApplicationAIScoringGeneratedEvent
{
ApplicationId = args.ApplicationId
});
@@ -91,12 +82,10 @@ await localEventBus.PublishAsync(new AIApplicationScoringGeneratedEvent
logger.LogError(ex, "Error executing AI application scoring stage for application {ApplicationId}.", args.ApplicationId);
}
}
-
if (scoringException != null)
{
throw scoringException;
}
-
if (analysisException != null)
{
throw analysisException;
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Events/ApplicationAIScoringGeneratedEvent.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Events/ApplicationAIScoringGeneratedEvent.cs
new file mode 100644
index 0000000000..a22b5a7154
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Events/ApplicationAIScoringGeneratedEvent.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace Unity.GrantManager.GrantApplications.Automation.Events;
+
+public class ApplicationAIScoringGeneratedEvent
+{
+ public Guid ApplicationId { get; set; }
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/Handlers/CreateAIAssessmentHandler.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Handlers/CreateAIAssessmentOnScoringGeneratedHandler.cs
similarity index 77%
rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/Handlers/CreateAIAssessmentHandler.cs
rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Handlers/CreateAIAssessmentOnScoringGeneratedHandler.cs
index b75842328c..599de3d1a9 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/Handlers/CreateAIAssessmentHandler.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Handlers/CreateAIAssessmentOnScoringGeneratedHandler.cs
@@ -2,43 +2,38 @@
using System;
using System.Threading.Tasks;
using Unity.AI.Settings;
-using Unity.GrantManager.Assessments;
using Unity.GrantManager.Applications;
-using Unity.GrantManager.Intakes.Events;
+using Unity.GrantManager.Assessments;
+using Unity.GrantManager.GrantApplications.Automation.Events;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
using Volo.Abp.Features;
using Volo.Abp.Settings;
using Volo.Abp.Uow;
-
-namespace Unity.GrantManager.Intakes.Handlers;
-
-public class CreateAIAssessmentHandler(
+namespace Unity.GrantManager.GrantApplications.Automation.Handlers;
+public class CreateAIAssessmentOnScoringGeneratedHandler(
AssessmentManager assessmentManager,
IApplicationRepository applicationRepository,
IFeatureChecker featureChecker,
ISettingProvider settingProvider,
IUnitOfWorkManager unitOfWorkManager,
- ILogger logger) : ILocalEventHandler, ITransientDependency
+ ILogger logger) : ILocalEventHandler, ITransientDependency
{
- public async Task HandleEventAsync(AIApplicationScoringGeneratedEvent eventData)
+ public async Task HandleEventAsync(ApplicationAIScoringGeneratedEvent eventData)
{
if (eventData == null || eventData.ApplicationId == Guid.Empty)
{
- logger.LogWarning("Event data or application ID is null in CreateAIAssessmentHandler.");
+ logger.LogWarning("Event data or application ID is null in CreateAIAssessmentOnScoringGeneratedHandler.");
return;
}
-
if (!await featureChecker.IsEnabledAsync("Unity.AI.Scoring"))
{
return;
}
-
if (!await settingProvider.GetAsync(AISettings.ScoringAssistantEnabled, defaultValue: false))
{
return;
}
-
try
{
using var uow = unitOfWorkManager.Begin(requiresNew: true);
@@ -52,4 +47,4 @@ public async Task HandleEventAsync(AIApplicationScoringGeneratedEvent eventData)
logger.LogError(ex, "Error creating AI assessment for application {ApplicationId}.", eventData.ApplicationId);
}
}
-}
+}
\ No newline at end of file
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Handlers/QueueApplicationAIPipelineOnProcessHandler.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Handlers/QueueApplicationAIPipelineOnProcessHandler.cs
new file mode 100644
index 0000000000..f8261b66f5
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Handlers/QueueApplicationAIPipelineOnProcessHandler.cs
@@ -0,0 +1,35 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Threading.Tasks;
+using Unity.GrantManager.GrantApplications.Automation.BackgroundJobs;
+using Unity.GrantManager.Intakes.Events;
+using Volo.Abp.BackgroundJobs;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.EventBus;
+namespace Unity.GrantManager.GrantApplications.Automation.Handlers;
+public class QueueApplicationAIPipelineOnProcessHandler(
+ IBackgroundJobManager backgroundJobManager,
+ ILogger logger) : ILocalEventHandler, ITransientDependency
+{
+ public async Task HandleEventAsync(ApplicationProcessEvent eventData)
+ {
+ if (eventData?.Application == null)
+ {
+ logger.LogWarning("Event data or application is null in QueueApplicationAIPipelineOnProcessHandler.");
+ return;
+ }
+ try
+ {
+ await backgroundJobManager.EnqueueAsync(new RunApplicationAIPipelineJobArgs
+ {
+ ApplicationId = eventData.Application.Id,
+ TenantId = eventData.Application.TenantId
+ });
+ logger.LogInformation("Queued AI pipeline for application {ApplicationId}.", eventData.Application.Id);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Error queueing AI pipeline for application {ApplicationId}.", eventData.Application.Id);
+ }
+ }
+}
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 022a1ad34d..c587627aac 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs
@@ -14,8 +14,8 @@
using System.Threading.Tasks;
using Unity.Flex.WorksheetInstances;
using Unity.Flex.Worksheets;
-using Unity.GrantManager.AI.Models;
-using Unity.GrantManager.AI.Responses;
+using Unity.AI.Models;
+using Unity.AI.Responses;
using Unity.GrantManager.Applicants;
using Unity.GrantManager.ApplicationForms;
using Unity.GrantManager.Applications;
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationAutoMapperProfile.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationAutoMapperProfile.cs
index 2cfa84db89..168a2dc905 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationAutoMapperProfile.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationAutoMapperProfile.cs
@@ -45,6 +45,8 @@ public GrantManagerApplicationAutoMapperProfile()
.ForMember(dest => dest.OwnerId, opt => opt.MapFrom(src => src.AssessmentId));
CreateMap()
.ForMember(dest => dest.OwnerId, opt => opt.MapFrom(src => src.ApplicationId));
+ CreateMap()
+ .ForMember(dest => dest.OwnerId, opt => opt.MapFrom(src => src.ApplicantId));
CreateMap()
.ForMember(dest => dest.Badge, opt => opt.MapFrom(src => src.CommenterBadge))
.ForMember(dest => dest.Commenter, opt => opt.MapFrom(src => src.CommenterDisplayName))
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Handlers/ContactCreateHandler.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Handlers/ContactCreateHandler.cs
index 1e618a100b..769e4b1135 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Handlers/ContactCreateHandler.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Handlers/ContactCreateHandler.cs
@@ -1,13 +1,10 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
-using Unity.GrantManager.Applications;
using Unity.GrantManager.Contacts;
using Unity.GrantManager.GrantsPortal.Messages;
using Unity.GrantManager.GrantsPortal.Messages.Commands;
-using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Uow;
@@ -17,10 +14,10 @@ namespace Unity.GrantManager.GrantsPortal.Handlers;
public class ContactCreateHandler(
IContactRepository contactRepository,
IContactLinkRepository contactLinkRepository,
- IApplicationFormSubmissionRepository applicationFormSubmissionRepository,
- IApplicantAgentRepository applicantAgentRepository,
ILogger logger) : IPortalCommandHandler, ITransientDependency
{
+ private const string ApplicantEntityType = "Applicant";
+
public string DataType => "CONTACT_CREATE_COMMAND";
[UnitOfWork]
@@ -29,7 +26,7 @@ public virtual async Task HandleAsync(PluginDataPayload payload)
var contactId = Guid.Parse(payload.ContactId ?? throw new ArgumentException("contactId is required"));
var profileId = Guid.Parse(payload.ProfileId ?? throw new ArgumentException("profileId is required"));
var innerData = payload.Data?.ToObject()
- ?? throw new ArgumentException("Contact data is required");
+ ?? throw new ArgumentException("Contact data is required");
// Idempotency: if the contact already exists, treat as success
var existing = await contactRepository.FindAsync(contactId);
@@ -54,22 +51,29 @@ public virtual async Task HandleAsync(PluginDataPayload payload)
EntityHelper.TrySetId(contact, () => contactId);
- // Lookup applicant agent IDs associated with this subject's submissions
- var applicantAgentIds = await GetApplicantAgentIdsAsync(payload.Subject);
- if (applicantAgentIds.Count > 0)
+ await contactRepository.InsertAsync(contact);
+
+ // Demote existing primary contact links for the same applicant
+ if (innerData.IsPrimary)
{
- contact.SetProperty("applicantAgentIds", applicantAgentIds);
- logger.LogInformation("Found {Count} applicant agent(s) for subject {Subject}", applicantAgentIds.Count, payload.Subject);
+ var contactLinks = await contactLinkRepository.GetListAsync(
+ cl => cl.RelatedEntityType == ApplicantEntityType
+ && cl.RelatedEntityId == innerData.ApplicantId
+ && cl.IsActive);
+
+ foreach (var stale in contactLinks.Where(cl => cl.IsPrimary))
+ {
+ stale.IsPrimary = false;
+ await contactLinkRepository.UpdateAsync(stale);
+ }
}
- await contactRepository.InsertAsync(contact);
-
// Create a contact link to track the relationship and primary status
var contactLink = new ContactLink
{
ContactId = contactId,
- RelatedEntityType = innerData.ContactType ?? "PORTAL",
- RelatedEntityId = profileId,
+ RelatedEntityType = ApplicantEntityType,
+ RelatedEntityId = innerData.ApplicantId,
Role = innerData.Role,
IsPrimary = innerData.IsPrimary,
IsActive = true
@@ -79,43 +83,7 @@ public virtual async Task HandleAsync(PluginDataPayload payload)
logger.LogInformation("Contact {ContactId} created successfully", contactId);
return "Contact created successfully";
- }
-
- private async Task> GetApplicantAgentIdsAsync(string? subject)
- {
- if (string.IsNullOrWhiteSpace(subject))
- {
- return [];
- }
-
- var normalizedSub = NormalizeOidcSub(subject);
- if (string.IsNullOrWhiteSpace(normalizedSub))
- {
- return [];
- }
-
- // Find submissions matching the normalized OidcSub
- var submissions = await applicationFormSubmissionRepository.GetListAsync(s => s.OidcSub == normalizedSub);
- if (submissions.Count == 0)
- {
- logger.LogDebug("No submissions found for subject {Subject} (normalized: {NormalizedSub})", subject, normalizedSub);
- return [];
- }
-
- // Get distinct application IDs from the submissions
- var applicationIds = submissions
- .Select(s => s.ApplicationId)
- .Distinct()
- .ToList();
-
- // Lookup applicant agents linked to those applications
- var agents = await applicantAgentRepository
- .GetListAsync(a => a.ApplicationId != null && applicationIds.Contains(a.ApplicationId!.Value));
-
- return [.. agents
- .Select(a => a.Id.ToString())
- .Distinct()];
- }
+ }
///
/// Normalizes a raw OIDC subject by stripping the IDP suffix (after @) and uppercasing.
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Handlers/ContactEditHandler.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Handlers/ContactEditHandler.cs
index c640ec3808..856821d7f7 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Handlers/ContactEditHandler.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Handlers/ContactEditHandler.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Unity.GrantManager.Contacts;
@@ -11,8 +12,11 @@ namespace Unity.GrantManager.GrantsPortal.Handlers;
public class ContactEditHandler(
IContactRepository contactRepository,
+ IContactLinkRepository contactLinkRepository,
ILogger logger) : IPortalCommandHandler, ITransientDependency
{
+ private const string ApplicantEntityType = "Applicant";
+
public string DataType => "CONTACT_EDIT_COMMAND";
[UnitOfWork]
@@ -20,7 +24,12 @@ public virtual async Task HandleAsync(PluginDataPayload payload)
{
var contactId = Guid.Parse(payload.ContactId ?? throw new ArgumentException("contactId is required"));
var innerData = payload.Data?.ToObject()
- ?? throw new ArgumentException("Contact data is required");
+ ?? throw new ArgumentException("Contact data is required");
+
+ if (innerData.ApplicantId == Guid.Empty)
+ {
+ throw new ArgumentException("applicantId is required");
+ }
logger.LogInformation("Editing contact {ContactId} for profile {ProfileId}", contactId, payload.ProfileId);
@@ -34,6 +43,37 @@ public virtual async Task HandleAsync(PluginDataPayload payload)
contact.WorkPhoneNumber = innerData.WorkPhoneNumber;
contact.WorkPhoneExtension = innerData.WorkPhoneExtension;
+ // Sync contact-link primary flags to match the incoming value
+ var contactLinks = await contactLinkRepository.GetListAsync(
+ cl => cl.RelatedEntityType == ApplicantEntityType
+ && cl.RelatedEntityId == innerData.ApplicantId
+ && cl.IsActive);
+
+ if (innerData.IsPrimary)
+ {
+ foreach (var stale in contactLinks.Where(cl => cl.IsPrimary && cl.ContactId != contactId))
+ {
+ stale.IsPrimary = false;
+ await contactLinkRepository.UpdateAsync(stale);
+ }
+
+ var newPrimary = contactLinks.FirstOrDefault(cl => cl.ContactId == contactId && !cl.IsPrimary);
+ if (newPrimary != null)
+ {
+ newPrimary.IsPrimary = true;
+ await contactLinkRepository.UpdateAsync(newPrimary);
+ }
+ }
+ else
+ {
+ var demoted = contactLinks.FirstOrDefault(cl => cl.ContactId == contactId && cl.IsPrimary);
+ if (demoted != null)
+ {
+ demoted.IsPrimary = false;
+ await contactLinkRepository.UpdateAsync(demoted);
+ }
+ }
+
await contactRepository.UpdateAsync(contact);
logger.LogInformation("Contact {ContactId} updated successfully", contactId);
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Handlers/ContactSetPrimaryHandler.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Handlers/ContactSetPrimaryHandler.cs
index b02a8c43fa..0c2fbbf151 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Handlers/ContactSetPrimaryHandler.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Handlers/ContactSetPrimaryHandler.cs
@@ -1,8 +1,10 @@
+using Microsoft.Extensions.Logging;
using System;
+using System.Linq;
using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
using Unity.GrantManager.Contacts;
using Unity.GrantManager.GrantsPortal.Messages;
+using Unity.GrantManager.GrantsPortal.Messages.Commands;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Uow;
@@ -12,6 +14,8 @@ public class ContactSetPrimaryHandler(
IContactLinkRepository contactLinkRepository,
ILogger logger) : IPortalCommandHandler, ITransientDependency
{
+ private const string ApplicantEntityType = "Applicant";
+
public string DataType => "CONTACT_SET_PRIMARY_COMMAND";
[UnitOfWork]
@@ -19,17 +23,33 @@ public virtual async Task HandleAsync(PluginDataPayload payload)
{
var contactId = Guid.Parse(payload.ContactId ?? throw new ArgumentException("contactId is required"));
var profileId = Guid.Parse(payload.ProfileId ?? throw new ArgumentException("profileId is required"));
+ var innerData = payload.Data?.ToObject()
+ ?? throw new ArgumentException("Contact data is required");
+
+ if (innerData.ApplicantId == Guid.Empty)
+ {
+ throw new ArgumentException("applicantId is required");
+ }
logger.LogInformation("Setting contact {ContactId} as primary for profile {ProfileId}", contactId, profileId);
- // Find all contact links for this profile and clear their primary flag
- var profileLinks = await contactLinkRepository.GetListAsync(
- cl => cl.RelatedEntityId == profileId && cl.IsActive);
+ // Only update links whose primary flag actually needs to change
+ var contactLinks = await contactLinkRepository.GetListAsync(
+ cl => cl.RelatedEntityType == ApplicantEntityType
+ && cl.RelatedEntityId == innerData.ApplicantId
+ && cl.IsActive);
+
+ foreach (var stale in contactLinks.Where(cl => cl.IsPrimary && cl.ContactId != contactId))
+ {
+ stale.IsPrimary = false;
+ await contactLinkRepository.UpdateAsync(stale);
+ }
- foreach (var link in profileLinks)
+ var newPrimary = contactLinks.FirstOrDefault(cl => cl.ContactId == contactId && !cl.IsPrimary);
+ if (newPrimary != null)
{
- link.IsPrimary = link.ContactId == contactId;
- await contactLinkRepository.UpdateAsync(link);
+ newPrimary.IsPrimary = true;
+ await contactLinkRepository.UpdateAsync(newPrimary);
}
logger.LogInformation("Contact {ContactId} set as primary", contactId);
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Messages/Commands/ContactCreateData.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Messages/Commands/ContactCreateData.cs
index e89566c42d..532edf4224 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Messages/Commands/ContactCreateData.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Messages/Commands/ContactCreateData.cs
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
+using System;
namespace Unity.GrantManager.GrantsPortal.Messages.Commands;
@@ -33,4 +34,7 @@ public class ContactCreateData
[JsonProperty("isPrimary")]
public bool IsPrimary { get; set; }
+
+ [JsonProperty("applicantId")]
+ public Guid ApplicantId { get; set; }
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Messages/Commands/ContactEditData.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Messages/Commands/ContactEditData.cs
index a4f4f5d605..1f0acb2651 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Messages/Commands/ContactEditData.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Messages/Commands/ContactEditData.cs
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
+using System;
namespace Unity.GrantManager.GrantsPortal.Messages.Commands;
@@ -33,4 +34,7 @@ public class ContactEditData
[JsonProperty("isPrimary")]
public bool IsPrimary { get; set; }
+
+ [JsonProperty("applicantId")]
+ public Guid ApplicantId { get; set; }
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Messages/Commands/ContactSetPrimaryData.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Messages/Commands/ContactSetPrimaryData.cs
new file mode 100644
index 0000000000..a213a846a8
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantsPortal/Messages/Commands/ContactSetPrimaryData.cs
@@ -0,0 +1,10 @@
+using Newtonsoft.Json;
+using System;
+
+namespace Unity.GrantManager.GrantsPortal.Messages.Commands;
+
+public class ContactSetPrimaryData
+{
+ [JsonProperty("applicantId")]
+ public Guid ApplicantId { get; set; }
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/Events/AIApplicationScoringGeneratedEvent.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/Events/AIApplicationScoringGeneratedEvent.cs
deleted file mode 100644
index b5dc7bdf72..0000000000
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/Events/AIApplicationScoringGeneratedEvent.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using System;
-
-namespace Unity.GrantManager.Intakes.Events;
-
-public class AIApplicationScoringGeneratedEvent
-{
- public Guid ApplicationId { get; set; }
-}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/Handlers/GenerateAIContentHandler.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/Handlers/GenerateAIContentHandler.cs
deleted file mode 100644
index c4d882b043..0000000000
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/Handlers/GenerateAIContentHandler.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using Microsoft.Extensions.Logging;
-using System;
-using System.Threading.Tasks;
-using Unity.GrantManager.AI.BackgroundJobs;
-using Unity.GrantManager.Intakes.Events;
-using Volo.Abp.BackgroundJobs;
-using Volo.Abp.DependencyInjection;
-using Volo.Abp.EventBus;
-using Volo.Abp.MultiTenancy;
-
-namespace Unity.GrantManager.Intakes.Handlers;
-
-public class GenerateAIContentHandler(
- IBackgroundJobManager backgroundJobManager,
- ICurrentTenant currentTenant,
- ILogger logger) : ILocalEventHandler, ITransientDependency
-{
- public async Task HandleEventAsync(ApplicationProcessEvent eventData)
- {
- if (eventData?.Application == null)
- {
- logger.LogWarning("Event data or application is null in GenerateAIContentHandler.");
- return;
- }
-
- try
- {
- await backgroundJobManager.EnqueueAsync(new GenerateContentBackgroundJobArgs
- {
- ApplicationId = eventData.Application.Id,
- TenantId = currentTenant.Id
- });
-
- logger.LogInformation("Queued AI content generation for application {ApplicationId}.", eventData.Application.Id);
- }
- catch (Exception ex)
- {
- logger.LogError(ex, "Error queueing AI content generation for application {ApplicationId}.", eventData.Application.Id);
- }
- }
-}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/SubmissionAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/SubmissionAppService.cs
index 3bdefcbbd7..7d2faae6a8 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/SubmissionAppService.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/SubmissionAppService.cs
@@ -1,4 +1,4 @@
-using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -105,7 +105,7 @@ public async Task GetChefsFileAttachment(Guid? formSubmissionId, Guid?
// Check and decode the file name if it is URL encoded
if (!string.IsNullOrEmpty(name) && name.Contains('%'))
{
- name = Uri.UnescapeDataString(name);
+ name = Uri.UnescapeDataString(name);
}
return new BlobDto { Name = name, Content = contentBytes, ContentType = contentType };
@@ -141,7 +141,7 @@ join applicationForm in await applicationFormRepository.GetQueryableAsync() on a
return applicationFormSubmissionData;
}
- public async Task> GetSubmissionsList(bool allSubmissions)
+ public async Task> GetSubmissionsListAsync(GetSubmissionsListInput input)
{
var chefsSubmissions = new List();
var serializerOptions = CreateJsonSerializerOptions();
@@ -149,19 +149,49 @@ public async Task> GetSubmissionsList(b
var unityRefNos = new HashSet();
var checkedForms = new HashSet();
+ if (!string.IsNullOrWhiteSpace(input.TenantName))
+ {
+ tenants = tenants
+ .Where(t => string.Equals(t.Name, input.TenantName, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+ }
+
foreach (var tenant in tenants)
{
using (CurrentTenant.Change(tenant.Id))
{
+ //This could be overloaded to include the input.DateFrom, and input.DateTo when calling applicationRepository,
+ //the problem is we need to use all forms returned from app repository to search Chefs via IDs.
+ //so date filtering at the moment occurs post-processing
await ProcessTenantSubmissions(tenant, chefsSubmissions, serializerOptions, checkedForms, unityRefNos);
}
}
- UpdateSubmissionsWithUnityFlags(chefsSubmissions, unityRefNos);
+ foreach (var submission in chefsSubmissions)
+ {
+ submission.inUnity = unityRefNos.Contains(submission.ConfirmationId.ToString());
+ }
- if (!allSubmissions)
+ if (input.DateFrom.HasValue || input.DateTo.HasValue)
{
- chefsSubmissions.RemoveAll(s => unityRefNos.Contains(s.ConfirmationId.ToString()));
+ DateTime? exclusiveUpperBoundForDateTo = null;
+ if (input.DateTo.HasValue && input.DateTo.Value.TimeOfDay == TimeSpan.Zero)
+ {
+ // Inclusive end-of-day
+ exclusiveUpperBoundForDateTo = input.DateTo.Value.Date.AddDays(1);
+ }
+ chefsSubmissions.RemoveAll(submission =>
+ (input.DateFrom.HasValue && submission.CreatedAt < input.DateFrom.Value) ||
+ (input.DateTo.HasValue && (
+ exclusiveUpperBoundForDateTo.HasValue
+ ? submission.CreatedAt >= exclusiveUpperBoundForDateTo.Value
+ : submission.CreatedAt > input.DateTo.Value
+ )));
+ }
+
+ if (!input.ReturnAllSubmissions)
+ {
+ chefsSubmissions.RemoveAll(submission => submission.inUnity);
}
return new PagedResultDto(chefsSubmissions.Count, chefsSubmissions);
@@ -246,12 +276,4 @@ private async Task FetchChefsSubmissions(
Logger.LogError(ex, "GetSubmissionsList Exception: {Message}", ex.Message);
}
}
-
- private static void UpdateSubmissionsWithUnityFlags(List chefsSubmissions, HashSet unityRefNos)
- {
- foreach (var submission in chefsSubmissions)
- {
- submission.inUnity = unityRefNos.Contains(submission.ConfirmationId.ToString());
- }
- }
-}
\ No newline at end of file
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Unity.GrantManager.Application.csproj b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Unity.GrantManager.Application.csproj
index 3ff76f7291..dcd0c31780 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Unity.GrantManager.Application.csproj
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Unity.GrantManager.Application.csproj
@@ -1,4 +1,4 @@
-
+
@@ -49,11 +49,5 @@
-
-
- PreserveNewest
- PreserveNewest
-
-
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Comments/CommentType.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Comments/CommentType.cs
index fdf669ff36..5e4f3be809 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Comments/CommentType.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Comments/CommentType.cs
@@ -1,8 +1,12 @@
-namespace Unity.GrantManager.Comments
+using System.Text.Json.Serialization;
+
+namespace Unity.GrantManager.Comments;
+
+/// If changing this enum, also update the corresponding Unity.Notifications.Comments.CommentType enum, as it is shared between the two projects and used in the API layer.
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum CommentType
{
- public enum CommentType
- {
- ApplicationComment,
- AssessmentComment
- }
+ ApplicationComment,
+ AssessmentComment,
+ ApplicantComment,
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/ApplicantComment.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/ApplicantComment.cs
new file mode 100644
index 0000000000..8c9e257e9a
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/ApplicantComment.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace Unity.GrantManager.Comments;
+
+public class ApplicantComment : CommentBase
+{
+ public Guid ApplicantId { get; set; }
+}
\ No newline at end of file
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/ApplicationComment.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/ApplicationComment.cs
index a300320d4a..c50a6d5603 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/ApplicationComment.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/ApplicationComment.cs
@@ -1,9 +1,8 @@
using System;
-namespace Unity.GrantManager.Comments
+namespace Unity.GrantManager.Comments;
+
+public class ApplicationComment : CommentBase
{
- public class ApplicationComment : CommentBase
- {
- public Guid ApplicationId { get; set; }
- }
+ public Guid ApplicationId { get; set; }
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/AssessmentComment.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/AssessmentComment.cs
index c0f39d6152..b21da5c24b 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/AssessmentComment.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/AssessmentComment.cs
@@ -1,9 +1,8 @@
using System;
-namespace Unity.GrantManager.Comments
+namespace Unity.GrantManager.Comments;
+
+public class AssessmentComment : CommentBase
{
- public class AssessmentComment : CommentBase
- {
- public Guid AssessmentId { get; set; }
- }
+ public Guid AssessmentId { get; set; }
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/CommentsManager.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/CommentsManager.cs
index 1f185ab2e7..68695904e8 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/CommentsManager.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Comments/CommentsManager.cs
@@ -13,16 +13,21 @@ public class CommentsManager : DomainService, ICommentsManager
{
private readonly ICommentsRepository _applicationCommentsRepository;
private readonly ICommentsRepository _assessmentCommentsRepository;
+ private readonly ICommentsRepository _applicantCommentsRepository;
+
private readonly ICurrentUser _currentUser;
private readonly IPersonRepository _personRepository;
public CommentsManager(ICommentsRepository applicationCommentsRepository,
ICommentsRepository assessmentCommentsRepository,
+ ICommentsRepository applicantCommentsRepository,
ICurrentUser currentUser,
IPersonRepository personRepository)
{
_applicationCommentsRepository = applicationCommentsRepository;
_assessmentCommentsRepository = assessmentCommentsRepository;
+ _applicantCommentsRepository = applicantCommentsRepository;
+
_currentUser = currentUser;
_personRepository = personRepository;
}
@@ -39,7 +44,10 @@ public async Task CreateCommentAsync(Guid ownerId, string comment,
CommentType.AssessmentComment => await _assessmentCommentsRepository
.InsertAsync(new AssessmentComment()
{ Comment = comment, AssessmentId = ownerId, CommenterId = commenterId }, autoSave: true),
- _ => throw new NotImplementedException(),
+ CommentType.ApplicantComment => await _applicantCommentsRepository
+ .InsertAsync(new ApplicantComment()
+ { Comment = comment, ApplicantId = ownerId, CommenterId = commenterId }, autoSave: true),
+ _ => throw new ArgumentOutOfRangeException(nameof(assessmentComment)),
};
}
@@ -53,8 +61,11 @@ public async Task> GetCommentsAsync(Guid ownerId, Com
case CommentType.AssessmentComment:
var assessmentCommentsQry = await _assessmentCommentsRepository.GetQueryableAsync();
return assessmentCommentsQry.Where(c => c.AssessmentId.Equals(ownerId)).OrderByDescending(s => s.CreationTime).ToList();
+ case CommentType.ApplicantComment:
+ var applicantCommentsQry = await _applicantCommentsRepository.GetQueryableAsync();
+ return applicantCommentsQry.Where(c => c.ApplicantId.Equals(ownerId)).OrderByDescending(s => s.CreationTime).ToList();
default:
- throw new NotImplementedException();
+ throw new ArgumentOutOfRangeException(nameof(type));
}
}
@@ -70,8 +81,12 @@ public async Task UpdateCommentAsync(Guid ownerId, Guid commentId,
var assessmentComment = await GetCommentAsync(ownerId, commentId, type) ?? throw new EntityNotFoundException();
assessmentComment.Comment = comment;
return await _assessmentCommentsRepository.UpdateAsync((AssessmentComment)assessmentComment!, autoSave: true);
+ case CommentType.ApplicantComment:
+ var applicantComment = await GetCommentAsync(ownerId, commentId, type) ?? throw new EntityNotFoundException();
+ applicantComment.Comment = comment;
+ return await _applicantCommentsRepository.UpdateAsync((ApplicantComment)applicantComment!, autoSave: true);
default:
- throw new NotImplementedException();
+ throw new ArgumentOutOfRangeException(nameof(type));
}
}
@@ -85,8 +100,11 @@ public async Task UpdateCommentAsync(Guid ownerId, Guid commentId,
case CommentType.AssessmentComment:
var assessmentCommentsQry = await _assessmentCommentsRepository.GetQueryableAsync();
return assessmentCommentsQry.FirstOrDefault(s => s.AssessmentId == ownerId && s.Id == commentId);
+ case CommentType.ApplicantComment:
+ var applicantCommentsQry = await _applicantCommentsRepository.GetQueryableAsync();
+ return applicantCommentsQry.FirstOrDefault(s => s.ApplicantId == ownerId && s.Id == commentId);
default:
- throw new NotImplementedException();
+ throw new ArgumentOutOfRangeException(nameof(type));
}
}
@@ -134,8 +152,28 @@ assessmentComment.CreationTime descending
PinDateTime = assessmentComment.PinDateTime,
};
return assessmentCommentsQry.ToList();
+ case CommentType.ApplicantComment:
+ var applicantCommentsQry = from applicantComment in await _applicantCommentsRepository.GetQueryableAsync()
+ join user in await _personRepository.GetQueryableAsync() on applicantComment.CommenterId equals user.Id
+ where applicantComment.ApplicantId == ownerId
+ orderby applicantComment.PinDateTime.HasValue descending,
+ applicantComment.PinDateTime ascending,
+ applicantComment.CreationTime descending
+ select new CommentListItem
+ {
+ Comment = applicantComment.Comment,
+ CommenterId = applicantComment.CommenterId,
+ CommenterDisplayName = user.OidcDisplayName,
+ CommenterBadge = user.Badge,
+ CreationTime = applicantComment.CreationTime,
+ OwnerId = ownerId,
+ Id = applicantComment.Id,
+ LastModificationTime = applicantComment.LastModificationTime,
+ PinDateTime = applicantComment.PinDateTime,
+ };
+ return applicantCommentsQry.ToList();
default:
- throw new NotImplementedException();
+ throw new ArgumentOutOfRangeException(nameof(type));
}
}
@@ -173,6 +211,9 @@ private async Task UpdateCommentInRepositoryAsync(CommentBase comment, CommentTy
case CommentType.AssessmentComment:
await _assessmentCommentsRepository.UpdateAsync((AssessmentComment)comment, autoSave: true);
break;
+ case CommentType.ApplicantComment:
+ await _applicantCommentsRepository.UpdateAsync((ApplicantComment)comment, autoSave: true);
+ break;
default:
throw new ArgumentOutOfRangeException(nameof(type));
}
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 ee2bdd7ed2..f20765fecc 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs
@@ -38,6 +38,7 @@ public class GrantTenantDbContext : AbpDbContext
public DbSet ApplicantAddresses { get; set; }
public DbSet ApplicationTags { get; set; }
public DbSet ApplicantAgents { get; set; }
+ public DbSet ApplicantComments { get; set; }
public DbSet ApplicationAttachments { get; set; }
public DbSet ApplicationFormSubmissions { get; set; }
public DbSet AssessmentAttachments { get; set; }
@@ -180,6 +181,19 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
b.HasOne().WithMany().HasForeignKey(x => x.ApplicantId).IsRequired();
});
+ modelBuilder.Entity(b =>
+ {
+ b.ToTable(GrantManagerConsts.TenantTablePrefix + "ApplicantComments", GrantManagerConsts.DbSchema);
+
+ b.ConfigureByConvention();
+ b.HasOne().WithMany().HasForeignKey(x => x.ApplicantId).IsRequired();
+
+ b.HasOne()
+ .WithMany()
+ .HasPrincipalKey(x => x.Id)
+ .HasForeignKey(x => x.CommenterId);
+ });
+
modelBuilder.Entity(b =>
{
b.ToTable(GrantManagerConsts.TenantTablePrefix + "ApplicationFormSubmissions",
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260328004721_AB9074_Add_Applicant_Comments.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260328004721_AB9074_Add_Applicant_Comments.Designer.cs
new file mode 100644
index 0000000000..8408a892cd
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260328004721_AB9074_Add_Applicant_Comments.Designer.cs
@@ -0,0 +1,4865 @@
+//
+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("20260328004721_AB9074_Add_Applicant_Comments")]
+ partial class AB9074_Add_Applicant_Comments
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql)
+ .HasAnnotation("ProductVersion", "9.0.5")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .HasMaxLength(40)
+ .HasColumnType("character varying(40)")
+ .HasColumnName("ConcurrencyStamp");
+
+ b.Property("CorrelationId")
+ .HasColumnType("uuid");
+
+ b.Property("CorrelationProvider")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uuid")
+ .HasColumnName("CreatorId");
+
+ b.Property("DeleterId")
+ .HasColumnType("uuid")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("DeletionTime");
+
+ b.Property("ExtraProperties")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uuid")
+ .HasColumnName("LastModifierId");
+
+ b.Property("ReportData")
+ .IsRequired()
+ .HasColumnType("jsonb");
+
+ b.Property("ScoresheetId")
+ .HasColumnType("uuid");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("TenantId");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ScoresheetId");
+
+ b.ToTable("ScoresheetInstances", "Flex");
+ });
+
+ modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uuid")
+ .HasColumnName("CreatorId");
+
+ b.Property("CurrentValue")
+ .HasColumnType("jsonb");
+
+ b.Property("DeleterId")
+ .HasColumnType("uuid")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("DeletionTime");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uuid")
+ .HasColumnName("LastModifierId");
+
+ b.Property("QuestionId")
+ .HasColumnType("uuid");
+
+ b.Property("ScoresheetInstanceId")
+ .HasColumnType("uuid");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("TenantId");
+
+ b.Property("Version")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("QuestionId");
+
+ b.HasIndex("ScoresheetInstanceId");
+
+ b.ToTable("Answers", "Flex");
+ });
+
+ modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uuid")
+ .HasColumnName("CreatorId");
+
+ b.Property("Definition")
+ .IsRequired()
+ .HasColumnType("jsonb");
+
+ b.Property("DeleterId")
+ .HasColumnType("uuid")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("DeletionTime");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Enabled")
+ .HasColumnType("boolean");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("Label")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uuid")
+ .HasColumnName("LastModifierId");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Order")
+ .HasColumnType("bigint");
+
+ b.Property("SectionId")
+ .HasColumnType("uuid");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("TenantId");
+
+ b.Property("Type")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SectionId");
+
+ b.ToTable("Questions", "Flex");
+ });
+
+ modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .HasMaxLength(40)
+ .HasColumnType("character varying(40)")
+ .HasColumnName("ConcurrencyStamp");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uuid")
+ .HasColumnName("CreatorId");
+
+ b.Property("DeleterId")
+ .HasColumnType("uuid")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("DeletionTime");
+
+ b.Property("ExtraProperties")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uuid")
+ .HasColumnName("LastModifierId");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Order")
+ .HasColumnType("bigint");
+
+ b.Property("Published")
+ .HasColumnType("boolean");
+
+ b.Property("ReportColumns")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ReportKeys")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ReportViewName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("TenantId");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Version")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.ToTable("Scoresheets", "Flex");
+ });
+
+ modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uuid")
+ .HasColumnName("CreatorId");
+
+ b.Property("DeleterId")
+ .HasColumnType("uuid")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("DeletionTime");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uuid")
+ .HasColumnName("LastModifierId");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Order")
+ .HasColumnType("bigint");
+
+ b.Property("ScoresheetId")
+ .HasColumnType("uuid");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("TenantId");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ScoresheetId");
+
+ b.ToTable("ScoresheetSections", "Flex");
+ });
+
+ modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uuid")
+ .HasColumnName("CreatorId");
+
+ b.Property("CurrentValue")
+ .IsRequired()
+ .HasColumnType("jsonb");
+
+ b.Property("CustomFieldId")
+ .HasColumnType("uuid");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uuid")
+ .HasColumnName("LastModifierId");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("TenantId");
+
+ b.Property("WorksheetInstanceId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("WorksheetInstanceId");
+
+ b.ToTable("CustomFieldValues", "Flex");
+ });
+
+ modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .HasMaxLength(40)
+ .HasColumnType("character varying(40)")
+ .HasColumnName("ConcurrencyStamp");
+
+ b.Property("CorrelationId")
+ .HasColumnType("uuid");
+
+ b.Property("CorrelationProvider")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uuid")
+ .HasColumnName("CreatorId");
+
+ b.Property("CurrentValue")
+ .IsRequired()
+ .HasColumnType("jsonb");
+
+ b.Property("DeleterId")
+ .HasColumnType("uuid")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("DeletionTime");
+
+ b.Property("ExtraProperties")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uuid")
+ .HasColumnName("LastModifierId");
+
+ b.Property("ReportData")
+ .IsRequired()
+ .HasColumnType("jsonb");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("TenantId");
+
+ b.Property("UiAnchor")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("WorksheetCorrelationId")
+ .HasColumnType("uuid");
+
+ b.Property("WorksheetCorrelationProvider")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("WorksheetId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.ToTable("WorksheetInstances", "Flex");
+ });
+
+ modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .HasMaxLength(40)
+ .HasColumnType("character varying(40)")
+ .HasColumnName("ConcurrencyStamp");
+
+ b.Property("CorrelationId")
+ .HasColumnType("uuid");
+
+ b.Property("CorrelationProvider")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uuid")
+ .HasColumnName("CreatorId");
+
+ b.Property("ExtraProperties")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("ExtraProperties");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uuid")
+ .HasColumnName("LastModifierId");
+
+ b.Property("Order")
+ .HasColumnType("bigint");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("TenantId");
+
+ b.Property("UiAnchor")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("WorksheetId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("WorksheetId");
+
+ b.ToTable("WorksheetLinks", "Flex");
+ });
+
+ modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uuid")
+ .HasColumnName("CreatorId");
+
+ b.Property("Definition")
+ .IsRequired()
+ .HasColumnType("jsonb");
+
+ b.Property("DeleterId")
+ .HasColumnType("uuid")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("DeletionTime");
+
+ b.Property("Enabled")
+ .HasColumnType("boolean");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("Key")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Label")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uuid")
+ .HasColumnName("LastModifierId");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Order")
+ .HasColumnType("bigint");
+
+ b.Property |